Upgrading to Admin 0.39.3

flocms-admin 0.39.3 · October 23, 2026 View as Markdown Edit on GitHub

Update with composer require "hostkurd/flocms-admin:^0.39.3" -W and reload open Admin pages. Nothing changes unless you add 'site_link' => false (or an address) to config/admin.php.


Upgrading to Admin 0.39.2

Update with composer require "hostkurd/flocms-admin:^0.39.2" -W and reload open Admin pages (Ctrl+F5). Nothing else to do.


Upgrading to Admin 0.39.1

Update with composer require "hostkurd/flocms-admin:^0.39.1" -W and reload open Admin pages. Nothing else to do: only the look of editable list cells changed.


Upgrading to Admin 0.39.0

Update with composer require "hostkurd/flocms-admin:^0.39.0" -W, run php flo view:clear and reload open Admin pages. Nothing to migrate, and nothing changes until a form uses Wizard. See Wizard forms.

  • Wizards are the whole form: put every field of schema() in a Step. Fields that should stay beside every step (status, featured) go in aside().
  • Passwords in a wizard go on its last step (or in aside()): the form refuses one on an earlier step, because a typed password is never shown again and would be lost on Next.
  • Pending uploads last 30 minutes, as before: a wizard left open longer asks for its files again.
  • Translations: the new Arabic and Kurdish words (wizard.*) need a read by a native speaker.

Upgrading to Admin 0.38.0

Update with composer require "hostkurd/flocms-admin:^0.38.0" -W, run php flo view:clear and reload open Admin pages. Nothing to migrate, and nothing changes until you use the new columns, summarize() or groups(). See Editable cells, totals and grouped lists.

  • beforeSave() and list cells: a cell saved from the list runs your resource's beforeSave($data, $record) with $data holding only that column. If your beforeSave() reads other fields ($data['title'] to make a slug, say), check array_key_exists() first. The same goes for normalizeState() and validate().
  • Custom pages: an editable column in a custom page's table is refused with a message; use TextColumn, BadgeColumn or BooleanColumn there.
  • Translations: the new Arabic and Kurdish words (cells.*, totals.*, groups.*) need a read by a native speaker.

Upgrading to Admin 0.37.0

Update with composer require "hostkurd/flocms-admin:^0.37.0" -W, run php flo view:clear and reload open Admin pages. Nothing to migrate, and nothing changes until you use the new fields.

  • New fields CheckboxList, TagsInput and TimePicker, with TagsColumn, TimeColumn and three filters. A column that already holds comma-separated text can switch to CheckboxList or TagsInput as it is. See Checkbox lists, tags and times.
  • Times in lists and view pages show as 24-hour 09:30. For 12-hour time add 'time_format' => 'g:i A' to config/admin.php.
  • Translations: the new Arabic and Kurdish words (Select all, the tags' messages, the time and list errors) need a read by a native speaker.

Upgrading to Admin 0.36.2

Update with composer require "hostkurd/flocms-admin:^0.36.2" -W and run php flo view:clear. Nothing else to do.

  • make:admin-page --table now asks which table (or takes --table=listings) instead of using users. In scripts, name the table.
  • A page whose defaultSort() names a column that isn't in its columns() (such as id) now shows its rows in that order. Before, they came in the database's own order.

Upgrading to Admin 0.36.1

Update with composer require "hostkurd/flocms-admin:^0.36.1" -W and run php flo view:clear. Nothing else to do. A page made with make:admin-page --table in 0.36.0 still reads the example bookings table: change its query() to your own table.


Upgrading to Admin 0.36.0

Update with composer require "hostkurd/flocms-admin:^0.36.0" -W, run php flo view:clear and reload open Admin pages. Nothing to migrate, and no change until you add a page.

  • Adding pages: php flo make:admin-page SalesReport --view --table, then tick Panel pages on the roles that should see it (only Super admin can open a new page). See Custom pages.
  • A report built as a skeleton controller (ReportsController::admin_index) keeps working. To move it into the panel, make a page with another slug, then remove the controller: a site controller always wins its address.
  • Resource slugs: a page can't take a resource's slug, so resources are never affected. admin:doctor says when a page is skipped.
  • The sidebar: with pages that have a $group, new headings appear between Content and Site.
  • Translations: the new Arabic and Kurdish words (the page's filters, CSV and messages, the Roles group) need a read by a native speaker.

Upgrading to Admin 0.35.0

Update with composer require "hostkurd/flocms-admin:^0.35.0" -W (it needs flocms-core 2.7, which Composer brings along), run php flo view:clear and reload open Admin pages. Nothing changes until you turn the new features on.

  • To use them, add 'two_factor' => true and/or 'email_verification' => true to config/admin.php, then run php flo admin:install and php flo migrate (one new migration, login_security) and php flo admin:doctor. See Login security.
  • Two-factor sign-in needs APP_KEY of at least 32 random characters in .env. Don't change it later: the stored secrets can't be read with another key and everyone sets two-factor sign-in up again.
  • Mail: both features send emails; set MAIL_* and a full APP_URL as for Forgot password, and test with php flo admin:mail-test you@example.com.
  • Resource slugs: with two-factor sign-in on, two-factor is reserved for the panel; with email verification on, verify is. A resource of yours with that slug needs another $slug first.
  • Your own login step: a site with its own TwoFactorController or VerifyController keeps it, and the panel's /admin/two-factor or /admin/verify page doesn't show. admin:doctor points this out.
  • Required verification ('required' => true) applies to existing accounts too: confirm addresses first (the banner and Send verification email help), or people with an unconfirmed address wait at "Check your email" after signing in.
  • Requiring two-factor for a role: everyone with that role sets it up at their next sign-in. Tell them before you switch it on.
  • Translations: the new Arabic and Kurdish words (the sign-in step, Profile → Security, the email links and emails, Users and Roles) need a read by a native speaker.

Upgrading to Admin 0.34.0

Update with composer require "hostkurd/flocms-admin:^0.34.0" -W, run php flo view:clear and reload open Admin pages. No migration: stored blocks don't change.

  • YouTube and Vimeo videos now load when clicked, on existing pages too: visitors see the cover (the block's Cover image, or a dark frame) and a play button. Videos with Autoplay still load at once. Tell your editors; add a Cover to videos that need one.
  • Heroes on phones: below 768 px a hero's text is aligned to the start, also for heroes saved with Centre (as the 0.34 design asks, so it stays readable). Set a CSS rule of your own if you want it centred there.
  • Warnings and errors close after 12 seconds instead of staying until closed. Use ->persistent() on a notification that must stay.
  • Copied templates and styles (php flo admin:blocks): run it again to get the new templates (contact.html, faq.html, testimonials.html, stats.html, team.html, logos.html) and scripts (contact.js, faq.js, stats.js, video.js); it never overwrites files that are there. If you copied blocks.css, video.html, hero.html, gallery.html, gallery.js or gallery.css, take the 0.34 changes from the package (or --force= the ones you didn't change): without them the video shapes and click-to-load, the hero's position and the gallery's captions don't show. A copied hero.html keeps working with Start and Centre.
  • Site blocks whose type is contact, faq, testimonials, stats, team or logos replace the new core ones, as before.
  • Content-Security-Policy: a site that sends one allows the Contact map in frame-src (https://www.google.com and/or https://www.openstreetmap.org).
  • Translations: the new Arabic and Kurdish words (the six blocks, the website's labels, video shape, gallery captions) need a read by a native speaker.

Upgrading to Admin 0.33.0

Update with composer require "hostkurd/flocms-admin:^0.33.0" -W, run php flo view:clear and reload open Admin pages. No migration: stored blocks don't change.

  • A copied blocks.css: if php flo admin:blocks copied the styles to public/assets/flo-blocks/, add the new rules from vendor/hostkurd/flocms-admin/resources/site/blocks.css (flo-space-*-xlarge, the phone spacing in the 767 px media query, and the has-bg / has-text rules), or replace the file with php flo admin:blocks --force=blocks.css if you didn't change it. admin:doctor warns until you do. Without them Extra large, phone spacing and backgrounds have no effect.
  • A copied section.html: the package's Section template adds has-bg-<name> for the theme's colours; copy that line if you use block_colours with your own names.
  • Field names of your own blocks: the shared settings now include space_above_phone and space_below_phone (the others added start with _). A block of yours with a field of either name must rename it.
  • Permissions: give blocks.custom_css only to people you trust with the site's design (Super admin has it). settings.blocks_preview opens Pages › Blocks preview; the Admin role has it through settings.*.
  • Styles and colours are optional: add 'block_styles' and 'block_colours' to config/admin.php when you want them (see the Builder guide). Without block_colours editors get the four colours of blocks.css.
  • A strict Content-Security-Policy on the website: the blocks' CSS is a file under /flo-admin/asset/, so style-src 'self' covers it. Only when storage/cache/blocks can't be written is it a <style> tag; then set 'block_style_nonce'. The blocks preview shows the site in a frame: a site that sends frame-ancestors 'none' or X-Frame-Options: DENY needs 'self' / SAMEORIGIN for /<lang>/flo-preview/ pages, or use Open in a new tab.
  • Translations: the new Arabic and Kurdish words (Display tab groups, styles, colours, phone spacing, custom CSS, the blocks preview and the two permissions) need a read by a native speaker.

Upgrading to Admin 0.32.2

Update with composer require "hostkurd/flocms-admin:^0.32.2" -W and reload open Admin pages. No migration, nothing else to do: this release changes one style in the editor.


Upgrading to Admin 0.32.1

Update with composer require "hostkurd/flocms-admin:^0.32.1" -W, run php flo view:clear and reload open Admin pages. No migration.

  • Show on and Advanced are now the Display tab of a block's dialog. Tell your editors.
  • Pages keep all three contents (blocks, HTML, rich text) when their type changes; the website still shows only the one chosen. All three are saved with every save, so a page's other contents are checked too (an invalid block shows its error even on a Rich text page) and its rich text is cleaned again, which can show in the activity log once.
  • Translations: blocks.tab_display in Arabic and Kurdish needs a read by a native speaker.

Upgrading to Admin 0.32.0

Update with composer require "hostkurd/flocms-admin:^0.32.0" -W, run php flo view:clear and reload open Admin pages. No migration: stored blocks don't change.

  • Builders are saved as one value: nothing to do. max_input_vars now matters only for editors without JavaScript; you may keep the higher value you set for 0.29.
  • The Builder editor looks different (columns side by side, folding, ⋯ menus, the new drag and drop). Tell your editors; nothing else changes for them.
  • Sites that copied the block templates with php flo admin:blocks keep their copies, so the website fixes reach them only when they take the new ones. Changed: views/blocks/{hero,image,video,heading,cards,call_to_action,section,columns,slider}.html, public/assets/flo-blocks/{blocks.css,slider.js}, and the new motion.js. Copy one again with php flo admin:blocks --force=hero.html (that replaces your copy), or merge the changes by hand. New fields arrive in your templates as variables with their defaults ($new_tab is '0', $level is 'h1'…), so old templates keep working.
  • Empty containers print nothing: a Section with no blocks in it (or only hidden ones) is no longer printed, even with a background colour or image; the same goes for an empty Columns block and a Slider without slides. Put a block in a Section to keep its band.
  • Columns 40 / 60 and 60 / 40 are 5 / 7 and 7 / 5 twelfths on the website (41.7 % / 58.3 %), so they work with the 12-column grid of copied styles.
  • Site blocks may use ->shownWhen() on fields and check() for errors across fields. Nothing changes for the ones you have.
  • Translations: the new words in Arabic and Kurdish need a read by a native speaker.

Upgrading to Admin 0.31.0

Update with composer require "hostkurd/flocms-admin:^0.31.0" -W, run php flo view:clear and reload open Admin pages. No migration: stored blocks don't change.

  • The Builder editor looks different: a block's own fields open in a dialog (Edit), and the panel beside the structure shows the settings every block shares. Tell your editors; nothing else changes for them.
  • A site block with fields named _label or _note can't be declared (names starting with _ were already refused; these two are now the panel's Name and Note).
  • Site blocks that name themselves for editors (like a Section's Name) can return that field's name from nameField() to show it in the panel instead of the panel's own Name. Optional.
  • Translations: the new words in Arabic and Kurdish need a read by a native speaker.

Upgrading to Admin 0.30.1

Update with composer require "hostkurd/flocms-admin:^0.30.1" -W, run php flo view:clear and reload open Admin pages. No migration.


Upgrading to Admin 0.30.0

Update with composer require "hostkurd/flocms-admin:^0.30.0" "hostkurd/flocms-core:^2.6" -W, run php flo view:clear and reload open Admin pages. Admin 0.30 needs flocms-core 2.6.

  • Nothing changes until you turn pages on. Add 'pages' => true to config/admin.php, then run php flo admin:install and php flo migrate for the admin_pages table. Pages are at Site → Pages (/admin/site-pages).
  • Your layout: put {!! page_meta() !!} in the <head> of layouts/default.html (it prints nothing on your other pages, so keep your own <title> for them) and, for a menu, loop over pages_menu(). See Pages.
  • The home page: to show a page at the main address, start your home controller's index() with if ($page = home_page()) { return $page->response(); }.
  • Your controllers win: a page can't take an address your site's controllers answer (a page called contact next to a ContactController). php flo admin:doctor lists any that clash.
  • /sitemap.xml is answered by the panel when pages are on. A sitemap.xml file in public/ keeps working; 'pages' => ['sitemap' => false] turns the panel's off.
  • Tick pages.publish on the Roles page (System) for the people who may publish pages. Without it they can write drafts, and pages visitors see are read-only for them. Super Admins have it (*).
  • Block styles and scripts, when the site hasn't copied them with php flo admin:blocks, are now linked as files from /flo-admin/asset/ instead of printed in the page. Nothing to do; a strict Content-Security-Policy no longer blocks them.
  • Translations: the new words in Arabic and Kurdish need a read by a native speaker.

Upgrading to Admin 0.29.0

Update with composer require "hostkurd/flocms-admin:^0.29.0" -W, run php flo view:clear and reload open Admin pages. No migration: the Builder needs only a column on the tables that use it.

  • Nothing changes until you add a Builder or Slider field. Give its column a type that holds large JSON: JSON, LONGTEXT or MEDIUMTEXT on MySQL and MariaDB (TEXT stops at 64 kB), TEXT on SQLite and PostgreSQL. php flo admin:doctor warns about a column that is too small.
  • Printing blocks: put {!! blocks_assets() !!} in your layout's <head> and {!! blocks($record->content) !!} where they go. Run php flo admin:blocks when you want to change the templates, styles or slider script: they are copied into your site and never replaced by updates.
  • max_input_vars: every block's fields are sent with the form (about 20 per block). Pages of more than about 40 blocks need max_input_vars = 5000 in php.ini or .user.ini; a request cut short is refused, not saved half.
  • Tick blocks.html on the Roles page (System) for the people who may add or change HTML blocks. No existing role gets it, Super Admins aside (*).
  • Select shown as buttons: ->segmented(), ->pictures() and ->positions() are new; a select without them is drawn as before. The field's label now has an id (<id>-label).
  • make:admin-widget shares its config code with the new make:admin-block; it behaves as before.
  • Translations: the new words in Arabic and Kurdish need a read by a native speaker.

Upgrading to Admin 0.28.2

Update with composer require "hostkurd/flocms-admin:^0.28.2" -W, run php flo view:clear and reload open Admin pages. No migration.


Upgrading to Admin 0.28.1

Update with composer require "hostkurd/flocms-admin:^0.28.1" -W, run php flo view:clear and reload open Admin pages.

  • Run php flo admin:install, then php flo migrate to add the optional bio column to your users table (added only if it isn't there; php flo migrate:rollback removes it again). Without it the user page simply has no Bio field.
  • The user form is laid out in sections with a sidebar. A site that extends UsersResource::form() should check its fields still sit where it wants them.
  • Translations: the new words in Arabic and Kurdish need a read by a native speaker.

Upgrading to Admin 0.28.0

Update with composer require "hostkurd/flocms-admin:^0.28.0" -W, run php flo view:clear and reload open Admin pages.

  • Run php flo admin:install, then php flo migrate for the mailing tables (admin_mailings, admin_mailing_recipients, admin_mail_optouts). Until then Users works as before and Email users says the tables are missing.
  • Mailings need APP_KEY in .env (at least 16 characters, as most FloCMS sites already have): it signs the unsubscribe links. Without it direct emails still work.
  • Tick the new permissions on the Roles page: users.email (direct emails to up to 20 chosen users) and users.mailing (mailings). No existing role gets them, Super Admins aside (*).
  • Optional cron so mailings go on with no page open: * * * * * cd /path/to/site && php flo admin:mailings. Check per_hour against your host's limit ('mailings' => ['per_hour' => 200]).
  • Photos are stored in public/uploads/admin, so the folder must be writable. Existing values of users.image (a path under public/ or an address) are shown as they are.
  • The ID column is gone from Users (the list is sorted as before). The row title still opens the user.
  • Translations: the new words in Arabic and Kurdish need a read by a native speaker.

Upgrading to Admin 0.27.0

Update with composer require "hostkurd/flocms-admin:^0.27.0" -W, run php flo view:clear and reload open Admin pages. No migration.

  • People with View permission can now open records. Until now content.<slug>.view without update showed the list only; now the row title opens the record's view page, which shows every field except passwords, Hidden fields and fields marked ->hiddenOnView(). Check your roles: if a field must stay out of sight of viewers, add ->hiddenOnView(), refuse whole records with canView(), or turn the page off with protected static bool $viewPage = false;.
  • Viewers can download a record's files (FileUpload fields not marked hiddenOnView()), and see related records on the view page as a plain table.
  • History on a record is now also open to people with activity.view who may view the record (before: only to people who may edit it).
  • The row title is a link now (to Edit, or to the view page). A column that already renders its own link or button is left as it is. If your own JavaScript or tests click the title cell, they now follow the link.
  • Links to records in Ctrl+K search, recent records and the Activity page go to the view page for people who may not edit. A resource with searchUrl() keeps its own link.
  • Translations: the new view.* words in Arabic and Kurdish need a read by a native speaker.

Upgrading to Admin 0.26.0

Update with composer require "hostkurd/flocms-admin:^0.26.0" -W (this also brings flocms-core 2.5), run php flo view:clear and reload open Admin pages.

  • Forgot password is off on existing sites until you add it to config/admin.php:

    'password_reset' => true,
    

    Then run php flo admin:install, then php flo migrate for the admin_password_resets table. The login page shows Forgot password? once the table exists (with 'login_theme' => true).

  • Email settings in .env: on most cPanel hosts PHP mail() works with nothing more than PRIMARY_EMAIL (the sender). For SMTP add MAIL_TRANSPORT=smtp, MAIL_HOST, MAIL_PORT, MAIL_ENCRYPTION, MAIL_USERNAME, MAIL_PASSWORD and optionally MAIL_FROM / MAIL_FROM_NAME. APP_URL must be the site's full address (https://…). Check with php flo admin:doctor and php flo admin:mail-test you@example.com.

  • Other sessions now end when a password changes, also on the profile page and when an admin changes a user's password on Users. Each session gets its fingerprint on its next admin request, so nobody is signed out by the upgrade itself.

  • Your own PasswordController (if the site has one) keeps the URL /admin/password; the panel's pages are then not shown. A resource with the slug password is refused (admin:doctor says so).

  • Translations: the Arabic and Kurdish words of the new pages and emails need a read by a native speaker; override any of them in your lang files as usual.


Upgrading to Admin 0.25.1

Update with composer require "hostkurd/flocms-admin:^0.25.1" -W, run php flo view:clear and reload open Admin pages. No migration.

  • Rich text is imported now. The Match step offers RichEditor fields (one column per language). If a resource must not take rich text from a file, mark the field ->notImportable().
  • Templates: the empty template and exported files of resources with rich text already had those columns; the import now reads them instead of skipping them.
  • Translations: import.tip_media changed; the Arabic and Kurdish words need a native read.

Upgrading to Admin 0.25.0

Update with composer require "hostkurd/flocms-admin:^0.25.0" -W, run php flo view:clear and reload open Admin pages.

  • Run php flo admin:install, then php flo migrate: this adds the admin_imports and admin_import_rows tables. Until then lists show the Export menu but no Import button. php flo admin:doctor checks the tables and the folder storage/admin-imports/ (keep it out of the public folder, as storage/ already is).
  • New permissions content.<slug>.export and content.<slug>.import. Roles with content.* or * (Editors and Super Admin in the skeleton) get both at once; to keep import for some roles only, list their permissions per resource, or untick the new Export and Import columns on Site → Roles. Import also needs create, update to change existing records, and delete to undo an import that created records.
  • Turning it off: 'export' => false / 'import' => false in config/admin.php for the whole panel, or protected static bool $exportable = false; / $importable = false; on a resource. Use ->hiddenFromExport() on fields with private data (phone numbers, internal notes): every field of the form except passwords is exported by default.
  • Lists with bulk actions show Export selected in the bulk bar. Lists' toolbars gain the Export button; tests that count the toolbar's buttons may need a look.
  • Cron (optional): php flo admin:import-purge once a day removes old upload files and Undo data; panel pages do the same at most once an hour.
  • Activity log: an import is one imported entry, not one entry per row. Exports are logged as exported.
  • Translations: the Arabic and Kurdish words of the import and export pages need a read by a native speaker; override any of them in your lang files as usual.

Upgrading to Admin 0.24.1

Update with composer require "hostkurd/flocms-admin:^0.24.1" -W, run php flo view:clear and reload open Admin pages. No migration, no code changes: row lists (->table() and KeyValue) look tidier, and a table's row menu opens without a scrollbar flash.


Upgrading to Admin 0.24.0

Update with composer require "hostkurd/flocms-admin:^0.24.0" -W, run php flo view:clear and reload open Admin pages. No migration.

  • Nothing changes until a form declares a Repeater or KeyValue. Each needs a TEXT or JSON column of its own (for example ALTER TABLE flats ADD rooms JSON NULL); php flo admin:doctor warns about a smaller type. See Repeaters and key-value lists.
  • On the website read a list with repeater_items($flat->rooms) or key_values($flat->specs); both return [] for an empty column.
  • Choosing between a Repeater and a relation manager: the guide has a table; in short, a Repeater for a few small items that only live inside the record, a relation manager for records of their own.
  • Dialog drafts: the browser now keeps nested input names (rooms[k1][name][en]) as nested values in the draft. Flat names and title[en] arrive exactly as before. Inputs of your own named with two to four […] parts (extra[a][b]), which the draft used to skip, are now sent as nested arrays; names ending in [] are still skipped.
  • Enter in dialogs and action pages now always saves: their forms start with a hidden .fa-default-submit button (as record and settings pages already did), so Enter never presses a list's Add or Up button. A test that clicked dialog button[type=submit] should use .fa-dialog-foot button[type=submit] or the button's name.
  • A disabled Repeater is shown read-only, without its buttons.
  • Translations: the Arabic and Kurdish words for the new buttons and messages need a read by a native speaker; override any of them in your lang files as usual.
  • Templates and tests: a list is [data-repeater] with data-name; each item is [data-repeater-item] with data-key, its inputs are named rooms[<key>][<field>] (and [<lang>]) with ids rooms-<key>-f-<field>; the buttons are [data-repeater-add] and [data-repeater-action="up|down|duplicate|remove|toggle"], and without JavaScript they post _repeater=rooms:add (or rooms:up:<key>).

Upgrading to Admin 0.23.1

Update with composer require "hostkurd/flocms-admin:^0.23.1" -W, run php flo view:clear and reload open Admin pages. No migration.

  • Settings pages show the same language switch as record pages, under the title (buttons for up to four languages). Tests that chose a language with the select [data-settings-language] should click #fa-settings-switch .fa-seg-btn[data-lang="ar"] instead; with five or more languages the select (and its data-settings-language attribute) stays.

Upgrading to Admin 0.23.0

Update with composer require "hostkurd/flocms-admin:^0.23.0" -W, run php flo view:clear and reload open Admin pages. No migration.

  • Nothing changes until you choose page mode: without 'translation_mode' in config/admin.php every resource keeps the language badges.
  • Turning it on: add 'translation_mode' => 'page', to config/admin.php for the whole panel, or protected static string $translationMode = 'page'; to one resource (and 'field' to keep a resource on badges). php flo admin:doctor lists the mode of each resource. See Forms.
  • What still needs translating: add TranslationsColumn::make('translations') to a table; it brings a Missing in filter (named missing_in; a filter of your own with that name keeps it).
  • Error messages now name the language of a translation's error in both modes: "Please check: Title (العربية)" (the site's lng.ar name, else the language's own name). Tests that compare that text need the new wording.
  • Settings pages: the language select is the same control, now with counts in its options, Next missing and reference lines; the "All languages" note keeps its place. Code that hooked [data-settings-language] keeps working.
  • Rich text: an empty editor (<p></p>, <p>&nbsp;</p>) is now saved as nothing, so it no longer counts as filled; existing empty values are cleaned the next time the record is saved.
  • Preferences: the last language saved in is kept as the editing_language preference (in admin_user_preferences, or the session before that table exists).
  • Templates and tests: a page-mode form is [data-lang-scope] with data-editing, a hidden _editing input and a .fa-lang-switch; each language input sits in a .fa-lang-pane; shared fields have data-same-all. Field-mode markup is unchanged.

Upgrading to Admin 0.22.0

Update with composer require "hostkurd/flocms-admin:^0.22.0" -W, run php flo view:clear and reload open Admin pages. No migration.

  • Nothing changes until a resource declares relations(): edit pages look as before.
  • Adding managers: return RelationManager::make(ChildResource::class, 'parent_id') from the parent's relations(). The child must be registered in config/admin.php; php flo admin:doctor checks it and its key column. See the Relation managers guide.
  • Children in the sidebar: a child managed only inside its parent can set protected static bool $navigation = false;. Its URLs keep working.
  • Your own child resources: in a manager, the child's query() still applies, and the server adds parent_id = <parent>. beforeSave() runs as usual; the key is set after it, so it can't move a record to another parent from a manager.
  • The parent's History now includes the related records' entries. Entries written before 0.22 have no parent note and stay only on the child.
  • Templates and tests: the record page has a <section class="fa-managers"> after the form, each manager a Reactive component rx-rel-<name>; the delete confirmation of a parent ends with "This property has …". A resource with $navigation = false has no sidebar link.

Upgrading to Admin 0.21.0

Update with composer require "hostkurd/flocms-admin:^0.21.0" -W (this also installs flocms-core 2.4), then:

php flo admin:install   # adds the roles migration (nothing is overwritten)
php flo migrate         # creates admin_roles
php flo admin:doctor    # checks it
php flo view:clear

Reload open Admin pages.

  • Nothing changes until someone saves on the Roles page: without rows in admin_roles the roles of config/config.php apply exactly as before.
  • Who sees the page: roles.manage. Super Admin has it through *. Give it to other roles only on purpose.
  • The page needs the permissions and admin_access_roles maps in config/config.php (the skeleton 1.8+ has both). Without them it stays off and admin:doctor says so.
  • Once roles are changed on the page, config/config.php is no longer the whole story: edits to a config role there only apply while the role has no changes on the page (Reset to default, or php flo admin:roles-reset). php flo admin:roles prints the roles in force.
  • Emergency switch: 'roles' => false in config/admin.php makes the config roles apply again at once, without deleting anything.
  • Your own permission checks: code that compares role numbers to rank users can use Auth::level($role) (core 2.4) so it agrees with roles added on the page; Auth::roles() lists every role's name. Name your own permissions in 'roles' => ['permissions' => [...]] to give them a row in the grid.
  • Skeleton 1.8.7 (optional) asks core for admin access at login, so a new role can sign in at once; older skeletons get there one redirect later.
  • /admin/roles: a site resource or application controller with the slug roles keeps it; the panel's page is then not shown.
  • Templates and tests: the sidebar's Site group has a Roles link for people with roles.manage, and Ctrl+K a Roles group.

Upgrading to Admin 0.20.0

Update with composer require "hostkurd/flocms-admin:^0.20.0" -W, run php flo view:clear and reload open Admin pages. No migration.

  • A search field in the top bar, and Ctrl+K, ⌘K and / open the search box on every panel page. 'search' => false in config/admin.php switches it off; 'search' => ['shortcut' => false] keeps the field but leaves the keys to the browser.
  • What is found is what each list's search box finds: the columns marked ->searchable(). Check that the first searchable column of each resource is its title (it titles the result). A resource that should stay out (a large log table): protected static bool $globallySearchable = false;.
  • /admin/search: a site resource or application controller with the slug search keeps it; the panel's search is then off.
  • Templates and tests: the top bar has a <form class="fa-topsearch"> and the panel a <dialog id="fa-search-dialog">; site tests that select input[type=search] or the page's only form, or count top-bar links or dialogs, should look inside .fa-content (the browser tests here do). PanelView::pageUrls() is new; activityUrl() and trashUrl() are public.

Upgrading to Admin 0.19.1

Update with composer require "hostkurd/flocms-admin:^0.19.1" -W, run php flo view:clear and reload open Admin pages. No migration.

  • Form and action dialogs are now as tall as their content (before, their body was at least 150 px). To keep room, for example for a form whose fields appear as you choose, set 'dialog_min_height' => 240 in config/admin.php, or ->modalMinHeight(320) on one action.
  • Bulk confirmations for a single record use new keys (bulk.confirm_one, bulk.confirm_trash_one, bulk.confirm_trash_kept_one, bulk.confirm_delete_one). A site that translates or changes admin.bulk.confirm* in its own lang files keeps its wording until it adds the _one keys too. Site tests that looked for "1 selected records" should look for "the selected record".

Upgrading to Admin 0.19.0

Update with composer require "hostkurd/flocms-admin:^0.19.0" -W, then:

php flo admin:install   # adds the trash and activity log migration (nothing is overwritten)
php flo migrate         # creates admin_trash and admin_activity
php flo admin:doctor    # checks them
php flo view:clear

Reload open Admin pages.

  • Until the migration has run deleting works as before (at once, no Undo) and nothing is logged.
  • Deleting no longer erases at once: deleted records wait in the trash for 30 days. A site that must erase at once sets 'trash' => false in config/admin.php, or protected static bool $trash = false; on a resource. afterDelete() still runs at the delete; upload files are now removed when the record is deleted for good, not at the delete.
  • The Activity page needs activity.view. Add it to the roles that should see it in config/config.php (the skeleton's Admin role: 2 => [..., 'activity.view']). Super Admin (*) has it. History on a record needs only the update permission.
  • Resources that override query() to show part of a table (one site, one owner): their trash shows each person only what they deleted. Define canSeeInTrash(object $record, ?int $deletedBy): bool to share it by your own rule, or return true when query() only sorts.
  • Fields with secrets that are not password fields (an API key, a personal note): add ->hiddenFromActivity() so the log records the change without the value. Passwords are never logged.
  • /admin/trash and /admin/activity: a resource or application controller with the slug trash or activity keeps it (the panel's page is then not shown; admin:doctor says so).
  • Cron (optional): php flo admin:trash-purge and php flo admin:activity-purge once a day. Panel pages do the same at most once an hour.
  • Templates and tests: a delete now shows "Post deleted." with an Undo button (a session notification) when the trash is on, instead of the flash message; Action::getDescription() takes the resource as an optional argument; the panel's #fa-confirm dialog's title, text and button can change per form (data-confirm-title, data-confirm-text, data-confirm-button).

Upgrading to Admin 0.18.1

Update with composer require "hostkurd/flocms-admin:^0.18.1" -W, run php flo view:clear and reload open Admin pages. No migration.

  • The message after a failed save now names the fields ("Please check: Price (Details)." — key flash.fix_errors_in); an error that belongs to no field adds its own text to that list. flash.fix_errors remains for the rare case with nothing to list. Site tests that looked for "Please correct the errors below." on resource forms should look for the new text.
  • Tab buttons holding an error have the class has-error and an aria-describedby.

Upgrading to Admin 0.18.0

Update with composer require "hostkurd/flocms-admin:^0.18.0" -W, then:

php flo admin:install      # adds the media folders migration (nothing is overwritten)
php flo migrate            # creates media_folders; adds folder_id, hash, focal_x, focal_y to media
php flo admin:media-import # optional: fingerprints for items uploaded before 0.18 (for the duplicate check)
php flo admin:doctor       # checks them
php flo view:clear

Reload open Admin pages.

  • Until the migration has run the media library is off: no Media link, and media fields say "The media library is not set up" (stored ids are kept on save). admin:doctor says what is missing.
  • Renamed media tables: if you set 'table' in 'media_library', the migration uses it. To rename the new folders table, set 'folders_table' before running admin:install.
  • Permissions: folders, moving items and the focal point need content.media.update, like the title and alt text. Nothing new to grant. An uploader without update can upload into a field's ->folder() only once it exists; otherwise the files go to Unfiled.
  • Upload fields: inside the .fa-upload wrapper (unchanged, with its data-upload-* attributes) the input is now a FileDrop (.fa-filedrop, [data-admin-filedrop]); the input keeps its name, id, accept and data-upload-field. Site CSS or tests that targeted the old current-file link, .fa-upload-remove or the input's .fa-input class should use the new markup (Uploads guide). Saving, validation and storage are unchanged.
  • Media fields and the Media page: new markup (.fa-mf, .fa-mb-*); the 0.17 classes (.fa-media-grid, .fa-media-tile, .fa-media-chip, .fa-media-dialog, …) are gone. The item's page (/admin/media/edit/<id>), .fa-media-thumb in lists and the rich text editor's .fa-media-* classes are unchanged. The picker needs a click on Choose (or a double-click) for one item.
  • Forms of your own in the panel that a script posts itself can opt out of page navigation with data-admin-local.

Upgrading to Admin 0.17.0

Update with composer require "hostkurd/flocms-admin:^0.17.0" -W, then:

php flo admin:install      # adds the media library migration (nothing is overwritten)
php flo migrate            # creates the media and media_usage tables
php flo admin:doctor       # checks them
php flo view:clear

Reload open Admin pages. Existing forms and pages are unchanged.

  • Roles: give editors content.media.view (and .create, .update, .delete as needed; php flo admin:list --permissions prints them). Without view, the Media link and the editor's library are hidden. Without create, uploads in the rich text editor keep working as before and don't join the library.
  • Rich text uploads: with the library set up, images and videos uploaded in the editor are stored in library/<year>/<month>/ of the media folder and become library items. Files uploaded before stay where they are and keep working; php flo admin:media-import adds them to the library (--dry-run lists them first).
  • A media table of your own: the migration stops with a message instead of touching it. Set 'media_library' => ['table' => 'site_media', 'usage_table' => 'site_media_usage'] in config/admin.php, delete the generated migration and run php flo admin:install again. Or set 'media_library' => false to leave the library off.
  • Custom CSS or tests: the panel's CSS now has .fa-media-* classes for the library (.fa-media-box, .fa-media-tile, .fa-media-chip, …). The rich text editor's .fa-media, .fa-media-frame, .fa-media-handle and .fa-media-caption are unchanged.
  • This release includes the 0.16.1 fix (focus after a failed rich text save), which wasn't tagged on its own.

Upgrading to Admin 0.16.0

Update with composer require "hostkurd/flocms-admin:^0.16.0" -W, then run php flo view:clear and reload open Admin pages. No migration is needed; nothing changes until a table calls ->reorderable().

  • Using Reorder: add an integer column (ALTER TABLE faqs ADD sort INT NOT NULL DEFAULT 0; and CREATE INDEX faqs_sort_index ON faqs (sort);) and ->reorderable('sort') to the resource's table(). Existing rows keep their current order: the first save numbers them. Remove a defaultSort() you no longer want, so the list follows the editors' order. On the website, read the records with ORDER BY sort, id. Run php flo admin:doctor to check the column.
  • Custom resources that already define a method named canReorder or afterReorder must rename it (the panel now calls them).
  • Site CSS or tests that look at the list toolbar: a reorderable list has one more button there ([data-admin-reorder-link]).

Upgrading to Admin 0.15.5

Update with composer require "hostkurd/flocms-admin:^0.15.5" -W, run php flo view:clear and reload open Admin pages. Nothing else changes.


Upgrading to Admin 0.15.4

Update with composer require "hostkurd/flocms-admin:^0.15.4" -W, run php flo view:clear and reload open Admin pages. No migration. The .fa-input-row around a translatable input no longer has a dir attribute (the input keeps its own); site CSS or tests that relied on it should use the input's dir.


Upgrading to Admin 0.15.3

Update with composer require "hostkurd/flocms-admin:^0.15.3" -W, run php flo view:clear and reload open Admin pages. No migration. To give an existing list a Columns menu, add ->toggleable() (or ->toggleable(hiddenByDefault: true)) to the columns users may hide.


Upgrading to Admin 0.15.2

Update with composer require "hostkurd/flocms-admin:^0.15.2" -W, run php flo view:clear and reload open Admin pages. No migration.

  • Notifications now close by themselves (success and info, after 5 seconds). Add ->persistent() to one that must stay, such as a message the user needs to copy. Browser tests that check a success or info notification after more than a few seconds should make it persistent or check sooner.
  • Browser tests that looked for a relation list with position: absolute should expect fixed.

Upgrading to Admin 0.15.1

Update with composer require "hostkurd/flocms-admin:^0.15.1" -W, run php flo view:clear and reload open Admin pages. Nothing else changes. Run php flo admin:doctor once: if it reports a table that "stores text as latin1", convert it with the ALTER TABLE it prints before entering Arabic or Kurdish text (text already saved as ? must be typed again).


Upgrading to Admin 0.15.0

Update with composer require "hostkurd/flocms-admin:^0.15.0" -W, then run php flo view:clear and reload open Admin pages. No migration is needed; nothing changes until a resource uses the new relation fields.

  • Using relations: see docs/relationships.md. A BelongsTo column should allow NULL unless the field is required(); a BelongsToMany needs its pivot table. Run php flo admin:doctor afterwards: it checks the tables and suggests foreign keys.
  • Custom columns and filters: classes that extend Column or Filter and already define a method named prepareRecords, isDatabaseColumn, renderFilterInput, describeFilterValue or emptyFilterValue must rename it (these names are now used by the panel). The same applies to storesInColumn, saveRelated and submitted on custom Field classes.
  • Deletes: RecordRepository::delete() now runs in a transaction and turns a foreign-key refusal into a ValidationException with a readable message; code that caught PDOException around it should catch FloCMS\Core\ValidationException.

Upgrading to Admin 0.14.0

Update with composer require "hostkurd/flocms-admin:^0.14.0" -W, then:

php flo admin:install     # adds the two new migrations; leaves config/admin.php alone
php flo migrate           # creates `settings` and `admin_user_preferences`
php flo view:clear
php flo admin:doctor

Reload open Admin pages. Until the tables exist the panel works as before, the Settings page asks you to migrate, and preferences last until logout.

  • One settings table. Settings pages use FloCMS's own settings table (param, value, lang), the one core reads for offline_mode. Each admin setting is one row with an empty lang and a JSON value; existing rows are left alone. Sites that already have the table keep it (the migration only creates it when missing); admin:doctor checks its columns.
  • A resource with the slug settings? That URL now belongs to the panel: give the resource another $slug (the doctor and debug log name it).
  • Who sees Settings: website pages need settings.manage, the Admin panel page settings.panel. The skeleton's Admin (settings.*) and Super admin (*) roles have both; give them to other roles if needed.
  • Settings files are public. If your web root is not public/, set settings_uploads (['root' => …, 'url' => …]) in config/admin.php.
  • Brand settings move to the browser (optional). brand, logo, primary_color, theme, sidebar, content_width in config/admin.php keep working as defaults; a value saved in Settings → Admin panel overrides them. Add 'panel_settings' => false to keep config-only branding.
  • Language links: the top-bar language links now end with ?switch_language=1. Browser tests that matched those URLs exactly need updating.
  • Browser tests that located the Filters menu with a generic details.fa-popover should use details.fa-popover[id$="-filters-menu"]: tables with toggleable columns now have a second popover.
  • Profile page: it now uses the settings layout with Account and My preferences tabs; the form fields are unchanged.

Upgrading to Admin 0.13.1

Update with composer require "hostkurd/flocms-admin:^0.13.1" -W and reload open Admin pages. Nothing else changes; if your site sends a Content-Security-Policy, allow frame-src https://www.youtube-nocookie.com https://player.vimeo.com on admin pages so embeds show their player.


Upgrading to Admin 0.13.0

Update with composer require "hostkurd/flocms-admin:^0.13.0" -W, then run php flo view:clear and reload open Admin pages. No migration or dependency change is needed.

  • Add {!! \FloCMS\Admin\Support\RichText::stylesheet() !!} to the <head> of the website's layout, and print rich text inside an element with class rt-content. Without the stylesheet, sizes, positions, captions and alignment set in the editor are saved but not shown.
  • Fields with their own toolbar([...]) keep exactly their buttons; add textAlign, direction, indent, outdent or clearFormatting to offer the new ones.
  • Browser tests that pressed End in the editor toolbar now land on Clear formatting (the last enabled button) instead of the embed button.

Upgrading to Admin 0.12.0

Update with composer require "hostkurd/flocms-admin:^0.12.0" -W, then run php flo view:clear and reload open Admin pages. No migration or dependency change is needed.

  • Create and Edit now open as full pages. To keep the dialog for a resource, add protected static string $formMode = 'modal'; (or 'slide-over').
  • Full-page forms: the default submit (Enter, or the first submit button) now saves and stays on the record; Save and close (_close) returns to the list. Scripts that posted the form and expected the list should send _close=1. Browser tests that clicked "Save changes" on a page click "Save and close" (or "Save") now; edit links carry ?back=…, so match them with a[href^=".../edit/8?"].
  • Pages now use the full window width. Add 'content_width' => 'boxed' to config/admin.php for the previous centred layout.

Upgrading to Admin 0.11.0

Update with composer require "hostkurd/flocms-admin:^0.11.0" -W, then run php flo view:clear and reload open Admin pages. Composer ships admin-v0.11.0.css/.js and admin-editor-v0.11.0.js; no Node build, migration, configuration or dependency change is needed.

  • Browser tests or scripts that drove the editor's insert panels ([data-insert=…], [data-insert-file]) need updating: links, images from a link and video links are now <dialog data-rich-dialog="link|image|embed">, the Image button opens a menu (role="menu") when uploads are available, and Video opens the file picker directly. Upload messages are in [data-rich-status-text].
  • The "Loading…" box during navigation is gone; the .fa-navigation-status region still exists for screen readers and gets the class is-busy while loading. If your styles targeted it, check them.

Upgrading to Admin 0.10.0

Update with composer require "hostkurd/flocms-admin:^0.10.0" -W (this also brings hostkurd/flocms-reactive 0.5.1), then run php flo view:clear and reload open Admin pages. Composer ships admin-v0.10.0.css/.js and admin-editor-v0.10.0.js; no Node build or migration is needed, and nothing changes until a resource uses RichEditor.

  • To use rich text, replace Textarea::make('body') with RichEditor::make('body') (keep ->translatable()). Existing plain text shows as one paragraph and is saved as HTML from then on; print the column with {!! RichText::clean($value) !!} on the website instead of escaping it.
  • Image and video uploads need a public, writable folder. The default is public/uploads/editor, served at /uploads/editor; set media in config/admin.php if your web root differs, and run php flo admin:doctor. Images need PHP's GD or Imagick extension.
  • If your website sends a Content-Security-Policy, allow embeds with frame-src https://www.youtube-nocookie.com https://player.vimeo.com.

Upgrading to Admin 0.9.0

Update with composer require "hostkurd/flocms-admin:^0.9.0" -W, then run php flo view:clear and reload open Admin pages. Composer ships admin-v0.9.0.css/.js and the fonts in resources/fonts/; no Node build, migration or dependency change is needed.

  • The panel looks different (new fonts, colours, spacing and focus ring). If your template overrides admin styles by selector, check them; prefer the new --fa-* custom properties (README, "Look and theming").
  • Optional: add 'theme' => 'light' (or 'dark', 'auto') to config/admin.php. Without it the panel stays light.
  • The sidebar is now light. Add 'sidebar' => 'dark' to keep a dark one, 'primary_color' => '#rrggbb' for your brand colour and 'logo' => '/path/logo.svg' for a logo (see the README).
  • The topbar links (View site, your name, Log out) moved into the account menu; profile is "My profile" there.
  • On phones, list rows are shown as cards; the card title is the first searchable() column. Make your title column searchable (it usually is) so cards are titled by it rather than by the ID.
  • On a site with a single language, translatable fields are now plain inputs; stored values keep the same JSON shape.
  • If a strict font-src Content-Security-Policy is set, allow 'self'; the fonts are served from the site's own /flo-admin/asset/ route.

Upgrading to Admin 0.8.1

Update with composer require "hostkurd/flocms-admin:^0.8.1" -W and reload open Admin pages. Composer ships admin-v0.8.1.css/.js in place of the 0.8.0 files; no configuration, resource, database or dependency change is needed. The only visible change is that the panel no longer sits inside the browser's default 8 px page margin.


Upgrading to Admin 0.8.0

Update after release with composer require "hostkurd/flocms-admin:^0.8.0" -W, then run php flo view:clear and php flo admin:doctor, and reload open Admin pages. Composer ships admin-v0.8.0.css/.js; no application Node build or manual asset publishing is required. Existing Core ^2.3.2, Reactive ^0.4.0, Uploader ^1.2.0 and PHP ^8.1 requirements are unchanged.

Widgets are opt-in. Existing configuration with no widgets entry, or 'widgets' => [], keeps the original resource-count dashboard. To use widgets, add the explicit class list and optional widgets_path, widgets_namespace and widget_columns settings shown in docs/widgets.md, then create your own widget PHP classes. No migration, example database, application layout replacement or skeleton update is installed.

Use only registered source resources and the scoped read helpers for tenant-sensitive dashboard data. Widgets check all declared source view permissions before their providers run; optional widget permissions can narrow access. Providers must be read-only, because rendering/refresh repeats them. Do not put user- or tenant-specific results in shared caches. See the guide for supported widget types, bounds, failure handling and the normal GET refresh fallback.


Upgrading to Admin 0.7.0

0.6 → 0.7

Update hostkurd/flocms-admin to ^0.7.0. Composer adds the already-released hostkurd/flocms-uploader ^1.2.0 (requires ext-fileinfo); Core stays ^2.3.2, Reactive stays ^0.4.0, PHP stays ^8.1. No separate prerequisite release or Core/skeleton update is required.

Upload fields are opt-in: add nullable string/TEXT columns in your application migration and declare FileUpload / ImageUpload in the resource form. Keep <root>/storage/admin-uploads outside public web access, writable by PHP, backed up with the database, and configure multipart request limits. See uploads.md for exact supported types/size/dimensions, editor-only download permissions, token expiry/quotas, and scheduled cleanup. No file table, filesystem symlink, application migration, public storage route or global uploader config is installed.

Managed file columns belong to CRUD: do not rewrite them in beforeSave/afterSave/custom callbacks or copy their values between records. beforeSave must preserve prepared upload columns. Deleting through Admin row/bulk actions also deletes managed files after commit; raw SQL and external application code remain responsible for their own file handling. Legacy external paths are retained but not served/deleted. Custom/bulk input forms cannot declare upload fields in this version.

Publish/distribute dist/admin-v0.7.0.css and .js together with this package and clear cached asset references. Existing resources without upload fields retain their form contracts. All standard POST form navigation now uses FormData to preserve binary file inputs.


Upgrading to Admin 0.6.0

Release Reactive 0.4.0 before Admin 0.6.0. Then run composer require hostkurd/flocms-admin:0.6.0 -W, php flo view:clear and php flo admin:doctor, and reload open Admin pages. Admin now requires Reactive ^0.4.0; PHP 8.1+ and Core ^2.3.2 remain unchanged. Deploy the supplied admin-v0.6.0.js / .css and matching Reactive assets. Full release-version filenames remain the convention. No Node build, package database migration, skeleton change or resource rewrite is required.

Existing flat resource definitions remain supported. Opt into Form\Layout\Section, Grid, Tabs, and Tab, with 1–4 columns and columnSpan('full'). Fields retain flat column names across all layouts and must be globally unique. Tab names must be unique within a group. Without JavaScript, every tab remains visible as a section.

Use Select::optionsFor(parent, callback), visibleWhen(parent, values) and requiredWhen(parent, values) for dependent fields. Parent values must be scalar and non-password. Declare no cycles or unknown fields. Callbacks must be read-only and use scoped, bound database queries; they can run multiple times. Current options and requirements are checked again on save. Hidden values are not written; existing stored values remain on edit, and hidden create columns need database defaults/nullability. Resource permissions and query scope still provide authorization.

A live dependency change refreshes controls without saving or consuming the operation. Invalid child select values clear, unrelated drafts remain, and passwords are scrubbed after the response. Enter passwords after changing dependencies. With JavaScript disabled, use the Update fields button before choosing a child value. Existing transaction, stale-record, actor/CSRF and operation replay protections remain in effect for CRUD and bulk/custom actions.

The complete modal draft limit increases from 7,000 to 64,000 UTF-8 JSON bytes, including keys, values and translations. Full-page fallback remains above the limit and does not transfer unsaved text. Reactive opted-in requests may use up to a 1 MiB HTTP envelope; allow sufficient PHP/web-server request-body size. Signed snapshots remain readable client data; do not put secrets in state. This release adds no repeaters, uploads, nested/object state or relationship managers.

See Forms API and example. Application fields still need matching database columns; installing/updating Admin never automatically adds example Property columns. Existing optional login-theme configuration remains unchanged.


Upgrading to Admin 0.5.0

Update the package after release with composer require hostkurd/flocms-admin:0.5.0, run php flo view:clear and php flo admin:doctor, then reload open Admin pages. Deploy supplied admin-v0.5.0.js and admin-v0.5.0.css; no Node build is needed on the application host. Core ^2.3.2, Reactive ^0.3.0 and PHP 8.1+ remain unchanged. No package database migration or existing resource rewrite is required.

Filters and bulk actions

Both features are opt-in resource definitions. Add Table::filters() and Table::bulkActions() as shown in the API guide. Application columns such as status, featured or publication date must exist before you configure filters. No generated Property fields or schema are changed automatically.

Selections contain at most 50 explicit IDs, carry across pagination, and clear on search/sort/filter changes or page navigation. There is no select-all-matching workflow. Every record must pass current scope, permission, policy and stale-row checks; one failure rolls back all same-connection writes. Bulk callbacks own one transaction, with commit-only notification delivery and operation consumption. Do not dispatch inside an outer transaction or manually commit from a callback. External side effects require an outbox. Custom helpers do not run full resource save hooks automatically.

Action form callbacks receive a null record. Bulk actions always require review, and the existing 7,000-byte modal draft limit remains. Use ?static=1 for larger bulk forms or without JavaScript. Oversized drafts do not transfer automatically; copy unsaved text first. Filter state is a bounded JSON scalar in Reactive URLs, with equivalent standard form query parameters. Invalid filter values show errors and are not applied. Existing list/bookmark URLs remain usable.

Enable the optional login theme

Existing config/admin.php files are preserved by the installer. Add:

'login_theme' => true,

Skeleton 1.8.3 adds a presentation hook to views/users/admin_login.html. For an existing 1.8.2 application, prepend this to that view (leave the existing markup below it):

<?php
$loginRenderer = \FloCMS\Core\Config::get('auth.login_view');
if (is_callable($loginRenderer)) {
    $loginMarkup = $loginRenderer();
    if (is_string($loginMarkup) && $loginMarkup !== '') {
        echo $loginMarkup;
        return;
    }
}
?>

Then clear compiled views. admin:doctor warns when the enabled theme is missing this hook. Updating Composer alone cannot update skeleton application views. For language metadata without JavaScript, also copy the skeleton 1.8.3 admin layout's dynamic lang and dir attributes if your layout still hardcodes English. Your authentication controller, CSRF checks, login throttling and session logic remain the same.

New admin:install configurations enable the theme, but the view hook is still needed on older skeletons. Set 'login_theme' => false to keep your own design. An application-provided auth.login_view callable takes precedence; return null/empty to use the skeleton view. Without Admin/config, the skeleton view remains usable, with no Admin or Reactive dependency added to the skeleton.


Upgrading to Admin 0.4.1

Update Admin with Composer after release and deploy its supplied admin-v0.4.1.js and admin-v0.4.1.css. Reload already-open Admin pages to load the updated client. No database migration, application resource change, or Core/Reactive dependency upgrade is required.

Create/Edit and custom action forms now show the same default-language input and language badge as full-page CRUD forms. Clicking the badge opens that field's translation editor. Save accepts the translations; Cancel/Escape restores the values from when it opened and returns focus to the badge without closing the resource editor. Secondary-language validation opens the affected translation dialog. The legacy sixth FormRenderer constructor argument remains accepted for compatibility but no longer selects expanded language inputs.

The normal "Open standard view" link is removed. Reactive dialogs and navigation remain the default. Existing full-page CRUD/action URLs, ?static=1, and 'reactive' => false still work, including with JavaScript disabled. The 7,000-byte modal draft limit is unchanged: an oversized form offers Continue on full page. Copy unsaved text before using that link; it does not transfer the draft. With JavaScript disabled, the resource list exposes the same fallback link through <noscript>.

Standalone FloCMS and its login page remain unchanged. This patch does not start the 0.5.0 filters/bulk-actions milestone.


Upgrading to Admin 0.4.0

Update Admin with Composer after release. Core ^2.3.2, Reactive ^0.3.0, PHP 8.1+ and the existing secret APP_KEY requirements remain unchanged. No database migration or application resource rewrite is required. Deploy the supplied admin-v0.4.0.js and admin-v0.4.0.css; filenames keep the full release version and Composer distributions include compiled assets. Core and the skeleton remain independent of optional Admin/Reactive packages.

Action definitions and compatibility

Existing resources receive Create, Edit and Delete definitions by default. Add custom actions through Resource::headerActions(), Table::headerActions() and Table::actions(). These definitions render in Reactive mode and the standard view. An overridden collection replaces the defaults, so include the built-in definitions when retaining those workflows. Removing the default Create/Edit/Delete definition also removes its standard CRUD route. Customized built-in names use the generic action route.

Action visible() and authorize() callbacks are server guards, not just presentation hints. A hidden or disallowed action cannot be invoked through a crafted request. Custom actions default to Resource::permission($name); apply current record policies explicitly with authorize(). Keep tenant/record scope in Resource::query(). Built-in actions retain the existing resource policies, save service and hooks. Application controllers still take precedence.

Custom row, table and page actions use independent forms. Only declared writable fields reach a handler. Custom updates do not automatically run full-form beforeSave()/afterSave() hooks; invoke application business services explicitly where appropriate. Standard action forms have their own CSRF-protected, session-bound operation token and can be used when Reactive is disabled. Existing standard CRUD URLs retain their previous save semantics, plus guards from their default definitions.

Transactions, stale pages and notifications

Action dispatch owns its request transaction. Do not wrap the dispatcher/App action request inside an already-open database transaction; it is rejected before callback execution. Use same-connection nested transactions inside an action callback when needed. This ensures successful notification delivery and operation consumption follow the actual action commit. Existing FormSubmission savepoint semantics remain intact.

Complete-row fingerprints and locks reject stale/deleted/out-of-scope row actions before their callbacks run. ActionContext::update() and delete() provide guarded mutation helpers. Locks do not fire extra UPDATE triggers. Transactions cannot undo external deliveries, nontransactional tables or work on other connections. Existing hook/outbox guidance below still applies.

Notifications sent inside action execution are staged and discarded on failure. Success/info announce politely; warning/danger announce as alerts. Messages persist through panel navigation until dismissed, with the latest 10 retained; they are not a durable inbox. Core flash messages map to the same presentation. Custom CSS selectors targeting the old flash banner or per-component success banner should migrate to .fa-notification / [data-admin-notification]; stable delivery containers must not be removed by application scripts.

Reload pages left open across deployment: the component schema and operation store now include action context, so old 0.3 snapshots are rejected with 409. Renewing the CSRF session or changing the signed-in actor invalidates outstanding action operations. Operation expiry remains 30 minutes/32 outstanding operations per session. The 7,000-byte modal draft limit remains; standard action pages support larger forms and still navigate asynchronously. Active draft values are retained on validation/conflict errors, with password values scrubbed from responses. Requests do not automatically retry writes.

'reactive' => false still restores standard forms/navigation. No nested state, upload API, bulk selection, table filters or general Widgets are introduced here. See the Actions and Notifications guide for the public API, working example and test commands.


Upgrading to Admin 0.3.0

After release, update hostkurd/flocms-admin with Composer and its dependencies. Admin requires Core ^2.3.2 and Reactive ^0.3.0. No database migration is required. The dependency direction is Admin → Reactive → Core; standalone Core and the skeleton remain independent.

Deployment and configuration

  • Configure a stable, secret APP_KEY of at least 32 decoded bytes, or Config::set('reactive.key', ...). Reactive also accepts base64: keys. All application workers must use the same key. Rotating it invalidates open component snapshots.
  • Keep the skeleton's current auth.user_loader configured. Reactive calls Core's Auth::refresh() on each update; its refresh interval determines when account/role changes are reloaded. Resource permissions, scoped queries, canCreate(), canEdit() and canDelete() run again during actions.
  • Allow the normal language-prefixed /flo-reactive/update, /flo-reactive/asset/... and /flo-admin/asset/... routes through your existing front controller. Preserve application controller precedence and do not create controllers that shadow these package routes.
  • Deploy the supplied dist/admin-v0.3.0.js and dist/admin-v0.3.0.css through Composer. Filenames retain the complete release version; immutable caching applies only to these assets. The package supplies external asset tags; no Node process or build is needed on cPanel.
  • The built-in UI works with script-src 'self' and style-src 'self', without inline scripts or unsafe evaluation. Custom field renderers and the application's layout remain responsible for their own CSP compliance.

config/admin.php defaults to 'reactive' => true. Existing resource definitions need no new base class. Set 'reactive' => false to restore full-page forms/navigation; this does not uninstall the Composer dependency. Removing Admin still leaves Core and the site's own controllers usable.

Interaction and form limits

Resource search/sort/page state is namespaced in URLs (admin-resource_q, _page, _sort, _dir). Bookmarks and back/forward restore lists by fetching current server state. Old q/page/sort/dir links remain supported by the standard view (?static=1). Panel navigation covers built-in dashboard, resources, accounts and profile, including standard form submissions. Logout/login, external links and application-owned controllers retain normal navigation.

Modal drafts encode declared form fields in one bounded JSON scalar, because Reactive 0.3 supports scalar state. The modal limit is 7,000 UTF-8 bytes, including field names and translations. Use Open standard view before editing larger records; standard form pages also submit without reloading while Reactive navigation is enabled. If a draft grows past the limit, it stays in the dialog for copying, but cannot be saved there. Standard pages remain usable with JavaScript disabled. This release does not add nested components, dependent-field RPCs, uploads or stacked dialogs.

Dialog operations expire after 30 minutes, with up to 32 outstanding operations per session. Save/delete consume the operation after a successful transaction; replaying it is rejected. Cancellation discards the server draft and stays locally available after permission, CSRF or network failures. It cannot undo a save already sent. Requests are not automatically retried. After a lost save response, cancel/reopen to check the persisted result before trying again.

Concurrent edits and hooks

Modal edit/delete snapshots seal a fingerprint of the complete loaded row. The action reloads the record through Resource::query(), checks current record policy, then conditionally writes only if all original columns still match. A deleted, changed or newly out-of-scope record cannot be overwritten. Case-only changes are detected even under case-insensitive database collations. No new version column is needed. This guard applies to modal actions; standard pages retain their existing save semantics.

Keep row/tenant scope in Resource::query() and record rules in canEdit() / canDelete(). Snapshot state is signed, not encrypted: do not place secrets in custom component state. Password controls are blank after responses and password drafts never enter returned snapshots.

Normalization, field/resource validation, dehydration and hooks are shared with standard forms. Database writes from save hooks on the same connection roll back together. afterSave() is still before commit. A beforeSave() hook must return changes for the active record rather than writing that same row itself, which would invalidate the optimistic guard. Hooks must not commit manually. External side effects and other connections cannot be rolled back; use an outbox for external deliveries.

Read the working integration guide for development commands and a runnable resource example. The 0.2.1 upgrade details below still apply, including the limits of validation-only uniqueness.


Upgrading to Admin 0.2.1

Update hostkurd/flocms-admin with Composer after the release. This patch is tested with released Core 2.3.2; Core's existing ^2.3 constraint remains unchanged. No database migration, new runtime dependency or application asset build is required. Admin remains optional; Core and the skeleton do not require Admin or Reactive. Reactive integration is deferred to 0.3.0. Existing assets are unchanged; full release-version filenames remain the convention for compiled assets.

Normalize before validating

The save sequence is field input extraction → field prepare() → resource normalizeState() → skip rules → field validation → resource validate() → transactional dehydration/hooks/write.

Override normalizeState(array $state, ?object $record): array to derive or normalize user-editable values. Return the complete state, using strings or language-to-string arrays. Do not write to the database, deliver messages, or hash passwords here. Normalized values pass all field rules and uniqueness checks before saving. Keep trusted storage-only metadata in beforeSave(); changes to editable values in that late hook are not automatically revalidated.

Accounts and profiles now derive blank login from email before uniqueness validation. An unchanged login excludes the current account correctly. Blank passwords on edit retain the stored hash; new passwords are validated before hashing and never echoed back after an error. Profile email/password changes still require the current password.

Existing duplicate logins

This fix prevents the blank-login validation bypass; it does not repair existing data or add a unique index. On a backup or through a read-only connection, audit the skeleton users table:

SELECT login, COUNT(*) AS occurrences
FROM users
GROUP BY login
HAVING COUNT(*) > 1;

Review each group and assign appropriate distinct logins with the account owners. Do not automatically delete or merge accounts. If database-enforced uniqueness is required, first resolve duplicates and review login collation, blank values, column size and existing application write paths before planning a unique-index migration. Validation alone cannot guarantee uniqueness under concurrent writes; this limitation also applies to translatable fields.

Translatable uniqueness and preservation

->translatable()->unique() checks each nonempty submitted translation against the same language on other rows. Equality is exact and case-sensitive on SQLite and MySQL/MariaDB; equal text in different languages is allowed. Legacy plain-text columns count only as the default language. Empty optional translations do not trigger uniqueness checks. The current record is excluded on edit.

FormValidator uniqueness callbacks may accept a third ?string $language argument (null for ordinary fields). Existing two-argument callbacks continue working in PHP, but custom callbacks must use the language argument to implement translated uniqueness. RecordRepository::exists() accepts the optional fourth language argument. Custom translation stores supply the per-language scalar SQL expression through the existing sortExpression() contract; values remain parameter-bound.

Saving a translation field retains stored valid language keys that are no longer configured. Posted unconfigured keys cannot replace these retained values. The form remains a full submission for configured languages: blank or omitted configured translations are cleared. Removing a language from configuration is no longer a way to delete its stored content; use an explicit application migration if deletion is intended.

Save hooks and transactions

Resource dehydration, beforeSave(), the insert/update, and afterSave() now run in one transaction on the resource's Core database connection. afterSave() means after the write, before commit. Exceptions propagate and roll back the record and transactional database work performed by either hook on that same connection. An outer Core transaction is respected through savepoints; a resource save does not commit the caller's transaction.

Use transactional tables (for example InnoDB). DDL, nontransactional tables, separate database connections, filesystem changes, emails and HTTP deliveries are not undone by database rollback. Hooks must not manually commit the connection. Put external delivery requests in a transactional outbox and process them after commit; do not treat afterSave() as an after-commit notification hook.

Read-only fields, including Placeholder callbacks, now receive the original record again after failed edit validation. Editable controls retain the submitted draft.

Test databases

vendor/bin/phpunit runs SQLite coverage without database configuration; MySQL-specific tests skip cleanly. Set FLO_ADMIN_TEST_DB_HOST, FLO_ADMIN_TEST_DB_PORT, FLO_ADMIN_TEST_DB_NAME, FLO_ADMIN_TEST_DB_USER, and FLO_ADMIN_TEST_DB_PASS to run MySQL/MariaDB repository and full request tests. Use a dedicated disposable database: fixtures drop/recreate posts, tags and users. CI exercises PHP 8.1–8.4 with MySQL 8.0 and PHP 8.3 with MariaDB 10.6/11.4.

Esc