ソースを参照

Replace OCMOD PHP injection with AJAX-based product tab loading

The OC3 product controller's setOutput call differs across versions,
causing the OCMOD PHP search to fail and abort the entire modification
(including the Twig injection).

New approach:
- OCMOD only patches product.twig (no PHP controller patching)
- Tab content is loaded via AJAX from a new productTab() controller action
- Saving still handled by the existing OC event hooks (eventProductSave)
- error="skip" on each OCMOD operation prevents one miss aborting the other

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Benjamin Harris 2 週間 前
コミット
00e906869f

+ 29 - 129
install.xml

@@ -2,155 +2,55 @@
 <modification>
     <name>Reverb Integration</name>
     <code>reverb</code>
-    <version>1.0.2</version>
+    <version>1.0.3</version>
     <author>Reverb OpenCart</author>
     <link>https://reverb.com/au/page/integrations</link>
 
     <!--
         ========================================================================
-        FILE 1: admin/controller/catalog/product.php
-        Inject Reverb data loading before the view is rendered,
-        and Reverb data saving after addProduct / editProduct calls.
-        ========================================================================
-    -->
-    <file path="admin/controller/catalog/product.php">
+        FILE: admin/view/template/catalog/product.twig
+        Injects a Reverb tab into the product edit page.
 
-        <operation>
-            <search><![CDATA[$this->response->setOutput($this->load->view('catalog/product', $data));]]></search>
-            <add position="before"><![CDATA[
-                $this->load->model('extension/module/reverb');
-                $reverb_product_id = isset($this->request->get['product_id']) ? (int)$this->request->get['product_id'] : 0;
-                $reverb_row = $this->model_extension_module_reverb->getProductMap($reverb_product_id);
-                $data['reverb_sync_enabled']   = $reverb_row ? (int)$reverb_row['sync_enabled']        : 0;
-                $data['reverb_condition_uuid']  = $reverb_row ? $reverb_row['condition_uuid']           : '';
-                $data['reverb_category_uuid']   = $reverb_row ? $reverb_row['reverb_category_uuid']     : '';
-                $data['reverb_listing_id']      = $reverb_row ? $reverb_row['reverb_listing_id']        : '';
-                $data['reverb_conditions']      = $this->model_extension_module_reverb->getListingConditions();
-                $data['reverb_categories']      = $this->model_extension_module_reverb->getReverbCategories();
-            ]]></add>
-        </operation>
+        Tab content is loaded via AJAX from extension/module/reverb/productTab
+        so no PHP controller patching is required. Saving is handled by the
+        OC event hooks registered in the module install() method.
 
-        <operation>
-            <search><![CDATA[$this->model_catalog_product->editProduct($this->request->get['product_id'], $this->request->post);]]></search>
-            <add position="after"><![CDATA[
-                if (isset($this->request->post['reverb_sync_enabled']) || isset($this->request->post['reverb_condition_uuid'])) {
-                    $this->load->model('extension/module/reverb');
-                    $this->model_extension_module_reverb->saveProductMap((int)$this->request->get['product_id'], array(
-                        'sync_enabled'         => isset($this->request->post['reverb_sync_enabled']) ? 1 : 0,
-                        'condition_uuid'       => isset($this->request->post['reverb_condition_uuid'])  ? $this->request->post['reverb_condition_uuid']  : '',
-                        'reverb_category_uuid' => isset($this->request->post['reverb_category_uuid'])  ? $this->request->post['reverb_category_uuid']  : '',
-                    ));
-                }
-            ]]></add>
-        </operation>
-
-        <operation>
-            <search><![CDATA[$product_id = $this->model_catalog_product->addProduct($this->request->post);]]></search>
-            <add position="after"><![CDATA[
-                if (isset($this->request->post['reverb_sync_enabled']) || isset($this->request->post['reverb_condition_uuid'])) {
-                    $this->load->model('extension/module/reverb');
-                    $this->model_extension_module_reverb->saveProductMap((int)$product_id, array(
-                        'sync_enabled'         => isset($this->request->post['reverb_sync_enabled']) ? 1 : 0,
-                        'condition_uuid'       => isset($this->request->post['reverb_condition_uuid'])  ? $this->request->post['reverb_condition_uuid']  : '',
-                        'reverb_category_uuid' => isset($this->request->post['reverb_category_uuid'])  ? $this->request->post['reverb_category_uuid']  : '',
-                    ));
-                }
-            ]]></add>
-        </operation>
-
-    </file>
-
-    <!--
-        ========================================================================
-        FILE 2: admin/view/template/catalog/product.twig
-        Add a "Reverb" tab to the product edit page.
-        Anchored on the SEO tab (always present in OC3) rather than Design tab
-        to avoid mismatches with customised themes. Content embedded inline to
-        avoid Twig include path resolution issues.
+        error="skip" on each operation means a single search miss will not
+        abort the other operation.
         ========================================================================
     -->
     <file path="admin/view/template/catalog/product.twig">
 
-        <operation>
+        <!-- Tab nav item — inserted after the SEO tab -->
+        <operation error="skip">
             <search><![CDATA[<li><a href="#tab-seo" data-toggle="tab">{{ tab_seo }}</a></li>]]></search>
             <add position="after"><![CDATA[<li><a href="#tab-reverb" data-toggle="tab">Reverb</a></li>]]></add>
         </operation>
 
-        <operation>
+        <!-- Tab pane placeholder + AJAX loader — inserted before the SEO pane -->
+        <operation error="skip">
             <search><![CDATA[<div class="tab-pane" id="tab-seo">]]></search>
             <add position="before"><![CDATA[
 <div class="tab-pane" id="tab-reverb">
-  <div class="panel panel-default" style="margin-top:15px;">
-    <div class="panel-body">
-
-      <div class="form-group">
-        <label class="col-sm-2 control-label">List on Reverb</label>
-        <div class="col-sm-10">
-          <input type="hidden" name="reverb_sync_enabled" value="0" />
-          <label class="checkbox-inline">
-            <input type="checkbox" name="reverb_sync_enabled" value="1"
-                   {% if reverb_sync_enabled %}checked="checked"{% endif %} />
-            Enable sync for this product
-          </label>
-          <p class="help-block">Enable to include this product in Reverb syncs. The product must also be in an allowed category (set in the Reverb module settings).</p>
-        </div>
-      </div>
-
-      <div class="form-group">
-        <label class="col-sm-2 control-label">Condition</label>
-        <div class="col-sm-4">
-          <select name="reverb_condition_uuid" class="form-control">
-            <option value="">-- Select Condition --</option>
-            {% for condition in reverb_conditions %}
-            <option value="{{ condition.uuid }}"
-              {% if reverb_condition_uuid == condition.uuid %}selected="selected"{% endif %}>
-              {{ condition.display_name }}
-            </option>
-            {% endfor %}
-          </select>
-          <p class="help-block">Required by Reverb. Describes the physical state of the item.</p>
-        </div>
-      </div>
-
-      <div class="form-group">
-        <label class="col-sm-2 control-label">Reverb Category</label>
-        <div class="col-sm-6">
-          <select name="reverb_category_uuid" class="form-control">
-            <option value="">-- Use category mapping default --</option>
-            {% for rc in reverb_categories %}
-            <option value="{{ rc.uuid }}"
-              {% if reverb_category_uuid == rc.uuid %}selected="selected"{% endif %}>
-              {{ rc.full_name|default(rc.name) }}
-            </option>
-            {% endfor %}
-          </select>
-          <p class="help-block">Leave blank to use the default mapping from the Reverb module settings.</p>
-        </div>
-      </div>
-
-      {% if reverb_listing_id %}
-      <div class="form-group">
-        <label class="col-sm-2 control-label">Reverb Listing</label>
-        <div class="col-sm-10">
-          <p class="form-control-static">
-            <a href="https://reverb.com/item/{{ reverb_listing_id }}" target="_blank" rel="noopener">
-              <i class="fa fa-external-link"></i> View on Reverb (ID: {{ reverb_listing_id }})
-            </a>
-          </p>
-        </div>
-      </div>
-      {% else %}
-      <div class="form-group">
-        <label class="col-sm-2 control-label">Reverb Listing</label>
-        <div class="col-sm-10">
-          <p class="form-control-static text-muted">Not yet synced to Reverb.</p>
-        </div>
-      </div>
-      {% endif %}
-
-    </div>
+  <div id="reverb-tab-content" style="padding:20px;">
+    <i class="fa fa-spinner fa-spin"></i> Loading&hellip;
   </div>
 </div>
+<script>
+(function () {
+  $(document).ready(function () {
+    var p  = new URLSearchParams(window.location.search);
+    var url = 'index.php?route=extension/module/reverb/productTab'
+            + '&user_token=' + encodeURIComponent(p.get('user_token') || '')
+            + '&product_id=' + encodeURIComponent(p.get('product_id') || 0);
+    $.get(url).done(function (html) {
+      $('#reverb-tab-content').html(html);
+    }).fail(function () {
+      $('#reverb-tab-content').html('<p class="text-danger"><i class="fa fa-exclamation-circle"></i> Could not load Reverb tab. Check that the Reverb module is installed correctly.</p>');
+    });
+  });
+}());
+</script>
 ]]></add>
         </operation>
 

+ 29 - 0
upload/admin/controller/extension/module/reverb.php

@@ -212,6 +212,35 @@ class ControllerExtensionModuleReverb extends Controller {
         $this->response->setOutput(json_encode($json));
     }
 
+    // -------------------------------------------------------------------------
+    // Per-product Reverb tab (AJAX — loaded into product edit page)
+    // URL: extension/module/reverb/productTab?product_id=N
+    // -------------------------------------------------------------------------
+
+    public function productTab() {
+        if (!$this->user->isLogged()) {
+            $this->response->setOutput('');
+            return;
+        }
+
+        $this->load->language('extension/module/reverb');
+        $this->load->model('extension/module/reverb');
+
+        $product_id = isset($this->request->get['product_id']) ? (int)$this->request->get['product_id'] : 0;
+        $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(),
+        ];
+
+        $this->response->setOutput($this->load->view('extension/module/reverb_product', $data));
+    }
+
     // -------------------------------------------------------------------------
     // Reverb categories (AJAX — for category mapping dropdowns)
     // -------------------------------------------------------------------------

+ 61 - 72
upload/admin/view/template/extension/module/reverb_product.twig

@@ -1,86 +1,75 @@
 {#
-  Per-product Reverb tab content.
-  Injected into admin/view/template/catalog/product.twig via reverb.ocmod.xml.
-  Variables populated by the OCMOD PHP patch in admin/controller/catalog/product.php.
+  Per-product Reverb tab content — rendered as an AJAX fragment by
+  extension/module/reverb/productTab and injected into #reverb-tab-content.
+  The tab-pane wrapper is provided by the OCMOD injection in install.xml.
 #}
-<div class="tab-pane" id="tab-reverb">
-  <div class="panel panel-default" style="margin-top: 15px;">
-    <div class="panel-body">
+<div class="panel panel-default" style="margin-top:15px;">
+  <div class="panel-body">
 
-      {# Sync toggle #}
-      <div class="form-group">
-        <label class="col-sm-2 control-label">List on Reverb</label>
-        <div class="col-sm-10">
-          {# Hidden field ensures unchecked checkbox sends 0 #}
-          <input type="hidden" name="reverb_sync_enabled" value="0" />
-          <label class="checkbox-inline">
-            <input type="checkbox" name="reverb_sync_enabled" value="1" id="reverb-sync-enabled"
-                   {% if reverb_sync_enabled %}checked{% endif %} />
-            Enable sync for this product
-          </label>
-          <p class="help-block">
-            Enable to include this product in Reverb syncs. The product must also be
-            in an allowed category (configured in the Reverb module settings).
-          </p>
-        </div>
+    <div class="form-group">
+      <label class="col-sm-2 control-label">List on Reverb</label>
+      <div class="col-sm-10">
+        <input type="hidden" name="reverb_sync_enabled" value="0" />
+        <label class="checkbox-inline">
+          <input type="checkbox" name="reverb_sync_enabled" value="1"
+                 {% if reverb_sync_enabled %}checked="checked"{% endif %} />
+          Enable sync for this product
+        </label>
+        <p class="help-block">Enable to include this product in Reverb syncs. The product must also be in an allowed category (configured in the Reverb module settings).</p>
       </div>
+    </div>
 
-      {# Condition #}
-      <div class="form-group">
-        <label class="col-sm-2 control-label">Condition</label>
-        <div class="col-sm-4">
-          <select name="reverb_condition_uuid" id="reverb-condition-uuid" class="form-control">
-            <option value="">-- Select Condition --</option>
-            {% for condition in reverb_conditions %}
-            <option value="{{ condition.uuid }}"
-              {% if reverb_condition_uuid == condition.uuid %}selected{% endif %}>
-              {{ condition.display_name }}
-            </option>
-            {% endfor %}
-          </select>
-          <p class="help-block">Required by Reverb. Describes the physical state of the item.</p>
-        </div>
+    <div class="form-group">
+      <label class="col-sm-2 control-label">Condition</label>
+      <div class="col-sm-4">
+        <select name="reverb_condition_uuid" class="form-control">
+          <option value="">-- Select Condition --</option>
+          {% for condition in reverb_conditions %}
+          <option value="{{ condition.uuid }}"
+            {% if reverb_condition_uuid == condition.uuid %}selected="selected"{% endif %}>
+            {{ condition.display_name }}
+          </option>
+          {% endfor %}
+        </select>
+        <p class="help-block">Required by Reverb. Describes the physical state of the item.</p>
       </div>
+    </div>
 
-      {# Reverb Category override #}
-      <div class="form-group">
-        <label class="col-sm-2 control-label">Reverb Category</label>
-        <div class="col-sm-6">
-          <select name="reverb_category_uuid" id="reverb-category-uuid" class="form-control">
-            <option value="">-- Use category mapping default --</option>
-            {% for rc in reverb_categories %}
-            <option value="{{ rc.uuid }}"
-              {% if reverb_category_uuid == rc.uuid %}selected{% endif %}>
-              {{ rc.full_name|default(rc.name) }}
-            </option>
-            {% endfor %}
-          </select>
-          <p class="help-block">Leave blank to use the default mapping from the Reverb module settings.</p>
-        </div>
+    <div class="form-group">
+      <label class="col-sm-2 control-label">Reverb Category</label>
+      <div class="col-sm-6">
+        <select name="reverb_category_uuid" class="form-control">
+          <option value="">-- Use category mapping default --</option>
+          {% for rc in reverb_categories %}
+          <option value="{{ rc.uuid }}"
+            {% if reverb_category_uuid == rc.uuid %}selected="selected"{% endif %}>
+            {{ rc.full_name|default(rc.name) }}
+          </option>
+          {% endfor %}
+        </select>
+        <p class="help-block">Leave blank to use the default mapping from the Reverb module settings.</p>
       </div>
+    </div>
 
-      {# Reverb listing link (read-only, shown once synced) #}
-      {% if reverb_listing_id %}
-      <div class="form-group">
-        <label class="col-sm-2 control-label">Reverb Listing</label>
-        <div class="col-sm-10">
-          <p class="form-control-static">
-            <a href="https://reverb.com/item/{{ reverb_listing_id }}" target="_blank" rel="noopener">
-              <i class="fa fa-external-link"></i>
-              View on Reverb (ID: {{ reverb_listing_id }})
-            </a>
-          </p>
-        </div>
+    {% if reverb_listing_id %}
+    <div class="form-group">
+      <label class="col-sm-2 control-label">Reverb Listing</label>
+      <div class="col-sm-10">
+        <p class="form-control-static">
+          <a href="https://reverb.com/item/{{ reverb_listing_id }}" target="_blank" rel="noopener">
+            <i class="fa fa-external-link"></i> View on Reverb (ID: {{ reverb_listing_id }})
+          </a>
+        </p>
       </div>
-      {% else %}
-      <div class="form-group">
-        <label class="col-sm-2 control-label">Reverb Listing</label>
-        <div class="col-sm-10">
-          <p class="form-control-static text-muted">Not yet synced to Reverb.</p>
-        </div>
+    </div>
+    {% else %}
+    <div class="form-group">
+      <label class="col-sm-2 control-label">Reverb Listing</label>
+      <div class="col-sm-10">
+        <p class="form-control-static text-muted">Not yet synced to Reverb.</p>
       </div>
-      {% endif %}
-
     </div>
+    {% endif %}
+
   </div>
 </div>