diff --git a/admin/products.php b/admin/products.php index f187ba7e..908d923f 100644 --- a/admin/products.php +++ b/admin/products.php @@ -378,7 +378,17 @@ - eBay + getEbayStoreCategoryPath($prod); + ?> + + title="" + data-bs-toggle="tooltip" + data-bs-placement="top" + > + eBay + Manual @@ -545,7 +555,7 @@
- + + For manual products only. eBay products use store categories below.
+ + + getEbayStoreCategoryArray($product); ?> + +
+ eBay Store Categories (3-tier):
+
+ +
+ Level 1: + +
+ + +
+ Level 2: + +
+ + +
+ Level 3: + +
+ + Automatically synced from eBay store during product sync +
+
+ +
+ No eBay store categories found. Run sync to update. +
+ +
diff --git a/api/products.php b/api/products.php new file mode 100644 index 00000000..f4f76256 --- /dev/null +++ b/api/products.php @@ -0,0 +1,381 @@ + 'PHP Error', + 'message' => $errstr, + 'file' => $errfile, + 'line' => $errline + ]); + exit; +}); + +require_once __DIR__ . '/../src/config/Database.php'; +require_once __DIR__ . '/../src/models/Product.php'; +require_once __DIR__ . '/../src/utils/SyncLogger.php'; +require_once __DIR__ . '/../src/integrations/EbayAPI.php'; + +use FAS\Config\Database; +use FAS\Models\Product; +use FAS\Integrations\EbayAPI; + +// Normalize image paths to ensure they start with / for local images +function normalizeImagePath($path) { + if (empty($path)) { + return $path; + } + // If it's an external URL, return as-is + if (strpos($path, 'http://') === 0 || strpos($path, 'https://') === 0) { + return $path; + } + // If it's a local path without leading /, add it + if (strpos($path, '/') !== 0) { + return '/' . $path; + } + return $path; +} + +// Get filter parameters +$ebayCat1 = $_GET['cat1'] ?? null; +$ebayCat2 = $_GET['cat2'] ?? null; +$ebayCat3 = $_GET['cat3'] ?? null; +$manufacturer = $_GET['manufacturer'] ?? null; +$search = $_GET['search'] ?? null; +$page = isset($_GET['page']) ? (int)$_GET['page'] : 1; +$perPage = 24; + +// Initialize database and product model +$db = Database::getInstance()->getConnection(); +$productModel = new Product($db); + +// Get eBay API for category names +try { + $config = require __DIR__ . '/../src/config/config.php'; + $ebayAPI = new EbayAPI($config); + $ebayCategories = $ebayAPI->getStoreCategoriesHierarchical(); +} catch (Exception $e) { + $ebayAPI = null; + $ebayCategories = []; +} + +// Get products from database +$products = $productModel->getAllByEbayCategory($page, $perPage, $ebayCat1, $ebayCat2, $ebayCat3, $search, $manufacturer); +$totalProducts = $productModel->getCountByEbayCategory($ebayCat1, $ebayCat2, $ebayCat3, $search, $manufacturer); + +// Get unique manufacturers +$allManufacturers = $productModel->getManufacturers(); + +$totalPages = ceil($totalProducts / $perPage); + +// Get current category name for display +$currentCategoryName = 'All Products'; +if (($ebayCat3 || $ebayCat2 || $ebayCat1) && $ebayAPI) { + $flatCategories = $ebayAPI->getStoreCategories(); + if ($ebayCat3 && isset($flatCategories[$ebayCat3])) { + $cat = $flatCategories[$ebayCat3]; + $currentCategoryName = ($cat['topLevel'] ?? '') . ' > ' . ($cat['parent'] ?? '') . ' > ' . $cat['name']; + } elseif ($ebayCat2 && isset($flatCategories[$ebayCat2])) { + $cat = $flatCategories[$ebayCat2]; + $currentCategoryName = ($cat['topLevel'] ?? '') . ' > ' . $cat['name']; + } elseif ($ebayCat1 && isset($flatCategories[$ebayCat1])) { + $currentCategoryName = $flatCategories[$ebayCat1]['name'] ?? 'Category'; + } +} + +// Build HTML output +ob_start(); +?> + +
+
+

+ + Search Results for "" + + + +

+

products found

+
+
+ +
+ + + + +
+ + +
+
+
+
+ + + +
+
+ + +
+
+ + + + +
+ No products found in this category. Try browsing other categories or use the search function. +
+ +
+ $product): ?> + +
+
+ +
+ + + <?php echo htmlspecialchars($product['name']); ?> + +
+ +
+ + + + + + + Save % + +
+
+
+
+ + + +
+

+ getEbayStoreCategoryPath($product); + if ($catPath): ?> + Category:
+ + + Mfg:
+ + + Model: + +

+
+
+
+ + $ + $ + + $ + +
+ SKU: +
+ +
+
+
+
+ +
+ + + + 1): ?> + + + + + + All Products + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Categories will appear after eBay sync +
+ + $html, + 'sidebar' => $sidebarHtml, + 'totalProducts' => $totalProducts, + 'currentPage' => $page, + 'totalPages' => $totalPages, + 'success' => true + ]; + + echo json_encode($response); +} catch (Exception $e) { + http_response_code(500); + echo json_encode([ + 'error' => 'Failed to encode response', + 'message' => $e->getMessage(), + 'success' => false + ]); +} diff --git a/database/README_MIGRATION.md b/database/README_MIGRATION.md new file mode 100644 index 00000000..06e6767e --- /dev/null +++ b/database/README_MIGRATION.md @@ -0,0 +1,110 @@ +# Database Migration Guide + +## eBay Store Category Columns Migration + +This migration adds support for storing the complete 3-level eBay store category hierarchy in the products table. + +### What This Migration Does + +Adds 6 new columns to the `products` table: +- `ebay_store_cat1_id` - Level 1 category ID (e.g., ID for "MOTORCYCLE") +- `ebay_store_cat1_name` - Level 1 category name (e.g., "MOTORCYCLE") +- `ebay_store_cat2_id` - Level 2 category ID (e.g., ID for "Honda") +- `ebay_store_cat2_name` - Level 2 category name (e.g., "Honda") +- `ebay_store_cat3_id` - Level 3 category ID (e.g., ID for "CR500") +- `ebay_store_cat3_name` - Level 3 category name (e.g., "CR500") + +### How to Run the Migration + +**IMPORTANT:** You must run this migration before syncing products, otherwise you'll get an error like: +``` +SQLSTATE[HY000]: General error: 1 table products has no column named ebay_store_cat1_id +``` + +#### Step 1: Navigate to the project directory +```bash +cd /path/to/FAS +``` + +#### Step 2: Run the migration script +```bash +php database/migrate-add-ebay-store-categories.php +``` + +#### Step 3: Verify the migration +You should see output like: +``` +Adding eBay store category columns to products table... +Adding column 'ebay_store_cat1_id'... +Adding column 'ebay_store_cat1_name'... +Adding column 'ebay_store_cat2_id'... +Adding column 'ebay_store_cat2_name'... +Adding column 'ebay_store_cat3_id'... +Adding column 'ebay_store_cat3_name'... + +Successfully added columns: ebay_store_cat1_id, ebay_store_cat1_name, ebay_store_cat2_id, ebay_store_cat2_name, ebay_store_cat3_id, ebay_store_cat3_name + +Migration completed successfully! +``` + +If the columns already exist, you'll see: +``` +All eBay store category columns already exist. No changes needed. +``` + +### After Running the Migration + +Once the migration completes successfully, you can: +1. Run eBay sync normally - products will now store the full 3-level category hierarchy +2. View product details pages to see the complete category path (e.g., "MOTORCYCLE > Honda > CR500") +3. Check admin product listings to see category information in tooltips + +### Troubleshooting + +**Error: Config file not found** +- Make sure you have `src/config/config.php` set up +- Copy `src/config/config.example.php` to `src/config/config.php` if needed + +**Error: Database not found** +- Initialize the database first using `php database/init-sqlite.php` + +**Error: Permission denied** +- Ensure the database file has write permissions +- Check that the database directory is writable + +**Already ran but getting errors?** +- The migration is idempotent (safe to run multiple times) +- It will skip columns that already exist +- If you see the error again after running successfully, check your database connection + +### Manual Migration (Alternative) + +If the automated script doesn't work, you can add the columns manually: + +**For SQLite:** +```sql +ALTER TABLE products ADD COLUMN ebay_store_cat1_id INTEGER; +ALTER TABLE products ADD COLUMN ebay_store_cat1_name TEXT; +ALTER TABLE products ADD COLUMN ebay_store_cat2_id INTEGER; +ALTER TABLE products ADD COLUMN ebay_store_cat2_name TEXT; +ALTER TABLE products ADD COLUMN ebay_store_cat3_id INTEGER; +ALTER TABLE products ADD COLUMN ebay_store_cat3_name TEXT; +``` + +**For MySQL:** +```sql +ALTER TABLE products ADD COLUMN ebay_store_cat1_id INT NULL; +ALTER TABLE products ADD COLUMN ebay_store_cat1_name VARCHAR(255) NULL; +ALTER TABLE products ADD COLUMN ebay_store_cat2_id INT NULL; +ALTER TABLE products ADD COLUMN ebay_store_cat2_name VARCHAR(255) NULL; +ALTER TABLE products ADD COLUMN ebay_store_cat3_id INT NULL; +ALTER TABLE products ADD COLUMN ebay_store_cat3_name VARCHAR(255) NULL; +``` + +### Support + +If you encounter issues: +1. Check the error message carefully +2. Verify your database is accessible +3. Ensure PHP has permissions to modify the database +4. Review the migration script at `database/migrate-add-ebay-store-categories.php` diff --git a/database/migrate-add-ebay-store-categories.php b/database/migrate-add-ebay-store-categories.php new file mode 100644 index 00000000..e3fba15b --- /dev/null +++ b/database/migrate-add-ebay-store-categories.php @@ -0,0 +1,82 @@ +getConnection(); + + echo "Adding eBay store category columns to products table...\n"; + + // Check if columns already exist + $columns = [ + 'ebay_store_cat1_id' => 'INT NULL', + 'ebay_store_cat1_name' => 'VARCHAR(255) NULL', + 'ebay_store_cat2_id' => 'INT NULL', + 'ebay_store_cat2_name' => 'VARCHAR(255) NULL', + 'ebay_store_cat3_id' => 'INT NULL', + 'ebay_store_cat3_name' => 'VARCHAR(255) NULL' + ]; + + // Whitelist of allowed column names for security + $allowedColumns = [ + 'ebay_store_cat1_id', + 'ebay_store_cat1_name', + 'ebay_store_cat2_id', + 'ebay_store_cat2_name', + 'ebay_store_cat3_id', + 'ebay_store_cat3_name' + ]; + + $addedColumns = []; + foreach ($columns as $columnName => $columnDef) { + // Security check: only allow whitelisted column names + if (!in_array($columnName, $allowedColumns, true)) { + echo "Column '$columnName' is not in the whitelist. Skipping for security...\n"; + continue; + } + + // Check if column exists using PRAGMA for SQLite + $result = $db->query("PRAGMA table_info(products)"); + $existingColumns = $result->fetchAll(PDO::FETCH_ASSOC); + + $columnExists = false; + foreach ($existingColumns as $col) { + if ($col['name'] === $columnName) { + $columnExists = true; + break; + } + } + + if ($columnExists) { + echo "Column '$columnName' already exists. Skipping...\n"; + } else { + // Column doesn't exist, add it - safe because columnName is whitelisted + echo "Adding column '$columnName'...\n"; + $db->exec("ALTER TABLE products ADD COLUMN $columnName $columnDef"); + $addedColumns[] = $columnName; + } + } + + if (empty($addedColumns)) { + echo "All eBay store category columns already exist. No changes needed.\n"; + } else { + echo "\nSuccessfully added columns: " . implode(', ', $addedColumns) . "\n"; + } + + echo "\nMigration completed successfully!\n"; + echo "Products can now store exact 3-level eBay store category hierarchy.\n"; + echo "\nColumn structure:\n"; + echo "- ebay_store_cat1_id / ebay_store_cat1_name: Level 1 (main category: Motorcycle, ATV, Boat, etc.)\n"; + echo "- ebay_store_cat2_id / ebay_store_cat2_name: Level 2 (manufacturer/subcategory)\n"; + echo "- ebay_store_cat3_id / ebay_store_cat3_name: Level 3 (model/sub-subcategory)\n"; + +} catch (Exception $e) { + echo "Migration failed: " . $e->getMessage() . "\n"; + exit(1); +} diff --git a/database/schema.sql b/database/schema.sql index bbe828f6..c6e6e53c 100644 --- a/database/schema.sql +++ b/database/schema.sql @@ -21,6 +21,13 @@ CREATE TABLE IF NOT EXISTS products ( source VARCHAR(20) DEFAULT 'manual', -- 'ebay' or 'manual' show_on_website BOOLEAN DEFAULT TRUE, -- TRUE = visible, FALSE = hidden is_active BOOLEAN DEFAULT TRUE, + -- eBay store category hierarchy (exact 3-level mapping) + ebay_store_cat1_id INT, + ebay_store_cat1_name VARCHAR(255), + ebay_store_cat2_id INT, + ebay_store_cat2_name VARCHAR(255), + ebay_store_cat3_id INT, + ebay_store_cat3_name VARCHAR(255), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_category (category), @@ -29,7 +36,10 @@ CREATE TABLE IF NOT EXISTS products ( INDEX idx_is_active (is_active), INDEX idx_manufacturer (manufacturer), INDEX idx_source (source), - INDEX idx_show_on_website (show_on_website) + INDEX idx_show_on_website (show_on_website), + INDEX idx_ebay_store_cat1 (ebay_store_cat1_id), + INDEX idx_ebay_store_cat2 (ebay_store_cat2_id), + INDEX idx_ebay_store_cat3 (ebay_store_cat3_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Categories table diff --git a/database/schema.sqlite.sql b/database/schema.sqlite.sql index da52cc02..9679006e 100644 --- a/database/schema.sqlite.sql +++ b/database/schema.sqlite.sql @@ -27,6 +27,13 @@ CREATE TABLE IF NOT EXISTS products ( show_on_website INTEGER DEFAULT 1, -- 1 = visible, 0 = hidden warehouse_id INTEGER, is_active INTEGER DEFAULT 1, + -- eBay store category hierarchy (exact 3-level mapping) + ebay_store_cat1_id INTEGER, + ebay_store_cat1_name TEXT, + ebay_store_cat2_id INTEGER, + ebay_store_cat2_name TEXT, + ebay_store_cat3_id INTEGER, + ebay_store_cat3_name TEXT, created_at TEXT DEFAULT (datetime('now')), updated_at TEXT DEFAULT (datetime('now')), FOREIGN KEY (warehouse_id) REFERENCES warehouses(id) @@ -39,6 +46,9 @@ CREATE INDEX IF NOT EXISTS idx_products_is_active ON products(is_active); CREATE INDEX IF NOT EXISTS idx_products_manufacturer ON products(manufacturer); CREATE INDEX IF NOT EXISTS idx_products_source ON products(source); CREATE INDEX IF NOT EXISTS idx_products_show_on_website ON products(show_on_website); +CREATE INDEX IF NOT EXISTS idx_products_ebay_store_cat1 ON products(ebay_store_cat1_id); +CREATE INDEX IF NOT EXISTS idx_products_ebay_store_cat2 ON products(ebay_store_cat2_id); +CREATE INDEX IF NOT EXISTS idx_products_ebay_store_cat3 ON products(ebay_store_cat3_id); -- Categories table CREATE TABLE IF NOT EXISTS categories ( diff --git a/docs/EBAY_CATEGORY_MAPPING.md b/docs/EBAY_CATEGORY_MAPPING.md new file mode 100644 index 00000000..5d6dca06 --- /dev/null +++ b/docs/EBAY_CATEGORY_MAPPING.md @@ -0,0 +1,141 @@ +# eBay Store Category Mapping + +## Overview + +This document describes how the FAS e-commerce platform synchronizes and maintains exact 3-level category hierarchy from eBay store to ensure perfect alignment between the website and eBay store categories. + +## Architecture + +### Database Schema + +Products now store the complete eBay store category hierarchy with 6 new columns: + +- `ebay_store_cat1_id` (INT) - Level 1 category ID +- `ebay_store_cat1_name` (VARCHAR) - Level 1 category name (e.g., "MOTORCYCLE") +- `ebay_store_cat2_id` (INT) - Level 2 category ID +- `ebay_store_cat2_name` (VARCHAR) - Level 2 category name (e.g., "Honda") +- `ebay_store_cat3_id` (INT) - Level 3 category ID +- `ebay_store_cat3_name` (VARCHAR) - Level 3 category name (e.g., "CR500") + +All columns are indexed for efficient querying. + +### Category Hierarchy + +eBay stores support a 3-level category structure: + +1. **Level 1 (Top Level)**: Main product category (Motorcycle, ATV, Boat, etc.) +2. **Level 2 (Manufacturer)**: Product manufacturer or subcategory +3. **Level 3 (Model)**: Specific model or sub-subcategory + +### Synchronization Process + +When products are synced from eBay: + +1. The eBay API fetches store category IDs for each product +2. The full category hierarchy is extracted using `getStoreCategories()` +3. All 3 levels are stored in the database +4. The hierarchy is preserved exactly as defined in the eBay store + +## Implementation Details + +### Product Model Methods + +#### `extractStoreCategoryHierarchy($storeCategoryId, $storeCategory2Id, $ebayAPI)` +Extracts the complete 3-level hierarchy from eBay store categories. + +**Returns:** +```php +[ + 'cat1_id' => int|null, + 'cat1_name' => string|null, + 'cat2_id' => int|null, + 'cat2_name' => string|null, + 'cat3_id' => int|null, + 'cat3_name' => string|null +] +``` + +#### `getEbayStoreCategoryPath($product)` +Returns a formatted category path string. + +**Example:** +```php +"MOTORCYCLE > Honda > CR500" +``` + +#### `getEbayStoreCategoryArray($product)` +Returns categories organized by level. + +**Example:** +```php +[ + 1 => 'MOTORCYCLE', + 2 => 'Honda', + 3 => 'CR500' +] +``` + +### Sync Behavior + +**For New Products:** +- All 6 eBay store category columns are populated +- The `category` field is set based on the mapped website category +- Products maintain exact eBay store classification + +**For Existing Products:** +- eBay store category columns are ALWAYS updated to match current eBay store structure +- The `category` field is preserved (admin override) +- This ensures eBay store changes are reflected while respecting admin customizations + +## Display + +### Product Detail Page +Shows the full eBay store category path in the product details section: +``` +eBay Category: MOTORCYCLE > Honda > CR500 +``` + +### Admin Product List +Hover over the "eBay" badge to see the full category path in a tooltip. + +### Admin Product Edit +For products synced from eBay, displays the full category hierarchy with badges: +- L1: MOTORCYCLE +- L2: Honda +- L3: CR500 + +## Migration + +To add category support to an existing database: + +```bash +php database/migrate-add-ebay-store-categories.php +``` + +This migration: +- Adds all 6 new columns +- Creates indexes +- Is idempotent (safe to run multiple times) + +## Benefits + +1. **Exact Synchronization**: Website categories perfectly match eBay store structure +2. **Data Preservation**: Products maintain their exact eBay classification +3. **Automatic Updates**: Category changes in eBay store are reflected on next sync +4. **No Data Loss**: All 3 levels preserved, no information discarded +5. **Efficient Queries**: Indexed columns enable fast category-based searches + +## Future Enhancements + +Potential improvements: +- Filter products by eBay store categories on website +- Display category breadcrumbs on product listings +- Category-based product recommendations +- Store category analytics and reporting + +## Notes + +- The `category` field (motorcycle, atv, boat, etc.) remains for website navigation +- eBay store categories are for reference and sync accuracy +- Products without eBay categories (manual products) have null values in these columns +- The hierarchy supports partial categorization (1, 2, or 3 levels) diff --git a/includes/header.php b/includes/header.php index 2afc9650..5b8be77e 100644 --- a/includes/header.php +++ b/includes/header.php @@ -94,20 +94,8 @@ - -