Официальный сайт SLAED CMS
Журнал изменений
Implements the full 2026-07 performance plan: exact locked statistics, deferred post-response tracking, derived config, streaming GeoIP, shared category map, parser cache, cache GC, and a guest page cache that keeps every visitor-bound token isolated through signed dynamic-region markers.
Core changes:
- Statistics and tracking (core/system.php):
Rewrite updateStatsTrack around one c+ handle with a single LOCK_EX
- read, day/month rotation, truncate and write share one lock
- first hit of a new day now records its own IP (hosts=1) and user
- three duplicated counter-field blocks collapsed into one
Replace sessions.log with a signed base64url stats cookie
- fields v1|sid|fst|lst|hits|country|cts|uniq-day|ip-hash, HMAC via
getSecret('stats'), verified with hash_equals, tamper resets session - country carries a 24h TTL and is bound to the IP hash
- updateSessionState removed, sessions.log and its global lock retired
Split tracking into pre-output cookies and post-response writes
- addDeferredTask/setDeferredTasks queue with shutdown backstop
- session_write_close before deferred writes, drains on all exits
- news article counter update moved into the deferred queue
Literal stripos matching for bots, fbots, and auto_links patterns
- removes regex injection from config and DB values, caches results
- Conditional lastvis update (60s window) and _session upsert
- Session schema (setup/sql/table.sql, table_update6_3.sql):
- UNIQUE KEY on _session.uname for new installs
mksessuniq migration: dedup by max(time, id), drop old index, add unique, idempotent on re-run; code uses INSERT ... ON DUPLICATE KEY
- Derived config cache v2 (core/system.php):
getConfig stores derived data in config/local.php (version 2)
- per-theme asset manifests with stat fingerprints, parsed SEO
graph/schema templates, logo dimensions per theme doCss/doScript drop per-request glob/stat sweeps, bundle hash only computed when bundling is enabled
- Streaming GeoIP (core/classes/geoip.php):
MMDB reader works through fseek/fread ranges instead of loading the whole database file; metadata resolved from the last 128 KiB
- byte-exact with the old reader, peak memory 25 MB -> 2 MB
- fread(handle, 0) guarded (old substr was silently empty)
Category map and parser cache (core/system.php, core/helpers.php, modules/news/index.php):
getCategoryMap consolidates two duplicated category queries behind an epoch-keyed data cache with raw titles
news list caches filterContent output keyed by content hash, parser config, theme, locale, and parser version; [block]/[hide]/[usephp]/ [attach] and local <img> content bypasses the cache
Guest page cache with dynamic regions (core/system.php, core/classes/cache.php, core/security.php, blocks/*):
Signed markers [[sldyn:type:par:hmac]] via getSecret('dynreg') keep user content from forging substitutable markers
Regions: CSRF tokens (getPageToken), captcha (getPageCaptcha), and the whole voting widget; cache files contain zero live tokens
Poison guard: a live getSiteToken/getCaptcha call during a cacheable build prevents storing the page entirely
- Default-deny route allowlist inside checkPageCache (news list only)
Fail-closed sidecar with body hash and dyn flag; dynamic pages are no-store and never answer 304; substitution runs on hit and miss
- cache = 1 enabled in config/global.php
- Cache GC (core/classes/cache.php, core/system.php):
deleteStaleTree recursively sweeps storage/cache/templates and the data cache joins the cachegc scheduler job
Benefits:
- Guest cache hit generation time 0.307s -> ~0.055s (5.3x)
- Uncached pages roughly halved (search 0.17s -> 0.09s)
- Day counters survive concurrent requests and day rollover
- CSRF/captcha isolation between visitors verified with two cookie jars
Technical notes:
- config/local.php cache version bumped to 2, rebuilt automatically
theme asset changes now require a config rebuild (admin save or deleting config/local.php)
_session migration applied to the dev DB; verify on MySQL before production rollout (developed against MariaDB 11.7)
- ip/agent freshness for logged-in users delayed up to 60 seconds
docs/PERFORMANCE.md updated; docs/PERFORMANCE-REMEDIATION-2026.md is the completed plan, ready for deletion after review
Strengthen OAuth account creation, linking, unlinking, and login finalization with fail-closed transactions, atomic temporary-state consumption, safer provider validation, and consolidated session handling. Add focused regression coverage for JWT validation, transaction failures, redirect safety, and unlink invariants.
Core changes:
- OAuth security and lifecycle (oauth.php, modules/account/index.php):
Harden redirect, provider response, JWKS, claim, and one-time state handling
- Bound provider response bodies and validate JSON content types
- Consume callback state atomically and preserve retryable pending records
Make account creation and unlink operations transaction-safe
- Roll back failed user/link creation as one unit
- Serialize unlink operations and protect the final login method
- Consolidate normal and OAuth login session finalization
- Database transaction support (pdo.php):
- Add begin, commit, and rollback primitives for shared PDO connections
- Normalize transaction exceptions to the existing boolean error contract
- Admin output and regression tests (modules/account/admin/index.php, tests/Unit):
- Render provider identity data through escaped text template fields
- Cover redirects, claims, JWT signatures, transaction failures, and unlink outcomes
Benefits:
- Prevents orphan accounts, replayed callbacks, unsafe redirects, and last-method unlink races
- Reduces duplicate login state handling and unnecessary unlink queries
- Improves maintainability with isolated OAuth and database regression tests
Technical notes:
- Uses the existing OAuth tables and configuration without schema changes
- Preserves current routes and backward-compatible account behavior
Implements a dependency-free OAuth2 Authorization Code Flow with PKCE as the new external sign-in, removes the legacy uLogin integration completely and consolidates theme templates while keeping both themes self-contained.
Core changes:
- OAuth client (core/classes/oauth.php, config/oauth.php):
Static Oauth class: provider config, PKCE auth URL, token exchange, hardened curl layer, JWKS cache with rotation, full RS256 id_token validation (exp/nbf/iat with skew, iss list, aud/azp, nonce)
- Microsoft email trusted only with xms_edov or consumer tenant
- state bound to the browser via __Host-oauth-st cookie (anti link-CSRF)
One-time flow storage in {prefix}_oauth_temp (state/pending kinds), permanent links in {prefix}_user_oauth, audit log in log_oauth.log
- Account module (modules/account/index.php):
New ops: oauth_init, oauth (callback), oauth_finish, oauth_unlink
- first sign-in offers linking to an existing account or creating a new
one; OAuth-created accounts store an unguessable '!'-prefixed marker - unlink refuses to remove the last login method
uLogin op network and all its guards removed; login/passmail/savepass work by password state instead of the dropped users.network column
users_temp now stores bcrypt hashes; activate() keeps a transitional fallback for old plaintext rows
- Admin (modules/account/admin/index.php):
- Provider settings (activation, Client ID/Secret) in account preferences
oauthlist view with forced unlink via POST buttons, link cleanup on user delete, pending-users list shows the activation code instead of the stored password
- Schema and migration (setup/sql/table.sql, table_update6_3.sql):
- New tables user_oauth and oauth_temp with engine/charset placeholders
Idempotent migration archives legacy identities as provider 'ulogin' rows and drops the users.network column
- Themes (templates/admin, templates/lite):
- OAuth buttons, finish form and profile links tab; unified post-button
Both login blocks render one block-login-form fragment via include, fixing the broken sidebar login (wrong field name, missing CSRF token)
block-search-form merged into search-form; nine twin fragments synced between themes; speed dial fan opens only from its toggle
Session write lock released before scheduler jobs (index.php) so long pseudo-cron runs no longer block the visitor session
Benefits:
- Passwordless sign-in without composer dependencies on shared hosting
- No plaintext passwords at rest anywhere in the system
- Fewer duplicated templates with identical twin fragments across themes
Technical notes:
- Redirect URI: index.php?name=account&op=oauth (HTTPS required)
- Providers ship disabled; admin help documents the full setup procedure
- Verified by 29 JWT unit tests, mock-OIDC E2E flows and phpunit (194 OK)
Port the account cabinet (index.php?name=account) to the Command Deck layout from the 2026 demos: identity rail, action tile shelf, live scroll panels and a full-width lower section, all built on existing profile and category mechanics instead of new one-off styles.
Core changes:
- Cabinet page and data (modules/account/index.php, core/user.php):
profil() renders the new account-home partial
- identity rail: profile avatar ring, online status, rating chip and
the community progress ring (points, reached group, level percent) - panels: last incoming private messages, favorites, last activity
feed and the RSS reader with the legacy preset select restored getUserNavItems() builds icon/tone tiles per active module with the new-PM badge and favorites counter; getUserNav() renders the compact strip for inner cabinet pages via the account-nav fragment
getUserLevelData() extracts the ring/level computation shared with the public profile view; rssfeed() serves the feed over htmx POST
- getFavoriteList() initializes its arrays (warnings on empty lists)
Lite templates and styles (partials/account-home.html, fragments/account-nav.html, assets/css/theme.css):
panels reuse sl-profile-panel, sl-profile-scroll and the single sl-profile-feed-live spinner, now title-line aligned in profile and cabinet; panel headings are flex with the spinner in the right corner
action tiles reuse the files-category icon mechanics (sl-cat-ico with sl-cat-tone-N cycling); favorites rows carry configured module icons
sl-cab-* additions cover only the deck grid, tiles, rows, badge, progress ring (sized and shadowed like the avatar) and the RSS forms
- legacy PNG navigation removed (images/account/*.png, catflex flags)
- RSS reader (core/system.php):
rss_read() decodes the entity-encoded config template, so stored markup renders instead of printing as text
Benefits:
- cabinet matches the profile design language with no duplicated styles
- module tiles reflow automatically when modules are disabled
- RSS reading works inline over htmx without page reloads
Technical notes:
- RSS requests must POST the url (security url_get blocks URLs in GET)
- no schema changes; legacy account nav markup and icons are gone
Post-release polish of the redesigned profile plus design demos for the account home page.
Core changes:
- Profile status and identity (modules/account/index.php, account-profile.html):
Online/offline chip with bi-wifi / bi-wifi-off on success/danger tokens; new global _OFFLINE constant and capitalized _ONLINE in all six locales
Avatar ring colored by group like the user block: special group color first, otherwise the highest reached points group, validated hex passed as --sl-profile-ring
Signature block, site and hub queries reuse the existing selects, no extra queries added
- Activity feed (core/user.php, account-profile-feed*.html, theme.css):
Entry dates render through the standard date-badge fragment at system chip sizes; custom 10px sizing removed
Spinning bi-arrow-repeat indicator on the tabs row in primary tone; overlay positioning keeps tab panels at full feed width
- Indicator pauses together with the hovered feed scroll via :has()
- Avatar geometry (theme.css):
box-sizing fixes for the profile avatar and the user block ring and avatar, so images no longer overlap the conic ring and sit centered
- Dead code (theme.css, modules/account/index.php):
unused sl-user-group-special styles and the unused g.color column in the profile select removed
- Cabinet design demos (demo/):
account-home-2026.html with six directions and account-home-final-2026.html with three finals, a module toggle simulator, auto-fit action shelf, files-category tone tiles and auto-scrolling panels; deferred work listed in docs/PROFILE-BACKLOG.md
Benefits:
- Status and group identity readable at a glance on any profile
- Feed visuals match the sitewide chip and date language
- Ready-to-transfer blueprint for the account home redesign
Replace the legacy table profile (account&op=view) with the approved Dense Split composition: identity rail, trust strip, symmetric fact panels, module contribution hub and a tabbed last-activity feed, all rendered from live data in four SQL queries.
Core changes:
- Profile view (modules/account/index.php):
Rebuild view() around template data arrays: role-aware fact panels, trust strip, level progress and group chips
- online status via a subquery in the main user select
- one groups select drives chips, rank fallback and level thresholds
- module hub counts, weighted ratings and favorites in one UNION ALL
- hidden fields keep the localized placeholder for panel symmetry
- profil() reuses the shared feed; the legacy last() helper is removed
- Shared profile helpers (core/user.php):
getProfileModules() maps module tables, icons, rating column pairs and favorites keys in one place
getProfileLastView() renders the per-module activity feed from a single UNION ALL round-trip and respects per-module rating settings
- computed union columns are aliased because a literal 0 column would
otherwise shadow numeric row index 0 under FETCH_BOTH - Theme (templates/lite):
account-profile.html rewritten for the split card; new feed partials account-profile-feed.html and account-profile-feed-list.html
sl-profile-* styles on base.css tokens; chip labels truncate with an ellipsis; dead sl-table-acc-info and sl-user-group-special styles removed
- signature renders as the system blockquote and is visible to everyone
- Feed auto-scroll (plugins/system/slaed.js):
setProfileScrolls clones visible feed entries and animates them with reduced-motion and hover-pause support, re-measuring on tab switches
- Language constants (lang/, modules/account/lang/):
nine ACCOUNT* module constants and the global _LASTACTIVITY in all six locales
Benefits:
- Profile page cost drops from ~17 module queries to 4
- One module map feeds the hub, the feed and future consolidation
- Deferred follow-ups documented in docs/PROFILE-BACKLOG.md
Align every editor surface with the active theme's visual language and add a size-limited base64 embed option to the image dialog. Harden the integration with the vendor engine after three virtual-DOM regressions.
Core changes:
- Theme token contract (templates/*/assets/css/base.css):
field group (--sl-field-*), --sl-color-text-heading, --sl-color-on-dark, --sl-color-primary-hover, --sl-radius-card, --sl-shadow-input canonized; admin defines the field group, lite gains primary-hover
- --sl-field-focus-ring is a color composed as 0 0 0 3px var() in place
- Editor skin vendor alignment (templates/*/assets/editors/toastui/skin.css):
chrome, work area, toolbar states, mode switch, dropdown, popups, buttons, inputs, tabs, table selector, context menu, focus rings, selection, placeholders, md syntax accents and WYSIWYG content colors mapped to tokens
one control standard across all windows: theme font chain (vendor font stack removed), fields 32px/12px/field tokens, buttons 32px/12px, hints 11px; emoji tabs restyled as the popup button pair with primary active
toolbar header painted by the wrapper (rounded corners fixed), md tab bottom border restored, popup pinned to the right edge by CSS instead of the racy JS left assignment
- Image dialog (plugins/editors/toastui/, templates/*/partials/):
three insert modes as theme-owned radios: upload to server (default), attachment thumbnail, and NEW base64 embed into the text with a 32 KB client limit (_EMODE* constants in all six locales)
- file selection uploads automatically (no OK click), drag and drop included
system sl-alert everywhere: upload limits, messages and errors render in an extras strip; file field and hints unified with the file manager
- Vendor virtual-DOM integration hard rules (editor-upload.js):
no foreign DOM inside vendor popups: SLAED nodes live in the extras strip and absolutely positioned blocks raised together with the window
- vendor node text patched via nodeValue, never textContent
popup closing detected by a MutationObserver on the popup style attribute, fixing stuck header strips for guests and async close paths
- Fullscreen (editor-tags.js):
height driven by the engine setHeight('100%') with save/restore, bottom tabs and mode switch stay visible; the 100vh CSS override removed
Benefits:
- Switching a theme now changes the complete editor look including geometry
- Anonymous and privileged flows verified end to end without JS errors
- docs/DATA-URI-SECURITY-PLAN.md records the follow-up server-side hardening
Remove the last visual-contract leak in the editor plugin JS: setMsg() no longer assembles sl-toastui-message-* class names itself and instead clones the theme-owned msg-info/msg-warn template blocks.
Core changes:
- Upload messages (plugins/editors/toastui/assets/editor-upload.js):
setMsg() clones the msg-info/msg-warn <template> from the theme and only fills textContent; the message container keeps no plugin-chosen classes
getWarn() and the inline empty-list branch consolidated into one getMsg() helper used by all message paths
Benefits:
- Theme designers restyle upload messages purely in the theme partial
- One message-building path instead of three
Complete the 'theme owns visuals, plugin owns mechanics' migration: every SLAED-authored visual asset now lives in the theme, plugins keep only logic and versioned vendor engines, and switching a theme changes the entire look.
Core changes:
- Editor asset contract (core/classes/editor.php, plugins/editors/toastui/):
Theme skin moved to templates/<theme>/assets/editors/toastui/skin.css
- loaded by Editor::getThemeSkin() right after the engine CSS
- resolved from the effective editor key after all fallbacks
- deduplicated per theme:key pair, missing declared skin is logged
Dialog markup moved to theme partials editor-toastui-{dialogs,files,templates}.html
- dialogs partial renders window headers via a template loop
- EditorToastTemplate bypass class deleted, standard getHtmlPart() used
Plugin JS builds no markup: emoji panel and file manager clone theme-owned <template> blocks and fill textContent/src/data-* only
Plugin JS renamed to editor-{tags,emoji,upload}.js; dead unminified vendor build toastui-editor.js removed
- Theme structure validator (core/system.php):
checkThemeAssets() checks the canonical structure including editor assets declared in editor manifests
applied on all theme selection paths: user list/save, admin user edit, global config, getTheme() runtime guard
- Avatars (core/user.php, modules/account/index.php, setup/):
- system avatars and presets live in templates/<theme>/images/avatars/
getUserAvatarUrl() resolves presets/system from the active theme and accepts only the canonical presets/<name>.<ext> format
idempotent upgrade migration added to table_update6_3.sql with a PHP-side verification counter in setup
- Icons and tokens (templates/, core/security.php):
- Bootstrap Icons vendor directory renamed to assets/vendor/bootstrap-icons/
canonical token dictionary normalized: primary, bg-soft, text-muted, shadow-panel replace brand-link, surface, muted, shadow-medium everywhere
setExit() uses getThemeAssets() for emergency pages, getThemeCssFiles() and the manual icon CSS include removed
- editor classes renamed to sl-editor-icon- / sl-editor-emoji-
- toolbar icon color now follows --sl-color-text-muted
- img_find() renamed to getThemeImagePath()
Multilingual search (lang/, plugins/editors/toastui/assets/i18n/, templates/admin/assets/js/):
emoji set stored as plain symbols, names and search keywords generated from Unicode CLDR for de/en/fr/pl/ru/uk with localized tooltips
editor labels moved to language constants (_EEMOJI*, _ETABS) in all six locale files
admin icon picker searches by official Bootstrap Icons tags plus CLDR-derived locale term maps, lazy-loaded on first open
Benefits:
- A theme is a complete, replaceable visual package validated on activation
- Editor CSS order is deterministic: engine first, theme skin second
- No innerHTML markup in plugin JS; designers restyle without touching plugins
- Reproducible avatar migration for existing installations
Unify editor dialogs, upload validation, localization, and full-width form layout while keeping Toast UI resources independent from site themes.
Core changes:
- Toast UI editor (plugins/editors/toastui):
- Add unified upload and file-manager dialogs with drag, fullscreen, and attachment modes
- Move editor templates and translations into the plugin
- Upload handling (core and modules):
- Validate file types, dimensions, sizes, and aggregate upload limits
- Surface clear localized upload errors and help text
- Shared interface assets (plugins/bootstrap-icons):
- Consolidate Bootstrap Icons outside individual themes
- Align editor controls, dialog buttons, inputs, and responsive behavior
- Module forms (modules and templates/lite):
- Expand editors and intuitive fields to the available width
- Preserve explicit labels where additional field context is required
- Browser verification (tools/browser-audit.mjs):
- Extend authenticated browser checks for editor and upload workflows
Benefits:
- Consistent editor behavior across themes and modules
- Clearer upload limits and failure messages
- Reduced theme duplication and simpler maintenance
Technical notes:
- Existing CKEditor and TinyMCE drivers load the shared icon assets
- Toast UI templates no longer depend on theme partials
- Existing parser attachment behavior remains compatible