Bludit Analytics Implementation | OpsBlu Docs

Bludit Analytics Implementation

Install tracking scripts and data layers on Bludit using theme templates, plugin hooks, and the $site/$page helper objects.

Analytics Architecture on Bludit

Bludit is a flat-file PHP CMS that stores content as JSON with no database. The template system uses raw PHP files inside bl-themes/[theme-name]/, with two primary files controlling page output:

  • site.php - Master layout wrapper (equivalent to a base template)
  • index.php - Page content rendering

All analytics code injection happens through these theme files or through Bludit's plugin hook system. Bludit exposes two key helper objects in templates:

// $site - global site configuration
$site->title()        // Site name
$site->description()  // Site meta description
$site->language()     // Language code
$site->url()          // Base URL

// $page - current page context (available on page views)
$page->title()        // Page title
$page->slug()         // URL slug
$page->category()     // Category key
$page->tags()         // Array of tag strings
$page->username()     // Author username
$page->dateRaw()      // Raw publish date

The plugin system provides lifecycle hooks that inject content at specific DOM positions without modifying theme files directly.


Installing Tracking Scripts

Method 1: Theme Template Injection

Edit bl-themes/[your-theme]/site.php to add scripts in the <head> or before </body>:

<!DOCTYPE html>
<html>
<head>
    <meta charset="<?php echo $site->charset() ?>">
    <title><?php echo $page->title() ?> - <?php echo $site->title() ?></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 Theme::plugins('siteHead') ?>
</head>
<body>
    <!-- GTM noscript fallback -->
    <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXX"
    height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>

    <?php Theme::plugins('siteBodyBegin') ?>

    <?php echo $content ?>

    <?php Theme::plugins('siteBodyEnd') ?>
</body>
</html>

Method 2: Plugin Hook System

Bludit plugins use hooks to inject code without editing theme files. Create a custom plugin at bl-plugins/my-analytics/plugin.php:

<?php
class pluginMyAnalytics extends Plugin {

    public function init() {
        $this->dbFields = array(
            'gtmId' => '',
            'gaId'  => ''
        );
    }

    // Inject into <head> via siteHead hook
    public function siteHead() {
        $gtmId = $this->getValue('gtmId');
        if (!empty($gtmId)) {
            return "<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','{$gtmId}');</script>";
        }
    }

    // Inject after <body> via siteBodyBegin hook
    public function siteBodyBegin() {
        $gtmId = $this->getValue('gtmId');
        if (!empty($gtmId)) {
            return '<noscript><iframe src="https://www.googletagmanager.com/ns.html?id='
                . $gtmId . '" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>';
        }
    }

    // Admin settings panel
    public function form() {
        $html  = '<div class="alert alert-primary">Analytics Configuration</div>';
        $html .= '<label>GTM Container ID</label>';
        $html .= '<input name="gtmId" type="text" value="' . $this->getValue('gtmId') . '">';
        $html .= '<label>GA Measurement ID</label>';
        $html .= '<input name="gaId" type="text" value="' . $this->getValue('gaId') . '">';
        return $html;
    }
}

Create the metadata file at bl-plugins/my-analytics/metadata.json:

{
    "author": "Your Name",
    "name": "Analytics Tracking",
    "description": "Adds GTM and GA tracking to all pages"
}

Available Plugin Hooks for Script Injection

Hook Location Use Case
siteHead Inside <head> GTM, GA config, meta pixels
siteBodyBegin After <body> open GTM noscript, above-fold scripts
siteBodyEnd Before </body> Deferred analytics, chat widgets
beforeSiteHead Before <head> content Early script loading
afterSiteBody After body content Late-loading trackers
pageBegin Before page content Page-level tracking pixels
pageEnd After page content Content engagement scripts

Data Layer Implementation

Build a data layer in site.php before the GTM snippet using Bludit's helper objects:

<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
    'siteName': '<?php echo htmlspecialchars($site->title(), ENT_QUOTES) ?>',
    'siteLanguage': '<?php echo $site->language() ?>',
    <?php if (isset($page) && $page->key()): ?>
    'pageType': '<?php echo $page->type() ?>',
    'pageTitle': '<?php echo htmlspecialchars($page->title(), ENT_QUOTES) ?>',
    'pageSlug': '<?php echo $page->slug() ?>',
    'pageCategory': '<?php echo $page->category() ?>',
    'pageTags': <?php echo json_encode($page->tags()) ?>,
    'pageAuthor': '<?php echo $page->username() ?>',
    'pageDate': '<?php echo $page->dateRaw() ?>',
    'contentType': 'page'
    <?php else: ?>
    'contentType': 'index'
    <?php endif; ?>
});
</script>

For blog listing pages, detect the page type from the URL helper:

<?php
$pageType = 'other';
if ($WHERE_AM_I === 'page') {
    $pageType = 'article';
} elseif ($WHERE_AM_I === 'home') {
    $pageType = 'homepage';
} elseif ($WHERE_AM_I === 'category') {
    $pageType = 'category';
} elseif ($WHERE_AM_I === 'tag') {
    $pageType = 'tag';
}
?>
<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
    'contentGroup': '<?php echo $pageType ?>'
});
</script>

Common Issues

Scripts not rendering from plugins. The theme must call the plugin hooks. Verify that site.php includes <?php Theme::plugins('siteHead') ?> inside <head> and <?php Theme::plugins('siteBodyEnd') ?> before </body>. Many third-party themes omit these calls.

$page is null on listing pages. The $page object is only populated on individual page views. On the homepage, category, or tag listing views, $page is not set. Always wrap page-specific data layer values in <?php if (isset($page) && $page->key()): ?> guards.

Caching prevents script updates. Bludit has a built-in static page cache. After modifying tracking code, clear the cache from Admin > Settings > General or delete files in bl-content/tmp/.

Plugin not appearing in admin. The plugin directory name must match the class name pattern. Directory bl-plugins/my-analytics/ requires class pluginMyAnalytics (camelCase prefixed with plugin). The metadata.json file is required.

Double-encoded HTML entities in data layer. When outputting page titles or descriptions that contain quotes, use htmlspecialchars() with ENT_QUOTES to prevent JavaScript syntax errors.


Platform-Specific Considerations

Bludit stores all content as JSON files in bl-content/pages/. There is no database query layer, which means server-side analytics (log-based tools) will see flat file reads rather than database queries. Performance impact from tracking scripts is minimal given the lightweight architecture.

The $WHERE_AM_I global variable identifies the current view context: home, page, category, tag, or search. Use this to segment analytics data by content section.

Bludit does not have a built-in consent management system. For GDPR compliance, implement cookie consent before firing tracking scripts by wrapping GTM initialization in a consent check within your plugin's siteHead() method.

Custom fields added via plugins (using the pageBegin or custom hooks) can extend the data layer, but Bludit's core content model is fixed. There are no custom content types; all content uses the same page schema with categories and tags for classification.