4862526fa1
Lets a page under App/pages/ be previewed by an admin before the public can see it: list its route in draft_routes (App/config.php), checked in bootstrap.php alongside the existing /admin/* gate. Not authenticated -> same plain 404 an unmatched route gets, not a login prompt, so a draft's existence isn't revealed. Authenticated -> renders normally. No separate login flow needed - Basic Auth credentials are scoped to the whole origin/realm, so authenticating once at /admin covers draft URLs too. AdminAuth::isAuthenticated() is extracted out of requireLogin() so the draft gate can reuse the same credential check with a different failure response (404 vs. a 401 challenge). Renderer::render() gains an $excludeFromCache param so a draft without its own sidecar can't get written to the static HTML cache - .htaccess serves a cached file before PHP, and therefore any auth check, ever runs again, so an uncached exception is required, not just the auth gate. While testing this, found the same bug already existed for /admin itself: novaconium/pages/admin/index.twig has no sidecar, so it was already being cached - meaning any admin visiting /admin once caused the panel to be served to everyone, unauthenticated, straight from public/cache/admin/. Fixed in this change by excluding every /admin/* route from the cache the same way, and documented as a standing rule in AGENTS.md: any future mechanism that conditionally hides page content from the public has to make the same check, not just gate the initial request. Closes the "Draft pages (admin-only preview)" backlog item in novaconium/ISSUES.md.
115 lines
9.5 KiB
Markdown
115 lines
9.5 KiB
Markdown
# novaconium
|
|
|
|
A tiny, Hugo-flavored PHP micro-framework. Routes are directories on disk, pages render with [Twig](https://twig.symfony.com/), 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-path** — `App/` (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 validation** — `Lib\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 default** — `Lib\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 setup** — `Lib\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 data** — `Lib\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`.
|
|
- **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`.
|
|
|
|
### 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](http://127.0.0.1:8000/admin/docs/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](http://127.0.0.1:8000/admin/docs/sidecars) in the docs for the full contract, or [SEO](http://127.0.0.1:8000/admin/docs/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, 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:
|
|
|
|
- [Getting started](http://127.0.0.1:8000/admin/docs/getting-started)
|
|
- [Routing](http://127.0.0.1:8000/admin/docs/routing)
|
|
- [Sidecars](http://127.0.0.1:8000/admin/docs/sidecars)
|
|
- [Libraries](http://127.0.0.1:8000/admin/docs/libraries)
|
|
- [Database](http://127.0.0.1:8000/admin/docs/database)
|
|
- [Session](http://127.0.0.1:8000/admin/docs/session)
|
|
- [Layouts](http://127.0.0.1:8000/admin/docs/layouts)
|
|
- [Static caching](http://127.0.0.1:8000/admin/docs/caching)
|
|
- [SEO](http://127.0.0.1:8000/admin/docs/seo)
|
|
- [Matomo](http://127.0.0.1:8000/admin/docs/matomo)
|
|
- [Admin authentication](http://127.0.0.1:8000/admin/docs/admin-auth)
|
|
- [Draft pages](http://127.0.0.1:8000/admin/docs/drafts)
|
|
- [Styling](http://127.0.0.1:8000/admin/docs/styling)
|
|
- [Project layout](http://127.0.0.1:8000/admin/docs/project-layout)
|
|
- [Third-party](http://127.0.0.1:8000/admin/docs/third-party)
|
|
|
|
`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](http://127.0.0.1:8000/admin/docs/project-layout) for the full tree with every file explained.
|
|
|
|
## Third-party
|
|
|
|
[Twig](https://twig.symfony.com/) 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`.
|