Switch the captcha to the ALTCHA external widget build so the proof-of-work runs in a self-hosted same-origin Worker instead of a blob: Worker. This makes the captcha work under a strict Content-Security-Policy (default-src 'self') out of the box, with no per-site CSP change required.
Core changes:
- Vendored ALTCHA assets (plugins/altcha/):
- Replace the bundled build with the external build altcha.min.js (no blob worker)
Add altcha-sha.js (official SHA-256 PoW worker) and altcha.css
- Files taken verbatim from altcha@3.0.11 dist (external + workers/sha)
- Bootstrap (plugins/altcha/altcha-init.js):
Import the external build and register the SHA-256 worker through $altcha.algorithms.set(...) with a same-origin worker URL
- Load altcha.css once and register localized strings via $altcha.i18n
- Provider (core/classes/captcha.php):
- Document the external-build / self-hosted-worker contract
Benefits:
- Captcha works under strict CSP on every installation without server changes
- No blob: workers; proof-of-work stays same-origin
Technical notes:
Verified in a real browser under the production CSP (default-src 'self' ...): verified state, same-origin worker, zero CSP violations
- Backward compatible with the existing SHA-256 challenge/verify (HMAC) logic
Activate the built-in sitemap job (active 0 -> 1); no secret involved.
The vendored ALTCHA widget is v3.0.11, whose API differs from the previous
integration: it reads the challenge attribute (not challengeurl), hides
footer/logo via configuration, and ignores a strings attribute. The old
markup made the widget fetch the page itself and fail with "Verification
failed". This restores verification and adds full localization.
Core changes:
- Widget contract (templates/{lite,admin}/fragments/captcha-altcha.html):
- Use
challenge,languageandconfiguration(hideFooter/hideLogo) - Add an inert JSON island with the localized strings (CSP-safe)
- Localized bootstrap (plugins/altcha/altcha-init.js, new):
Imports the widget and registers strings via the $altcha.i18n global before first render, since v3 ships English only
- Provider (core/classes/captcha.php):
Pass lang_code from the active $locale (not the default $conf language), strings_json (JSON_HEX_TAG) and the configuration attribute
- Document the version-sensitive v3 contract
Strings (lang/*.php): add _CAPTCHA_LOADING/REQUIRED/WAIT in all six languages for complete widget coverage
Verified in a real browser on the registration and admin-login pages, in Russian and after switching to German.
The password and repeat-password rows passed getTplTitleTip()._PASSWORD
through the escaped label key, so the tooltip markup showed as literal
text. Use label_html so the hint renders, matching the admin form.
Mirror the refactor analysis in Russian (identifiers and code stay English per project rules) so it matches the team's working language.
Self-heals stale scheduler configs and surfaces real run results, and fixes two task-level bugs that made jobs fail silently or report wrong metadata.
Core changes:
- Scheduler job normalization (core/system.php):
Add getSchedulerJob() as the single read/normalize accessor
- Enforces canonical type/system for built-in jobs (dbbackup, filescan,
newsletter, sitemap) and drops the legacy 'handler' key
- Replaces raw $conf['scheduler']['jobs'][...] reads in lock/due/run paths
A pre-refactor config (empty 'system', legacy 'handler') no longer makes every system job exit early as 'idle'
- Manual run feedback (admin/modules/scheduler.php):
run() now reports the actual addSchedulerRun() status/message instead of always showing success, so disabled/locked/idle/failed are visible
save() derives built-in jobs via getSchedulerJob() and normalizes every job before writing, so a stale schema cannot survive a save
- Sitemap task (core/system.php):
Initialize $info/$htm/$cd before use; count($info) on an undefined value threw a TypeError on PHP 8 when no module produced data
- Backup task (core/system.php):
Resolve the produced archive by actual extension (.zip/.gz/.bz2) instead of a hardcoded .sql.gz, fixing wrong last_backup_file/size metadata
Benefits:
- Production jobs run regardless of a drifted config; no more silent no-ops
- Failures become visible in the admin UI
Technical notes:
- No schema migration shim; normalization is applied at read and write
- Behavior unchanged when the config is already in the current schema
scandir('modules') also returns files (.htaccess, index.html); probing 'modules/<file>/index.php' tripped open_basedir warnings on production. Guard the file_exists() checks with is_dir() since modules are always directories.
Records which procedural core subsystems would benefit more from class encapsulation than the (stateless) scheduler family, ranked by state/resource coupling, with explicit non-candidates and per-rule refactor constraints.
The vendored meta files are not present and no longer need ignoring.
Add getTplPagerView() as the one place that builds the pager wrapper, numbered links, prev/next and dots. getTplPager (count query), getPageNumbers (known counters) and getAsyncPager (HTMX) become thin adapters that supply a per-page link target. The hand-rolled pager loops in the account, auto_links and changelog admin lists and the language editor now call getPageNumbers. Output is unchanged; each theme keeps its own pager fragments, so admin and lite stay independent.