Официальный сайт SLAED CMS
Журнал изменений
The label crawl had been red since before Node: its baseline predated every Node page, so no new label defect could be told apart from the old ones. Three defects behind it are fixed and the baseline is taken again over the routes that exist today.
Core changes:
- One id key for the select fragment (templates/lite/fragments/select.html, callers):
The lite fragment read input_id while the admin one reads selectid, so a caller naming selectid left the site select without an id and its label pointing nowhere
- Node categories, extra fields of Field and getTplCatSelect() on the site were affected
- Both fragments read selectid; account, contact, rss, Node and the category select pass it
- TemplateValidationTest holds both fragments and refuses input_id at any select caller
- Hints of the Node admin forms (modules/node/admin/index.php):
The poll, publish date, type name, extension and view mode hints carry an id, and their field names it in aria-describedby, as every other admin form already does
- Accessible name of the code editor (core/classes/editor.php, plugins/editors/codemirror/driver.php):
- Editor::getCode() takes a label and falls back to _TEXT, as the content editors do
CodeDriver::getWidget() receives it; CodeMirror writes it as aria-label of its content through EditorView.contentAttributes, JSON-encoded so a file path cannot close the inline script
- All nine code editors of the panel name their file, template, block, query or log
- EditorFormatTest drives the driver and refuses a caller without a label
- Label baseline (tools/label-audit-baseline.json):
Taken again as a first store: the routes of the removed modules no longer exist, which the crawl counts as fallen coverage and refuses to overwrite; 20 entries become 3
- Kept: two duplicate ids of the security ban list, and the avatar hint of the settings tile,
which is referenced by its radio group but sits outside the rows the crawl knows
Benefits:
- A click on a label focuses its select, and a screen reader announces field names and hints
- ui:label is green and can report the next regression
Technical notes:
Breaking: CodeDriver::getWidget() takes a sixth argument string $label; a code editor driver of a third party must add it (UPGRADING.md, docs/EDITORS.md)
- The select fragment of the site no longer reads input_id
65ce7687 shipped config/fields.php without its account and forum sections. The fields unit of the 6.3 update reads a named file as the 6.3 format and requires each of its three areas to be a definition list, so a 6.2 site without a field configuration of its own stopped with a refusal before its schema ran.
Core changes:
- Shipped field definitions (config/fields.php):
account and forum ship as empty definition lists instead of being absent
- The demo fields stay removed; order keeps its example with field1 not required
Benefits:
- The 6.2 update finishes on the first run again (UpdateSiteTest)
- A clean installation carries no demo profile or forum fields
Technical notes:
- Configuration only; setUpdateFields() in setup/index.php is unchanged
- The stand keeps its own config/fields.php, only the shipped copy changes
docs/NODE-FINDINGS-2026.md planned the records the Node plan left open. All ten batches are in the previous commit, so the plan document goes; what outlives it is already in docs/NODE.md, docs/POINTS.md, docs/RATINGS.md, docs/TEMPLATES.md and in the tests that hold each fix.
Core changes:
- Plan document (docs/NODE-FINDINGS-2026.md):
- Removed after its last batch
Benefits:
- No plan document describes work that is already done
Technical notes:
- Documentation only, no code or behaviour change
The Node plan left open records outside its acceptance: defects of shop, order, forum, account, the core and the installer that its windows met and were not allowed to fix. This commit closes all of them in ten batches, each with a test that fails without its fix, and widens the gates so the code rules and the tooling hold.
Core changes:
- Shipped configuration and admin security (config/, admin/modules/.php, modules//admin/index.php):
- The debug panel ships closed (var_view 0) and getVariables() escapes every value
- The test bans of the stand leave config/security.php; the demo fields of account and forum leave config/fields.php
Every state-changing admin handler checks checkAdminPost() with its module scope; row actions post
- GET, header and query tokens are refused, AdminGuardTest scans every admin file
- The admin file rename accepts a plain name only and reports a failed rename
- Form fields named name are renamed, category parents refuse cycles, setForumLast() ends on a stored cycle
- Shop, order, forum and contact (modules/shop, order, forum, money, contact):
- The checkout reads the cart through getCartCookie() and clears it with setCookiesDelete()
- getTplPager() takes a known count; the product list qualifies p.status; mass actions split categories and modes
- Point compensations roll the owner back on false, as docs/POINTS.md now describes
- The CSV import leaves votes and comments alone, opens only files of its own directory, and stops repeating the id
- Settings texts of shop, order, money and contact use the editor field and the breaks format, so a save is stable
- Core writes, cache and templates (core/classes/template.php, pdo.php, cache.php, comment.php, core/system.php):
- Compiled templates and scheduler state files are written through a temporary file and a rename under a lock
- The panel bumps the page cache on its first write and once more at shutdown; _session writes no longer bump
- Comment::updateBody() and the deferred recount run under the write guard; silent failures log their reason
- Cache::setDirPath() creates every cache directory and tolerates a lost mkdir race
- Node (core/classes/node/*.php, modules/node/):
- getTreeRows() limits replies per root in one light walk; batch sizes follow NodeQuery::getTargetSize()
- Comment::getThread() reads a support thread at once; stored types normalise a late rule on read
- view.mode is a closed list, the report switch follows the mode, categories and sitemap follow the language
- A non-moderator without pre-moderation gets no external address field; import failures answer their reason
- Installer and the 6.3 update (setup/index.php, setup/sql/table_update6_3.sql):
- Duplicate rating rows are removed before the unique key, the 6.2 loader is replaced by the shipped panel
- checkSetupCode() counts failures under a lock and removes the key after the fifth
The update carries an early 6.2 schema, NULL addresses, negative balances and MySQL 8 zero dates
- New fixture tests/Fixtures/update62early; the fixtures' site.sql is kept by .gitignore
- Interface and language (templates/, lang/, plugins/system/slaed.js):
- Favourite buttons post without an href, POST-only ops answer 405, the admin pager shows its total
- _BACK means back in Polish and Ukrainian; the rating widget reads the checked rule and answers blocked scopes
- The forum repair of counters is a POST button; auto_links reads banners of the site theme
- Code rules sweep (whole tree, tests/PhpFileFormatTest.php, .php-cs-fixer.dist.php):
- Comments leave function bodies, one line above a class, no period, no Cyrillic, 180 characters per line
- list_syntax and indentation_type join the fixer; shop and whois are indented with spaces
- Gate tooling (tools/ui-shots.mjs, tests/Support/tree_walk.php, tests/bootstrap.php, tools/upload-route-check.php):
- ui-shots signs the modes in one after another with one retry; --before --only keeps the rest of the pair
- Every source gate walks the tree through getTreeFiles(), which never enters storage or a scratch theme
- In-process tests log into a directory of their run instead of the site log of the stand
- setFailTrigger() is defined again; PointTest and RatingTest fail on a broken probe instead of skipping
Benefits:
- A clean installation no longer exposes cookies and sessions or carries test bans
- Admin state changes need a POST of their own scope
- The full suite and ui:gates can run side by side without false failures
Technical notes:
- Schema: table_update6_3.sql removes duplicate _rating rows and aligns the early 6.2 column shapes
API: getTplPager() accepts count, NodeQuery::getTargetSize(), NodeQuery::MODES, Comment::getThread(), Rating::getRule() and the blocked code 503, Point::$active readable, checkCatRight() replaces checkCatRead()
Breaking: admin handlers refuse tokens outside a POST of their scope; a stored forum or category cycle is refused on save; view.mode support requires the support extension
The work plan docs/node guided the replacement of the nine content modules by Node from S00 to S23 and is complete. Its contracts are condensed into permanent references that describe the current code, and every record it left open is checked against the tree and planned in batches.
Core changes:
- Node reference (docs/NODE.md):
- Goals, naming, code layout and the database of the eight Node tables with the state matrix
Core API of the models, NodeQuery and NodeService with exact signatures, exceptions and site helpers, plus the shared Field and Feed subsystems
Types with every settings key and default, the ten shipped profiles and the configuration protocol; routing, response codes, page cache and rendering keys
Integrations, the extension contract with support and sync, security, statement budgets, the single lock order, testing with every probe mode, and the internals of the 6.3 update
- Points and ratings (docs/POINTS.md, docs/RATINGS.md):
- Model, configuration, public API, transactions, owner map, administration, 6.3 carry-over and tests of each
- Open findings (docs/NODE-FINDINGS-2026.md):
About 80 records of the Node windows, verified against the tree, in ten batches ordered by severity; forks are marked for the owner
- Related documents (docs/PARSER.md, docs/VERSIONS.md, docs/node):
- PARSER.md describes the backslash literal rule Feed depends on; VERSIONS.md points to docs/NODE.md
- docs/node is removed
Benefits:
- One current reference per subsystem instead of a 1.8 MB plan with history, decisions and stage notes
- Open work is visible in one plan with its order
Technical notes:
- Documentation only; no code, schema or configuration changes
- The plan remains in the git history up to this commit
The third audit (S21) and the fourth (S22) of the Node implementation were split into fix windows; the final acceptance of S22.6 opened S23, which proved its two findings unreachable. Every finding was re-checked in code before its fix, and every fork was decided by the project owner.
Core changes:
- Security of Node - S21.1, S22.3 (core/system.php, modules/node/index.php, core/classes/node/service.php, core/user.php):
addAdminMail() escapes the material title; the form offers only categories the writer may view and post into (NodeQuery::getNodePostCats()), and the writer checks pview and ppost itself
- Relations and the parent must be readable targets, so preview is no oracle for hidden or scheduled materials
addFavorite and deleteFavorite are POST-only with the token in X-CSRF-TOKEN; the profile import checks the extension
op=asset redirects to an external address only for the link and download roles, and a non-moderator sets a new external address only in a material that goes to pre-moderation
- Installer and the 6.3 update - S21.2, S22.1, S22.2 (setup/index.php, setup/sql/table_update6_3.sql, config/modules.php, UPGRADING.md):
config/setup.unlock carries an owner code of at least 8 characters, verified by password_verify() before the first write and replaced by its hash on the first request
update6_3 removes the shipped types without a _node_types row, renames the shipped admin.php to the entered name, keeps the language and address of the 6.2 site and switches off the blocks of the removed modules
- setConfigFile() results are checked, a failed module SQL leaves no modules mark
Found on a real 6.2 schema: the UNIQUE key mid_modul_uid of _rating is dropped, whois.hometext becomes body before MODIFY, admins.editor NULL becomes 0, and the _nodes counter reads the auto_increment column of the old tables
- The clients module ships without a group; UPGRADING.md lists what the update changes in the data
- Model integrity - S21.3, S22.3 (core/classes/node/service.php, core/classes/comment.php, core/classes/node/ext/sync.php, admin/modules/categories.php):
- Deleting a category takes its whole subtree; stored category loops no longer spin forms, screens or RSS
- A type cannot switch off categories, tree or related while it holds such data
- A moderator's own report earns no report points; a replaced resource loses its metadata and hits
The profile comment count follows the visibility of the feed; the sync extension refuses features.submit; deleteNode() reads the material before BEGIN
- Routes, head and page cache - S21.4, S22.4 (modules/node/index.php, core/system.php, templates/lite/index.php, blocks/node.php):
- Reader refusals answer their status code, a page past the list bound answers 404
- Flash notices show on public pages; the FAQ marquee binds the cached page to its deadline
Header and breadcrumb categories follow the read right; canonical of the start page with a category or page is the type address; letter navigation keeps the category; report is limited to download and link roles, the published order is open to every type, feeds come from getRssFeeds()
- Code rules - S21.5, S22.5 (core, modules, setup, admin, tests/PhpFileFormatTest.php):
- Wrapped # comments are rewritten as independent lines and PhpFileFormatTest refuses new ones
Redundant casts are removed; code comments name docs/NODE.md, docs/POINTS.md and docs/RATINGS.md instead of the retired plan and its stage ids
- Tests - S22.2, S22.6, S23 (tests/Fixtures/update62, tests/Unit/UpdateSiteTest.php, tests/Support/.php, tests/Unit/.php):
tests/Fixtures/update62 is an invented 6.2 site on the real 6.2 DDL; UpdateSiteTest runs the installer update over it in every phpunit run
The first administrator of the update probe gets the probe address; an author who does not moderate the type is refused by updateNode(), also when moderating another type
Benefits:
- The update is proven on a real 6.2 schema instead of a stand already migrated by earlier builds
Every finding of the third and fourth audits is fixed; the open remainder is planned in docs/NODE-FINDINGS-2026.md
Technical notes:
Schema: table_update6_3.sql drops mid_modul_uid of _rating and repairs whois and admins before MODIFY; table.sql unchanged in structure
API: getLetterNavi(string $mod, int $cat = 0); NodeQuery::getNodePostCats(); getFavoriteList readable without token; checkTypeAssets() becomes checkTypeStore()
Breaking: addFavorite and deleteFavorite accept only POST; a type cannot switch off categories, tree or related while data uses them; op=asset of image, gallery, player and none roles answers 404 for external addresses
- config/security.php is not part of this commit; the shipped secret stays empty
Verification: php -l, phpstan, php-cs-fixer check, full phpunit 1558 tests with 22872 assertions and 7 environment skips, npm run ui:gates 234 tests
The Node plan kept the configuration of the stand out of its commits. On request of the project owner it now ships as it runs on the stand: the Node types with their settings, the field definitions, the rating and upload rules of these types and the protective files of their upload directories.
Core changes:
- Node types (config/node.php):
- The types of the stand with their full settings replace the empty shipped list
- Fields, ratings and uploads (config/fields.php, config/ratings.php, config/uploads.php):
- The field definitions of the stand replace the demo definitions
- Rating rules node.<name> and upload rules of the stand types are added
- Upload directories (uploads/content, uploads/docs, uploads/jokes, uploads/media):
- .htaccess and index.html guard the directories of the stand types
Benefits:
- The repository reflects the configuration the stand runs with
Technical notes:
- config/security.php is not part of this commit; the shipped secret stays empty
A clean installation reads config/node.php from the release: types listed there have no rows in _node_types until the installer or the panel creates them, and NodeProfileTest checks a clean installation against this configuration
The second audit of stages S00-S19.8 split its findings into eight fix windows. This commit carries all of them: every finding was re-checked in code before its fix, the forks the contracts left open were decided by the user and written into the owning documents, and the plan of docs/node is complete.
Core changes:
- Public form security - S20.1 (modules/node/index.php, core/classes/node/query.php, core/classes/node/service.php):
A guest passes the comment captcha on every POST before any file is read; uploads of the public form need checkEditorUploadAccess() and respect the role max and maxfiles, inactive and link roles take no file
A new per-IP window limits.send (60 seconds) answers NodeException::LIMITED with 429, the moderator of the type is exempt; a closed category answers 404 through NodeQuery::checkNodeCategory()
- Search titles of Node are decoded once and escaped once
- Installer and 6.3 update - S20.2 (setup/index.php, core/classes/pdo.php, setup/lang, UPGRADING.md):
The form never prints the database password, an empty field keeps the stored one, a refusal keeps config/setup.unlock
- The table prefix must match [A-Za-z0-9_]{1,32} and the admin file name goes through filterVar()
setUpdateModules() keeps the switches a 6.2 site stored in _modules over the shipped config/modules.php; checkUpdateBase() refuses a clean install over existing tables of the prefix and an update without _users and _admins
- Integrity of comments, points and categories - S20.3 (core/classes/comment.php, core/classes/node/service.php, admin/modules/categories.php):
- deleteNode() refuses with STORAGE when Point cannot confirm the compensation of an award
A premoderated comment of a Node material locks the material; NodeService::checkTypeRegistry() guards the category screen, which now answers _ERROR_UP instead of a raw SQL failure
- Document tree - S20.4 (modules/node/index.php, templates/lite, setup/sql):
A type with features.tree shows the tree of its documents with the current node, previous and next links and a paged remainder, read in batches of NodeQuery::TREEPART
- _nodes gets the index tree (tid, status, id) in table.sql and table_update6_3.sql
- The moderate action and small rendering fixes - S20.5 (core/classes/node/service.php, core/classes/comment.php, core/classes/node/view.php):
Approving a material, a comment or an asset report writes a Point event moderate with the aid of the moderator; comment event scopes live in one place
NodeView renders only card for a NodeTarget, the unused fragment node/search.html is removed, the sitemap reads Node in batches of limits.syncbatch, related cards no longer count views
- Display modes - S20.6 (modules/node/index.php, templates/lite, tools/ui-contract.php):
- docs, faq, files and media get their own card and view templates, media also its list
Cards and og:image prefer the poster role, the list card carries download, every view gets download_label and hits_label
- Code rules and consolidation - S20.7 (core/system.php, modules/node, core/classes, admin/modules, setup/index.php, tools/node-profile.php):
getNodeReader(), getNodeWriter() and addNodeMail() live in the core next to getNodeHandler(); support and admin mail go through addNodeMail()
render_blocks() becomes setBlockView(int $bid); arrow functions and parameters of the plan code are typed, redundant casts are removed
- Plan documents - S20.8 (docs/node):
Status lines, the test structure of 13 and 02, the NodeContext and target signatures of 05, the cover label of 06 and the module tree of 04 follow the code
Benefits:
- Every finding of the second audit is fixed or recorded with its reason in docs/node/PROGRESS.md
- Promised but missing features of the plan - the document tree, moderate and display modes - are shipped
Technical notes:
- Schema: KEY tree (tid, status, id) on _nodes in table.sql and table_update6_3.sql
- Config: config/node.php gets limits.send = 60; NodeException gains LIMITED = 6
API: render_blocks() is replaced by setBlockView(int $bid); getConst() resolves only names with a leading underscore; checkUpdateBase() takes bool $fresh; NodeService::checkTypeRegistry() is new; getNodeReader(), getNodeWriter() and addNodeMail() are core functions
Breaking: the installer refuses a prefix outside [A-Za-z0-9_]{1,32}, so a 6.2 site with such a prefix cannot pass the update; the theme fragment fragments/node/search.html is gone; NodeView accepts only card for targets
The audit of stages S00-S18 split its findings into eight fix windows. This commit carries all of them: every finding was re-checked in code before its fix, the decisions the contracts left open were taken by the user and written into the owning documents, and the plan of docs/node ends here.
Core changes:
- Input and output security - S19.1 (setup/index.php, core/classes/field.php, core/classes/node/*.php):
An installed site keeps setup.php shut: any op without the owner key config/setup.unlock shows only the lock screen, a clean run removes the key, and the key is ignored by git
Node titles in search are escaped, fields are saved only from a full POST, a Node delete checks its type, favorites no longer farm points, and Node categories are written through NodeService::addNodeCategory()
- The 6.3 update - S19.2 (setup/index.php, setup/sql/table_update6_3.sql, UPGRADING.md):
A 6.2 configuration is converted inside the update branch, the old files are kept as snapshots until a clean finish, the site is closed by removing config/local.php, and a DDL error stops the branch
- Newsletter recipients move into the mail queue, the MariaDB floor is checked in preflight
- Data integrity - S19.3 (core/classes/node/service.php, core/classes/point.php, core/classes/comment.php):
update6_3 raises AUTO_INCREMENT of _nodes above every id of the nine removed tables, so old addresses cannot land on new materials
Leftover comments and favorites of removed modules are listed and removed through op=remains; deleteNode() removes the comments of a material inside its transaction
- Cache, locks and response codes - S19.4 (core/classes/cache.php, core/classes/comment.php, core/classes/filemanager.php):
Comment writes take the write guard before BEGIN and bump the epoch after COMMIT; account, shop and voting owners follow the same protocol
- The configuration is built from its sources while an unfinished journal exists
- Rendering - S19.5 (core/classes/parser.php, core/classes/node/view.php, setup/sql/table.sql):
Trusted rendering runs only inside tags: filterDoc()/filterContent() take bool $trust, Node views and Node search render with safe and trust
- The first publication of a comment is stored in _comment.shown; plain text drops script and style
- Performance and Feed - S19.6 (core/classes/node/query.php, core/classes/feed.php, tools/node-profile.php):
- Ordered reads split into pinned and unpinned branches over the new _nodes indexes title, updated and expires
- The point journal counts at most 5000 rows, the account and RSS feed page is cached for 900 seconds
- Lost functions of the nine removed modules - S19.7 (core/classes/node/query.php, core/user.php, templates/lite):
NodeQuery::getNodeAuthorStat() feeds the author profile per type, NodeQuery::getNodeCategoryCount() the category screen; theme links to old ids are gone; maildrain runs at priority 8 and update6_3 repairs it
- Code rules - S19.8 (admin/index.php, core/.php, core/classes/node/.php, modules/*, tests/):
The starter news of a clean installation reads _NODE_START_TITLE, _NODE_START_INTRO and _NODE_START_BODY of admin/lang in six languages
Lines over 180 characters and comments inside function bodies added by the plan are gone; 97 redundant (string)/(bool) casts over PDO rows of NOT NULL columns are removed
getUserInfo() returns array, the directory constants of core/system.php respect earlier definitions, filterFields() and four unused core functions are removed, StatsContractTest follows the site time zone, and the presentation demo query no longer names the comment table
Benefits:
- Every audit finding of S00-S18 is fixed or recorded with its reason in docs/node/PROGRESS.md
- The full phpunit suite runs without failures, including the long-standing CommentIsolationTest
Technical notes:
- Schema: _comment.shown, the _nodes indexes title, updated and expires; both in table.sql and update6_3
API: NodeExtension::updateNodeAction() takes int $uid; Parser filterDoc()/filterContent() take bool $trust; NodeQuery gains getNodeAuthorStat() and getNodeCategoryCount(); getUserInfo(): array; filterText() accepts only a string; getTplRefreshTimeSelect(), getAsyncPager(), getTranslit(), url_types() and filterFields() are removed
- Breaking for callers of the removed functions and of filterText() with an array; no caller remains in the tree
Verification: php -l, php-cs-fixer check, phpstan, full phpunit 1501 tests with 0 failures, npm run ui:gates 234 tests
The audit of stages S00-S18 read the implementation in seven slices against the Node contracts and the project rules. Its findings enter the plan as stage S19, split into eight windows S19.1-S19.8, so each fix follows the same one-window-one-stage protocol and closes with its own verification.
Core changes:
- Roadmap cards (docs/node/14-roadmap.md):
An S19 introduction: where the findings come from, lines pinned to 70224f65, each finding re-checked in code before a fix, and two decisions asked of the user before code
Eight cards with dependency, reading list, findings with file:line and function, files and acceptance
- S19.1 input and output security: Node title XSS in search, field save over GET or a truncated POST,
Node delete without the type check, favorite point farming, Node categories outside the service
- S19.2 the 6.3 update: the site stays open because config/local.php outlives close, DDL errors do not stop
the branch, newsletter recipients, the MariaDB 10.5.2 floor, the 6.2 Pro configuration format
- S19.3 data integrity, S19.4 cache and locks, S19.5 rendering, S19.6 read performance, S19.7 leftovers
of the nine removed modules, S19.8 code rules
- Progress (docs/node/PROGRESS.md):
- Eight not-started rows in the stage table; the protocol ends the plan after S19.8
The handoff block names S19.1 as the next window, records the check results at audit time and drops the stale note that S17-S18 were uncommitted
Benefits:
- Every audit finding has an owner window, a verification and a place in the plan instead of the chat
- Open product decisions are marked as such and cannot be settled silently by an executor
Technical notes:
- Documentation only; no code, schema or configuration changes