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.