Footer Hit Counter Badge (No DB)
Here's a tiny “stats icon” you can drop into your site footer that counts page hits and shows a small badge like 👁 12,345.
What it does:
- Counts total hits across your site (simple and lightweight).
- Stores the count in a single local file (no database).
- Uses file locking to avoid race conditions.
- Optionally ignores bots and admin pages.
Install:
- Create:
/tools/hitbadge/ - Add these 2 files inside it:
hit.php(increments & prints the badge HTML)count.txt(an empty file — writable)- In your footer, paste:
<?php include $_SERVER['DOCUMENT_ROOT']."/tools/hitbadge/hit.php"; ?>
Tip: If you want “per-page” counts later, you can expand this to hash the URL and store multiple counters. This version stays intentionally tiny.
<?php
declare(strict_types=1);
/**
* /tools/hitbadge/hit.php
* Tiny Footer Hit Counter Badge (No DB)
*
* - Increments a total hit counter (count.txt)
* - Prints a small footer badge
*
* Include in your footer:
* <?php include $_SERVER['DOCUMENT_ROOT']."/tools/hitbadge/hit.php"; ?>
*/
// ---------------- CONFIG ----------------
$COUNT_FILE = __DIR__ . '/count.txt';
// Optional: ignore these URL prefixes
$IGNORE_PATHS = ['/admin', '/private', '/tools/hitbadge'];
// Optional: basic bot ignore (very simple)
$IGNORE_BOTS = true;
// Badge label/icon
$LABEL = 'Views';
$ICON = '👁';
// --------------------------------------
function is_bot(string $ua): bool {
$ua = strtolower($ua);
$needles = ['bot','spider','crawl','slurp','bingpreview','facebookexternalhit','archive','fetch','lighthouse'];
foreach ($needles as $n) {
if (strpos($ua, $n) !== false) return true;
}
return false;
}
function should_ignore(array $ignorePaths, bool $ignoreBots): bool {
$uri = $_SERVER['REQUEST_URI'] ?? '/';
foreach ($ignorePaths as $p) {
if ($p !== '' && strpos($uri, $p) === 0) return true;
}
if ($ignoreBots) {
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
if ($ua !== '' && is_bot($ua)) return true;
}
return false;
}
function read_int(string $file): int {
if (!is_file($file)) return 0;
$raw = trim((string)@file_get_contents($file));
if ($raw === '' || !ctype_digit($raw)) return 0;
return (int)$raw;
}
function write_int_locked(string $file, int $n): bool {
$fh = @fopen($file, 'c+'); // create if missing
if (!$fh) return false;
if (!flock($fh, LOCK_EX)) { fclose($fh); return false; }
rewind($fh);
$raw = stream_get_contents($fh);
$cur = 0;
if (is_string($raw)) {
$raw = trim($raw);
if ($raw !== '' && ctype_digit($raw)) $cur = (int)$raw;
}
$cur++;
rewind($fh);
ftruncate($fh, 0);
fwrite($fh, (string)$cur);
fflush($fh);
flock($fh, LOCK_UN);
fclose($fh);
return true;
}
function badge_html(string $icon, string $label, int $count): string {
$countFmt = number_format($count);
return '<span class="hitbadge" title="'.htmlspecialchars($label,ENT_QUOTES,'UTF-8').'">'
. '<span class="hitbadge-ico" aria-hidden="true">'.htmlspecialchars($icon,ENT_QUOTES,'UTF-8').'</span>'
. '<span class="hitbadge-num">'.htmlspecialchars($countFmt,ENT_QUOTES,'UTF-8').'</span>'
. '</span>'
. '<style>'
. '.hitbadge{display:inline-flex;align-items:center;gap:6px;'
. 'padding:6px 10px;border-radius:999px;'
. 'border:1px solid rgba(255,255,255,.14);'
. 'background:rgba(255,255,255,.06);'
. 'font:700 12px/1 system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;'
. 'color:inherit}'
. '.hitbadge-ico{font-size:13px;opacity:.9}'
. '.hitbadge-num{letter-spacing:.2px}'
. '</style>';
}
// If ignored, just display current count without incrementing
if (should_ignore($IGNORE_PATHS, $IGNORE_BOTS)) {
$count = read_int($COUNT_FILE);
echo badge_html($ICON, $LABEL, $count);
return;
}
// Increment (locked), then read back
if (!write_int_locked($COUNT_FILE, 0)) {
// fail open: show placeholder if file isn't writable
echo '<span class="hitbadge" style="font:700 12px system-ui">👁 —</span>';
return;
}
$count = read_int($COUNT_FILE);
echo badge_html($ICON, $LABEL, $count);
?>
Comments (0)
No comments yet — be the first.