From 4ea2ec206c5107ca7ab5067414a63c647d64defe Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 16 Mar 2026 21:45:41 +0000
Subject: [PATCH 1/3] Initial plan
From 705748ae29f88af4173fdf9c808c480ae446c322 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 16 Mar 2026 21:59:49 +0000
Subject: [PATCH 2/3] chore: add comprehensive linting workflow
Co-authored-by: Stage4000 <46226385+Stage4000@users.noreply.github.com>
---
.eslintrc.json | 27 +
.github/workflows/lint.yml | 72 +
.htmlhintrc | 14 +
.semgrepignore | 3 +
.stylelintrc.json | 32 +
about.php | 12 +-
admin/ebay-oauth-callback.php | 7 +
admin/products.php | 23 +-
api/contact-form.php | 6 +
api/ebay-sync.php | 2 +
api/paypal-webhook.php | 6 +
api/process-order.php | 3 +-
api/products.php | 18 +-
api/shipping-rates.php | 3 +-
api/validate-coupon.php | 3 +-
cart.php | 10 +-
composer.json | 11 +
contact.php | 8 +-
includes/footer.php | 34 +-
package-lock.json | 3894 +++++++++++++++++++++++++++++++++
package.json | 20 +
phpstan-baseline.neon | 2096 ++++++++++++++++++
phpstan.neon.dist | 27 +
product.php | 21 +-
products.php | 21 +-
public/js/animations.js | 29 +-
public/js/main.js | 13 -
scripts/render-pages.sh | 74 +
src/config/config.example.php | 5 -
29 files changed, 6422 insertions(+), 72 deletions(-)
create mode 100644 .eslintrc.json
create mode 100644 .github/workflows/lint.yml
create mode 100644 .htmlhintrc
create mode 100644 .semgrepignore
create mode 100644 .stylelintrc.json
create mode 100644 package-lock.json
create mode 100644 package.json
create mode 100644 phpstan-baseline.neon
create mode 100644 phpstan.neon.dist
create mode 100644 scripts/render-pages.sh
diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 00000000..556d39f1
--- /dev/null
+++ b/.eslintrc.json
@@ -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"
+ }
+ ]
+ }
+}
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 00000000..947b20e2
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,72 @@
+name: Comprehensive linting
+
+on:
+ pull_request:
+ push:
+ branches:
+ - main
+ - master
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+
+ 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
diff --git a/.htmlhintrc b/.htmlhintrc
new file mode 100644
index 00000000..d9067b51
--- /dev/null
+++ b/.htmlhintrc
@@ -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
+}
diff --git a/.semgrepignore b/.semgrepignore
new file mode 100644
index 00000000..061b2b62
--- /dev/null
+++ b/.semgrepignore
@@ -0,0 +1,3 @@
+vendor/
+node_modules/
+database/*.db
diff --git a/.stylelintrc.json b/.stylelintrc.json
new file mode 100644
index 00000000..a86ad3e6
--- /dev/null
+++ b/.stylelintrc.json
@@ -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
+ }
+}
diff --git a/about.php b/about.php
index 182b6ca6..67d3b076 100644
--- a/about.php
+++ b/about.php
@@ -3,14 +3,14 @@
require_once __DIR__ . '/includes/header.php';
?>
-
+
About Flip and Strip
-
Quality Motorcycle, ATV/UTV, Boat & Automotive Parts
+
Quality Motorcycle, ATV/UTV, Boat & Automotive Parts
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.
@@ -21,7 +21,7 @@
what you're getting. We take pride in offering low-mileage parts that are in excellent working condition.
-
What We Offer
+
What We Offer
Tested and inspected parts
Low-mileage components
@@ -32,7 +32,7 @@
Live chat support via Tawk.to
-
Our Inventory
+
Our Inventory
We maintain a large inventory of motorcycle, ATV/UTV, boat, and automotive parts, including:
@@ -58,7 +58,7 @@
-
Ready to Find Your Parts?
+
Ready to Find Your Parts?
-
+
diff --git a/admin/ebay-oauth-callback.php b/admin/ebay-oauth-callback.php
index b6a4a681..9e8dda26 100644
--- a/admin/ebay-oauth-callback.php
+++ b/admin/ebay-oauth-callback.php
@@ -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);
diff --git a/admin/products.php b/admin/products.php
index 908d923f..32375c8a 100644
--- a/admin/products.php
+++ b/admin/products.php
@@ -48,8 +48,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);
}
}
@@ -90,8 +95,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']);
@@ -134,8 +141,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]);
@@ -420,7 +429,7 @@
- 1): ?>
+ 1): ?>