Users (Tools > Users) - Everyone can update their display name, email and password; administrators can add, edit, invite (one-time link), block and delete accounts. - Accounts in config.local.php stay locked to config (the main administrator); CMS accounts live in data/users.json. Sign-in is by login name. - Roles are now editor and administrator (publisher is an alias of editor). - Account protection: password hashes only (12-72 characters), administrator password confirmation for sensitive actions, rate-limited failures, hashed single-use invite tokens, self-lockout guards, sessions of blocked/deleted users end on the next request, every account event audited without secrets. - dataDirectory can move data outside the web root; the dashboard recommends it. Media library - Upload images, documents, video and audio, sorted into four categories by extension. - Extension allow-list; executables, scripts, HTML/SVG, archives, legacy and macro Office files refused; content sniffing; files containing code refused; PDFs with scripts refused; Office/OpenDocument packages inspected for macros and embedded objects. - Media library button in the editor inserts an image, document link, video or audio. Navigation menus, pages and interface - The site scan detects primary/secondary navigation and seeds Navigation Menus; menus can be renamed, edited and deleted but not created by the CMS; a scan button finds new navigation. - New page wizard (template, name, navigation group) in the Pages dialogue; new pages stay private drafts until published; pages can be used as templates. - Deleted pages can be recovered from the Dashboard. - Full-width top bar with the user at the top right, device icons, consistent buttons. Tests and docs updated (tests/run.php, README, ARCHITECTURE, DEVELOPMENT, CHANGELOG). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
7.4 KiB
Development and Testing
Source Layout
| Path | Responsibility |
|---|---|
cms/src/NeoCMS/Authentication.php |
Sessions, credentials, roles, capabilities, and CSRF tokens. |
cms/src/NeoCMS/CMSController.php |
API dispatch and the page, draft, revision, schedule, media, menu, and shared-content actions. |
cms/src/NeoCMS/FileStore.php |
Atomic JSON metadata storage, locked update() read-modify-write, and managed directories. |
cms/src/NeoCMS/UserStore.php |
Accounts: locked config accounts plus CMS accounts in data/users.json, invitations, and the password policy. |
cms/src/NeoCMS/MediaTypes.php |
Allowed media extensions and categories, and the upload safety checks (inspect()). |
cms/src/NeoCMS/Activity.php |
Records an event in both the dashboard feed and the audit log. |
cms/src/NeoCMS/PagePaths.php |
URI normalisation and containment of page paths inside the document root. |
cms/src/NeoCMS/SiteAnalyser.php |
Byte-exact analysis and tagging of content regions, images, and SEO tags for the Site scan. |
cms/src/NeoCMS/ContentDom.php |
Pure HTML transforms for shared regions, menus, and editable-class detection. |
cms/bootstrap.php |
Shared entry-point setup: configuration and autoloader. |
cms/src/NeoCMS/LoginRateLimiter.php |
Locked, filesystem-backed login throttling. |
cms/src/NeoCMS/Logger.php |
Daily audit logging with field normalisation, size rotation, and retention. |
cms/src/NeoCMS/SecurityHeaders.php |
CSP, anti-framing, cache, transport, and browser-policy headers. |
cms/js/cms.js |
Iframe editing, dialogues, workflow state, and API client. |
cms/index.php |
Authenticated administration shell. |
cms/image_upload.php |
Multipart media upload endpoint (images, documents, video, audio); also used by TinyMCE for pasted images. |
cms/publish_scheduled.php |
CLI scheduled-publication worker. |
tests/run.php |
Isolated integration suite. |
TinyMCE 8.6.0 beneath cms/tinymce/ is vendored third-party code and should not be reformatted or documented as first-party
source. Its package metadata, changelog, and licence information remain in that directory.
Every read-modify-write of a JSON document must go through FileStore::update(); plain read() then write() loses concurrent updates.
Documentation Style
Comments use British English and explain intent, constraints, side effects, and security decisions. They should not narrate obvious syntax. A comment saying that a variable is assigned adds little; a comment explaining why a temporary file is renamed atomically earns its tea.
Public and non-obvious private methods use docblocks. Browser workflows use JSDoc-style comments. CSS and HTML comments describe logical regions rather than every individual declaration or tag.
Required Checks
Run the integration suite:
php tests/run.php
Lint all first-party PHP files:
find cms tests -path 'cms/tinymce' -prune -o -name '*.php' -print0 | xargs -0 -n1 php -l
Parse-check the administration client:
node --check cms/js/cms.js
Check patch whitespace before committing:
git diff --check
Adding an API Action
- Add a private method named
<action>Action()toCMSController. - Use
requirePost('<capability>')before every mutation. - Obtain required strings through
requiredPost()orrequiredRequest(). - Constrain paths through the existing URI and page-path helpers.
- Record meaningful mutations with
activity()and create revisions before replacing public content. - Return data through
respond()so headers and JSON encoding remain consistent. - Add an integration assertion to
tests/run.php. - Document user-visible behaviour in
README.mdandcms/welcome.htmlwhen appropriate.
Mutating actions that accept full documents must use requiredContentPost() rather than reading $_POST directly. New
filesystem operations must reuse canonical path helpers, reject symbolic links, and respect the configured count and byte
ceilings. New HTML entry points should use SecurityHeaders before emitting output.
Filesystem Tests
The integration suite creates unique public and metadata directories under the operating system's temporary directory. It
seeds an authenticated administrator session, invokes controller actions directly, and cleans up in a finally block.
Tests must never rely on or modify the repository's public HTML, uploads, logs, or data files.
Local Testing on XAMPP (Windows)
Place the project in XAMPP's htdocs folder (for example htdocs/neocms/) and browse to http://localhost/neocms/cms/. No virtual host is
needed: NeoCMS detects the /neocms URL prefix from the request and uses the folder containing cms/ as the site root. On a
live site at the domain root the prefix is empty and behaviour is unchanged. If a proxy hides the real path, set 'basePath'
in cms/config.local.php.
- Copy
cms/config.local.php.exampletocms/config.local.php. - Generate a hash (use XAMPP's
php.exeifphpis not on your PATH):php -r "echo password_hash('choose-a-password', PASSWORD_DEFAULT);". - Add the hash under
authenticationand a role underroles, then browse to/cms/. This config account is the main administrator; further accounts are added in Tools > Users.
Windows ignores chmod, so private-file permissions are not enforced there; the permission assertions in tests/run.php
run only on Linux. Keep .htaccess enabled (AllowOverride All) so cms/data/ and cms/logs/ stay private.
Production Checklist (Linux + Apache)
- Serve over HTTPS only. Behind a TLS-terminating proxy set
'security' => ['cookieSecure' => true]inconfig.local.php. - Create
cms/config.local.phpwith password hashes for the main administrator(s) (editororadministratorroles); never commit it. Other accounts are created in Tools > Users. - Set
'dataDirectory'inconfig.local.phpto an absolute folder outside the web root (for example/var/neocms-data, writable by PHP). Accounts, drafts and revisions then can never be served. Administrators see a Dashboard recommendation until this is done. - Copy
uploads/.htaccesswith the site: it denies executable and markup extensions and serves documents as downloads. On Nginx add the/uploads/rule from the README. - Enable
AllowOverride Allfor the site so the shipped.htaccessfiles apply, and confirmmod_headersis loaded. - Own the writable paths (
cms/data/,cms/logs/,uploads/, and managed HTML) by the PHP user, for examplewww-data. - Set PHP
post_max_sizeandupload_max_filesizeto at leastuploads.maxFileBytes(10 MiB by default; raise both together for video). The PHPfileinfoextension is required for upload checks; thezipextension is not. - Add the cron entry from the README for
cms/publish_scheduled.php. - The admin loads jQuery from
code.jquery.com; self-host it and updatecms/index.phpand the CSP if the server must work offline. - Back up
cms/data/together with the public HTML. - Verify after deploying:
/cms/data/,/cms/logs/,/cms/config.php, and any.phpor.exeplaced in/uploads/return 403; the session cookie isSecure; five wrong passwords trigger throttling;php tests/run.phppasses on the server. - Upload
cms/,uploads/, and the HTML to the site root. Nothing needs changing from the XAMPP setup. Links written as root-relative (/about.html) resolve under/neocms/only on the live site, so use relative links or test them after deployment.