HikaShop checkout blocks lost on AJAX refresh: ob_start leak in the hikashop plugin


  • Default avatar
    interGen    
     6 days ago
    0

    Hi Seyi,

    We run AwoCoupon on a HikaShop store and traced a checkout display bug back to the AwoCoupon HikaShop plugin. Small patch below, verified on our store.

    The issue

    plg_hikashop_awocoupon opens an output buffer in onHikashopBeforeDisplayView on every checkout render:

    public function onHikashopBeforeDisplayView( & $view ){

        if ( @ $view->ctrl == 'checkout' ) {

            ob_start();

        }

    }

    The matching onHikashopAfterDisplayView only calls ob_get_clean() on the narrow path that injects the coupon field. It has four early returns that skip it: init_awocoupon() failing, no coupon session, more than one processed coupon, and a non-auto coupon. Each of those returns leaks the buffer, and everything HikaShop echoed between the two events is stranded inside it.

    On a normal full page load the leaked buffer usually gets flushed at request end, so nothing looks wrong. HikaShop's checkout AJAX block refreshes are different: checkout/showblock captures the block HTML with its own output buffer (hikashop_getHTML), and the leaked buffers stack on top of that capture. ob_get_clean() then harvests the wrong, empty buffer and the block's markup is silently discarded.

    Real-world scenario

    On our store the visible symptom was on the checkout Fields step: a customer or staff member changes the shipping method, HikaShop fires checkout.shipping.changed and re-fetches the custom fields block via AJAX, and the whole Additional Information section disappears from the page. The AJAX response contains only script tags, zero markup. Reloading the page brings the fields back, which is why this can go unnoticed for a long time. Whether the leak becomes visible depends on how many times the Before event fires during a request (it fires more than once through HikaShop's nested view pipeline in some setups), but the unbalanced buffer itself happens on any store where the After handler takes one of its early returns, for example any checkout without an active auto coupon.

    We confirmed causality three ways: instrumenting the event dispatcher showed this listener raising the output buffer level without closing it, disabling the plugin restored the AJAX render, and the patch below restored it with the plugin enabled.

    Environment where reproduced

    • AwoCoupon: 4.0.0 installed; also verified the same code is present in 4.1.0.1 (fresh download)
    • HikaShop: 6.5.0 Business
    • Joomla: 6.1.2
    • PHP: 8.3.32

    Proposed fix

    Close the buffer unconditionally at the top of onHikashopAfterDisplayView and re-emit the captured content on every early return. The coupon-field injection logic is unchanged; only the buffer handling moves. Lines starting with + are added, the two lines starting with - are removed:

       if ( @ $view->ctrl != 'checkout' ) {

           return;

       }

    +  // Always close the output buffer opened in onHikashopBeforeDisplayView

    +  // and keep its content. Returning below without closing it leaks the

    +  // buffer, and the checkout markup captured so far is silently discarded

    +  // (breaks HikaShop checkout AJAX block refreshes, e.g. the fields block).

    +  $html = ob_get_clean();

       if ( ! $this->init_awocoupon() ) {

    +      echo $html;

           return;

       }

       if ( ! AC()->param->get( 'enable_multiple_coupon' ) ) {

           $coupon_session = AC()->storediscount->get_coupon_session();

           if ( empty( $coupon_session ) ) {

    +          echo $html;

               return;

           }

           if ( ! empty( $coupon_session->processed_coupons ) && count( $coupon_session->processed_coupons ) > 1 ) {

    +          echo $html;

               return;

           }

           $coupon = current( $coupon_session->processed_coupons );

           if ( $coupon->isauto == false ) {

    +          echo $html;

               return;

           }

       }

    -  $html = ob_get_clean();

    -

    An alternative shape that avoids buffering entirely would be to only ob_start in the Before handler when the After handler is actually going to inject (auto coupon active, single coupon mode), but that spreads the condition across two events, so the always-close approach felt safer.

    Tested against

    Applied to our store's copy of the plugin: the shipping-method change no longer empties the fields block, and the checkout AJAX responses contain the full block markup again. The coupon field injection still works when an auto coupon is active. The same file in the exj6 legacy variant of the plugin looks like it has the same pattern and would need the same treatment.

    Thanks for taking a look. Happy to adjust the patch or provide more detail on the repro if useful.

    Brian

  • Your avatar
    seyi    
     2 days ago
    +1

    Hello,

    Thanks for taking the time to write out the issue.  I have reviewed it and will be implementing your solution of always closing the buffer in the next update.
  • Default avatar
    interGen    
     5 hours ago
    0

    Appreciate the quick turnaround, Seyi. Glad the write-up was useful and that the always-close-the-buffer approach works for you. Happy to test against a pre-release build if that's helpful before it ships. Thanks for maintaining a solid plugin.