How It Works (Technical Overview)
Audience: developers, integrators and reviewers who want to understand the data flow and security model.
Overview
mod_bearsampp_stats is a Joomla site module that reads a Markdown dashboard from a GitHub repository, renders it in the browser and injects it into the page. The server side is deliberately thin: it computes URLs and passes a small JSON options object to JavaScript. All fetching, Markdown parsing and DOM manipulation happen client side.
Files
| File | Role |
|---|---|
mod_bearsampp_stats.xml | Manifest: metadata, dependencies (PHP 8.1, Joomla 5.0), parameters, update server. |
mod_bearsampp_stats.php | Entry point: loads the helper, reads params, loads the layout. |
helper.php | Builds the list of modules and the raw URLs. |
tmpl/default.php | Renders the grid or the single-article container and registers web assets. |
media/js/mod_bearsampp_stats.js | Fetches, renders, sanitizes, rewrites URLs and manages cache. |
media/css/style.css | Card grid and modal styling. |
language/en-GB/*.ini | Translatable strings. |
updates.xml | Joomla Update System feed. |
Server side
ModBearsamppStatsHelper::getModules() (helper.php) does the following for each entry in the Modules list:
- Splits the list on commas, newlines, semicolons and whitespace, trims and de-duplicates.
- Treats each entry as the literal repository name (no auto-prefixing).
- Derives a display name by stripping
module-, replacing separators with spaces and title-casing (for examplemod_bearsampp_stats→Mod Bearsampp Stats), with a small fallback map that keeps common names nicely cased. - Builds the raw URL:
https://raw.githubusercontent.com/<owner>/<repo>/<branch>/<stats-folder>/dashboard.md
The getOwner() helper normalises the Repository owner value so that Bearsampp, github.com/Bearsampp and https://github.com/Bearsampp all resolve to Bearsampp. The stats folder is sanitised to a single path segment (letters, digits, ., _, -).
tmpl/default.php registers the stylesheet, Marked.js and DOMPurify through the Joomla Web Asset Manager, then emits either:
- a single
.bearsampp-stats-inlinecontainer (single-article layout, exactly one repository), or - a
.bearsampp-stats-gridof.bearsampp-stats-cardbuttons plus one Bootstrap 5 modal.
Each element carries data-rawurl, data-module, data-slug and data-display. Runtime options (branch, TTL, grid columns and translated strings) are passed via Joomla.getOptions('mod_bearsampp_stats').
Client side
media/js/mod_bearsampp_stats.js runs on DOMContentLoaded:
- Reads options and, if a modal exists, initialises
bootstrap.Modal. - On card click (or immediately for the inline case) calls
fetchAndRender().
fetchAndRender():
- Derives
baseUrlfrom the raw URL and builds a cache keybearsampp-stats:v3:<module>:<slug>:<branch>. - Returns cached HTML from
localStorageif present and not expired. - Otherwise fetches the raw URL with
cache: 'no-cache'and checksresponse.ok. - Normalises the heading: the first Markdown
#heading is replaced with# <slug> Statistics, and the link text in the "Release asset download totals for [...]" line is retargeted at the actual slug. - Verifies Marked.js and DOMPurify are available, then renders and sanitizes:
const html = window.DOMPurify.sanitize(window.marked.parse(markdown)); - Stores the sanitized HTML in cache and calls
renderContent().
renderContent():
- Sets
innerHTMLon the target content element. - For every
img[src],a[href]andsource[src]with a relative value, resolves it againstbaseUrlwithnew URL(value, baseUrl). Absolute URLs (Shields.io badges, fullraw.githubusercontent.comURLs,#anchors and protocol-relative URLs) are left untouched. - Forces external links (
a[href^="http"]) to open in a new tab and addsrel="noopener noreferrer".
The dashboard.md file
The module displays whatever Markdown lives at <stats-folder>/dashboard.md. The module never creates that file, so a repository must already publish it.
The recommended way to produce it is Bearsampp's stats-daily-with-chart workflow. That workflow runs the GitHub Downloads Action, which writes downloads.json and the generated SVG charts into the stats folder, and then runs an additional render step - not part of the action - to produce or update dashboard.md. The auto-creation behaviour below is provided by that render step, so it only happens in repositories that use Bearsampp's workflow (or another workflow that adds the same step). A repository that runs the action alone, or uses any other stats tool, must supply its own dashboard.md.
Dashboard auto-creation (Bearsampp workflow only). The render step:
- Reads
stats/downloads.json. - If
stats/dashboard.mdis missing, generates a default one from the snapshot - a title, a one-line description, four Shields.io badges (total / day / week / month), the total-trend chart, all generated charts and the data table. - If it exists, refreshes only the
## Datatable so the numbers stay in sync with the JSON, leaving the rest of the file untouched.
So a repository that uses Bearsampp's workflow gets a working dashboard automatically on the first run, and you can freely customise dashboard.md afterwards - only the data table is regenerated.
Caching
The browser cache is a localStorage entry per module + slug + branch holding the sanitized HTML and an expiry timestamp. cache_ttl_minutes controls the TTL; 0 disables client caching so every view re-fetches. The server sends cache: 'no-cache' on the fetch so the browser revalidates with GitHub rather than serving a stale HTTP response.
Security model
- Sanitization: all fetched Markdown is parsed to HTML and then passed through DOMPurify before insertion, so scripts and unsafe markup in a repository's
dashboard.mdare stripped. - CDN integrity: Marked.js and DOMPurify are loaded from jsDelivr via the Web Asset Manager with declared dependencies, so they load before the module script runs.
- No server-side writes: the module does not write files, query the database beyond normal module parameter storage, or collect visitor data.
- Link hardening: outbound links open with
noopener noreferrer. - URL construction: owner, repo and folder components are URL-encoded; the folder is restricted to a single safe path segment.
Failure handling
If the fetch fails, the HTTP status is not OK, or the renderer/sanitizer is unavailable, the module hides the loader, shows a translated "Failed to load stats" alert and (in modal mode) closes the modal. When a repository has no dashboard.md (for example, the stats workflow has not run yet), the browser request returns a 404 and the same graceful error path is used; the translated fallback string is "Stats coming soon for this module."
Distribution and updates
Releases are produced by the repository's packaging workflow. updates.xml advertises the latest version to the Joomla Update System, with a download URL pointing at the release ZIP on GitHub.