Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"root": true,
"env": {
"browser": true,
"es2021": true
},
"ignorePatterns": [
"node_modules/",
"vendor/"
],
"extends": [
"eslint:recommended"
],
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "script"
},
"rules": {
"no-unused-vars": [
"error",
{
"args": "none",
"caughtErrors": "none"
}
]
}
}
74 changes: 74 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
name: Comprehensive linting

on:
pull_request:
push:
branches:
- main
- master

jobs:
lint:
runs-on: ubuntu-latest
permissions:
contents: read

steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
coverage: none

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
cache: npm

- name: Install Composer dependencies
run: composer install --no-interaction --no-progress

- name: Install npm dependencies
run: npm ci

- name: Prepare local configuration
run: |
cp src/config/config.example.php src/config/config.php
touch database/flipandstrip.db

- name: Validate PHP syntax
run: composer lint:php

- name: Run PHPStan
run: composer lint:phpstan

- name: Run ESLint
run: npm run lint:js

- name: Run stylelint
run: npm run lint:css

- name: Run htmlhint
run: npm run lint:html

- name: Run axe-core smoke scan
run: npm run lint:a11y

semgrep:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write

steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Run Semgrep
uses: semgrep/semgrep-action@v1
with:
config: p/default
14 changes: 14 additions & 0 deletions .htmlhintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"alt-require": true,
"attr-lowercase": true,
"attr-no-duplication": true,
"attr-value-double-quotes": true,
"doctype-first": true,
"head-script-disabled": false,
"id-unique": true,
"spec-char-escape": true,
"src-not-empty": true,
"tag-pair": true,
"tagname-lowercase": true,
"title-require": true
}
3 changes: 3 additions & 0 deletions .semgrepignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
vendor/
node_modules/
database/*.db
32 changes: 32 additions & 0 deletions .stylelintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"ignoreFiles": [
"node_modules/**",
"vendor/**"
],
"rules": {
"block-no-empty": true,
"color-no-invalid-hex": true,
"comment-no-empty": true,
"declaration-block-no-duplicate-properties": [
true,
{
"ignore": [
"consecutive-duplicates-with-different-values"
]
}
],
"font-family-no-duplicate-names": true,
"function-calc-no-unspaced-operator": true,
"function-linear-gradient-no-nonstandard-direction": true,
"keyframe-block-no-duplicate-selectors": true,
"media-feature-name-no-unknown": true,
"named-grid-areas-no-invalid": true,
"no-empty-source": true,
"no-invalid-double-slash-comments": true,
"property-no-unknown": true,
"selector-pseudo-class-no-unknown": true,
"selector-pseudo-element-no-unknown": true,
"string-no-newline": true,
"unit-no-unknown": true
}
}
12 changes: 6 additions & 6 deletions about.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
require_once __DIR__ . '/includes/header.php';
?>

<div class="container my-5">
<main class="container my-5" id="main-content">
<div class="row">
<div class="col-lg-8 mx-auto">
<h1 class="mb-4 fw-bold text-center" data-aos="fade-down">About Flip and Strip</h1>

<div class="card border-0 shadow-sm mb-4" data-aos="fade-up" data-aos-delay="100">
<div class="card-body p-5">
<h3 class="text-danger mb-3">Quality Motorcycle, ATV/UTV, Boat & Automotive Parts</h3>
<h2 class="text-danger mb-3">Quality Motorcycle, ATV/UTV, Boat & Automotive Parts</h2>
<p class="lead">
Flip and Strip specializes in providing high-quality, tested motorcycle, ATV/UTV, boat, and automotive parts
from top manufacturers including Harley Davidson, Yamaha, Honda, Kawasaki, Suzuki, BMW, and more.
Expand All @@ -21,7 +21,7 @@
what you're getting. We take pride in offering low-mileage parts that are in excellent working condition.
</p>

<h4 class="mt-4 mb-3">What We Offer</h4>
<h2 class="mt-4 mb-3">What We Offer</h2>
<ul class="list-unstyled">
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i> Tested and inspected parts</li>
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i> Low-mileage components</li>
Expand All @@ -32,7 +32,7 @@
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i> Live chat support via Tawk.to</li>
</ul>

<h4 class="mt-4 mb-3">Our Inventory</h4>
<h2 class="mt-4 mb-3">Our Inventory</h2>
<p>
We maintain a large inventory of motorcycle, ATV/UTV, boat, and automotive parts, including:
</p>
Expand All @@ -58,14 +58,14 @@
</div>

<div class="text-center" data-aos="fade-up" data-aos-delay="200">
<h3 class="mb-4">Ready to Find Your Parts?</h3>
<h2 class="mb-4">Ready to Find Your Parts?</h2>
<div class="d-flex gap-3 justify-content-center">
<a href="products.php" class="btn btn-danger btn-lg">Browse Products</a>
<a href="contact.php" class="btn btn-danger btn-lg">Contact Us</a>
</div>
</div>
</div>
</div>
</div>
</main>

<?php require_once __DIR__ . '/includes/footer.php'; ?>
7 changes: 7 additions & 0 deletions admin/ebay-oauth-callback.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@
}

// Parse response
if (!is_string($response)) {
error_log('eBay OAuth token exchange returned a non-string response');
$_SESSION['error'] = 'Invalid response from eBay. Please try again.';
header('Location: settings.php');
exit;
}

$tokenData = json_decode($response, true);
if (!$tokenData || !isset($tokenData['access_token'])) {
error_log('eBay OAuth invalid token response: ' . $response);
Expand Down
22 changes: 16 additions & 6 deletions admin/products.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
$error = '';
$action = $_GET['action'] ?? 'list';
$productId = $_GET['id'] ?? null;
$totalPages = 0;

// AJAX endpoint for immediate image removal
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['ajax_remove_image'])) {
Expand All @@ -48,8 +49,13 @@

// Try to delete the physical file if it's a local upload
if ($updateSuccess && (strpos($imagePathToRemove, 'gallery/uploads/') === 0 || strpos($imagePathToRemove, '/gallery/uploads/') === 0)) {
$physicalPath = __DIR__ . '/../' . ltrim($imagePathToRemove, '/');
if (file_exists($physicalPath)) {
$uploadsDir = realpath(__DIR__ . '/../gallery/uploads');
$physicalPath = realpath(__DIR__ . '/../' . ltrim($imagePathToRemove, '/'));
if ($uploadsDir !== false &&
$physicalPath !== false &&
strpos($physicalPath, $uploadsDir . DIRECTORY_SEPARATOR) === 0 &&
is_file($physicalPath)
) {
@unlink($physicalPath);
}
}
Expand Down Expand Up @@ -90,8 +96,10 @@

$extension = strtolower(pathinfo($_FILES['image_file']['name'], PATHINFO_EXTENSION));
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $_FILES['image_file']['tmp_name']);
finfo_close($finfo);
$mimeType = $finfo ? finfo_file($finfo, $_FILES['image_file']['tmp_name']) : false;
if ($finfo) {
finfo_close($finfo);
}

// Additional validation: verify it's actually an image
$imageInfo = @getimagesize($_FILES['image_file']['tmp_name']);
Expand Down Expand Up @@ -134,8 +142,10 @@
if ($_FILES['additional_images']['error'][$i] === UPLOAD_ERR_OK) {
$extension = strtolower(pathinfo($_FILES['additional_images']['name'][$i], PATHINFO_EXTENSION));
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $_FILES['additional_images']['tmp_name'][$i]);
finfo_close($finfo);
$mimeType = $finfo ? finfo_file($finfo, $_FILES['additional_images']['tmp_name'][$i]) : false;
if ($finfo) {
finfo_close($finfo);
}

$imageInfo = @getimagesize($_FILES['additional_images']['tmp_name'][$i]);

Expand Down
6 changes: 6 additions & 0 deletions api/contact-form.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@
exit;
}

if (!is_string($verifyResponse)) {
http_response_code(500);
echo json_encode(['success' => false, 'message' => 'Unable to verify security token']);
exit;
}

$verifyResult = json_decode($verifyResponse, true);

if (!$verifyResult['success']) {
Expand Down
2 changes: 2 additions & 0 deletions api/ebay-sync.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
use FAS\Models\Product;
use FAS\Utils\SyncLogger;

$db = null;

// Initialize comprehensive logging to log.txt
SyncLogger::init(__DIR__ . '/../log.txt');

Expand Down
6 changes: 6 additions & 0 deletions api/paypal-webhook.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@

// Get webhook data
$rawInput = file_get_contents('php://input');
if (!is_string($rawInput)) {
http_response_code(400);
echo json_encode(['error' => 'Invalid webhook payload']);
exit;
}

$webhookData = json_decode($rawInput, true);

// Log webhook metadata only (not sensitive payment data)
Expand Down
3 changes: 2 additions & 1 deletion api/process-order.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
}

// Get request data
$input = json_decode(file_get_contents('php://input'), true);
$rawInput = file_get_contents('php://input');
$input = is_string($rawInput) ? json_decode($rawInput, true) : null;

if (!$input) {
http_response_code(400);
Expand Down
18 changes: 17 additions & 1 deletion api/products.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,21 @@ function normalizeImagePath($path) {
return $path;
}

function resolveLocalImagePath($path) {
if (!is_string($path) || $path === '' || strpos($path, '..') !== false) {
return null;
}

$basePath = realpath(__DIR__ . '/..');
$resolvedPath = realpath(__DIR__ . '/../' . ltrim($path, '/'));

if ($basePath === false || $resolvedPath === false) {
return null;
}

return strpos($resolvedPath, $basePath . DIRECTORY_SEPARATOR) === 0 ? $resolvedPath : null;
}

// Get filter parameters
$ebayCat1 = $_GET['cat1'] ?? null;
$ebayCat2 = $_GET['cat2'] ?? null;
Expand Down Expand Up @@ -180,9 +195,10 @@ function normalizeImagePath($path) {
<?php
// Check if image is external or local
$isExternal = strpos($imageUrl, 'http://') === 0 || strpos($imageUrl, 'https://') === 0;
$localImagePath = $isExternal ? null : resolveLocalImagePath($imageUrl);
$hasImage = !empty($imageUrl) && (
$isExternal ||
file_exists(__DIR__ . '/../' . ltrim($imageUrl, '/'))
($localImagePath !== null && file_exists($localImagePath))
);
?>
<?php if ($hasImage): ?>
Expand Down
3 changes: 2 additions & 1 deletion api/shipping-rates.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
}

// Get POST data
$input = json_decode(file_get_contents('php://input'), true);
$rawInput = file_get_contents('php://input');
$input = is_string($rawInput) ? json_decode($rawInput, true) : null;

if (!$input) {
http_response_code(400);
Expand Down
3 changes: 2 additions & 1 deletion api/validate-coupon.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
}

// Get request data
$input = json_decode(file_get_contents('php://input'), true);
$rawInput = file_get_contents('php://input');
$input = is_string($rawInput) ? json_decode($rawInput, true) : null;

if (!$input) {
http_response_code(400);
Expand Down
Loading
Loading