Files
neoCMS/docs/DEVELOPMENT.md
Geoff MooreandClaude Sonnet 5 8946915bfe Add Users, media library, navigation menus from the scan, new-page wizard and deleted-page recovery
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>
2026-09-24 17:47:34 +01:00

115 lines
7.4 KiB
Markdown

# 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:
```bash
php tests/run.php
```
Lint all first-party PHP files:
```bash
find cms tests -path 'cms/tinymce' -prune -o -name '*.php' -print0 | xargs -0 -n1 php -l
```
Parse-check the administration client:
```bash
node --check cms/js/cms.js
```
Check patch whitespace before committing:
```bash
git diff --check
```
## Adding an API Action
1. Add a private method named `<action>Action()` to `CMSController`.
2. Use `requirePost('<capability>')` before every mutation.
3. Obtain required strings through `requiredPost()` or `requiredRequest()`.
4. Constrain paths through the existing URI and page-path helpers.
5. Record meaningful mutations with `activity()` and create revisions before replacing public content.
6. Return data through `respond()` so headers and JSON encoding remain consistent.
7. Add an integration assertion to `tests/run.php`.
8. Document user-visible behaviour in `README.md` and `cms/welcome.html` when 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`.
1. Copy `cms/config.local.php.example` to `cms/config.local.php`.
2. Generate a hash (use XAMPP's `php.exe` if `php` is not on your PATH): `php -r "echo password_hash('choose-a-password', PASSWORD_DEFAULT);"`.
3. Add the hash under `authentication` and a role under `roles`, 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]` in `config.local.php`.
- Create `cms/config.local.php` with password hashes for the main administrator(s) (`editor` or `administrator` roles); never commit it. Other accounts are created in Tools > Users.
- Set `'dataDirectory'` in `config.local.php` to 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/.htaccess` with the site: it denies executable and markup extensions and serves documents as downloads. On Nginx add the `/uploads/` rule from the README.
- Enable `AllowOverride All` for the site so the shipped `.htaccess` files apply, and confirm `mod_headers` is loaded.
- Own the writable paths (`cms/data/`, `cms/logs/`, `uploads/`, and managed HTML) by the PHP user, for example `www-data`.
- Set PHP `post_max_size` and `upload_max_filesize` to at least `uploads.maxFileBytes` (10 MiB by default; raise both together for video). The PHP `fileinfo` extension is required for upload checks; the `zip` extension 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 update `cms/index.php` and 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 `.php` or `.exe` placed in `/uploads/` return 403; the session cookie is `Secure`;
five wrong passwords trigger throttling; `php tests/run.php` passes 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.