d1ce803412
Bug fixes: - Deleting a user with comments no longer 500s: remove the user's comments first (covers old DBs) and add ON DELETE CASCADE to the FK. - Drop 'svg' from the default media upload allowlist (stored-XSS vector for files served directly from public/uploads/). - Content index now reindexes on page deletion: track routable page count in content_index_meta and treat a count change as stale, since the newest-mtime check alone can't see a removal. - Dev router (public/router.php) preserves the query string across the canonical trailing-slash redirect, matching .htaccess. - Fix stale worked-example reference in the comments thread partial. Migrations: since v2 is unreleased with no live databases, fold the incremental ALTERs into the base migrations rather than shipping them separately: verification columns into 0002_create_users.sql, source_count into 0001_create_content_index.sql; renumber comments to 0003. Update all code/doc references to the removed/renamed files. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
143 lines
7.8 KiB
PHP
143 lines
7.8 KiB
PHP
<?php
|
|
|
|
// These are the framework defaults. A project overrides any subset of them
|
|
// by creating App/config.php returning an array of just the keys it wants
|
|
// to change — novaconium/bootstrap.php shallow-merges it over this file, the
|
|
// same App-over-novaconium override pattern used for pages/ and lib/. This
|
|
// file itself is not meant to be edited per-project.
|
|
return [
|
|
// Ordered override roots: App/pages is checked first so a project can
|
|
// override any page, sidecar, or layout by placing one at the same
|
|
// relative path there; novaconium/pages supplies the framework defaults.
|
|
'pages_dirs' => [
|
|
__DIR__ . '/../App/pages',
|
|
__DIR__ . '/pages',
|
|
],
|
|
'cache_dir' => __DIR__ . '/../public/cache',
|
|
'debug' => true,
|
|
|
|
// Site name used as the default page title, og:site_name, and the
|
|
// footer copyright line in novaconium/pages/_layout/layout.twig.
|
|
'site_name' => 'Novaconium Website',
|
|
|
|
// Matomo analytics. Leave both empty (the default) to disable tracking
|
|
// entirely — the layout emits no tracking script at all in that case.
|
|
// Set both via App/config.php to enable, e.g.:
|
|
// 'matomo_url' => 'https://matomo.example.com/',
|
|
// 'matomo_site_id' => '1',
|
|
'matomo_url' => '',
|
|
'matomo_site_id' => '',
|
|
|
|
// Gates every /admin/* route (clear-cache, docs, users, and any future
|
|
// admin page) behind a session login against the `users` table on
|
|
// Lib\Db's default connection — see /admin/docs/admin-auth. The first
|
|
// user created is the admin; users after that are 'registered', each
|
|
// with an optional group, and see whatever content sidecars grant via
|
|
// Lib\Access (see /admin/docs/access-control) — /admin/* itself 404s
|
|
// for them. Off by default because it depends on SQLite (same
|
|
// reasoning as content_index_enabled below): when false, /admin/* is
|
|
// wide open, /admin/login, /admin/logout, and /admin/users 404,
|
|
// Access::require() allows everything, and nothing ever touches
|
|
// Lib\Db because of this feature. After enabling it via
|
|
// App/config.php, create the first user at /admin/users (open access
|
|
// until at least one user exists) or with:
|
|
// php novaconium/bin/create-admin-user.php <username>
|
|
'admin_auth_enabled' => false,
|
|
|
|
// Lib\Db (see /admin/docs/database) — named, simultaneously-usable
|
|
// connections, keyed by name; 'default' is the only one required. A
|
|
// sidecar can use more than one at once, e.g. Db::query(...) (default)
|
|
// alongside Db::query(..., 'legacy'). Supported drivers: 'sqlite',
|
|
// 'mysql'. The default connection's path deliberately lives outside
|
|
// both public/ (must never be web-accessible) and novaconium/ (gets
|
|
// wholly replaced on a framework update — see
|
|
// /admin/docs/getting-started's "Updating the framework" section) — a
|
|
// top-level data/ directory, project-owned like App/, is the only safe
|
|
// place for it. migrations_dir is optional per connection (omit it to
|
|
// never run migrations against that connection, e.g. a read-only
|
|
// legacy database) and accepts either one path or an ordered list of
|
|
// roots — the default connection lists novaconium/migrations/ (framework
|
|
// -shipped schema, e.g. the content index — see /admin/docs/content-index)
|
|
// before App/migrations/ (project migrations), so framework migrations
|
|
// always apply first. NOTE: unlike every other key here, App/config.php
|
|
// merges into db_connections one level deeper than a normal shallow
|
|
// override — see the comment on Lib\Db::config() — so adding a second
|
|
// connection there doesn't require repeating 'default'.
|
|
'db_connections' => [
|
|
'default' => [
|
|
'driver' => 'sqlite',
|
|
'path' => __DIR__ . '/../data/novaconium.sqlite',
|
|
'migrations_dir' => [
|
|
__DIR__ . '/migrations',
|
|
__DIR__ . '/../App/migrations',
|
|
],
|
|
],
|
|
],
|
|
|
|
// Routes an admin can preview before the public can see them (see
|
|
// /admin/docs/drafts) — a list of Route::$dir-format paths, no leading
|
|
// slash, e.g. 'blog/upcoming-post'. Not authenticated as admin (per
|
|
// AdminAuth::isAuthenticated()) → 404, same as a route that doesn't
|
|
// exist at all, so a draft's existence isn't revealed to anyone
|
|
// poking at the URL. Authenticated → renders normally, and — critically
|
|
// — is never written to the static HTML cache regardless of whether
|
|
// the page has a sidecar (see Renderer::render()'s $isDraft param),
|
|
// since a world-readable cached copy would otherwise permanently leak
|
|
// the draft the first time an admin previewed it.
|
|
'draft_routes' => [],
|
|
|
|
// Content index (see /admin/docs/content-index) — backs /sitemap.xml,
|
|
// /search, and blog tag browsing. Off by default: all three depend on
|
|
// SQLite (Lib\Db), a real dependency plenty of sites built on this
|
|
// framework won't want at all, the same reasoning that keeps Matomo
|
|
// and admin auth off by default above. When false, all three routes
|
|
// 404 exactly as if they didn't exist, and nothing ever touches
|
|
// Lib\Db because of this feature — no data/novaconium.sqlite gets
|
|
// created just because the code exists. content_index_auto only
|
|
// matters once enabled: true (the default) reindexes lazily,
|
|
// on-demand, the first time a stale index is actually needed (never on
|
|
// a normal page view); false disables that and leaves indexing
|
|
// entirely to `php novaconium/bin/index-content.php`, e.g. from a
|
|
// deploy step.
|
|
'content_index_enabled' => false,
|
|
'content_index_auto' => true,
|
|
|
|
// Media manager (/admin/media — see /admin/docs/media-manager): an
|
|
// upload/browse/delete UI for files under public/uploads/, covered by
|
|
// the existing /admin/* auth gate the moment the page exists, so
|
|
// there's no separate *_enabled flag here (unlike admin_auth_enabled/
|
|
// content_index_enabled above, it has no SQLite dependency to gate).
|
|
// media_upload_extensions is an allowlist, matched case-insensitively
|
|
// against the uploaded filename's extension; media_upload_max_bytes
|
|
// caps a single file's size (checked against both $_FILES' reported
|
|
// size and PHP's own upload_max_filesize/post_max_size ini limits,
|
|
// see /admin/docs/media-manager). 'svg' is deliberately NOT in this
|
|
// default list: files under public/uploads/ are served directly from
|
|
// this origin, and an SVG can carry inline <script>, making it a
|
|
// stored-XSS vector. Add 'svg' back via App/config.php only if you
|
|
// serve uploads with a restrictive CSP or Content-Disposition:
|
|
// attachment.
|
|
'media_upload_extensions' => ['jpg', 'jpeg', 'png', 'gif', 'webp', 'pdf', 'txt', 'zip'],
|
|
'media_upload_max_bytes' => 10 * 1024 * 1024,
|
|
|
|
// Lib\Mailer's transactional-mail driver (see /admin/docs/admin-auth's
|
|
// email verification section) — separate from Mailer::send(), the
|
|
// contact form's own log-to-file stand-in, which this doesn't touch.
|
|
// 'log' (default) writes to the same novaconium/contact-log.txt with no
|
|
// external dependency, so a fresh checkout with admin_auth_enabled on
|
|
// can still create/verify accounts (via the logged link) with zero
|
|
// setup. 'mailjet' sends through MailJet's Send API v3.1
|
|
// (https://api.mailjet.com/v3.1/send) using the four keys below — set
|
|
// all four via App/config.php, e.g.:
|
|
// 'mail_driver' => 'mailjet',
|
|
// 'mail_from_email' => 'noreply@example.com',
|
|
// 'mail_from_name' => 'Example Site',
|
|
// 'mailjet_api_key' => '...',
|
|
// 'mailjet_api_secret' => '...',
|
|
'mail_driver' => 'log',
|
|
'mail_from_email' => '',
|
|
'mail_from_name' => '',
|
|
'mailjet_api_key' => '',
|
|
'mailjet_api_secret' => '',
|
|
];
|