Analytics Architecture on GetSimple CMS
GetSimple CMS is a flat-file PHP CMS that stores content as XML files in the data/pages/ directory. There is no database. The template system uses raw PHP files in theme/[theme-name]/, with a single primary template file (template.php) that controls the full page output.
GetSimple provides PHP helper functions that extract page metadata from the XML store:
<?php get_page_title(); ?> // Outputs current page title
<?php get_page_slug(); ?> // Returns URL slug
<?php get_page_date(); ?> // Page creation date
<?php get_page_meta(); ?> // Custom meta description
<?php get_site_name(); ?> // Global site name
<?php get_site_url(); ?> // Base site URL
<?php get_page_content(); ?> // Rendered page body
<?php return_page_slug(); ?> // Returns slug as string (no echo)
<?php return_page_title(); ?> // Returns title as string (no echo)
The distinction between get_* (echoes output) and return_* (returns string) functions is critical for data layer construction, since get_* functions write directly to the output buffer.
Component callbacks provide hook points for plugins to inject content at specific template locations.
Installing Tracking Scripts
Method 1: Direct Theme Template Editing
Edit theme/[your-theme]/template.php to add tracking scripts:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title><?php get_page_title(); ?> - <?php get_site_name(); ?></title>
<!-- Google Tag Manager -->
<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>
<?php get_header(); ?>
</head>
<body id="<?php get_page_slug(); ?>">
<!-- GTM noscript -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXX"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<?php get_page_content(); ?>
<?php get_footer(); ?>
</body>
</html>
The <?php get_header(); ?> and <?php get_footer(); ?> calls are component callback points where plugins inject their code. Place your GTM snippet before get_header() to ensure it loads before any plugin-injected scripts.
Method 2: Plugin-Based Injection
Create a plugin at plugins/analytics-tracking/ with the required files:
plugins/analytics-tracking/analytics-tracking.php:
<?php
# Plugin registration
$thisfile = basename(__FILE__, ".php");
register_plugin(
$thisfile,
'Analytics Tracking',
'1.0',
'Your Name',
'https://yoursite.com',
'Adds GTM and analytics tracking',
'settings',
'analytics_settings'
);
# Hook into the theme header
add_action('theme-header', 'analytics_head_scripts');
# Hook into the theme footer
add_action('theme-footer', 'analytics_footer_scripts');
function analytics_head_scripts() {
$gtm_id = analytics_get_setting('gtm_id');
if (!empty($gtm_id)) {
echo "<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','" . htmlspecialchars($gtm_id) . "');</script>";
}
}
function analytics_footer_scripts() {
// Deferred analytics scripts
}
function analytics_settings() {
// Admin settings panel
echo '<label>GTM Container ID:</label>';
echo '<input type="text" name="gtm_id" value="' . analytics_get_setting('gtm_id') . '">';
}
function analytics_get_setting($key) {
$settings_file = GSDATAOTHERPATH . 'analytics-tracking.xml';
if (file_exists($settings_file)) {
$xml = simplexml_load_file($settings_file);
return (string)$xml->$key;
}
return '';
}
Available Hook Points
| Hook | Template Call | Location |
|---|---|---|
theme-header |
<?php get_header(); ?> |
Inside <head> |
theme-footer |
<?php get_footer(); ?> |
Before </body> |
content-top |
Automatic | Before page content |
content-bottom |
Automatic | After page content |
index-preheader |
Admin only | Admin panel header |
Data Layer Implementation
Use return_* functions (not get_*) to build the data layer, since you need string values rather than echoed output:
<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'siteName': <?php echo json_encode(strip_tags(ob_get_clean_site_name())); ?>,
'pageTitle': <?php echo json_encode(return_page_title()); ?>,
'pageSlug': '<?php echo return_page_slug(); ?>',
'pageUrl': '<?php echo get_site_url(true) . return_page_slug(); ?>',
<?php if (function_exists('return_page_meta')): ?>
'pageDescription': <?php echo json_encode(return_page_meta()); ?>,
<?php endif; ?>
'contentType': '<?php echo (return_page_slug() === 'index') ? 'homepage' : 'page'; ?>'
});
</script>
Since get_site_name() echoes directly, use output buffering to capture it as a string:
<?php
function ob_get_clean_site_name() {
ob_start();
get_site_name();
return ob_get_clean();
}
?>
For sites using the I18N plugin for multilingual content, add language data:
<?php if (function_exists('return_i18n_lang')): ?>
<script>
window.dataLayer.push({
'pageLanguage': '<?php echo return_i18n_lang(); ?>',
'i18nEnabled': true
});
</script>
<?php endif; ?>
Common Issues
get_* functions echo instead of returning. The most common mistake is using get_page_title() inside a JavaScript string literal, which echoes the title into the wrong context. Use return_page_title() and return_page_slug() when building data layer values. Not all fields have return_* equivalents; use output buffering (ob_start() / ob_get_clean()) as a workaround.
Plugin hooks not firing. The theme template must include <?php get_header(); ?> and <?php get_footer(); ?> for theme-header and theme-footer hooks to execute. Some minimal themes omit these calls.
XML encoding in page data. GetSimple stores content as XML. Page titles and content may contain XML-encoded entities (&, <). When passing values to JavaScript, run them through html_entity_decode() before json_encode() to avoid double-encoded strings.
No page type differentiation. GetSimple has no concept of content types, categories, or tags in its core. All pages share the same schema. Use the page slug or parent-child hierarchy to infer content grouping for analytics segmentation.
Cache issues after template changes. GetSimple caches rendered pages. Clear the cache by deleting files in data/cache/ or from the admin panel under Pages > Clear Cache.
Platform-Specific Considerations
GetSimple stores all page data as individual XML files in data/pages/. Each file contains the page title, slug, content, meta description, parent reference, and menu order. There are no custom fields, taxonomies, or content types in the core system.
The admin panel runs at admin/ and has no separation between frontend and backend rendering. Analytics scripts in template.php will not fire on admin pages.
GetSimple does not support URL parameters or query strings natively. All routing is based on page slugs. This means UTM parameters and campaign tracking URLs work only if the web server passes query strings through (which Apache with mod_rewrite does by default).
For sites with many pages, the flat-file XML store can slow down page listing operations. This does not affect frontend tracking performance, but server-side log analysis may show longer response times on pages that query multiple XML files (such as sitemap generation).