🚀 Changelog — Flatboard 5.3.0 — Lighthouse
Release date: March 19, 2026
Highlights
- FlatHome joins Flatboard Pro — FlatHome replaces EasyPages in the Pro plugin pack. It is a full CMS, homepage, and blog system that transforms any Flatboard forum into a complete web platform. EasyPages users can migrate their pages to FlatHome's richer page model.
Added
FlatHome —
forum_enabledtoggle — pure CMS mode — A newforum_enabledsetting lets admins disable the forum entirely so Flatboard runs as a pure CMS. When disabled: the/forumroute alias is no longer registered, the forum is removed from the navigation bar, and the "Forum" nav label row is hidden in the admin quick-settings card. Two toggle switches (Blog / Forum) are now shown at the top of the "Navigation labels" admin card; the corresponding label row hides dynamically when its feature is disabled, and the entire card collapses when both are off. Theblog_enabledandforum_enabledvalues are saved via the existing quick-settings AJAX endpoint.
Files changed:plugins/FlatHome/FlatHomePlugin.php,plugins/FlatHome/FlatHomeService.php,plugins/FlatHome/FlatHomePageController.php,plugins/FlatHome/views/admin/pages.php,plugins/FlatHome/plugin.json,plugins/FlatHome/langs/{en,fr,de,pt,zh}.json.FlatHome — Generic
home.phplanding page template — Thehome.phptemplate has been rewritten as a simple, site-agnostic landing page suitable for any Flatboard installation. It shows the site name and lead content (CMS page body, or a default message pointing to the admin when empty), CTA buttons for the forum and/or blog when those features are enabled, a dynamic accent band usingvar(--bs-primary), a recent blog posts section (whenblog_enabledis on and posts exist), and a recent forum discussions section (whenforum_enabledis on and discussions exist). Both activity sections are skipped when their respective feature is disabled or returns no data. The number of items shown in each section is controlled bytemplate_recent_blog(default 3) andtemplate_recent_forum(default 5). The previous Flatboard.org-specific layout (version pill, trust badges, browser mockup, feature chips, join cards) has been removed.
Files changed:plugins/FlatHome/modeles/home.php.discussions.index.filterhook — New hook fired inDiscussionController::index()after discussions are loaded, allowing plugins to remove discussions from the all-discussions feed before rendering. Used by FlatHome to hide blog discussions from the forum index when Hide blog category from forum is enabled — fixes the bug where the blog category and its discussions remained visible in SQLite mode.
Files changed:app/Controllers/Discussion/DiscussionController.php,plugins/FlatHome/FlatHomePlugin.php.
Fixed
FlatHome — Users link missing from navbar — The CSS rule that hid the core "Users" link also matched FlatHome's re-injected link (same
href). Fixed by adding:not([data-fh])to the hide selector and adata-fh="1"attribute to FlatHome's own link, so only the core duplicate is suppressed.
Files changed:plugins/FlatHome/FlatHomePlugin.php.FlatHome — Blog category hidden from forum in JSON but not SQLite — In SQLite mode,
getAllDiscussionsSorted()returns all discussions including blog ones, making the blog category label visible on discussion cards even when Hide blog category from forum was enabled. The newdiscussions.index.filterhook resolves this by filtering blog discussions from the forum feed at controller level, regardless of storage backend. ID comparisons now use explicit(string)casts for type safety.
Files changed:app/Controllers/Discussion/DiscussionController.php,plugins/FlatHome/FlatHomePlugin.php.FlatHome — Spurious "Comment published!" toast on blog article visit — The inline
#flathome-reply-successelement (class="alert alert-success") was always present in the DOM withdisplay:none, causing thetoast.jsfallback scanner (which reads all.alert.alert-successelements regardless of visibility) to fire a success toast on every page load. Fixed by addingdata-toast="none"to both the success and error inline alert divs, which are explicitly excluded by the scanner.
Files changed:plugins/FlatHome/views/blog/article.php.FlatHome — Share URL input empty on blog articles — The article share URL field appeared empty when the server-side
UrlHelper::full()call produced no output (e.g. misconfiguredsite_url). The JavaScript IIFE now falls back towindow.location.hrefwhen the input value is empty, and also updates all social share links in the same section to use the corrected URL.
Files changed:plugins/FlatHome/views/blog/article.php.FlatHome — Doubled icon live preview in Page groups form — The icon picker JS sets
preview.innerHTML = '<i class="..."></i>'on the element referenced bypreviewId. Because the group icon preview was an<i>element itself, this nested a new<i>inside it, rendering two icons. Fixed by changing the preview element to a<span>container (consistent with the core admin pattern) and updatingupdateGroupIconPreview()to useinnerHTMLinstead ofclassName.
Files changed:plugins/FlatHome/views/admin/pages.php.
🚀 Changelog — Flatboard 5.2.8
Release date: March 18, 2026
Added
FlatHome —
homelanding page template (modeles/home.php) — Initial version of thehomePHP template for CMS pages. The template displays the page's CMS content as a hero lead section with CTA buttons and recent forum activity. Three shortcodes become available in any page content:{remote_version},{remote_codename},{remote_release_date}. (The template was substantially revised in 5.3.0 into a generic, site-agnostic layout.)
Files changed:plugins/FlatHome/FlatHomeService.php,plugins/FlatHome/modeles/home.php(new).FlatHome — Template badge in pages list — When a CMS page has an explicit PHP template assigned, its name is shown as a yellow
<i class="fas fa-palette">badge in the pages list alongside the "Homepage" and "Draft" badges.
Files changed:plugins/FlatHome/views/admin/pages.php.FlatHome — Explicit template selector in page form — The CMS page create/edit form now shows a "PHP Template" card in the sidebar when template files exist in
plugins/FlatHome/modeles/. Admins can select a template from a dropdown (or leave it on "Default"). The selected template is stored in the page data as atemplatefield and takes priority over slug-based auto-detection in both the page view and the early banner-hiding hook.
Files changed:plugins/FlatHome/FlatHomePlugin.php,plugins/FlatHome/FlatHomePageController.php,plugins/FlatHome/views/admin/page_form.php,plugins/FlatHome/views/page.php,plugins/FlatHome/langs/{en,fr,de,pt,zh}.json.FlatHome — Blog hidden from nav when it is the homepage — When
homepage_typeis set toblog, the "Blog" link is no longer added to the navigation bar (same behaviour as the Forum link whenhomepage_type = forum). A dedicated nav link for the active homepage is redundant since/already points there.
Files changed:plugins/FlatHome/FlatHomeService.php.FlatHome — Contact page template (
modeles/contact.php) — A ready-to-use contact form template is included. It is activated by creating a CMS page with the slugcontact. The recipient email is resolved in order: plugin settingcontact_email, thenmail.from_address/site_email/admin_emailfrom the forum configuration. The form posts to/flathome/contactand displays flash messages on success or error. All labels are translated in 5 languages.
Files changed:plugins/FlatHome/FlatHomePlugin.php,plugins/FlatHome/FlatHomePageController.php,plugins/FlatHome/FlatHomeService.php,plugins/FlatHome/modeles/contact.php(new),plugins/FlatHome/langs/{en,fr,de,pt,zh}.json.FlatHome — PHP templates for CMS pages (
modeles/) — FlatHome now supports PHP template files placed inplugins/FlatHome/modeles/{slug}.php. When a CMS page's slug matches a file in this folder, the template is used instead of the default page view. If no template is found, the standard view is used as fallback. The banner is always hidden for CMS homepage pages (regardless of whether a template is active), and also hidden for any page with an active template. The number of discussions and articles displayed is configurable via the newtemplate_recent_forum(default 5) andtemplate_recent_blog(default 3) plugin settings.
Files changed:plugins/FlatHome/FlatHomePlugin.php,plugins/FlatHome/FlatHomeService.php,plugins/FlatHome/views/page.php,plugins/FlatHome/langs/{en,fr,de,pt,zh}.json.FlatHome — Multilingual labels for page groups — The group form in the "Page groups" card now shows one label input per available language. Labels are stored as
{"fr":"...","en":"..."}objects; the nav builder resolves the label using the current visitor language. Existing groups with plain-string labels continue to work without migration.
Files changed:plugins/FlatHome/FlatHomePageController.php,plugins/FlatHome/FlatHomeService.php,plugins/FlatHome/views/admin/pages.php,plugins/FlatHome/langs/{en,fr,de,pt,zh}.json.FlatHome — Date in banner for discussion-type CMS pages — Pages of type
discussionnow display the discussion's last-updated date below the page title in the banner. The$pageDatevariable is added to theview.banner.datahook data and rendered in thepage-type banner block.
Files changed:plugins/FlatHome/FlatHomePlugin.php,themes/premium/views/components/banner.php.FlatHome — Multi-language menu labels for CMS pages — The
menu.labelfield in FlatHome CMS pages now supports per-language values (one input per available language in the page form). The nav builder resolves the label using the visitor's current language, withenas fallback. Existing pages with a plain string label continue to work without migration.
Files changed:plugins/FlatHome/FlatHomeService.php,plugins/FlatHome/FlatHomePageController.php,plugins/FlatHome/views/admin/page_form.php,plugins/FlatHome/langs/{en,fr,de,pt,zh}.json.FlatHome — Discussion link on discussion-type pages — Pages of type
discussionnow display a "View discussion" button in the page footer, linking directly to the corresponding discussion thread.
Files changed:plugins/FlatHome/FlatHomePageController.php,plugins/FlatHome/views/page.php,plugins/FlatHome/langs/{en,fr,de,pt,zh}.json.FlatHome — Last-updated date on editor-type pages — Pages of type
editornow show theupdated_atdate in the page footer as a last-updated indicator.
Files changed:plugins/FlatHome/views/page.php,plugins/FlatHome/langs/{en,fr,de,pt,zh}.json.categories.list.filterhook — New hook fired inDiscussionController::index()andDiscussionController::forums()after categories are sorted, allowing plugins to remove or reorder categories before rendering. Used by FlatHome to hide the designated blog category from the forum list.
Files changed:app/Controllers/Discussion/DiscussionController.php.
Changed
FlatHome — Homepage page excluded from nav menu — A CMS page flagged as homepage (
is_homepage: true) is no longer listed as a separate nav item whenhomepage_typeis set topage, since it is already accessible via/.
Files changed:plugins/FlatHome/FlatHomeService.php.FlatHome — Remove page ordering from admin list — The drag-to-reorder handle has been removed from the CMS pages table; ordering is managed exclusively via the "Navigation order" panel.
Files changed:plugins/FlatHome/views/admin/pages.php.FlatHome — Toolbar preset visible for Quill editor only — The toolbar preset radio buttons in the page form are now shown only when the Quill (WYSIWYG) editor is active; EasyMDE preserves the saved preset via a hidden field.
Files changed:plugins/FlatHome/views/admin/page_form.php.FlatHome — Icon shown in CMS pages list — Each page row in the admin pages table now displays its configured menu icon before the title.
Files changed:plugins/FlatHome/views/admin/pages.php.FlatHome — Help text for page groups drag-and-drop — An info line in the "Navigation order" card explains that items can be dragged into a page group to create a dropdown menu.
Files changed:plugins/FlatHome/views/admin/pages.php,plugins/FlatHome/langs/{en,fr,de,pt,zh}.json.FlatHome — Single homepage enforcement — Setting
is_homepage: trueon a CMS page now automatically clears the flag on all other pages, preventing multiple simultaneous homepages.
Files changed:plugins/FlatHome/FlatHomeService.php.FlatHome — Wider content area for CMS pages — The page view uses
col-xl-10 col-lg-11instead ofcol-lg-9, giving more reading width on larger screens.
Files changed:plugins/FlatHome/views/page.php.
Fixed
Full backup —
bootstrap.phpmissing from archive — The complete archive (flatboard-complete-*.zip) omittedbootstrap.phpfrom the root files list, making the restored site completely blank sincepublic/index.phprequires it as its first instruction. The file is now included.
Files changed:app/Controllers/Admin/BackupController.php.CLI backup —
vendor/missing andstockage/backupsnot excluded — Thebackup:createCLI command did not include thevendor/directory, requiring a manualcomposer installafter any CLI-based restore. Additionally,stockage/backupswas not excluded, causing backups to embed themselves recursively. Both issues are corrected.
Files changed:app/Cli/Commands/BackupCommand.php.
Changed
Full backup — exclude
stockage/cache,stockage/logs, andstockage/backups— The complete archive (flatboard-complete-*.zip) no longer includes these three directories. The cache contains server-specific data (absolute paths, compiled assets) that is invalid on a different server; logs are irrelevant noise from the source environment; and backups must not embed themselves recursively. All three are now silently skipped during archive creation.
Files changed:app/Controllers/Admin/BackupController.php.2FA settings — OTP input boxes for enable and disable forms — The plain text inputs on the 2FA settings page are replaced by the same 6-box OTP UI used on the login page (grouped 3 + 3, keyboard navigation, paste support, shake/bounce animations, dark mode). A live TOTP progress bar and countdown badge are added to both forms. The enable form auto-submits 250 ms after the sixth digit is entered; the disable form requires an explicit button click and confirmation dialog since it is a destructive action.
Files changed:app/Views/auth/2fa-settings.php.StorageMigrator — smart quick switch with count-based sync detection — When switching storage engines (JSON ↔ SQLite), the plugin now checks whether the target storage already exists and contains data before launching a full migration. If the target file is present, entity counts (users, discussions, posts) are compared between source and target: an exact match triggers a near-instant switch (
storage_typeconfig update only); any discrepancy — or a comparison failure — falls back to the full migration, guaranteeing data integrity. This covers the common round-trip scenario (JSON→SQLite→JSON→SQLite) without redundant re-migration.
Files changed:plugins/StorageMigrator/StorageMigratorController.php.RSS/Atom feed links on forum category pages — Small RSS and Atom icon links are now shown on each category card in the forum list (
/forums) and next to the category title in the forum view (/f/{slug}). Routes/feed/rss/category/{id}and/feed/atom/category/{id}are registered; they delegate to the existingRssController::rssCategory()andatomCategory()methods.
Files changed:app/Core/App.php,themes/premium/views/categories/index.php,app/Views/categories/index.php,themes/premium/views/discussions/index.php,app/Views/discussions/index.php.FlatSEO — forum-adjusted scoring algorithm — The
analyzeSeo()scoring was calibrated for blog articles (300-word threshold, 1–3% density) which was inappropriate for forum topics. Content thresholds are now forum-aware: ≥150 words → good (+20), ≥50 → ok (+15), ≥20 → acceptable (+10). Title length range extended to 30–70 chars. Keyword density check now uses a relaxed range (0.5–4%) for long posts; for short posts (< 100 words) a simple presence check replaces density, downgrading failures from "bad" to "ok" so they no longer penalise naturally short replies. Average audit score improves from 53 to 61/100; Poor count drops to 0.
Files changed:plugins/FlatSEO/FlatSEOService.php.
🚀 Changelog — Flatboard 5.2.7
Release date: March 17, 2026
Fixed
- *Backup download — `auto_backup_beforeupdate
blocked by filename regex** —BackupController::download()validated the filename against a regex that only acceptedbackup_andflatboard-complete-prefixes. Backups created automatically byUpdateControllerbefore applying a local update (auto_backup_before_update_YYYY-MM-DD_HH-MM-SS.zip) were rejected with{"error":"Invalid filename"}. The pattern now also accepts theauto_backup_beforeupdateprefix. *Files changed:*app/Controllers/Admin/BackupController.php`.
Added
- 2FA — Flatboard logo in QR code (fully self-hosted) — The QR code on the 2FA setup page is now generated entirely client-side with no external service. A bundled
qrcode.min.js(qrcodejs, MIT, 20 KB) generates the matrix at error-correction level H (30 %, required to keep the code scannable under a logo), and thefavicon.svgFlatboard logo is superimposed at the centre via the HTML Canvas API with a white rounded background. The SVG proportions (270 × 401) are preserved. A graceful fallback displays the plain QR code if the SVG fails to load.
Files changed:app/Views/auth/2fa-settings.php,themes/assets/js/qrcode.min.js(added).
🚀 Changelog — Flatboard 5.2.6
Release date: March 16, 2026
Changed
Permissions — Full UX overhaul — The Admin → Permissions page has been completely redesigned for ergonomics and readability.
- Matrix view : new display mode accessible via the Badges / Matrix switcher (state persisted in
localStorage). In Matrix view each permission maps to a row and each group to a column; cells are clickable to toggle a permission directly without leaving the view. Badge view remains click-to-toggle as before. - Group filter bar : a contextual filter strip lists all forum groups. Clicking a group dims the other groups' badges in Badge view (opacity +
pointer-events: none) and hides the other groups' columns in Matrix view — allowing a full audit of one group's permissions in a single pass. - Streamlined two-column table : the separate Description column is removed; the description now appears below the permission name in the same cell (
.perm-name+.perm-desc). Sections are no longer wrapped in nested cards but in flat.perm-flat-sectionseparators with an uppercase label, matching the reference screenshot style. - Active tabs visible in light mode : the Administration & Moderation, Discussions & Posts, etc. tabs now display a primary-colour underline and a white background when active — fix applied globally to all tabbed tables in the backend via
backend.css(.card-header .nav-tabs .nav-link.active). - Zero gap between tabs and content : the
card-headercontainingnav-tabsloses itsborder-bottom(:has(.nav-tabs)); the active tab shifts down one pixel (transform: translateY(1px)) to visually fuse with the filter bar or the content below. - File settings redesigned : the File settings tab now presents the three categories (Attachments, Images, Avatars) side by side in a three-column CSS grid. Allowed file types are represented by toggleable chips (colour-coded pills) instead of checkboxes. The Max size field is embedded at the bottom of each column in a clean input without native spinners.
- Double-toggle fix : badge toggles and file-settings chips now listen to the
changeevent (fired after the browser's native toggle) instead ofclickwith manualcheckedmanipulation — eliminating the bug that cancelled every click. - Dashboard counter animation : the four quick-stat cards (Users, Discussions, Posts, Pending reports) on
/adminnow animate their values on load via the sharedstats.js(cubic ease-out, 600 ms) — the same asset already used by/admin/analytics. - Translations : keys
permissions.filter.*(label,all_groups,hint) added to all fiveadmin.jsonfiles (fr,en,pt,de,zh).
Files changed:
app/Views/admin/permissions.php,app/Views/admin/components/PermissionSection.php,themes/assets/js/admin/modules/permissions-management.js,themes/assets/css/admin/modules/permissions-management.css,themes/default/assets/css/backend.dev.css,themes/default/assets/css/backend.css,app/Views/admin/dashboard.php,languages/*/admin.json.- Matrix view : new display mode accessible via the Badges / Matrix switcher (state persisted in
🚀 Changelog — Flatboard 5.2.5
Release date: March 14, 2026
Added
- Hook
router.not_found— New hook fired byRouter::handleNotFound()for unmatched HTML routes (not API). Data:['url' => string, 'redirect' => ?string]passed by ref — plugins setredirectto issue a 301 before the default 404 response. Documented indocs/8-plugins.md. - Maintenance: Permissions Diagnostic tool — New tool in Admin → Dashboard → Maintenance (debug mode). Two actions: Scan (GET
?dry_run=1) reports group type conflicts without modifying anything; Fix conflicts (POST) corrects thetypefield of every group whose stored type does not match its canonical name, purges group/permission caches, and resynchronises plugin permissions. RouteGET|POST /admin/maintenance/permissions-diagnostic(POST is CSRF-protected). Translation keymaintenance.permissions_diagnosticadded to all 5admin.jsonfiles. JS bindings added todashboard-management.js.
Changed
nginx.conf: PHP 8.3 — FastCGI socket updated fromphp8.0-fpm.socktophp8.3-fpm.sockin both the HTTP and HTTPS server blocks.
Fixed
- Plugin permissions lifecycle —
PermissionHelper::getPluginPermissionPrefixes()now includesplugin.{id}.as the first candidate prefix (the format used by all modern plugin permissions). Previously the fallback only tried{id}., which never matched the actual key format, so disabling or uninstalling a plugin whoseplugin.jsonlacked a_permissionsfield would silently leave its permissions inpermissions.json/ SQLite. All packaged plugins now have_permissionspre-populated in theirplugin.json. Six plugins had a double-prefix bug whereregisterPermissions()returned keys like{id}.{action}instead of{action}, causingaddPluginPermissionPrefix()to produceplugin.{id}.{id}.{action}— corrected for polls, Flatbot, TranslationManager, FlatModerationExtend, Impersonate, and FlatLetter;permissions.jsonupdated accordingly. Orphan permissions from two uninstalled plugins (flatboat, aichatbox) removed frompermissions.json. On firstpermissions.initcycle after update,initDefaults()now detects stale double-prefix keys for each active plugin and renames them in-place (e.g.plugin.polls.polls.view→plugin.polls.polls_view), keeping group assignments intact — no manual reconfiguration required on existing forums. The legacy key mapping ininitDefaults()is scheduled for removal in 5.3 once all existing forums have migrated. App: duplicate route/d/{number_slug}/page/{page}— The pagination route was registered twice inloadRoutes(). The second (dead) registration has been removed.App: redundant path-traversal checks in avatar/attachment routes — Therealpath/file_exists/strpossecurity check in the/uploads/avatars/and/uploads/attachments/route handlers was performed twice. The second check (and its unreachableelsebranch) have been removed; a single check now guards file serving.Permission::set():DEFAULT_GROUP_IDScache not invalidated on save —clearCategoryCache()(called byPermission::set()after every permission save) now also deletes theDEFAULT_GROUP_IDScache key. Previously, saving permissions from the admin panel did not invalidate this cache, soGroupHelper::getGuestGroupId()could return a stale or incorrect group ID on the same request — causingcanGuest()to check the wrong group's permissions and potentially allowing guests to bypass permission restrictions.GroupHelper::getGuestGroupId(): unreliable heuristic replaced — The fallback that identified the guest group by counting its permissions (≤ 2=guest) could misidentify non-system groups (e.g. "Traducteurs" created by TranslationManager with few initial permissions) as the guest group. A name-based detection step (multilingual: invité, invite, guest, gast, invitado, convidado, 访客) is now inserted between thetypefield lookup and the permission-count heuristic. Groups created by plugins without a canonical guest name are no longer at risk of being assignedtype=guestby the fallback.
🚀 Changelog — Flatboard 5.2.4
Release date: March 12, 2026
Security
stockage/directory hardening — A singleDeny from all.htaccessis now placed at the root ofstockage/, covering all subdirectories via Apache inheritance — independently of the rootmod_rewriterules.App::ensureStorageHtaccess()is called once per 24 h (tied to the bootstrap cache) and automatically recreates the directory and its.htaccessif either is missing. Both events are recorded in the application log.
Added
NumberHelper::compact()— compact number display — New helper that formats large integers into readable compact notation (e.g.1 413→1.4K,25 000→25K,1 800 000→1.8M). Trailing decimal zeros are stripped automatically (25.0K→25K). A new Compact number display toggle in Admin → Settings → General controls the feature globally (off by default); when disabled, raw formatted numbers are shown as before. Applied to view counts across all themes and relevant plugins: default theme discussion list and search, premium theme discussion list and search, ClassicForum theme discussion list, EasyPages frontend banner. Translation keys added for all 5 languages.- Dashboard: masonry widget layout — The Quick Actions, Recent Activity, and plugin-contributed widgets (e.g. Forum Monitoring) are now laid out using CSS multi-column (
column-count: 2on desktop,1on mobile). Each card fills the available vertical space top-to-bottom before starting a new column, eliminating the large blank gaps that appeared on the right side when a tall widget occupied one column and a shorter widget sat alone in the other.break-inside: avoidensures no card is split across a column boundary.
Fixed
- Router: compiled-routes cache returns empty routes —
loadCompiledRoutes()stored the full['routes', 'routesByMethod', 'namedRoutes']object in$this->compiledRoutes, butdispatch()indexed it directly by HTTP method (e.g.$compiledRoutes['POST']), always resolving tonull— causing all requests to return 404 whenever the routes file cache was active. The loader now extracts$data['routesByMethod']into$compiledRoutes. - SQLite: permissions UNIQUE constraint crash — The
permissions.namecolumn incorrectly carried aUNIQUE NOT NULLconstraint, causing a fatalSQLSTATE[23000]: Integrity constraint violationon startup whenever two or more plugins registered permissions with the same display name. The constraint has been removed (onlyidneeds to be unique); a schema migration recreates the table without it for existing databases.SCHEMA_VERSIONbumped to 14. - Users page: visitors/bots pagination & tab order — The load-more button for the Anonymous visitors and Bots panels was silently targeting
#regular-users-container(the members panel container) due to$paginationContainerIdnot being reset between panel pagination includes. Addedid="visitors-container"andid="bots-container"to the respective grid rows, and now explicitly set$paginationContainerIdbefore each panel's pagination include so the load-more button correctly appends the next page of visitors/bots into their own panel. Tab order changed to Members → Anonymous visitors → Active bots. - Members list: duplicate users —
JsonStorage::createUser()andcreateUserWithId()now enforce username and email uniqueness before writing, throwing aRuntimeException(409)on conflict. Previously, neither method checked for existing entries, allowing concurrent registrations or storage migrations to silently create multiple user files with the same credentials.RegisterControlleralready caught the 409 code for race conditions and now also covers this case. The missing/users/searchroute has been registered, resolving a double error toast (one fromclonedResponse.json()failure, one from the fetch network catch) shown when filtering members by online/offline status.
🚀 Changelog — Flatboard 5.2.3
Release date: March 12, 2026
Added
view.footer.bottomhook — New hook triggered inside<footer>just before its closing tag, allowing plugins to append content at the bottom of the footer.
🚀 Changelog — Flatboard 5.2.2
Release date: March 10, 2026
Fixed
view.login.validationhook fallback —LoginControllernow falls back to the error string itself before the hardcoded'Captcha invalide'message (... ?: $captchaError ?: 'Captcha invalide'). Previously, any plugin setting a custom error message via this hook would have it silently replaced by'Captcha invalide'.
Changed
- SortableJS updated to 1.15.7 —
themes/assets/js/Sortable.min.jsreplaced. - PHPMailer updated to 7.0.2 — vendor directory restructured from
vendor/PHPMailer/PHPMailer/tovendor/PHPMailer/.EmailService::loadPHPMailer()now resolvesvendor/PHPMailer/src/as the primary lookup path (priority 2), with the previous nested structure and all legacy fallback paths retained for backwards compatibility. Language path updated accordingly (vendor/PHPMailer/language/).
Added
- Plugin installation from archive(admin/plugins): New "Install a plugin" button opens a modal where the admin can upload a ZIP archive. The server validates the MIME type, extension, size (respects PHP
upload_max_filesize/post_max_size), ZIP integrity, path-traversal safety, and the presence of aplugin.jsoninside a top-level directory. The archive is extracted directly intoplugins/from the PHP temp file (no permanent copy kept) and all caches are invalidated. The new routePOST /admin/plugins/uploadis CSRF-protected. Translation keys added for all 5 languages. - Theme installation from archive(admin/themes): Same mechanism for themes — new "Install a theme" button and modal, route
POST /admin/themes/upload, validatestheme.jsoninside a top-level directory (reserved namesassets,cache,premiumare rejected), invalidates theme asset cache after extraction. Translation keys added for all 5 languages.
Fixed
- Network error toasts on page navigation: The global
fetchinterceptor now ignores request cancellations caused by page navigation (beforeunload) and explicitAbortControlleraborts (AbortError), preventing spurious "network error" toasts from firing when the user changes pages. The raw browser error message (untranslated, browser-locale-dependent) is no longer appended to the toast text — only the translatednetwork_errorkey is shown. - Local update: plugin.json / theme.json preservation: The
deployUpdateFiles()step previously overwroteplugin.jsonandtheme.jsonwith a plaincopy(), erasing user data such as"active"state, plugin settings stored under the"plugin"key, and customised theme colours in"variables". These files are now merged instead of replaced: existing keys are preserved, new keys introduced by the archive are added, and nested associative arrays (e.g.form_config.fields) are deep-merged recursively. Indexed arrays (e.g. hook lists) are union-merged without duplicates. A direct copy fallback is used if either JSON file is unreadable or invalid.
🚀 Changelog — Flatboard 5.2.1
Release date: March 10, 2026
Added
- Local update system: New step-based update workflow for offline/self-hosted deployments. The Archive Builder plugin now injects a
manifest.json(type, software, edition, version, build_date, checksum, compatible_from) into every Community and Pro archive it generates. The Backup upload endpoint detects archives wheremanifest.type == "update"andmanifest.software == "flatboard", validates the manifest (edition, semver version, checksum), and routes them tostorage/updates/instead ofstorage/backups/. The admin/updates page scansstorage/updates/, displays a card for each valid archive that is newer than the installed version, and lets the admin apply it via a 5-step progress UI (verify → backup → extract → deploy → cleanup). Each step is executed over a separate CSRF-protected AJAX call with retry logic; a full automatic backup is created before any file is deployed; protected paths (stockage/,uploads/,.env,.htaccess,install.php) are never overwritten. Archives older than the current version are listed separately and can be deleted. Translation keys added for all 5 languages. - Plugin uninstall(Pro & Community): New "Uninstall" button (danger/trash) in the admin plugin list. Clicking it opens a Bootstrap confirmation modal (
ConfirmModal), then callsPOST /admin/plugins/uninstall. The server runs the plugin'suninstall()/deactivate()hook, removes its permissions, clears asset and translation caches, removes its entry fromplugins.enabled, then deletes the plugin directory recursively. The button is hidden for core plugins (cantDisable = "1") and blocked server-side if the plugin is still active. Translation keys added for all 5 languages.
Fixed
- User list tab counts: The "Active bots" and "Anonymous visitors" tab badges on
/usersnow display the real totals instead of the item count of the current page. Previously, both badges were capped at the pagination limit (e.g. 20) regardless of the actual number of active bots or visitors. - admin/updates archive display: Archives whose version equals the installed version were not rendered at all — the
<details>block for non-newer archives was only shown when newer archives were also present. Same-version archives now appear as standalone cards with a "Reinstall" button; truly older archives are listed directly when no newer/same-version card is shown. - admin/updates card colours: Update and reinstall cards now use Bootstrap 5.3 semantic CSS variables (
--bs-success-bg-subtle,--bs-warning-bg-subtle, etc.) instead of hard-coded utility classes, so backgrounds adapt correctly to both light and dark themes. - admin/backups upload hint: The "Upload backup" button area now includes a one-line hint explaining that Flatboard update archives can also be uploaded there, with a direct link to
admin/updates. The same hint is shown inside the upload modal. Translation keys added for all 5 languages. - Local update same-version reinstall: The admin/updates page now shows a "Reinstall" button (warning style) for archives whose version matches the currently installed version, in addition to the "Update" button (green) for newer archives. Previously, same-version archives were not displayed and could not be applied. The
verifystep now accepts archives with version ≥ current (previously required strictly greater). Archives strictly older than the current version remain listed for deletion only. Translation keys added for all 5 languages. - Backup upload size error: The "Fichier trop volumineux" error on backup upload now includes the effective PHP limit (
upload_max_filesize/post_max_size) in the message. The upload modal also displays the limit in the file input hint. A client-side pre-check validates the file size against the PHP limit before submitting, preventing a failed upload round-trip. Translation keys added for all 5 languages. - Update check false positive:
admin/updatesno longer shows "You have the latest version" whenupdate_check_urlis not configured — it now displays an explicit "not configured" notice.hasUpdateAvailable()now caches thefalseresult for 1 hour when the URL is absent (previously uncached, causing repeated unnecessary calls) and for 60 seconds when the remote request fails (previously uncached, causing a cURL request on every page load). - Discussion creation: Fixed duplicate AJAX submission caused by two concurrent submit handlers (
DiscussionFormManager+ inlineFormValidator) both firing on the same form. The second request hit the flood-protection cooldown and incorrectly showed a "wait 30 seconds" error. Also fixed the browser "leave or stay" popup that appeared during redirect becauseisDirtywas stilltrue. The inline submit handler has been removed from the create view;DiscussionFormManageris now the sole handler and now also validates the category field and synchronises the Markdown editor value before submission. - EasyMDE(Community & Pro): Fixed editor being inaccessible on iOS/macOS Safari — tapping the editing area did not open the virtual keyboard. Added an explicit
touchstart → focus()listener on the CodeMirror wrapper and set-webkit-user-select: text,user-select: text, andtouch-action: manipulationon the editor area to prevent focus being blocked by inheriteduser-select: nonefrom the toolbar and the 300 ms tap delay. - TUIEditor(Pro): Same iOS/macOS Safari fix applied to both the CodeMirror container (Markdown mode) and the ProseMirror container (WYSIWYG mode).
- Installer username validation:
install.phpnow accepts usernames starting with a digit (e.g.314r). The server-side validation regex has been aligned with the native Flatboard pattern (/^[a-zA-Z0-9_-]+$/), replacing two redundant and overly strict checks that incorrectly required the username to begin with a letter. - Theme logo deletion: Clicking "Delete logo" in the theme configuration showed the confirmation modal but did nothing on confirm. The
data-confirmattribute on the button caused the global capture-phase handler inconfirm-modal.jsto intercept and stop propagation before the module's own click handler could run; on confirm it attempted to callwindow.deleteLogowhich is undefined. Fixed by removing the redundantdata-confirm/data-confirm-titleattributes — the JS module already handles confirmation internally viaUIHelpers.confirm().
Edited on Mar 19, 2026 By Fred .