code a51faa7208 Fix tags/changefreq/priority leaking into rendered page output
A Twig {% block %} always emits its content wherever it's declared -
wrapping novaconium/pages/_layout/layout.twig's metadata-only blocks
(tags/changefreq/priority) in a plain {# #} comment doesn't suppress
that, and a literal {% block %} tag inside a {# #} comment doesn't even
compile (comments are stripped before parsing). Both bugs were present:
the first caused "monthly 0.5" to appear as visible text in <head> on
every page; fixing that by wrapping the blocks in a real HTML comment
tripped the second, a Twig SyntaxError, because the explanatory comment
above them contained literal {% %}/{# #} sequences that terminated the
outer Twig comment early.

Fixed by wrapping the three blocks in an actual HTML comment (invisible
to a reader/browser, still genuine Twig blocks - overridable and
harvestable via renderBlock() exactly as before) and rewriting the
explanatory comment without embedding literal Twig delimiter syntax.
Verified the fix doesn't regress ContentIndexer's metadata extraction.
2026-07-14 18:01:30 +00:00

novaconium

A tiny, Hugo-flavored PHP micro-framework. Routes are directories on disk, pages render with Twig, and any page that needs real logic gets an optional PHP "sidecar" file. Pages without a sidecar are pre-rendered once and served as static HTML straight from Apache afterwards. No Composer — Twig is vendored directly into the repo as plain source files.

Features

  • File-based routing — a directory under App/pages/ is a route (Hugo-style page bundles). No route table to maintain.
  • [param] segments — a directory literally named [param] (e.g. App/pages/products/[id]/) captures any single URL segment into $params['param'] for clean URLs, no query strings.
  • Optional PHP "sidecars" — drop an index.php next to any index.twig to supply Twig context data, or return a Response (redirect/JSON/XML/HTML) to short-circuit templating entirely.
  • Static caching, zero config — sidecar-less pages render once and are written to public/cache/; .htaccess serves the cached file directly on every later hit, skipping PHP and Twig entirely.
  • Override-by-pathApp/ (your project) is checked before novaconium/ (the framework defaults) for every page, layout, Lib\ class, and even the Sass color palette (App/sass/_colors.sass). Drop a file at the same relative path to override it; nothing needs duplicating to get a working site.
  • Layout inheritance_layout/layout.twig directories are resolved by walking upward from the matched page, so you can override the layout for a whole subtree.
  • SEO boilerplate out of the box — the default layout ships meta description, canonical link, robots, Open Graph, and Twitter Card tags, all overridable per-page via Twig blocks.
  • Built-in Matomo analytics — set matomo_url and matomo_site_id in App/config.php to enable tracking site-wide, including automatic 404 tracking. Off by default.
  • Admin authentication — gate every /admin/* route behind HTTP Basic Auth by setting admin_username/admin_password_hash in App/config.php; reusable for any admin page a project adds later, with a /admin/logout link to clear cached credentials and a built-in /admin/password-hash form so generating the hash doesn't require the CLI. Off by default.
  • Draft pages — list a route under draft_routes in App/config.php to make it visible only to an authenticated admin; anyone else gets a plain 404, not a login prompt. Reuses the admin auth check directly, and is excluded from static caching so a cached copy can't leak the draft to the public. See /admin/docs/drafts.
  • Dark/light theme toggle — a nav button flips a data-theme attribute (persisted to localStorage) that swaps every color via CSS custom properties; both palettes live in App/sass/_colors.sass, same override mechanism as everything else.
  • Self-hosted spam prevention & form validationLib\SpamGuard, a reusable class for any form: a CSS-hidden honeypot field plus a submission-timing check, no external CAPTCHA service, site key, or outbound API call. Pairs with Lib\FormValidator (accumulating required-field/email/length checks) and Lib\Validate (the underlying validation primitives — email, length, phone, postal/zip, spam-word checks). All three ship in novaconium/lib/, demonstrated on the contact form.
  • Form security by defaultLib\Input, a cleaning accessor for $_POST/$_GET (defense-in-depth against HTML/script injection, not a substitute for parameterized queries), and Lib\Csrf, standalone session-token CSRF protection called directly from a sidecar. Both ship in novaconium/lib/, wired into the contact form, /admin/clear-cache, and /admin/password-hash.
  • SQLite/MySQL database, zero setupLib\Db, a thin PDO wrapper (no ORM) supporting multiple named connections open at once — e.g. this site's own SQLite data plus a MySQL connection to a legacy database, usable in the same sidecar — each with its own plain-SQL migration convention, applied automatically on first use or via php novaconium/bin/migrate.php. SQLite data lives in a project-owned top-level data/ directory, outside both public/ and novaconium/. See /admin/docs/database.
  • Sessions with flash dataLib\Session, a thin wrapper around native PHP sessions with CodeIgniter-style flash values (set now, readable on exactly the next request) for post/redirect/GET flows without a query-string flag. Lazy-start, same mechanism Lib\Csrf already uses. See /admin/docs/session.
  • Content index: sitemap, search, tags/sitemap.xml, full-text /search (SQLite FTS5), and blog tag browsing all share one crawler that renders every page and harvests keywords/tags/changefreq/priority Twig blocks via Twig's own renderBlock() — no front-matter, no separate metadata files. Off by default (depends on SQLite); reindexes lazily on demand or via php novaconium/bin/index-content.php. See /admin/docs/content-index.
  • No build step, no Composer — clone it, point Apache (or php -S) at public/, and it runs. Twig is vendored as source; see /admin/docs/upgrading-twig for upgrading it.

Getting started

Requirements: PHP 8.1+ (uses readonly constructor-promoted properties) with the pdo_sqlite extension (bundled with PHP, just needs to be enabled — no separate install; add pdo_mysql too if using a MySQL connection), and, for production, Apache with mod_rewrite and AllowOverride All. The content index's search (/admin/docs/content-index) additionally needs SQLite's FTS5 extension, bundled with pdo_sqlite on virtually every modern PHP build — only relevant if content_index_enabled is turned on.

Run it locally (no Apache needed)

php -S 127.0.0.1:8000 -t public public/router.php

public/router.php is a dev-only script that mimics the .htaccess rules (canonical redirects + static cache lookup) so you can develop without Apache. It is never used in production — Apache reads public/.htaccess directly.

Visit http://127.0.0.1:8000/ for the static home page, then click around — /about, /blog/hello-world, /contact, and /admin (cache clearing + these same docs, rendered live) are all included as working examples.

Deploy on Apache

Point the vhost's document root at public/, make sure mod_rewrite is enabled and AllowOverride All is set for that directory so public/.htaccess takes effect, and it just works — no build step required.

Starting a new project

Clone this repo and drop its Git history — no Composer scaffold or installer:

git clone --depth 1 <novaconium-repo-url> my-new-project
cd my-new-project
rm -rf .git && git init && git add -A && git commit -m "Initial commit from novaconium template"

Then replace the example content under App/pages/ with your own; leave novaconium/ and public/ alone.

Updating the framework

Since the framework core lives entirely under novaconium/, pick up a new release by overwriting just that directory against a tag and committing the diff:

git clone --depth 1 --branch <release-tag> <novaconium-repo-url> /tmp/nova-update
rm -rf novaconium && cp -r /tmp/nova-update/novaconium ./novaconium && rm -rf /tmp/nova-update
git add novaconium && git commit -m "Update novaconium framework to <release-tag>"

Safe by construction — App/ always overrides novaconium/, so an update can't clobber project customizations. See Getting started for the full write-up.

Add a page

Create a directory under App/pages/ with an index.twig — the directory path is the URL:

App/pages/pricing/index.twig   ->   /pricing

Add an index.php next to it if the page needs data or logic. See Sidecars in the docs for the full contract, or SEO for a ready-to-paste starter template with every overridable block — or skip the copy-paste and scaffold it:

php novaconium/bin/create-static-page.php blog/my-new-post

Documentation

The full framework documentation — routing, sidecars, libraries, database, session, content index, layouts, static caching, SEO, Matomo analytics, admin authentication, draft pages, styling, project layout, and third-party notices — lives at /admin/docs on any running instance (so it travels with the code, no internet connection needed). Highlights:

AGENTS.md is the short, agent-facing version for coding assistants working in this repo, and novaconium/ISSUES.md is the roadmap/backlog.

Project layout

App/            your project — pages/ (routes), lib/ (Lib\ classes), sass/ (color overrides) — the only directory you're expected to edit
public/         Apache document root — front controller, .htaccess, static cache, compiled CSS
novaconium/     the framework itself — router, renderer, vendored Twig, default pages/lib/sass — not edited per-project

See Project layout for the full tree with every file explained.

Third-party

Twig is vendored in source form under novaconium/vendor/twig/ (no Composer — see /admin/docs/upgrading-twig for how to upgrade it). It's BSD-3-Clause licensed; the full license text ships alongside it at novaconium/vendor/twig/LICENSE.

S
Description
4LT's PHP framework
Readme MIT 1.1 MiB
1.0.10 Latest
2026-01-27 05:56:17 +00:00
Languages
PHP 37.1%
Twig 34.7%
Sass 22.9%
JavaScript 5.1%
Dockerfile 0.2%