Zen-Cart: Gimme a cart and hold everything else

It’s been 3 years since I wrote the little tutorial “Using Zen-Cart as a “Cart Only” System” and a lot has changed. The original tutorial was written for Zen-Cart 1.38 BEFORE they implemented the security token system for form submits. Since I moved to OpenCart as the shopping cart back-end for my main web project, I never bothered updating the Zen-Cart tutorial as I didn’t have the time or inclination to do so. However that changed recently after upgrading to Zen-Cart 1.51 for a recent website project where I encountered all the issues that commenters have pointed out over the last few years. I also wanted to expand upon it a bit and provide a better demonstration of the technique in action. So, let’s begin by outlining the issue.

The old method used remote form submits to perform tasks against the Zen-Cart install. So, I could have a form on a separate HTML page where its form URL pointed to my Zen-Cart install that looks something like this:

<form action="/zencart/index.php?action=add_product" enctype="multipart/form-data" method="post">
  <input type="text" maxlength="6" name="cart_quantity" size="4" value="1" />
  <input type="hidden" name="products_id" value="351" />
  <input title="Add to Cart" role="button" type="submit" alt="Add to Cart" value="Add to Cart" />
</form>

This code would show a quantity box and an “Add to Cart” submit button that would then add the quantity amount to your cart in the Zen-Cart install located in the “/zencart” directory. Pretty simple.

With the introduction of security tokens, this method will no longer work. You will simply get an “Expired Session” error. In order for the form above to work, we need that token so that it looks like this:

<form action="/zencart/index.php?action=add_product" enctype="multipart/form-data" method="post">
  <input type="hidden" name="securityToken" value="b7f5a1a7fae9c22acb0e22869da97124">
  <input type="text" maxlength="6" name="cart_quantity" size="4" value="1" />
  <input type="hidden" name="products_id" value="351" />
  <input title="Add to Cart" role="button" type="submit" alt="Add to Cart" value="Add to Cart" />
</form>

The issue is that our HTML page has no way of knowing what the token value should be. It is dynamically generated by Zen-Cart at the time of the page load and there is nothing we can do about that (without modifying some core Zen-Cart code). So, what can we do? We need to utilize JavaScript and jQuery to get this token. However, before that we need to get our iframe set and make some modifications to a Zen-Cart template.

Insert the Iframe

The iframe is the most important part of all this. Without it, none of this works. It’s our window to Zen-Cart.

In your page footer, paste in the following:

<iframe width="0" height="0" style="display:none;" name="cartframe" src="/path/to/your/cart/index.php?main_page=shopping_cart" id="cart_frame"></iframe>

Make note of your cart root path and replace accordingly. The URL being used is the shopping page of Zen-Cart. This will provide us with all the HTML information we need to pull the data into our external HTML page. The other reason for the iframe is that is keeps the session active as the user browses your site. This prevents the cart contents expiring prematurely.

Please note, This method only works if your Zen-Cart installation is on the SAME DOMAIN as your HTML/CMS pages. A small exception is that you can use sub-domains (with a small modification to your Zen-Cart html_header template).

This is GOOD

HTML/CMS domain = www.domain.com
Cart domain = www.domain.com/cartparth OR subdomain.domain.com

This is BAD

HTML/CMS domain = www.domain.com
Cart domain = www.someotherdomain.com

For testing purposes, you can leave off the width, height, and the style for now so you can see your iframe and what’s going inside it as you test.

Modify your Zen-Cart Templates

To get the most out of this, we need Zen-Cart to generate HTML that we can use on our own terms. To do this, I add a “hidden” section to our Zen-Cart shopping cart template that we pull from. This allows us to add divs, classes, IDs and make other changes without affecting the visible cart on our checkout page.

To do this, use a text editor, browse to your Zen-Cart install’s Shopping Cart template. The default path is “/yourzencartroot/includes/templates/template_default/templates/tpl_shopping_cart_default.php”.

In this file, find the comment <!--eof shopping cart buttons--> then a couple lines down, underneath <br class="clearboth" /> paste the following:

<!--START EXTERNAL CART -->
<div id="externalCart" style="display: none;">
  <?php echo zen_draw_form('cart_quantity', zen_href_link(FILENAME_SHOPPING_CART, 'action=update_product', $request_type)); ?>
  <h2 id="cartEmptyText" style="display: none;"></h2>
  <?php if (!empty($totalsDisplay)) { ?>
    <div><?php echo $totalsDisplay; ?></div>
    <br/>
  <?php } ?>
  <table border="0" width="100%" cellspacing="0" cellpadding="0" id="cartContentsDisplay">
  <tr>
    <th scope="col" id="scQuantityHeading"><?php echo TABLE_HEADING_QUANTITY; ?></th>
    <th scope="col" id="scProductsHeading"><?php echo TABLE_HEADING_PRODUCTS; ?></th>
    <th scope="col" id="scUnitHeading"><?php echo TABLE_HEADING_PRICE; ?></th>
    <th scope="col" id="scTotalHeading"><?php echo TABLE_HEADING_TOTAL; ?></th>
    <th scope="col" id="scRemoveHeading">Del</th>
  </tr>
  <!-- Loop through all products /-->
  <?php
  foreach ($productArray as $product) {
    if (strstr($product['productsName'], 'Package')) $manclass = "package";
  ?>
  <tr>
    <td>
      <?php if ($product['flagShowFixedQuantity']) { ?>
        N/A <input type="hidden" value="1" name="cart_quantity[]">
      <?php } else { ?>
        <?php echo $product['quantityField'] ?>
        <span><?php echo $product['flagStockCheck'] ?></span>
        <?php echo $product['showMinUnits'] ?>
      <?php } ?>
      <input type="hidden" value="<?php echo $product['id']; ?>" name="products_id[]">
    </td>
    <td>
      <span id="cartProdTitle">
        <?php echo $product['productsName'] ?>
        <span><?php echo $product['flagStockCheck'] ?></span>
      </span><br/>
      <?php echo $product['attributeHiddenField']; ?>
      <?php if (isset($product['attributes']) && is_array($product['attributes'])) { ?>
      <div>
        <ul>
          <?php reset($product['attributes']); ?>
          <?php foreach ($product['attributes'] as $option => $value) { ?>
            <li><?php echo $value['products_options_name'] . TEXT_OPTION_DIVIDER . nl2br($value['products_options_values_name']); ?></li>
          <?php } ?>
        </ul>
      </div>
      <?php } ?>
    </td>
    <td><?php echo $product['productsPriceEach']; ?></td>
    <td><?php echo $product['productsPrice']; ?></td>
    <td><?php
      if ($product['checkBoxDelete'] ) {
        echo zen_draw_checkbox_field('cart_delete[]', $product['id']);
      }
    ?></td>
  </tr>
  <?php } // end foreach ($productArray as $product) ?>
  <!-- Finished loop through all products /-->
  </table>
  <div id="cartSubTotal"><span><?php echo SUB_TITLE_SUB_TOTAL; ?></span> <?php echo $cartShowTotal; ?></div>
  <br/>
  <!--bof shopping cart buttons-->
  <input type="submit" value="Update Cart" alt="Update Cart" title="Update Cart" role="button" />
  <div><?php echo '<a target="_blank" href="/zencarttest/index.php?main_page=checkout_shipping">' .'Checkout Now</a>'; ?></div>
  <!--eof shopping cart buttons-->
  </form>
</div>
<!--END EXTERNAL CART-->
<br class="clearBoth" />

This gives us a good starting point and an area to customize to match our needs.

Next, scroll down toward the end and find:

<h2 id="cartEmptyText"><?php echo TEXT_CART_EMPTY; ?></h2>

Underneath that, paste in this:

<?php echo zen_draw_form('cart_quantity', zen_href_link(FILENAME_SHOPPING_CART, 'action=update_product', $request_type)); ?>
<!-- This is here to generate the Security token that is pulled into our HTML page -->
</form>

Because our form isn’t generated when the cart is empty, we won’t have a security token to grab. This fixes that by generating a blank form with the token.

You can save this file now.

If you’re going to be using a subdomain to host your Zen-Cart install, you need to make a small addition to the html_header.php file. The default location is: “/yourzencartroot/includes/templates/template_default/common/html_header.php”.

In here, add this after the :

<script>document.domain = 'yourdomain.com';</script>

Save this and your done editing your Zen-Cart Templates for now.

Building our “Add to Cart” buttons and Local HTML cart.

Now we are going to make an “Add to Cart” button and an area to display the contents of our cart.

Our cart is pretty simple, as we are just going to create a DOM element that will serve as a placeholder that our cart HTML can be injected into. Somewhere in your HTML Body, add the following:

<div id="fullcart"></div>

Later, you can hide this and call it up as a popup using jQuery or a jQuery UI dialog box (my personal preference).

Next, we are going to add a product. From our Zen-Cart side, we should already have a product entered for testing, or if you’re testing this against an existing install of Zen-Cart, you probably already have some products in mind. In the Zen-Cart admin area, go to your products list, and make note of the ID number.

In your HTML form, paste in the following:

<div>
  <h3>Product Name</h3>
  <p>Product Description</p>
  <form target="cartframe" action="/YOUR/CART/PATH/index.php?main_page=product_info&amp;cPath=1&amp;products_id=YOUR-PRODUCT-ID&amp;action=add_product" enctype="multipart/form-data" method="post">
    <input type="hidden" name="securityToken" value="">
    <input type="text" maxlength="6" name="cart_quantity" size="4" value="1" />
    <input type="hidden" name="products_id" value="YOUR-PRODUCT-ID" />
    <input title="Add to Cart" role="button" type="submit" alt="Add to Cart" value="Add to Cart" />
  </form>
</div>

Replace /YOUR/CART/PATH and YOUR-PRODUCT-ID with your own values. YOUR-PRODUCT-ID should be the product ID number in Zen-Cart.

Now, if you test this as is, what should happen is you will get a “SESSION EXPIRED” error in your iframe. This is now by design in Zen-Cart and needs to be fixed via JavaScript. Let’s do that now.

On to Scripting

Now since we can’t generate the token on our HTML page, we need to “steal” it from our Zen-Cart page. To do this, we are going to use a small bit of jQuery/JavaScript code to grab the token from our iframe and insert it into our “Add to Cart” form.

The one prerequisite for this to work is you MUST have jQuery loading on your page prior to this code running.

The following will perform all the basic functions needed to get the HTML, the token, and then insert it into our HTML page. You will add this to an existing JS file you’re using or create a new one and paste it into there.

document.domain = 'yourdomain.com';

$(document).ready(function() {
  //  ***************  Cart Functions Start ****************
  // This fires after the invisible iframe that contains the Zen-Cart loads up.
  $("#cart_frame").load(function () {
    var cartDisplay = "";
    var cartEmptyMessage = "";
    var cartIconDisplay = "";
    var sessionID = ""

    // grab html from the zen-cart iframe and store it in variables
    var cartDisplay = $('#cart_frame').contents().find('div#externalCart').html(); // grabs the full hidden cart html
    var cartEmptyMessage = $('#cart_frame').contents().find('h2#cartEmptyText').html(); // grabs the empty cart message
    var securityToken = $('#cart_frame').contents().find('input[name=securityToken]').val(); // grabs the token

    // write the variables into our html page
    if (cartEmptyMessage) {
      $('#fullcart').html(cartEmptyMessage);
    } else {
      $('#fullcart').html(cartDisplay);
    }

    // searches the iframe for the security token, grabs it and assigns it to
    // all inputs with the name "securityToken"
    if ($('input[name=securityToken]').length) {
      $('input[name=securityToken]').each(function () {
        $(this).attr('value', securityToken);
      });
    }

    $('form[name=cart_quantity]').each(function () {
      $(this).attr('target', 'cartframe');
    });
  });
  //  ***************  Cart Functions End ****************
});

The code is rather simple and shouldn’t be difficult to figure out. The top line is really only needed if your using different sub domains, but it won’t hurt to have it in there regardless.

Once this code is added (along with all the other changes in this post), you should see in your Cart area a message that says “Your shopping cart is empty”. Upon clicking the Add To Cart button, it should immediate update to reflect the newly added item.

And that’s it. For further experimentation, trying adding more products, move the cart around, use a jQuery UI dialog to load the cart into a popup window. To do that, you can make the “Add to Cart” form submit a trigger that calls a jQuery UI dialog box. For use in a “live” environment, you should remove any unneeded areas in Zen-Cart and strip it down to the bare essentials. Since I still transfer customers to the Zen-Cart install when checking out, I remove all side blocks, product links, and anything else I don’t want the customer to access inside of Zen-Cart.

Happy coding.

– Brian