CMS Made Simple Analytics Implementation Guide | OpsBlu Docs

CMS Made Simple Analytics Implementation Guide

Install tracking scripts, build data layers, and debug analytics on CMS Made Simple using Smarty templates, UDTs, Global Content Blocks, and modules.

Analytics Architecture on CMS Made Simple

CMS Made Simple (CMSMS) uses the Smarty templating engine for all frontend rendering. Analytics implementation works through several platform mechanisms:

  • Smarty templates define page layout with access to content variables, module output, and custom tags
  • User-Defined Tags (UDTs) are PHP functions callable from templates as Smarty tags -- used for dynamic data layer generation
  • Global Content Blocks (GCBs) store reusable HTML fragments that can be included in any template -- ideal for tracking script snippets
  • Module architecture provides self-contained functionality with their own templates and tags
  • Event system fires hooks on page render, content save, and other actions for server-side tracking integration
  • {content} tag marks where page-specific content renders within a template

CMSMS renders pages by loading the assigned template, processing Smarty tags, inserting page content at {content} markers, and executing module tags.


Installing Tracking Scripts

Create a Global Content Block for your tracking code:

  1. Go to Content > Global Content Blocks in the CMSMS admin
  2. Create a new block called analytics_head
  3. Paste your tracking script:
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'G-XXXXXXX');
</script>

Reference it in your page template before </head>:

<!DOCTYPE html>
<html lang="en">
<head>
  <title>{title} - {sitename}</title>
  {global_content name='analytics_head'}
</head>
<body>
  {content}
</body>
</html>

Every page using this template gets the tracking code. Update the GCB once to change tracking across the entire site.

Method 2: Directly in Template

For simpler setups, embed tracking code directly in your Smarty template:

<!DOCTYPE html>
<html>
<head>
  <title>{title} - {sitename}</title>

  <script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"></script>
  <script>
    window.dataLayer = window.dataLayer || [];
    function gtag(){ldelim}dataLayer.push(arguments);{rdelim}
    gtag('js', new Date());
    gtag('config', 'G-XXXXXXX');
  </script>
</head>

Note the {ldelim} and {rdelim} Smarty delimiters. Because Smarty uses { and } as tag delimiters, JavaScript curly braces must be escaped. Alternatively, wrap JavaScript in {literal}...{/literal}:

{literal}
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'G-XXXXXXX');
</script>
{/literal}

Method 3: User-Defined Tag (UDT)

Create a UDT for conditional analytics injection:

<?php
// UDT: analytics_tracker
// Callable as {analytics_tracker} in templates

// Skip for logged-in admins
if (cmsms()->test_state(CmsApp::STATE_ADMIN_PAGE)) {
    return '';
}

$propertyId = cms_siteprefs::get('analytics_property_id', 'G-XXXXXXX');

$output = <<<EOT
<script async src="https://www.googletagmanager.com/gtag/js?id={$propertyId}"></script>
{literal}
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
{/literal}
  gtag('config', '{$propertyId}');
</script>
EOT;

echo $output;

Call it in your template:

<head>
  {analytics_tracker}
</head>

Data Layer Implementation

Smarty Template Data Layer

Build the data layer using CMSMS's built-in Smarty variables:

{literal}
<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
{/literal}
  'page_title': '{title|escape:'javascript'}',
  'page_alias': '{page_alias}',
  'page_id': '{page_id}',
  'page_url': '{page_url}',
  'template_name': '{template_name}',
  'parent_alias': '{parent_alias}',
  'section': '{section_name}',
  'menu_text': '{menutext|escape:'javascript'}',
  'sitename': '{sitename|escape:'javascript'}',
  'modified_date': '{modified_date format="Y-m-d"}'
{literal}
});
</script>
{/literal}

UDT for Dynamic Data Layer

For complex data layer logic, use a UDT:

<?php
// UDT: build_data_layer

$gCms = cmsms();
$contentOps = $gCms->GetContentOperations();
$content = $contentOps->LoadContentFromId($gCms->get_variable('page_id'));

if (!$content) return '';

$data = [
    'page_id' => $content->Id(),
    'page_title' => $content->Name(),
    'page_alias' => $content->Alias(),
    'template_id' => $content->TemplateId(),
    'parent_id' => $content->ParentId(),
    'hierarchy' => $content->Hierarchy(),
    'active' => $content->Active(),
    'page_url' => $content->GetURL(),
    'menu_text' => $content->MenuText(),
];

// Add custom content block values (extra page fields)
$extraFields = ['page_category', 'page_type', 'product_sku'];
foreach ($extraFields as $field) {
    $val = $content->GetPropertyValue($field);
    if ($val !== false && $val !== '') {
        $data[$field] = $val;
    }
}

// Add metadata
$data['page_metadata'] = !empty($content->MetaData());
$data['has_search_text'] = !empty($content->GetPropertyValue('searchable'));

echo '<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push(' . json_encode($data, JSON_UNESCAPED_SLASHES) . ');
</script>';

Call it in the template:

<head>
  {build_data_layer}
  {global_content name='analytics_head'}
</head>

Module-Specific Data

CMSMS modules like News, Products, or CGBlog expose data in their templates. Add data layer output in module detail templates:

{* In the News module detail template *}

{literal}
<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
{/literal}
  'page_type': 'news_article',
  'article_id': '{$entry->id}',
  'article_title': '{$entry->title|escape:'javascript'}',
  'article_category': '{$entry->category_name|escape:'javascript'}',
  'publish_date': '{$entry->postdate|date_format:'%Y-%m-%d'}',
  'author': '{$entry->author|escape:'javascript'}'
{literal}
});
</script>
{/literal}

Common Issues

Smarty Delimiter Conflicts

The most common CMSMS analytics issue is Smarty interpreting JavaScript curly braces as template tags. There are three solutions:

  1. {literal} blocks -- wrap JavaScript in {literal}...{/literal} to disable Smarty parsing
  2. {ldelim} / {rdelim} -- use these Smarty tags to output literal { and }
  3. External JS file -- move JavaScript to a .js file and reference via <script src="...">, avoiding the conflict entirely
{* Approach 1: literal block *}
{literal}
<script>
  function example() { return true; }
</script>
{/literal}

{* Approach 2: delimiter tags *}
<script>
  function example() {ldelim} return true; {rdelim}
</script>

Template Assignment and Inheritance

CMSMS pages are assigned templates individually. If you add analytics code to one template but a page uses a different template, that page has no tracking. Verify all active templates include your analytics GCB or UDT.

List all templates and check for analytics inclusion:

  1. Go to Design > Templates in the admin
  2. Search each template for {global_content name='analytics_head'} or {analytics_tracker}

Content Blocks and Extra Pages

CMSMS uses {content} for the main content area and {content block='sidebar'} for additional content blocks. These are page-specific. Data layer values from content blocks need explicit extraction:

{* Capture a content block value for the data layer *}
{capture assign='sidebar_content'}{content block='sidebar'}{/capture}
{if $sidebar_content|strip|trim ne ''}
  {* Sidebar has content -- track this *}
{/if}

Caching

CMSMS caches rendered output. If Smarty caching is enabled, data layer values that depend on per-request data (time, user session) will be stale. Disable caching for pages that need dynamic data layer content:

{* Force no-cache for this template section *}
{nocache}
  {build_data_layer}
{/nocache}

Or disable output caching globally in the CMSMS settings under Site Admin > Global Settings.

Event System for Server-Side Tracking

CMSMS fires events like ContentPostRender, LoginPost, LogoutPost, and SearchCompleted. Use these for server-side analytics triggers:

<?php
// In a module's setup or event handler

\CMSMS\HookManager::add_hook('SearchCompleted', function($params) {
    // Log search query to server-side analytics
    $query = $params['search_query'] ?? '';
    $resultCount = $params['result_count'] ?? 0;
    // Send to measurement protocol or log file
});

Platform-Specific Considerations

Smarty 3 Syntax: CMSMS uses Smarty 3. Smarty tag syntax differs from Twig and other template engines. Key patterns for analytics:

  • Variables: {$variable} or {$object->property}
  • Modifiers: {$title|escape:'javascript'} (pipe syntax for filters)
  • Conditionals: {if $condition}...{else}...{/if}
  • Loops: {foreach $items as $item}...{/foreach}

Page Hierarchy: CMSMS organizes pages in a tree hierarchy. Access the full breadcrumb path for your data layer:

<?php
// In a UDT
$content = $contentOps->LoadContentFromId($gCms->get_variable('page_id'));
$hierarchy = $content->HierarchyPath();
$breadcrumb = explode('/', $hierarchy);

Custom Module Tags: CMSMS modules register Smarty tags. When building an analytics module, register a tag that templates can use:

<?php
// In your module's method
$this->RegisterSmartyPlugin('analytics_datalayer', 'function', 'smarty_analytics_datalayer');

function smarty_analytics_datalayer($params, &$smarty) {
    // Build and return data layer script
}

Admin vs Frontend: CMSMS uses separate template systems for admin and frontend. Analytics code in frontend templates does not appear in the admin interface. However, preview mode renders the frontend template, so tracking can fire during content previews. Add a preview check:

if (isset($_GET['__c']) && isset($_GET['__p'])) {
    // This is a preview request -- skip analytics
    return '';
}

File Manager and Asset Uploads: CMSMS manages files through a built-in file manager. If you create an external .js file for analytics, upload it via the file manager or place it in the uploads/ directory. Reference it in templates with:

<script src="{uploads_url}/js/analytics.js"></script>

Site Preferences: Store analytics configuration in site preferences accessed via cms_siteprefs::get() in PHP or configured through a custom admin panel in your analytics module. This keeps configuration separate from template code.