Analytics Architecture on CS-Cart
CS-Cart is a self-hosted PHP ecommerce platform that uses Smarty templates for rendering and a hook system for extending functionality. Analytics tracking integrates through three layers:
- Smarty templates (
.tplfiles) in the theme directory control where scripts are injected. Theindex.tplwrapper and block templates provide global and page-type-specific injection points - PHP hooks fire at key points in the request lifecycle (
dispatch_before_display,checkout_place_order) and let add-ons prepare analytics data server-side before templates render - CS-Cart add-ons package tracking integrations as installable modules. The Google Analytics add-on ships built-in, but custom add-ons provide more control over data layer structure and event timing
- Multi-vendor mode (CS-Cart Multi-Vendor) adds vendor-level data to orders, products, and categories that can be pushed to the data layer for marketplace analytics
CS-Cart uses a controller-based routing system where the dispatch parameter determines which page renders. The dispatch value (products.view, checkout.checkout, orders.details) maps directly to the analytics page type.
Installing Tracking Scripts
Via Smarty Templates
Add GTM to the main layout template:
{* design/themes/your_theme/templates/common/mainbox.tpl *}
{* Add before the closing </head> tag *}
<script>(function(w,d,s,l,i){lbrace}w[l]=w[l]||[];w[l].push({lbrace}'gtm.start':
new Date().getTime(),event:'gtm.js'{rbrace});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
{rbrace})(window,document,'script','dataLayer','GTM-XXXXXX');</script>
Note: Smarty uses { and } as delimiters, so JavaScript curly braces must be escaped with {lbrace} and {rbrace}, or wrapped in {literal}...{/literal} tags:
{literal}
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXX');</script>
{/literal}
Via CS-Cart Add-on
Create a custom add-on for cleaner analytics integration:
// app/addons/my_analytics/func.php
function fn_my_analytics_dispatch_before_display() {
$dispatch = $_REQUEST['dispatch'] ?? 'index.index';
$page_type = explode('.', $dispatch)[0];
Tygh\Registry::set('runtime.my_analytics.page_type', $page_type);
Tygh\Registry::set('runtime.my_analytics.dispatch', $dispatch);
}
<!-- app/addons/my_analytics/addon.xml -->
<addon scheme="3.0">
<id>my_analytics</id>
<name>Analytics Integration</name>
<version>1.0</version>
<priority>100</priority>
</addon>
Data Layer Setup
Build the data layer from CS-Cart's Smarty template variables. Product, category, and cart data is available in the template context:
Product Page
{* design/themes/your_theme/templates/views/products/view.tpl *}
{if $product}
{literal}
<script>
window.dataLayer = window.dataLayer || [];
dataLayer.push({
'event': 'view_item',
'ecommerce': {
'items': [{
'item_id': '{/literal}{$product.product_code}{literal}',
'item_name': '{/literal}{$product.product|escape:"javascript"}{literal}',
'price': {/literal}{$product.price}{literal},
'item_brand': '{/literal}{$product.header_features.brand.value|default:""}{literal}',
'item_category': '{/literal}{$product.main_category_name|escape:"javascript"|default:""}{literal}'
}]
}
});
</script>
{/literal}
{/if}
Category Page
{* design/themes/your_theme/templates/views/categories/view.tpl *}
{if $category}
{literal}
<script>
window.dataLayer = window.dataLayer || [];
var items = [];
{/literal}
{foreach from=$products item=product name=products}
items.push({lbrace}
'item_id': '{$product.product_code}',
'item_name': '{$product.product|escape:"javascript"}',
'price': {$product.price},
'item_list_name': '{$category.category|escape:"javascript"}',
'index': {$smarty.foreach.products.index}
{rbrace});
{/foreach}
{literal}
dataLayer.push({
'event': 'view_item_list',
'ecommerce': {
'item_list_name': '{/literal}{$category.category|escape:"javascript"}{literal}',
'items': items
}
});
</script>
{/literal}
{/if}
Ecommerce Event Tracking
Add to Cart
CS-Cart uses AJAX for add-to-cart actions. Hook into the cart update via a PHP hook:
// app/addons/my_analytics/func.php
function fn_my_analytics_add_to_cart(&$cart, $product_data, $auth) {
foreach ($product_data as $key => $item) {
$product = fn_get_product_data($item['product_id'], $auth);
Tygh\Registry::set('runtime.analytics_events.' . $key, [
'event' => 'add_to_cart',
'item_id' => $product['product_code'],
'item_name' => $product['product'],
'price' => $product['price'],
'quantity' => $item['amount'],
]);
}
}
Then output queued events in the template:
{* In mainbox.tpl or a hook template *}
{if $runtime.analytics_events}
{literal}<script>
window.dataLayer = window.dataLayer || [];
{/literal}
{foreach from=$runtime.analytics_events item=evt}
dataLayer.push({lbrace}
'event': '{$evt.event}',
'ecommerce': {lbrace}
'items': [{lbrace}
'item_id': '{$evt.item_id}',
'item_name': '{$evt.item_name|escape:"javascript"}',
'price': {$evt.price},
'quantity': {$evt.quantity}
{rbrace}]
{rbrace}
{rbrace});
{/foreach}
{literal}</script>{/literal}
{/if}
Purchase Event
Track completed orders on the order confirmation page:
// Hook: checkout_place_order (in func.php)
function fn_my_analytics_checkout_place_order(&$cart, &$auth, $action, &$order_id) {
if ($order_id) {
$order = fn_get_order_info($order_id);
$items = [];
foreach ($order['products'] as $p) {
$items[] = [
'item_id' => $p['product_code'],
'item_name' => $p['product'],
'price' => $p['price'],
'quantity' => $p['amount'],
];
}
$_SESSION['analytics_purchase'] = [
'transaction_id' => $order_id,
'value' => $order['total'],
'currency' => $order['secondary_currency'] ?? 'USD',
'tax' => $order['tax_subtotal'],
'shipping' => $order['shipping_cost'],
'items' => $items,
];
}
}
Multi-Vendor Analytics
CS-Cart Multi-Vendor adds vendor data to every product and order. Include vendor attribution in the data layer for marketplace analytics:
{if $product.company_id}
{literal}<script>
window.dataLayer = window.dataLayer || [];
dataLayer.push({
'vendor_id': '{/literal}{$product.company_id}{literal}',
'vendor_name': '{/literal}{$product.company_name|escape:"javascript"}{literal}'
});
</script>{/literal}
{/if}
For order confirmation, loop through order items and include vendor data per item to track marketplace commission and vendor performance:
// In the purchase event handler
foreach ($order['products'] as $p) {
$items[] = [
'item_id' => $p['product_code'],
'item_name' => $p['product'],
'price' => $p['price'],
'quantity' => $p['amount'],
'item_brand' => fn_get_company_name($p['company_id']), // vendor name
];
}
Common Errors
| Error | Cause | Fix |
|---|---|---|
| Smarty delimiters break JavaScript | { and } parsed as Smarty tags |
Wrap JS in {literal}...{/literal} or use {lbrace}/{rbrace} |
| Product data empty in template | Accessing variables outside the correct template scope | Check which dispatch renders the template; product data is only in products.view |
| Add-on hooks not firing | Hook function name does not follow naming convention | Function must be fn_{addon_id}_{hook_name} exactly |
| Purchase event fires twice | Order confirmation page reloaded or back button pressed | Store a processed flag in the session and check before pushing |
| Multi-vendor data missing | Standard CS-Cart edition does not include vendor fields | Vendor data is only available in CS-Cart Multi-Vendor edition |
| Cached pages show stale data layer | CS-Cart full-page cache serves old template output | Disable caching on analytics-critical pages or use client-side API calls |
| Theme update overwrites templates | Custom templates in the theme directory replaced | Use template hooks or add-on templates that override without modifying core files |
| AJAX cart updates not tracked | Server-side hook fires but page does not refresh to show the script | Use JavaScript to listen for AJAX cart responses and push events client-side |
Performance Considerations
- Smarty compilation: Smarty compiles templates to PHP on first load. Adding analytics code to heavily nested templates increases compilation time. Keep analytics scripts in the main layout template
- Full-page cache: CS-Cart's built-in caching serves pre-rendered HTML. Data layer values baked into templates remain static until cache invalidation. Use
no_cacheSmarty tags for dynamic analytics data - Add-on loading order: CS-Cart loads add-ons by priority value. Set your analytics add-on to a high priority number (100+) so it runs after all product and cart data is prepared
- AJAX optimization: CS-Cart uses extensive AJAX for cart, wishlist, and comparison features. Track these interactions with JavaScript event listeners on the client side rather than server-side hooks that require page reloads
- Database queries: Avoid heavy database queries in analytics hooks. Use data already available in the
$cartor$productobjects rather than callingfn_get_product_data()for every event