Официальный сайт SLAED CMS
Журнал изменений
The stand ran update.php on 2026-09-30: its Node types, fields, rating rules and upload rules are now the configuration the migrated content needs, and the upload folders of the removed modules carry the guards the migration left behind. The secret of config/security.php is stored empty, as the repository requires.
Core changes:
- Configuration (config/*.php):
- node.php, fields.php, ratings.php and uploads.php hold the migrated types and their rules
- security.php and global.php carry the test bans and the debug switch of the stand; the secret is empty
- contact.php is written in the format the settings save produces
- Upload guards (uploads/*):
A deny-all .htaccess in the folders of news, faq, files, help and links, and an index.html in the thumbnail folder of docs
Benefits:
- A clone of the repository shows the stand the migrated content was checked on
Technical notes:
- A site that deploys this tree keeps its own configuration; the secret is generated on first use
Two inline edits exist - the forum post and the comment - each with its own route, both losing the typed text on a refusal and letting the last write win, while Node has none at all. The plan replaces both with one protocol that Node joins; its batch 0, the forum rights, has landed.
Core changes:
- Versions (docs/VERSIONS.md):
2026-09-30: the migration of the removed modules into Node with its 301 addresses and the look of the old modules, the escaped code blocks, the forum rights, the thumb burst and the category descriptions
- The quick edit plan (docs/QUICK-EDIT-2026.md):
One QuickEdit protocol over three kinds - node, forum, comment - with an adapter per kind for the source, the write and the view, all read from the stored row
A stamp for optimistic locking without a schema change: the version of a node, a hash of the text and its edit time for a post and a comment
- Two strict routes, getQuickEdit on GET and updateQuickEdit on POST, one status per refusal
- One fragment and one script: cancel without a request, the typed text kept on a refusal, a conflict window
- NodeService::updateNodeText() for moderators at any time and for the author within the window of the type
- Batches 0 to 5; batch 0 is done, the forum trust mode stays an open decision
Benefits:
- The history of the day is readable without the diff
- The next step of the quick edit has a written contract to be measured against
Technical notes:
- The plan file is removed by its last batch, which moves what lasts into docs/ARCHITECTURE.md
The thumbs of the shared rating answered a vote with nothing but a new number, while the favourite star and the rating stars already burst. The category tiles of a Node list showed no description, which the old modules did.
Core changes:
- The burst reaches the thumbs (plugins/system/slaed.js):
A vote remembers whether a star or a thumb was pressed; once the counted rating is swapped in, a thumb vote bursts the thumb, a star vote the stars up to the choice, both through setBurst()
Every thumb of the site is one fragment, rating-like: the forum post, the profile, the rating under an avatar in the forum and the comments, and the user list
- The thumb in its tone (templates/lite/assets/css/theme.css):
- A bursting thumb takes the round the ring wave needs, as a star does
- The up thumb bursts green in the rule of the copy feedback, the down thumb red, the tones their hover promised
- Category descriptions (core/system.php, modules/node/index.php):
- setCategories(string $mod, string $id = '') always passes the description; an empty one is not rendered
- The two switches $sub and $desc go: their one caller set them the same way every time
Benefits:
- One gesture answers every vote of the site, from one mechanism
- A reader sees what a category holds before opening it
Technical notes:
- No new class, token or template; the ui-audit dup count stays at zero
- The screenshot pair differs only on node-list, whose tiles grew by the description line
The quick edit, the full edit, the reply, the deletion and the moderator actions of the forum read their rights from the category the request named. The moderator of one category could therefore edit, delete, close or move the posts of every other one, which a live request against the stand confirmed before this change.
Core changes:
- Two rules every handler takes (core/user.php):
- getForumPlace(int $id) reads the category, the topic with its status and the author of a post from its rows
checkForumRight(bool $mod, bool $may, int $uid, int $stat = 3) is the one right over a post: the moderator of its category, or its signed-in author with the category right while the topic is open
- a guest never matches a post written without an account, which a category open to guests allowed
updatePost() takes both, ignores the category of the address, and echoes its refusals instead of returning them into a route that discards them, which left an empty body on the page
- The handlers of the module (modules/forum/index.php):
add(), send() and delete() take the category and the topic of a named post from its row; only a new topic takes the category of the form
A reply always answers the topic of the post it names and needs the reply right; the topic right alone used to insert one
- move() acts only on the topics stored in the moderated category; delete() loses its category argument
- The edit and delete buttons of view() ask checkForumRight(), so the view never offers what the handler refuses
- Tests (tests/Unit/ForumRightTest.php, tests/Support/contract_probe.php):
- The probe forumright compares the place with live rows and asks the right as a guest and as an account
- The handlers are pinned to getForumPlace() and checkForumRight()
Benefits:
- A category moderator is a moderator of that category and nothing else
- One rule decides both the buttons and the writes
Technical notes:
Behaviour change: guests no longer edit or delete anonymous posts, authors see no edit button in a closed topic, and a reply without the reply right is refused
- The addresses keep their cat parameter; it no longer decides a right
- Batch 0 of docs/QUICK-EDIT-2026.md
update.php carries news, pages, faq, help, links, files and content of a site that ran the 6.3 update into Node types with their categories, comments, favorites, rating balances, resources and files. The new table _node_legacy maps every old address to its material, so the old links of the outside world follow the content. The views and cards of Node regain the dials, chips and marks the old modules rendered.
Core changes:
- The migration script (update.php, lang/*.php):
Main administrator only; a preview, then a POST with a token scoped update; the stages stash, types, data, files and activation run from a manifest in storage/backup/update/node and resume after a stop
- pages becomes the type docs with comments and rating, content a type without extension
- texts move from the trusted HTML the old modules rendered into BB and Markdown; unknown blocks stay in
[usehtml]; code blocks lose only editor line breaks
- [attach] files go to the root of the type under managed names; directly linked files are copied to
uploads/archive/<module>/ and their links rewritten
- help tickets are published materials whose open or closed state lives in _node_support; replies become
comments
- orphaned comments and favorites keep their rows under old<module>; admin rights move to node-<type>
- The counter of _nodes continues above every old id, so an old address never names a new material
- Seven _NODE_MIG* constants in all six locales
- Old addresses (setup/sql/*.sql, core/classes/node/query.php, core/system.php, index.php, modules/node/index.php):
- Table _node_legacy (modul, oid, nid), foreign key to _nodes with cascade, in table.sql and the 6.3 update
NodeQuery::getNodeLegacy() and getNodeLegacyUrl() answer 301 on the way to a 404: a missing material of a type, an unknown op of a type, and a name that is no longer a module (pages goes to docs)
- Writer rules (core/classes/node/service.php):
A new type of one of the nine replaced names takes over the upload rule its old module left in config/uploads.php instead of being refused as a taken name
- A migrated material earns no second publication award when a moderator publishes it, now or by schedule
- The look of the old modules (modules/node/index.php, modules/node/admin/, core/system.php, templates/lite//node/*):
getNodeMetaVars() gives views and cards the comment chip, the fresh mark, the category icon, favorite, rating and the moderator dial; the view adds share, the author menu and the #id anchor
getNodeModerDial() and getNodeMoveLabel() move to the core: the site dial and the panel list are one function, and the _NODE_TO constants move to lang/.php
- The category map carries the icon; the download chip counts links as well
Tests (tests/Unit/Node*Test.php, tests/Support/node_probe.php, tests/Support/contract_probe.php, tests/Unit/CommentIsolationTest.php):
- _node_legacy lists, constraints, indexes and cascade; getNodeLegacy(); the reward rule; the adopted upload rule
- The comment parity samples leave Node types out, whose read rights the legacy queries never knew
- update.php is exempt from the comment isolation scan: it reads the old tables by design
Benefits:
- One run moves a site off the removed modules without losing a comment, a vote or a link
- Crawlers and old bookmarks reach the moved content instead of a 404
Technical notes:
- New table _node_legacy; the old module tables are only read and stay in place
- docs/NODE.md documents the migration, the table, the 301 rules and the template keys
With syntax = 2, the shipped default, encode_php() handed the content of [php] and [code=language] to the page as markup, so any author of a comment, a forum post or a material could store a script. The renderer moved into the parser and escapes in every mode.
Core changes:
- One code renderer in the parser (core/classes/parser.php):
Parser::getCodeHtml(string $src, string $lang) replaces encode_php(); [code=] and [php] call it
- the entities of stored legacy text are decoded once, then escaped in every mode
- only the editor line breaks before a line end are dropped, instead of everything between the first <br
and the last >
- The highlighter mode joins the fingerprint of the parser cache, so a changed mode never serves an old render
- The old global goes (core/system.php):
- encode_php() is removed; nothing else called it
- Fixture (tests/Unit/ParserFixturesTest.php):
- checkCodeBlocksEscapeInEveryMode renders a script tag through both tags in all three modes
Benefits:
- A safe text stays safe whatever the highlighter mode is
- One renderer for code blocks, owned by the class that parses
Technical notes:
- Rendered code of existing texts changes only where it carried markup; the parser cache rebuilds by itself
- No schema or configuration change
The confirmation an admin screen shows after a save was compared in eight faces before one went into the theme. The series stays on the stand beside the others, so the choice can be seen against what it was chosen from.
Core changes:
- The series (demo/flash-01-stamp.html ... demo/flash-08-orbit.html):
One stage in all eight: the head of the blocks module, the flash where module-head.html prints it, the info alert of the live page under it, a desk that replays the flash, and the four tones side by side
- Each face changes the arrival, the countdown, the exit and the success tick, and nothing else
- The faces stand on the bar the theme had before the ring, which their shared styles restore
- Stand behaviours (demo/assets/demo.js, demo/assets/demo.css):
- DEMO_FLASH manifest and its place in DEMO_SERIES, so the gallery shows the series by itself
data-demo-flash replays a template into data-demo-flash-host and lets the theme's htmx:afterSwap hook arm the autohide; data-demo-flash-loop plays it again after the exit
- data-demo-about fills the card of a face from its manifest entry
- Reference (demo/README.md):
- The sixth series, its table of faces, and the note that 03 Ring lives in the admin theme
Benefits:
- The countdown and the exit on the stand are those of slaed.js, not an imitation
Technical notes:
- The stand does not ship; nothing outside demo/ changes
The favourite star already had a burst - a pop, a ring wave and sparks. The rating stars now share it: once the counted rating is swapped in, the stars up to the chosen one burst in a row. The toast a refused vote raises turned white in the dark scheme, and the repeated-vote message ended in a bare number of days.
Core changes:
- One burst for two owners (plugins/system/slaed.js):
- setBurst(node, turn) pops one node and staggers it by --sl-d-turn; the favourite star calls it as before
A star vote is remembered in htmx:configRequest with its target and value; htmx:afterSwap of that target bursts the stars up to the value, and a refused vote forgets it
- Burst and toast styles (templates/lite/assets/css/theme.css):
- .sl-is-burst is no longer bound to .sl-fav; a rating star takes the round the ring needs only while it bursts
- The sparks stay hidden until their star's turn; keyframes renamed sl-burst-pop, -ring and -spark
- The toast is an inverse band, as the top bar is: --sl-bg-inverse with --sl-band-text in both schemes
- The repeated-vote message (core/system.php, lang/*.php, docs/RATINGS.md):
_RATINGS_WAIT names the date the next vote opens, in _DATESTRING, in all six locales
- a date reads without plural forms, which a bare count of days could not
Benefits:
- The rating answers a vote with the same gesture the favourite star does, from one mechanism
- The toast reads on either page, and the message says when rather than a number of what
Technical notes:
- The --sl-fav-* tokens keep their names: the API is frozen, so the rating stars read them as they are
- Retry-After still carries seconds; only the text changed
- tools/ui-audit-baseline.json and tools/ui-contrast.json are stored for the whole series of three commits
The flash an admin screen prints after an action was an info alert that counted down with two hair lines, and the success tick and the accent megaphone pulsed exactly as the info letter did. A confirmation is now a success alert, the flash springs in and counts down on a ring round its tick, and the success and accent glyphs have motion of their own in both themes.
Core changes:
- The flash is a success (core/system.php, core/helpers.php):
- getFlashHtml() passes type success, or warn for a refusal, instead of is_warn
- The saved-help notice of a module passes type success as well
- The ring countdown (templates/admin/fragments/alert.html, templates/admin/assets/css/theme.css):
sl-alert-flash-bar holds an SVG circle with pathLength 100; stroke-dashoffset runs it out over --sl-flash-dur
- the ring is centred on the tick, its track an inset shadow of --sl-flash-mix of the tone
- The flash springs in with sl-alert-pop and leaves scaled down; the two hair lines and sl-alert-flash-frame are gone
- Tone glyphs (templates/admin and templates/lite, assets/css/theme.css):
- Success: sl-alert-land, a turn in, then sl-alert-glow
- Accent: the megaphone tips up from its grip (sl-alert-announce) and two arcs of voice leave its bell (sl-alert-voice)
- Info keeps its pulse, warn its shake, error its beat
- Tokens and contract (base.css of both themes, tools/ui-contract.php):
- --sl-alert-ease in both themes, --sl-flash-ring and --sl-flash-mix in admin
- The divergence reasons of .sl-alert-flash and .sl-alert-flash-bar describe the ring
Benefits:
- A confirmation reads as a success at a glance, and no two tones share a motion
- The countdown uses no registered property, so it animates the same in every browser
Technical notes:
- The fragment markup of a flash gains one SVG; the autohide of slaed.js is unchanged
- Motion stands under prefers-reduced-motion; the reduce rules also stop the arc and the ring
This commit also restores four lines of the previous one that its hunk-wise staging put a few lines off, among them the text colour of a toolbar item in hand
In the dark scheme the tone tokens turn into light tints: right for a glyph, glaring as the fill of a whole bar. Every solid surface of the admin theme now reads its own --sl-solid-* tokens, which hold the light scheme's tone in light and that tone under a seventy per cent shade in dark, so the bars read the same in both and in every browser.
Core changes:
- Solid bar tokens (templates/admin/assets/css/base.css):
--sl-solid-bg, -border, -text and -shadow for the primary bar
- -good-, -warn- and -bad- variants carry the success, warning and danger fills and borders
- -link-text is the glyph of a link on a solid band in hand
- --sl-footer-border, and the footer stripe gradient reads the shaded tone in dark
- --sl-beam-width, -dur, -bg and --sl-pointer-width for the motion layer
- Solid surfaces (templates/admin/assets/css/theme.css):
- Panel heads, sidebar block heads in all their tones, the toolbar item in hand, the footer band and the login footer
The toned buttons sl-but-blue, -green and -red, and the mini buttons in hand, pressed or leading, with the dial toggle
- the four mini rules shared one body and are one selector list
- the muted and disabled mini hover set the whole background, so the gradient never shows through
- A beam crosses each head once a cycle, staggered by --sl-d-turn, and at once under the hand
- A soft light follows the pointer over the page, as on the presentation page
- Behaviour (templates/admin/assets/js/admin-ui.js):
- setBeamTurns() writes each head's place in the beam order
- The pointer is written once a frame; touch and reduced motion get no light
- Contract (tools/ui-contract.php):
- Components pointer, solid, solid-bad, solid-good, solid-link, solid-warn
- --sl-d-turn registered; --sl-d-pointer-x/-y also written by admin-ui.js
Benefits:
- One colour principle for every solid surface of the panel, light and dark from one token
- No @property or color-scheme pinning, so Firefox renders the dark bars exactly as Chromium does
Technical notes:
- The light scheme is unchanged: each token's light half reads the semantic tone it replaced
- All motion stands under prefers-reduced-motion: no-preference
- The audit baseline and the contrast registry are stored with the rating commit that closes this series