Derive the input type from the field name in one shared place so admin and lite stop repeating per-call-site itype, and semantically equal fields can no longer drift apart.
Core changes:
- Input-type derivation (core/classes/template.php):
- Add getInputType() mapping field names to email/url/password
- Apply it in getHtmlFrag() for the input fragment
- Explicit itype always overrides the derived default
- Redundant itype sweep (admin/index.php, modules/*):
- Drop now-derived itype for mail/url/password fields across modules
- Set email/url types for guest author and site fields (files, links, auto_links)
- Keep special cases explicit (whois domain/host/dc, changelog ghtoken)
Benefits:
- Single source of truth for input type across both themes
- Native :user-invalid validation now works on email/url everywhere
- Less duplication and no per-module type drift
Technical notes:
- No database or schema changes
- Behavior preserved; only the default type derivation moved to the core
Relicense SLAED CMS from GPL-3.0 to MIT and remove the runtime license lock. The project author relicenses their own work, so attribution enforcement is dropped and the obfuscated copyright config is retired.
Core changes:
- License text and metadata (LICENSE, composer.json, package.json, README.md):
- Replace the GPL-3.0 text with the MIT license and update license fields/badges
- Source headers (~396 .php, .htaccess, setup/sql/.sql):
- Change "# License: GNU GPL 3" to "# License: MIT"
- Normalize the copyright line to "# 2005 - 2026 SLAED"
- Runtime license lock removal (core/system.php, setup/index.php):
- Remove the _NO_LICENSE admin and setup lock checks
- Drop the lic_h/lic_f config keys and their forced rewrite on config save
- Replace the four footer builders with a single getLicenseHtml()
- Decouple the legacy md5 password salt into a frozen PASS_SALT constant
- Remove the now-dead _NO_LICENSE constant from all locale files
- Documentation (README, CONTRIBUTING, SECURITY, UPGRADING, CODE_OF_CONDUCT, .rules/architecture.md):
- Replace GPL references with MIT
Technical notes:
- Legacy md5 logins keep working through the frozen PASS_SALT value
- Historical news seed data in setup/sql/insert.sql is left intact on purpose
- Third-party code under plugins and vendor is not touched
Adjust footer CSS so the MIT license line reads cleanly after the relicense changed the text length.
Core changes:
- Site footer (templates/lite/assets/css/theme.css):
- Center .sl-copyright/.sl-license and balance the wrapped lines
- Admin login footer (templates/admin/assets/css/theme.css):
Reduce .sl-admin-login-copyright font-size so the line fits one row on desktop and narrow widths
Benefits:
- Footer license stays readable and centered without clipping
Replace the hardcoded 'pages-v1' prefix in the page-cache key with the CMS version, so every SLAED release automatically invalidates stale page caches on update without a developer remembering to bump a magic string or admins running a manual cache wipe.
Core changes:
- Cache key composition (core/system.php):
- Use $conf['version'] as the first hash part in getPageHash() instead of 'pages-v1'
- Add $conf to the function globals and fall back to an empty string when unset
- Update the function comment to list the real identity parts
Benefits:
- Page cache regenerates lazily after every version upgrade, fleet-wide
- No buried version literal to maintain by hand
Technical notes:
Content edits keep invalidating through the epoch counter; the version part covers code/release level invalidation
- Regeneration is lazy per request; old files are reclaimed by the GC sweep
Surface the single-flight page-cache rebuild as an admin setting and harden the runtime so it is safe to enable on production sites. Default stays off; the behavior itself was added in the prior page-cache overhaul.
Core changes:
- Admin toggle (admin/modules/config.php):
- Add a yes/no control with hint for cache_l in the cache settings block
- Persist cache_l from the config save handler
- Read with a '0' fallback so the form is safe before local.php is regenerated
- Localized strings (admin/lang/de.php, en.php, fr.php, pl.php, ru.php, uk.php):
- Add _CACHELOCK label and _CACHELOCKINFO hint across all six languages
- Phrase the label per locale; keep the English "single-flight" term only in en
- Runtime hardening (core/system.php, core/classes/cache.php):
- Skip serving an empty stale body so waiting requests never get a blank page
- Keep held rebuild locks fresh with touch() so the sweeper leaves them alone
- Garbage-collect stale lock files via deleteStale('locks') and a SWEEP list
- Documentation (admin/info/config/ru.md):
- Describe cache_l as recommended for high-traffic projects, off by default
Benefits:
- Admins can enable single-flight rebuilds without editing config files
- No blank pages under concurrent expiry; lock directory stays bounded
Technical notes:
- cache_l default lives in config/global.php and is read via empty()
- Lock files live under storage/cache/pages/locks and are swept by the GC job
Cold-start (no stale file yet) is not de-duplicated by design; single-flight only applies once a previous version exists
Rework the full-page HTML cache so it scales for high traffic: normalize the cache key, serve correct browser/CDN headers, retain files by age, invalidate on content change, and collapse concurrent rebuilds. Generation timing now shows on cached responses without baking a stale value into the stored file.
Core changes:
- Generation timing on cached pages (core/system.php):
- Replace the per-render time string with a GEN_MARK sentinel baked into the body
Add getTimedHtml() to swap the marker for live timing right before output, or strip it when generation timing (db_t) is off
- Inject on both the live echo and the page-cache hit path
- Cache key normalization (core/classes/cache.php, core/system.php):
Add Cache::filterCacheUrl() to drop tracking parameters (utm_*, gclid, yclid, fbclid, _openstat) and order query groups while preserving array order
- Feed it into getPageHash() so equivalent URLs map to one cache file
- Browser/CDN headers and conditional GET (index.php, core/classes/cache.php, core/system.php):
- Replace the premature public header in index.php with a safe no-store default
Send public Cache-Control plus a real Last-Modified (file mtime) only for cacheable guest pages, and answer If-Modified-Since with 304
Add Cache::checkNotModified(); suppress browser caching while db_t is on so the timing line never goes stale
- Retention-based garbage collection (core/classes/cache.php, core/system.php, admin/info/config/ru.md):
- Add Cache::deleteStale() to remove page-cache files older than max(cache_t
, 86400) - Switch addCacheGcTask() from a full wipe to the retention sweep
Document that cache_t is HTML freshness, cache_d is browser max-age, and GC retention derives from cache_t
- Global epoch invalidation (core/classes/cache.php, core/system.php, core/classes/pdo.php, index.php):
Add Cache::getEpoch()/addEpoch() with a once-per-request flock bump; include the epoch in getPageHash() so a bump invalidates every cached page
Bump from the DB layer on successful state-changing SQL under ADMIN_FILE, which covers all admin content modules through one hook
- Bump on frontend comment, post and voting submissions
- Single-flight rebuild (core/classes/cache.php, core/system.php, config/global.php):
Add Cache::getRebuildLock()/setRebuildFree() using a non-blocking flock with shutdown release; serve the stale file when another worker already rebuilds
- Gate behind the new cache_l config flag, default off
Benefits:
- Tracking-only URL variants share one cache file instead of fragmenting it
- Cached pages can be revalidated by the browser/CDN with 304 instead of full sends
- Disk stays bounded; content edits no longer wait up to cache_t to refresh
- Concurrent expiry no longer triggers a thundering herd of full rebuilds
Technical notes:
New config flag cache_l (default 0); cache_b now also fixes public headers on non-cacheable HTML
Epoch and lock files live under storage/cache/pages; deleteStale and deleteAll preserve .htaccess and index.html
Single-flight serves stale only when cache_l is enabled; otherwise behavior is unchanged
Render the consolidated user actions menu as a popover trigger and rework the comment and forum-post header layout for symmetric, browser-independent alignment.
Core changes:
- Popover fragment (templates/lite/fragments/popover.html, templates/admin/fragments/popover.html):
- Add is_user_menu flag: user-menu trigger class and bi-menu-button-wide-fill icon
- Wrap items/view/edit/delete menu entries in li for valid list markup
- Comment header (templates/lite/fragments/comment.html, templates/lite/assets/css/theme.css):
- Place the user and editor gear cluster before the post number
Lay out the header on a 1fr auto 1fr grid: fixed-width author column, meta aligned from the left, action cluster on the right
- Forum post (templates/lite/fragments/forum-post.html, templates/lite/assets/css/theme.css):
- Move the user menu into the meta cluster before the post number
Flex the meta bar and drop its fixed height so date and cluster never overlap the post body
- Center the nickname plate with flexbox for consistent cross-browser rendering
- Gear styling (templates/lite/assets/css/theme.css):
- User gear shares the editor gear styling (grey, brand-blue on hover)
Benefits:
- Symmetric, consistent post headers regardless of nickname length
- Valid li/a menu markup in both lite and admin themes
Technical notes:
- Template markup and CSS only; no behavior change
Replace the duplicated action-menu builders (add_menu, getUserMenu and two inline $actionMenu closures) with a single getActionMenu() helper and route comment, forum, private-message and clients menus through it.
Core changes:
- Action-menu helper (core/system.php):
Add getActionMenu(array $items, bool $user = false)
- Wraps each prepared HTML item in list-item and builds the popover
- Editor gear by default, user menu (is_user_menu, _USER) when $user is true
- Remove add_menu() and getUserMenu()
Replace the inline implode(list-item) menu builds in getComments and getVotingView with getActionMenu()
- Comment author menu (core/system.php):
Build the user actions menu (personal/message/profile/site) and pass it as btn_user instead of separate btn_personal/btn_pm/btn_profile/btn_web
- Forum (modules/forum/index.php):
Fold author actions plus quote-reply into the user menu via getActionMenu(..., true); route the editor menu through getActionMenu
- Private messages (core/user.php):
- Remove both $actionMenu closures; use getActionMenu() for every menu
- Build the PM author menu (profile/site) via getActionMenu(..., true)
- Clients (modules/clients/index.php):
- Route the row action menu through getActionMenu(explode('||', ...))
Benefits:
- One menu builder instead of four near-duplicates
- Consistent user-menu contract across all front-end modules
Technical notes:
- Behavior preserved; popover fragment items_html API unchanged
Replace the cache-only getCompressHtml() with a single getOutputHtml() that normalizes the assembled page in setFoot() before echo and before page-cache write, so users always receive processed HTML even when page-cache is off.
Core changes:
- Output normalizer (core/system.php):
Add getOutputHtml(string $html, bool $full = false) replacing getCompressHtml()
- Protect script/style/pre/code/textarea bodies and comments first, exposing
only the opening tag so multiline <script src> tags get normalized too
- Squeeze multiline class lists and multiline tags to a single line
- Keep the template's own indentation; only drop blank lines from false {% if %}
- full=true keeps the legacy maximal-compression path and strips non-IE comments
- Output and cache wiring (core/system.php):
- Echo the readable (full=false) output to the user on every request
Compress (full=true) only the copy written to storage/cache/pages/html when cache_c=1, restoring the original cache-only compression contract
Benefits:
- Heavy parsing runs only on the few multiline matches, not on every tag
- Real homepage normalization measured at ~0.32 ms vs ~4.3 ms for a tokenizer
- view-source has no multiline tags, no multiline class, no giant glued lines
Technical notes:
- storage/cache/templates keeps compiled PHP only; final HTML is never written there
- Output is idempotent; script/style/pre/code/textarea bodies stay byte-for-byte
Behavior change: live echo is now normalized for readability on every request; with cache_c=1 a cache hit still serves compressed HTML while a miss serves readable HTML, matching the previous cache-only compression semantics
This commit refines lite theme layout spacing, rating control presentation, and floating feedback links. It removes the separate contact-block partial now that both layouts render the feedback link directly.
Core changes:
- Theme spacing and controls (theme.css, base.css):
Add a shared grid gap variable and apply it to card and post grids.
- Adjust full-view width and metadata alignment rules.
- Refine rating badges, star hover preview, and mobile metadata stacking.
Update floating idea and feedback tabs to grow from the screen edge.
- Uses sideways writing mode where supported.
- Avoids edge gaps during hover and focus states.
- Lite layouts (app.html, home.html):
- Render the feedback tab directly in both layouts.
- Keep the layout output independent of a separate partial payload.
- Theme foot variables (index.php, contact-block.html):
- Remove contactblock generation from theme foot variables.
- Delete the unused contact-block partial.
Benefits:
- Reduces one template indirection in the lite footer path.
- Improves consistency of grid spacing and floating controls.
- Keeps rating UI states clearer for interactive and static displays.
Technical notes:
- No database or storage changes.
- Template output changes are limited to the lite theme.
- Backward compatible for existing theme data except the removed internal contactblock variable.