Журнал изменений

Официальный сайт SLAED CMS

Журнал изменений

Фильтр и поиск

Всего: 1095 Доступных коммитов | Отфильтровано: 1095 Коммиты | Страница: 1 / 110
Сегодня (05.08.2026)
Docs: the private message plan names the commit its stage 2 rows landed in
Автор: Eduard Laas | Дата: 15:26 05.08.2026

Steps 9 and 10 of docs/PRIVAT-2026.md still recorded themselves as uncommitted after cfa91cb8 shipped them. The progress table is what a session reads to decide where the work stands, so a stale evidence line there is a wrong answer to the next question asked of it.

Core changes:

  1. Progress table (docs/PRIVAT-2026.md):
  2. rows 9 and 10 open with the commit that carries them instead of "Not committed"

    • row 9 is the format column in all three update channels
    • row 10 is tools/privat-migrate.php with its probe and its test

Benefits:

  • the plan reads the same on every machine that pulls it, which is what makes the migration resumable across sessions

Technical notes:

  • documentation only, no code, schema or behavior touched
Feature: private messages gain the format column and the tool that fills it
Автор: Eduard Laas | Дата: 15:19 05.08.2026

Stage 2 of docs/PRIVAT-2026.md opens: the message table gains the format column in all three update channels, and tools/privat-migrate.php is what computes the verdict per body and rewrites the stored source. No runtime code reads format yet, so this release changes nothing a visitor sees.

Core changes:

  1. Format column in all three update channels (setup/sql/table.sql, setup/sql/table_update6_3.sql, setup/sql/update6_3_patch.sql):

  2. format VARCHAR(20) NOT NULL DEFAULT '' as the last column of _privat

    • the 6.2 channel appends it in Batch K, after the three state columns and
before the backfill, so an upgraded table keeps the column order of a fresh
one
  • the patch channel gets its own section 6, because an installation that
already applied section 5 never re-runs it
  • the column ships empty on purpose and no statement backfills it: the verdict is per body, not per column

  • Migration tool (tools/privat-migrate.php):
  • five modes and no sixth: report, classify, convert, title, sample

    • classify writes format and the ledger storage/migrate/privat-format.json
and rewrites nothing, so the verdict is reviewed before any message changes
  • convert and title are two independent ledger-driven passes that store the
value they replaced and rewrite nothing twice
  • --db and --prefix run the whole thing against a restored copy first
  • the body classifier is the one tools/comment-migrate.php carries, because the writer is literally the same filterHtml()

  • the title has no column to keep its verdict in, so the ledger keeps it: a title was written by the html branch when its own row was

  • classify refuses a ledger whose rows a pass has already rewritten, counting the rows rather than reading a completion stamp, because an interrupted pass leaves finished rows behind and stamps nothing

  • Migration and tool tests (tests/Support/privat_probe.php, tests/Unit/PrivatMigrationTest.php, tests/Support/privat_format_probe.php, tests/Unit/PrivatFormatTest.php):

  • state E joins the probe: the table stage 1 shipped, with only format missing, which is the only shape the patch channel will ever meet in the field

  • the format probe boots the real core, so a fixture that names an editor is encoded by the writer an installation really runs

  • 10 fixture messages cover both entity maps, both break signatures, the ampersand-last rule and an interrupted pass

Benefits:

  • the irreversible half of the content contract is rehearsable: dump, restore, classify, read the report, and only then convert

  • an interrupted maintenance window resumes instead of reversing a body twice
  • all three channels still end on one byte-identical table definition

Technical notes:

  • storage: format is '' only inside the stage 2 maintenance window, between the schema section and the gate; the gate is what makes it a state the release cannot end in

  • the tag map runs on the legacy branch alone, because the plain entity map turns an authored <a> back into a real tag that must stay text

  • backward compatibility: unchanged, no runtime path reads or writes format yet
Feature: private messages get four independent states and a single owner
Автор: Eduard Laas | Дата: 14:09 05.08.2026

Stage 1 of docs/PRIVAT-2026.md. The procedural private-message subsystem becomes one Privat class, and the single status column both participants shared becomes four independent state columns, so what one of them saves or deletes no longer rewrites what the other one sees. Every frontend, block, profile and admin caller moves in the same change; no compatibility wrapper, schema detection or dual schema fallback is left behind.

Core changes:

  1. Storage model in all three update channels (setup/sql/):
  2. status becomes viewed, and saved, delin and delout join it as own columns

    • the backfill reads status through the new runifcol() guard before rencol()
consumes it, so the step disappears by itself once the rename has happened
  • modcol() forces viewed onto TINYINT UNSIGNED NOT NULL DEFAULT 0, which a bare
rename cannot do: it keeps the BOOLEAN a column storing 2 was declared as
  • the single-column keys uidin, uidout and status give way to five composites

    • in_box, in_new and out_box serve the mailboxes, their counts and their quotas
    • out_new and flood were added on measured plans, not on expectation: the
outgoing unread badge was a full table scan and the send interval a filesort
  • update6_3_patch.sql gains section 5, so an installation already running 6.3 is not left behind by a release whose runtime code reads the new columns

  • the stale addidx on status is deleted: the same file created and then dropped it
  • The subsystem (core/classes/privat.php, core/system.php):
  • final class Privat owns every read and write of the table, built beside Comment

    • every mailbox predicate is written once, so a list, its counter and its quota
can never disagree, and no caller restates a state column
  • every mutation is transactional and authorizes itself through the detail
predicate of the side that asked; one foreign id refuses the whole batch
  • addMessage() answers ['id' => int, 'error' => string] from a closed code set

    • the id is getSqlLastId(), not the newest row of the table, which under a
concurrent send put a foreign message id in the notification link
  • both accounts are locked in one statement ascending by id, and the interval
and both quotas are read behind that lock
  • the recipient is resolved before the transaction opens, because the first
plain read of a transaction fixes the snapshot every later one answers from
  • a deadlock or a lock timeout is retried once, and only for a send that owns
its transaction
  1. Mailboxes and routes (core/user.php, index.php, modules/account/index.php, blocks/user_info.php):

  2. reads are GET routes that carry no token, mutations are POST with the token in the body; save and delete no longer travel as credentials in an address

  3. opening a message is what marks it read, so that one is POST and CSRF checked
  4. one mutation route serves a row action and a bulk action alike; inbox, saved and outbox gained read, unread, save and delete over a selection

  5. only the opening mailbox is rendered, the other tabs are fetched on click
  6. the detail view resolves the counterpart, so the outbox shows the sender the recipient's profile instead of their own

  7. the session counter cache is gone: both sidebar badges are counted per render
  8. Administrator panel and account deletion (core/admin.php, admin/modules/ privat.php, admin/index.php, modules/account/admin/index.php, core/system.php):

  9. the admin list reads the class, pages from the same numbers its rows came from and renders the five derived states as labelled chips

  10. the duplicate go=5 delete route is deleted rather than re-secured; one POST handler remains behind checkAdminPost('privat') and the existing isAdmin(true)

  11. both paths that delete a user row now clean the mailboxes in the same transaction, and the counterpart keeps a readable copy of an anonymous account

  12. the private-message branch of ad_status() is gone with the column it rendered
  13. Templates and language (templates/lite/, lang/):
  14. table, table-row, content-list, block-content and inline-badge gained the check column, the bulk slot and the state flags; theme.css owns their styling

  15. six new PRIVAT constants in all six locales, none of them a scoped duplicate
  16. Verification (tests/):
  17. PrivatMigrationTest drives both upgrade channels through states A to D in a disposable schema and holds every one of them to the fresh definition

  18. PrivatClassTest holds the predicates, the limits, the independence of the two sides, the bounded bulk actions and two real concurrent sends

  19. SchemaUpdateValidationTest reads modcol declarations and both channels

Benefits:

  • one owner of the table, one place a mailbox rule can be wrong
  • sender and recipient states are independent, which is what the old model could not express at all

  • concurrent sends cannot both take the last free place of a mailbox
  • fresh install, 6.2 upgrade and 6.3 patch end on the same table, and each is safe to run twice

Technical notes:

  • the schema section and the runtime code of this release are deployed together: either half alone answers an SQL error on every private-message page

  • behavior changes, each intended: a sender may delete a message already read, a saved message stays in the sender's outbox, one participant's delete leaves the other's copy, read and unread are two actions, and a send is refused when the recipient's saved folder is full and not only their inbox

  • the notification preference is psmail, the setting the profile form has always offered and which had no effect while the code read the forum preference fsmail

  • docs/VERSIONS.md carries the dated entry with the channel table
  • docs/PRIVAT-2026.md stays: stage 2, the content contract, is not started
  • stored titles and bodies keep their current encoding contract until then
Вчера (04.08.2026)
Fix: the address policy follows the registry and every SQL script splits the same way
Автор: Eduard Laas | Дата: 23:02 04.08.2026

Six defects found by re-verifying the finished upload migration, plus the consolidation they exposed: three copies of the SQL statement splitter became one, and the copy the module installer used was corrupting every script it ran.

Core changes:

  1. Remote address policy (core/classes/upload.php):
  2. IPv6 became an allowlist of the prefixes IANA delegated to a regional registry

    • 2000::/3 is far from fully delegated, so reserved space is refused by absence
    • the deny-list let fec0::/10, 3ffe::/16 and everything outside 2000::/3 through
  3. a refusal answers its own code and names the address in error_file.log

    • a list behind the registry no longer looks like a host that is simply down
  4. Admin files module (modules/files/admin/index.php):
  5. a delete with no valid token no longer removes the file and its rows

    • save() dispatched posttype=delete outside its own token guard
    • delete() disabled its check whenever a row id was given
  6. every handler authorizes through checkAdminPost('files') and the dial posts
  7. an update matching no row counts as a failed write and compensates the file
  8. SQL statement splitting (core/admin.php, admin/modules/*, setup/index.php):
  9. getSqlbatch(), getSqlclean() and getSqlinfo() moved to core/admin.php

    • the Inquiry tab, the module installer and setup/index.php share one splitter
    • setup/index.php loads it by name and the guard of that file admits SETUP_FILE
  10. stripslashes() over SQL is gone from both places that ran it

    • it turned an escaped empty default into an unparsable column definition
  11. the module installer fills every placeholder, not only {prefix}

    • a table.sql declaring {engine} and {collate} could never install
  12. a comment in front of a statement is no longer part of the statement
  13. Smaller fixes (core/classes/parser.php, modules/files/index.php):
  14. a thumbnail that could not be produced renders the full size image
  15. a guest submitting a file binds 0 into the uid column instead of an empty string
  16. Verification (tests/, tools/, admin/info/):
  17. tools/ipv6-registry-check.php compares the shipped prefixes with the IANA CSV
  18. DatabaseBatchTest holds the splitter, the placeholders and the installer contract
  19. the uploads help page describes current behaviour and carries no changelog

Benefits:

  • one address policy, one splitter, one place to correct each of them
  • a refused address and a refused script are both diagnosable from the log
  • the module installer and the system installer run scripts the same way

Technical notes:

  • behavior changes: the files admin panel is POST-only, a remote address outside a delegated prefix is refused, and the install report names statement types

  • a prefix IANA delegates after this release is refused until it joins ALLOWSIX
  • docs/UPLOAD-2026.md is removed: the migration it planned is finished
Feature: the upload migration lands its format set and closes its last flow
Автор: Eduard Laas | Дата: 16:09 04.08.2026

Batches 8, 9 and 10 of docs/UPLOAD-2026.md: admin upload moves onto the Upload class and the procedural pipeline is deleted, the 2026 format set goes live across every configuration and render path, and the final sweep proves nothing legacy survived. Two audits of that sweep are folded in, each of which found real defects rather than style.

Core changes:

  1. Admin upload and settings (admin/modules/uploads.php, config/uploads.php):
  2. uploadsave() publishes local first and remote second through the class

    • every limit comes from the all record, nothing is hardcoded at the call site
    • the all record carries 104857600 bytes and 1600x1600 to match
  3. configsave() validates extensions against Upload::getSupportedTypes()

    • an unsupported extension is dropped, reported and never stored as enabled
    • both extension fields show the supported list as a hint
  4. upload(), check_file() and check_size() are gone with their last caller
  5. The 2026 format set (config/uploads.php, config/filetype.php, config/files.php):
  6. the canonical 21 formats replace the old 26

    • ten formats withdrawn, each on its own evidence, two spellings corrected
    • modules gain only webp and avif; the full set reaches typ and the all record
  7. config/filetype.php becomes five family templates over 21 keys

    • audio renders through audio, video through video, pdf through object
    • the dead screens class is gone and the lightbox binds to sl-attach
  8. Upload class hardening (core/classes/upload.php):
  9. images must decode, not merely parse a header

    • a truncated png passed getimagesize() and was published before this
    • a missing decoder answers unsupported instead of passing the file through
  10. the remote write callback rejects a short write instead of counting it whole
  11. the address policy blocks every IPv6 range IANA marks as not globally reachable
  12. the class owned naming grammar is matched at its fixed salt length
  13. Configuration writer (core/system.php, setup/index.php):
  14. a value whose line would pass 180 characters is written as a concatenation

    • the split is deterministic, so an unchanged save round trips byte for byte
    • the installer copy follows, or a fresh install would write a different file
  15. Verification (tests/, tools/upload-route-check.php):
  16. UploadIntegrationTest drives the resolver and the accessor against real config

    • all fourteen module records survive resolve and serialize byte for byte
    • the five adapters are held to the ordering rule of the plan
  17. tools/upload-route-check.php walks 47 rows of the route matrix over real HTTP

    • credentials come from the environment, TLS verification is on by default
    • every scenario compares tree and database around itself and cleans up

Benefits:

  • one publication primitive, one rule parser, one type policy, one lock location
  • every publishing flow validates by content rather than by extension alone
  • the acceptance of the plan is reproducible from the repository, not by hand

Technical notes:

  • ext-gd joins ext-curl and ext-fileinfo in the composer require block
  • behavior changes: content stored with a withdrawn format renders as a plain link, a guest no longer sees historical files in the editor panel, and a build without a GD decoder refuses that image format instead of accepting it

  • nine other configuration files still hold over-long lines and will be rewritten in the wrapped format the first time each is saved from its own settings tab

  • stored reference formats are unchanged: avatar filenames, project relative _files.url values and the editor JSON shape all keep their existing contracts

  • setContentActive() still runs before the checked UPDATE of the admin files handler; that ordering predates this migration and is left to its own task

Fix: the unlock tells the truth and the moderation handlers read only their body
Автор: Eduard Laas | Дата: 08:15 04.08.2026

The last three findings of the backup and scheduler plan, plus the comment rule applied to the code that work produced. The plan document is removed now that every batch of it lives in the code.

Core changes:

  1. Crash reconciliation reports its own failure (system.php, scheduler.php):
  2. updateSchedulerCrash() returns null when the repaired state cannot be stored

    • unlock() shows the state directory instead of its success message
    • addSchedulerRun() refuses to start rather than running an unrecorded job
  3. Two regression tests drive it through a read-only state file
  4. POST-only moderation (comments.php):
  5. The six write handlers read their payload from the body alone

    • typ is read once, so a valid typ=0 is no longer swallowed by ?: and
replaced by a crafted query value
  • status, modul, search and chng lost their query fallbacks as well
  • A source-level test asserts no handler reaches for get or req
  • Isolated mail probe (mail_probe.php):
  • The disposable schema is registered for cleanup before any table is copied, so a failure during setup cannot leave it behind

  • The class docblock states what the probe really works against
  • Comment placement (system.php, helpers.php, comments.php, probes, tests):
  • Comments moved out of function bodies into the headers above them
  • Two functions that had no header comment received one
  • Overlong comment lines shortened instead of wrapped

Benefits:

  • An operator is never told a crashed job was cleared when it was not
  • The admin boundary cannot be steered by the address bar
  • The test suite no longer depends on the site mail queue

Technical notes:

  • No storage or configuration format changes
  • Backward compatible: the removed query fallbacks were unreachable from the rendered forms, which have posted since the boundary was introduced

Эта неделя (03.08.2026)
Feature: the avatar and both file flows move onto the upload class
Автор: Eduard Laas | Дата: 23:18 03.08.2026

Batches 6 and 7 of docs/UPLOAD-2026.md. Every remaining visitor-facing publisher now reaches the filesystem through Upload::addUploadedFile() after its own method, token and business checks have passed, so a preview, a refused form or a failed row write can no longer leave a file nobody references.

Core changes:

  1. Account avatar (modules/account/index.php):
  2. saveavatar() gates the whole operation behind POST and the account token

    • the old line validated the token only when op arrived in the POST body,
so GET ?op=saveavatar&avatar=<preset> wrote users.avatar with no token
  • the preset branch now sits inside that gate and still calls no upload
  • the upload publishes through the class and stores the returned filename

    • the rule is built from the $conf['users'] avatar keys, maxquota 0
    • adirectory loses exactly one leading uploads/ before it is passed on
  • a failed profile write deletes the exact path this request published
  • Avatar preset grid (templates/lite/fragments/table-row.html, theme.css):
  • the is_avatar_link branch gained a form/button variant keyed on action
  • sl-avatar-link moved from the anchor onto the form and got the two rules that strip the button chrome; line-height: 0 keeps the row height intact

  • Frontend file module (modules/files/index.php):
  • send() calls the class only when the save passed every check

    • a file counts as submitted when its error is not UPLOAD_ERR_NO_FILE
    • a refused file no longer falls back to the typed external URL
    • the gate also reads $conf['files']['upload'], which the old code ignored
  • the row write is checked and compensated against the returned path
  • Admin file module (modules/files/admin/index.php):
  • save() publishes straight into the selected path field, else the configured one, so the publish-then-rename branch no longer runs for a new upload

    • relocation of an already stored file with no new upload is unchanged
    • preview and delete publish nothing at all now
  • both row writes are checked; a failure deletes the published path and logs a stranded file through Logger::addFile()

  • Plan record (docs/UPLOAD-2026.md):
  • last completed batch, the decisions of batches 6 and 7, and what the route tests actually proved on the stand

Benefits:

  • no upload path can publish before authorization, CSRF and validation pass
  • stored size and path come from the class instead of a probed request path
  • one publication primitive, lock-guarded and collision-free, for five flows
  • the admin file handler writes each file exactly once

Technical notes:

  • stored reference formats are unchanged: users.avatar keeps the filename only and _files.url keeps the project-relative uploads/<dir>/<file> shape

  • Breaking: avatar presets are POST-only now, so a bookmarked GET link that set a preset stops working

  • Breaking: an operator publishing through the frontend file form no longer gets a name without an owner suffix; the flow stores the user id or 0

  • config/files.php still lists gzip, which the type map does not know, so a .gzip upload is refused until batch 9 corrects the spelling to gz

  • admin upload and the format switch are untouched; upload(), check_file() and check_size() stay until batch 8 removes their last caller

Feature: uploads become one class and the editor becomes its first adapter
Автор: Eduard Laas | Дата: 21:58 03.08.2026

The procedural upload pipeline is being replaced by a single Upload class that owns validation, transfer, naming, quota and atomic publication for every publication flow. This lands batches 1 to 5 of docs/UPLOAD-2026.md: one configuration parser, the image pipeline the new formats need, the class itself with its local and remote halves, and the Toast UI editor moved onto it.

Core changes:

  1. Upload rule resolver (core/system.php, admin/modules/uploads.php, core/admin.php, core/classes/parser.php, core/helpers.php, plugins/editors/toastui/driver.php):
  2. getEditorUploadData() renamed to getUploadRuleData() and widened to all twelve named fields

    • every consumer reads named keys, so no call site splits the configuration string any more
    • setUploadRuleData() assembles the same twelve fields and configsave() writes through it
    • getUploadModuleList() derives the settings tab module list from the records themselves
  3. checkEditorUploadAccess() takes the resolved rule array instead of raw field indexes
  4. Image pipeline (core/system.php, core/classes/parser.php, templates/admin/assets/css/base.css, templates/lite/assets/css/base.css):
  5. create_img_gd() renamed to getImageThumb() and its WBMP/IMAGETYPE_SWF branch removed

    • IMAGETYPE_WEBP and IMAGETYPE_AVIF added behind function_exists(), so an older GD build degrades
    • the image lists and the data-URI pattern accept webp and avif
  6. .sl-attach and its alignment modifiers added to both themes, built from existing spacing tokens
  7. Upload class (core/classes/upload.php, core/system.php):
  8. new class with addUploadedFile(), addUploadedFiles(), addRemoteFile() and deleteStoredFile()

    • the configured allowlist plus an explicit extension to MIME map; executable and web-active formats are refused whatever configuration says
    • transfer into .upload-<hex>.part, then the destination lock, the stale partial sweep, the quota recheck, collision retry and one rename
    • remote fetch over ext-curl only: address policy, redirect revalidation, pinned address, connect and total timeouts, header and streaming byte limits
  9. getUploadService() builds the single instance over UPLOADS_DIR and LOGS_DIR/uploads
  10. Editor adapter (core/system.php, index.php):
  11. addEditorUpload() is a thin adapter over addUploadedFiles()

    • the result codes map onto the existing language constants, so no new constant is introduced
    • the owner is the site user id, no suffix for a privileged moderator and the guest value otherwise
  12. getEditorFileJson() answers a guest with an empty list instead of every file of the shared guest suffix
  13. the generic go=4 default answers 400 with the editor JSON instead of reaching the old upload()
  14. Tests and dependencies (tests/Unit/UploadContractTest.php, tests/Support/upload_probe.php, tests/Unit/ImageThumbTest.php, tests/Support/image_probe.php, composer.json):
  15. the contract suite covers every local result code, the naming grammar, the lock, quota boundaries, the partial sweep and every remote rejection path without opening a socket
  16. ext-fileinfo and ext-curl added to require, because the class fails closed without them

Benefits:

  • one validation, naming and publication path instead of a rule copy per call site
  • uploads are verified by content rather than by extension alone
  • remote fetch fails closed for private, reserved, redirected and rebound targets
  • concurrent publications to one directory can neither overwrite each other nor oversubscribe the quota

Technical notes:

  • stored reference formats are unchanged: users.avatar filenames, project relative _files.url values and the editor JSON shape
  • lock files live in storage/logs/uploads, an existing ignored tree; no new directory for user files
  • behavior change: a guest no longer sees previously uploaded editor files and receives only the files of the current upload response
  • the source path of an upload must stay exactly as the SAPI reported it, or move_uploaded_file() refuses it on Windows
  • account avatar, both file modules and admin upload still run the procedural pipeline; batches 6 to 10 of docs/UPLOAD-2026.md migrate them and delete it
Feature: the database backup becomes a verified artifact and the admin boundary becomes POST-only
Автор: Eduard Laas | Дата: 15:47 03.08.2026

Replaces the procedural database dump with a Backup class that proves what it produced, puts the scheduler on an operating system lock with a defined crash state, and closes the admin mutations that were reachable by a plain GET carrying a credential.

Core changes:

  1. Database backup (core/classes/backup.php):
  2. New Backup class replaces addBackupTask(), which is deleted without an alias or wrapper

    • Preflight fails before any output: settings, compressor, backup root, staging, link() probe, privileges
    • Export runs in one consistent snapshot, streams unbuffered, checks every write and fsyncs the dump
    • The archive is decompressed again and compared against the dump before it may become an artifact
    • Publication uses link(), which refuses to replace an existing name; the mode is set before linking
    • Retention ages both naming schemes together and is off by default
  3. Absence of an object class is proven by effective privilege, never inferred from an empty catalog

    • Role-derived grants and MySQL partial revokes are resolved per vendor branch
    • Views are counted through information_schema.TABLES, which needs no SHOW VIEW
  4. Scheduler protocol (core/system.php):
  5. The operating system lock is the only authority on whether a job runs; JSON is status

    • A crashed run is reconciled to a defined state and becomes due again without file surgery
    • last_run survives reconciliation, so a crashed job resumes at its next slot
    • A scheduled run re-checks its slot while holding the lock, so one slot cannot run twice
    • A state that cannot be written refuses the run instead of reporting success
  6. Job state now carries the contract fields plus the metrics of the run that just finished
  7. Request boundary (core/security.php, admin/modules/*.php, index.php):
  8. checkAdminPost() requires POST and reads the token from the body, scoped to its module
  9. Fifteen admin modules migrated; row actions render through the dial POST contract
  10. The direct scheduler endpoint accepts only pseudo and cron, and the isAdmin() bypass is gone
  11. The pseudo trigger carries its credential in a body instead of an address
  12. Consumers and documentation (admin/modules/scheduler.php, monitor.php, admin/info/scheduler/ru.md):
  13. The list derives its state from the lock and the JSON together and shows a crashed job as such
  14. A running job shows live elapsed time instead of the previous run's duration
  15. The monitor measures published artifacts instead of the whole directory tree
  16. The help page gained the backup section, a cron example with a POST body and the storage deny rule
  17. Tests (tests/Unit, tests/Support):
  18. Pure contract tests for the class, integration probes against a disposable schema, lock protocol tests
  19. The mail probe now runs in its own schema, because claiming a batch took rows from the live queue

Benefits:

  • A backup either exists as a verified, restorable artifact or fails before producing anything
  • Two instances of one job cannot overlap, and a crashed one recovers without manual intervention
  • An admin action can no longer be triggered by a link, a prefetcher or a link preview

Breaking changes:

  • Cron must call the scheduler with POST; a GET invocation is refused
  • addBackupTask() is removed; addSchedulerLock() and deleteSchedulerLock() are renamed
  • addSchedulerTrigger() returns an array instead of a URL string
Эта неделя (31.07.2026)
Fix: the forum answers for the whole branch and every form carries its token
Автор: Eduard Laas | Дата: 12:19 31.07.2026

Three denormalised numbers and one denormalised pointer were maintained by whoever happened to touch them last. The comment counter is now recomputed rather than nudged, the forum tells one story about its last message wherever that story is written, and the two forum forms that wrote without a token no longer do. The nightly sweep that existed to notice the drift is gone with the drift it watched for.

Core changes:

  1. Comment counter recomputed instead of nudged (core/classes/comment.php):
  2. setTargetCount() writes the live count in one statement, and it is the only place that statement exists

    • updateCountDrift() loops over it rather than repeating it
    • the visibility rule lives here once: published and not deleted
  3. addTargetCount() queues the recompute after the response through addDeferredTask(), because the subquery is a locking read of the comment table and two visitors commenting on one target would wait on each other

  4. updateTargetPoints() keeps the author points inside the transaction, where a rollback still has to take them back

  5. The nightly job is gone (config/scheduler.php, core/system.php, setup):
  6. commentsync removed from the config, the dispatch map, the dispatch branch and the installer, which now takes it out of an upgraded site as well

  7. what a write cannot reach is a target nobody comments on again; the first tab of the comments section reports those and repairs them on a click

  8. One answer for the last message of a forum section (core/system.php):
  9. getForumLast() answers the newest visible activity of a whole branch, as the topic it belongs to; a reply used to be answered as itself

  10. setForumLast() repairs whoever advertised a topic that has gone, matched on the stored value rather than walked from the category the request named

  11. delete(), move() and the hide action call it; each of them used to decide on its own, from one leaf, and write that answer over every ancestor

  12. the synchronisation tab asks the same function, compares before writing, and repairs the reply count of a topic, which nothing had ever recounted

  13. Forum forms carry a token (modules/forum/index.php):
  14. send() refuses without one, and the quick reply and the full form now hold it
  15. delete() and move() refuse as well
  16. removal is submitted rather than followed, so it cannot happen on a prefetch: the dial renders a form for it and the button owns it through the form attribute, which keeps the fan layout intact

  17. the quick edit link stopped carrying its token in the address
  18. Quick edit is the editor it was written in (core/helpers.php):
  19. getTplAjaxTextarea() renders the real editor with an id carrying the record, because the page it lands on already holds an editor of its own

  20. the forum message edit opens at ten rows like the comment edit
  21. Defects found on the way:
  22. updatePost() appended to a string, which is fatal in PHP 8, and measured the last word in bytes rather than the longest in characters

  23. is_acess() asked is_moder(isset($conf['name'])), a boolean, so an administrator of the module fell through to the visitor check

  24. the alert component wanted lines in one theme and messages in the other

Benefits:

  • A counter that drifted is corrected by the next comment on its target
  • A section advertises the newest message of its branch, not of one child
  • No forum write happens without the token of the page that offered it
  • One definition each for the comment count and for the last message

Technical notes:

  • The recompute runs after the commit, so a lost deferred task leaves the count one behind until the next write rather than blocking a visitor

  • Removal follows the recycle setting: a topic moves rather than disappears
  • The completed plan documents were removed; twenty-seven citations of them remain in surviving documents and test headers

Всего: 1095 на 110 страницах по 10 на каждой странице

1 2 3 4 5 6 7 8 9 10 110
Хотите опробовать SLAED CMS в действии?
Идеи и предложения
Обратная связь
Подтверждение

Поделиться
QR-код