瀏覽代碼

Sync Images Fix

Benjamin Harris 2 周之前
父節點
當前提交
8dcceb54a5

+ 21 - 7
upload/admin/controller/extension/module/reverb.php

@@ -159,6 +159,8 @@ class ControllerExtensionModuleReverb extends Controller {
             'sync_enabled'         => (int)(bool)$this->request->post['reverb_sync_enabled'],
             'condition_uuid'       => $this->request->post['reverb_condition_uuid'] ?? '',
             'reverb_category_uuid' => $this->request->post['reverb_category_uuid'] ?? '',
+            'handmade'             => (int)(bool)($this->request->post['reverb_handmade'] ?? 0),
+            'upc_does_not_apply'   => (int)(bool)($this->request->post['reverb_upc_does_not_apply'] ?? 1),
         ]);
     }
 
@@ -250,12 +252,14 @@ class ControllerExtensionModuleReverb extends Controller {
         $reverb_row = $this->model_extension_module_reverb->getProductMap($product_id);
 
         $data = [
-            'reverb_sync_enabled'   => $reverb_row ? (int)$reverb_row['sync_enabled']    : 0,
-            'reverb_condition_uuid' => $reverb_row ? $reverb_row['condition_uuid']        : '',
-            'reverb_category_uuid'  => $reverb_row ? $reverb_row['reverb_category_uuid'] : '',
-            'reverb_listing_id'     => $reverb_row ? $reverb_row['reverb_listing_id']     : '',
-            'reverb_conditions'     => $this->model_extension_module_reverb->getListingConditions(),
-            'reverb_categories'     => $this->model_extension_module_reverb->getReverbCategories(),
+            'reverb_sync_enabled'      => $reverb_row ? (int)$reverb_row['sync_enabled']      : 0,
+            'reverb_condition_uuid'    => $reverb_row ? $reverb_row['condition_uuid']          : '',
+            'reverb_category_uuid'     => $reverb_row ? $reverb_row['reverb_category_uuid']   : '',
+            'reverb_listing_id'        => $reverb_row ? $reverb_row['reverb_listing_id']       : '',
+            'reverb_handmade'          => $reverb_row ? (int)$reverb_row['handmade']           : 0,
+            'reverb_upc_does_not_apply'=> $reverb_row ? (int)$reverb_row['upc_does_not_apply'] : 1,
+            'reverb_conditions'        => $this->model_extension_module_reverb->getListingConditions(),
+            'reverb_categories'        => $this->model_extension_module_reverb->getReverbCategories(),
         ];
 
         $this->response->setOutput($this->load->view('extension/module/reverb_product', $data));
@@ -317,13 +321,23 @@ class ControllerExtensionModuleReverb extends Controller {
     }
 
     private function buildSettings() {
+        // config_url is often empty in OC3; fall back to the catalog constants from admin/config.php
+        $store_url = $this->config->get('config_url');
+        if (empty($store_url)) {
+            if (defined('HTTPS_CATALOG')) {
+                $store_url = HTTPS_CATALOG;
+            } elseif (defined('HTTP_CATALOG')) {
+                $store_url = HTTP_CATALOG;
+            }
+        }
+
         return [
             'api_token'              => $this->config->get('module_reverb_api_token'),
             'sync_direction'         => $this->config->get('module_reverb_sync_direction') ?? 'push',
             'shipping_domestic'      => $this->config->get('module_reverb_shipping_domestic') ?? '0',
             'shipping_international' => $this->config->get('module_reverb_shipping_international') ?? '0',
             'currency'               => $this->config->get('config_currency') ?? 'AUD',
-            'store_url'              => $this->config->get('config_url') ?? '',
+            'store_url'              => $store_url ?? '',
         ];
     }
 

+ 62 - 15
upload/admin/model/extension/module/reverb.php

@@ -13,10 +13,13 @@ class ModelExtensionModuleReverb extends Model {
                 `sync_enabled`         TINYINT(1) NOT NULL DEFAULT 0,
                 `condition_uuid`       VARCHAR(64) NOT NULL DEFAULT '',
                 `reverb_category_uuid` VARCHAR(64) NOT NULL DEFAULT '',
+                `handmade`             TINYINT(1) NOT NULL DEFAULT 0,
+                `upc_does_not_apply`   TINYINT(1) NOT NULL DEFAULT 1,
                 `last_synced_at`       DATETIME NULL DEFAULT NULL,
                 PRIMARY KEY (`product_id`)
             ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
         ");
+        $this->migrate();
 
         $this->db->query("
             CREATE TABLE IF NOT EXISTS `" . DB_PREFIX . "reverb_sync_log` (
@@ -38,11 +41,32 @@ class ModelExtensionModuleReverb extends Model {
         $this->db->query("DROP TABLE IF EXISTS `" . DB_PREFIX . "reverb_sync_log`");
     }
 
+    public function migrate() {
+        static $done = false;
+        if ($done) return;
+        $done = true;
+        $this->addColumnIfMissing(DB_PREFIX . 'reverb_product_map', 'handmade',           'TINYINT(1) NOT NULL DEFAULT 0');
+        $this->addColumnIfMissing(DB_PREFIX . 'reverb_product_map', 'upc_does_not_apply', 'TINYINT(1) NOT NULL DEFAULT 1');
+    }
+
+    private function addColumnIfMissing($table, $column, $definition) {
+        $r = $this->db->query("
+            SELECT COUNT(*) AS cnt FROM information_schema.COLUMNS
+            WHERE TABLE_SCHEMA = DATABASE()
+            AND TABLE_NAME    = '" . $this->db->escape($table) . "'
+            AND COLUMN_NAME   = '" . $this->db->escape($column) . "'
+        ");
+        if (empty($r->row['cnt'])) {
+            $this->db->query("ALTER TABLE `" . $table . "` ADD COLUMN `" . $column . "` " . $definition);
+        }
+    }
+
     // -------------------------------------------------------------------------
     // Product map CRUD
     // -------------------------------------------------------------------------
 
     public function getProductMap($product_id) {
+        $this->migrate();
         $query = $this->db->query("
             SELECT * FROM `" . DB_PREFIX . "reverb_product_map`
             WHERE `product_id` = '" . (int)$product_id . "'
@@ -51,6 +75,7 @@ class ModelExtensionModuleReverb extends Model {
     }
 
     public function saveProductMap($product_id, array $data) {
+        $this->migrate();
         $existing = $this->getProductMap($product_id);
 
         $sync_enabled         = isset($data['sync_enabled'])         ? (int)(bool)$data['sync_enabled']                         : 0;
@@ -58,13 +83,17 @@ class ModelExtensionModuleReverb extends Model {
         $reverb_category_uuid = isset($data['reverb_category_uuid']) ? $this->db->escape($data['reverb_category_uuid'])          : '';
         $reverb_listing_id    = isset($data['reverb_listing_id'])    ? $this->db->escape($data['reverb_listing_id'])             : '';
         $last_synced_at       = isset($data['last_synced_at'])       ? "'" . $this->db->escape($data['last_synced_at']) . "'"    : 'NULL';
+        $handmade             = isset($data['handmade'])             ? (int)(bool)$data['handmade']                             : 0;
+        $upc_does_not_apply   = isset($data['upc_does_not_apply'])   ? (int)(bool)$data['upc_does_not_apply']                   : 1;
 
         if ($existing) {
             $this->db->query("
                 UPDATE `" . DB_PREFIX . "reverb_product_map`
                 SET `sync_enabled`         = $sync_enabled,
                     `condition_uuid`       = '$condition_uuid',
-                    `reverb_category_uuid` = '$reverb_category_uuid'"
+                    `reverb_category_uuid` = '$reverb_category_uuid',
+                    `handmade`             = $handmade,
+                    `upc_does_not_apply`   = $upc_does_not_apply"
                 . (!empty($reverb_listing_id) ? ", `reverb_listing_id` = '$reverb_listing_id'" : '')
                 . (isset($data['last_synced_at']) ? ", `last_synced_at` = $last_synced_at" : '')
                 . " WHERE `product_id` = '" . (int)$product_id . "'"
@@ -72,13 +101,16 @@ class ModelExtensionModuleReverb extends Model {
         } else {
             $this->db->query("
                 INSERT INTO `" . DB_PREFIX . "reverb_product_map`
-                (`product_id`, `sync_enabled`, `condition_uuid`, `reverb_category_uuid`, `reverb_listing_id`, `last_synced_at`)
+                (`product_id`, `sync_enabled`, `condition_uuid`, `reverb_category_uuid`,
+                 `reverb_listing_id`, `handmade`, `upc_does_not_apply`, `last_synced_at`)
                 VALUES (
                     '" . (int)$product_id . "',
                     $sync_enabled,
                     '$condition_uuid',
                     '$reverb_category_uuid',
                     '$reverb_listing_id',
+                    $handmade,
+                    $upc_does_not_apply,
                     $last_synced_at
                 )
             ");
@@ -109,7 +141,9 @@ class ModelExtensionModuleReverb extends Model {
 
         $query = $this->db->query("
             SELECT p.product_id, pd.name, pd.description, p.model, p.price, p.quantity, p.image,
-                   r.reverb_listing_id, r.condition_uuid, r.reverb_category_uuid
+                   r.reverb_listing_id, r.condition_uuid, r.reverb_category_uuid,
+                   r.handmade, r.upc_does_not_apply,
+                   m.name AS manufacturer
             FROM `" . DB_PREFIX . "product` p
             INNER JOIN `" . DB_PREFIX . "product_description` pd
                 ON pd.product_id = p.product_id AND pd.language_id = '" . (int)$this->config->get('config_language_id') . "'
@@ -117,6 +151,8 @@ class ModelExtensionModuleReverb extends Model {
                 ON ptc.product_id = p.product_id AND ptc.category_id IN ($ids)
             INNER JOIN `" . DB_PREFIX . "reverb_product_map` r
                 ON r.product_id = p.product_id AND r.sync_enabled = 1
+            LEFT JOIN `" . DB_PREFIX . "manufacturer` m
+                ON m.manufacturer_id = p.manufacturer_id
             WHERE p.status = 1
             GROUP BY p.product_id
         ");
@@ -265,6 +301,29 @@ class ModelExtensionModuleReverb extends Model {
         $api     = $this->getApi();
         $payload = ProductMapper::toReverb($product, $reverb_data, $settings);
 
+        // Photos are plain URL strings sent inside the listing payload (Reverb API v3)
+        $store_url = $settings['store_url'] ?? '';
+        $images    = $this->getProductImages($product['product_id']);
+
+        if (!empty($store_url) && !empty($images)) {
+            $base   = rtrim($store_url, '/') . '/image/';
+            $photos = [];
+            foreach ($images as $path) {
+                if (!empty($path)) {
+                    $photos[] = $base . ltrim($path, '/');
+                }
+            }
+            if (!empty($photos)) {
+                $payload['photos']  = $photos;
+                $payload['publish'] = true;
+                $this->log($product['product_id'], 'push', 'success', 'Including ' . count($photos) . ' photo(s): ' . implode(', ', $photos));
+            }
+        } elseif (empty($store_url)) {
+            $this->log($product['product_id'], 'push', 'error', 'Photo upload skipped: store URL not configured (check System > Settings > Store URL)');
+        } else {
+            $this->log($product['product_id'], 'push', 'error', 'No images found for this product in OpenCart.');
+        }
+
         $listing_id = !empty($reverb_data['reverb_listing_id']) ? $reverb_data['reverb_listing_id'] : null;
 
         if ($listing_id) {
@@ -278,18 +337,6 @@ class ModelExtensionModuleReverb extends Model {
             $this->updateListingId($product['product_id'], $listing_id);
         }
 
-        // Upload images
-        $images    = $this->getProductImages($product['product_id']);
-        $store_url = $settings['store_url'] ?? '';
-        foreach (ProductMapper::buildPhotoPayloads($images, $store_url) as $photo) {
-            try {
-                $api->uploadPhoto($listing_id, $photo['image_url']);
-            } catch (Exception $e) {
-                // Non-fatal: log but continue
-                $this->log($product['product_id'], 'push', 'error', 'Photo upload failed: ' . $e->getMessage());
-            }
-        }
-
         return $listing_id;
     }
 

+ 31 - 0
upload/admin/view/template/extension/module/reverb_product.twig

@@ -51,6 +51,37 @@
       </div>
     </div>
 
+    <div class="form-group">
+      <label class="col-sm-2 control-label">Is this handmade?</label>
+      <div class="col-sm-10">
+        <input type="hidden" name="reverb_handmade" value="0" />
+        <label class="checkbox-inline">
+          <input type="checkbox" name="reverb_handmade" value="1"
+                 {% if reverb_handmade %}checked="checked"{% endif %} />
+          Yes, this item is handmade
+        </label>
+        <p class="help-block">Check if this is a handmade or custom-built item.</p>
+      </div>
+    </div>
+
+    <div class="form-group">
+      <label class="col-sm-2 control-label">Universal Product Code (UPC)</label>
+      <div class="col-sm-10">
+        <label class="radio-inline">
+          <input type="radio" name="reverb_upc_does_not_apply" value="0"
+                 {% if not reverb_upc_does_not_apply %}checked="checked"{% endif %} />
+          This item has a UPC or EAN
+        </label>
+        &nbsp;&nbsp;
+        <label class="radio-inline">
+          <input type="radio" name="reverb_upc_does_not_apply" value="1"
+                 {% if reverb_upc_does_not_apply %}checked="checked"{% endif %} />
+          Exempt from UPC or EAN
+        </label>
+        <p class="help-block">Most used and vintage items are exempt from UPC requirements.</p>
+      </div>
+    </div>
+
     {% if reverb_listing_id %}
     <div class="form-group">
       <label class="col-sm-2 control-label">Reverb Listing</label>

+ 16 - 3
upload/system/library/reverb/ProductMapper.php

@@ -12,19 +12,32 @@ class ProductMapper {
     public static function toReverb(array $product, array $reverb_data, array $settings) {
         $currency = !empty($settings['currency']) ? $settings['currency'] : 'AUD';
 
+        // Convert HTML description to clean plain text
+        $desc = $product['description'];
+        $desc = preg_replace('/<br\s*\/?>/i', "\n", $desc);
+        $desc = preg_replace('/<\/p>/i', "\n\n", $desc);
+        $desc = strip_tags($desc);
+        $desc = html_entity_decode($desc, ENT_QUOTES | ENT_HTML5, 'UTF-8');
+        $desc = trim(preg_replace('/\n{3,}/', "\n\n", $desc));
+
         $payload = [
-            'title'         => $product['name'],
-            'description'   => strip_tags($product['description']),
+            'title'         => html_entity_decode($product['name'], ENT_QUOTES | ENT_HTML5, 'UTF-8'),
+            'description'   => $desc,
             'sku'           => $product['model'],
             'inventory'     => max(0, (int)$product['quantity']),
             'has_inventory' => true,
-            'publish'       => true,
+            'handmade'      => !empty($reverb_data['handmade']),
+            'upc_does_not_apply' => !isset($reverb_data['upc_does_not_apply']) || (bool)$reverb_data['upc_does_not_apply'],
             'price'         => [
                 'amount'   => number_format((float)$product['price'], 2, '.', ''),
                 'currency' => $currency,
             ],
         ];
 
+        if (!empty($product['manufacturer'])) {
+            $payload['make'] = $product['manufacturer'];
+        }
+
         if (!empty($reverb_data['condition_uuid'])) {
             $payload['condition'] = ['uuid' => $reverb_data['condition_uuid']];
         }

+ 1 - 1
upload/system/library/reverb/ReverbApi.php

@@ -34,7 +34,7 @@ class ReverbApi {
     }
 
     public function uploadPhoto($listing_id, $image_url) {
-        return $this->request('POST', '/listings/' . urlencode($listing_id) . '/photos', [], [
+        return $this->request('POST', '/my/listings/' . urlencode($listing_id) . '/photos', [], [
             'image_url' => $image_url,
         ]);
     }