Changelog
Unreleased
Resources and pages: a code-first admin layer (no changes needed in existing modules)
A second way to build admin screens, next to classic modules. It lives in the new CrudBooster\ namespace (core/), works with Eloquent models, and is defined in code instead of in the Module Generator. Guides: docs/en/panel.md, docs/en/panel-pages.md, docs/en/panel-extending.md.
- Resources (
CrudBooster\Resources\Resource): one class per model gives a list with search, filters, sorting, pagination and row and bulk actions, plus create, edit, detail and delete pages, validation, a sidebar link and audit log entries. Lifecycle hooks (beforeCreate(),afterUpdate(), ...) replacehook_*(). - Tables:
TextColumn,BadgeColumn,BooleanColumnandImageColumnwith relationship paths (category.name, eager loaded);SelectFilter,TernaryFilterandFilter;ActionandBulkAction. - Forms:
TextInput,Textarea,Select(options, relationships, many-to-many sync),Radio,CheckboxList,Toggle,DatePicker,FileUpload,Hidden,Placeholder,ViewFieldandSection. Validation rules come from the fields, and only the fields in the form are saved. - Pages (
CrudBooster\Pages\Page): custom screens in the admin layout with injecteddata()and an optionalsubmit(). - Permissions: a model policy if there is one, otherwise the existing privilege matrix.
php artisan crudbooster:sync-panellists resources and pages on Privileges > Edit. Replace the whole policy by bindingCrudBooster\Auth\Authorizer. - Extending: custom field, column and filter classes, plugins (
CrudBooster\Plugins\Plugin), render hooks and sidebar links through theCrudBooster\Facades\Panelfacade. - Commands:
crudbooster:make-resource(with--generateto read the table),crudbooster:make-page. - Classes in
app/Admin/Resourcesandapp/Admin/Pagesare discovered; see the newPANELconfig key. - The admin layout accepts a
page_headersection and thehead/body.endrender hooks; the sidebar lists panel links after the Menu Management menus. Classic module pages render as before. - Playground:
ProductsandReviewsresources and aStore Overviewpage, under "Catalog (resources)", on the same tables as the classic shop modules. - 29 new tests (
tests/Feature/PanelTest.php,PanelCommandsTest.php,tests/Unit/Panel).
v7.1.0
SOLID architecture (no changes needed in existing modules)
Implements docs/CRUDBOOSTER_SOLID_ARCHITECTURE.md. CBController and the CRUDBooster helper keep their public API but delegate to services bound in the container:
Contracts in
src/contractsfor authentication, permissions, audit log, schema, grid query, filters, forms, validation, uploads, exporters and actions. Bind your own implementation to replace any of them.CBControllerendpoints run action classes (src/actions).CBController.phpwent from 1,790 to about 440 lines.Form field types are handlers in a
FieldHandlerRegistry: register new types or replace built-in ones.Export formats come from an
ExporterRegistry. Registered formats appear in the export dialog.Optional fluent module API:
modern\CrudControllerwithCrudDefinition,ColumnandField.New guide: How To Extend CRUDBooster (
docs/en/how-to-extend.md).About 30 new tests, including characterization tests that pin down classic module behaviour.
Settings, sidebar menus, template email, notifications and the module/API code generators are services too (
SettingsRepositoryInterface,MenuRepositoryInterface,TemplateMailerInterface,NotificationSenderInterface,ModuleGeneratorInterface,ApiGeneratorInterface). The generators render from stubs, and a golden-file test pins the generated source byte for byte.CRUDBooster's own tables are read and written through the Eloquent models in
src/models, with named scopes for shared queries. New services:PrivilegeRolesInterface(role matrix and session roles) andModuleRegistrarInterface(a module's menus). Module data stays on the query builder. See Phase 8 indocs/CRUDBOOSTER_SOLID_ARCHITECTURE.md.Tests are split into
UnitandFeaturesuites (composer test:unit), and CI reports coverage (composer test:coveragelocally).Playground: a Field Showcase module with every form field type and lookup variant on one form, and a seeded record with every field filled in.
AdminLTE 4 admin theme
The admin UI moves from AdminLTE 2.3.8 (Bootstrap 3.4) to AdminLTE 4.10 (Bootstrap 5.3). Run php artisan crudbooster:update to publish the new assets. Modules, hooks and custom views keep working:
- New layout (
app-wrapper,app-header,app-sidebar,app-main,app-footer) inadmin_template,header,sidebarandfooter. Element ids and the old hook classes (content-wrapper,content-header,#content_section,main-header,main-sidebar,notifications-menu) stay on the new elements. - Each privilege's skin (
skin-blue,skin-red-light, ...) still colours the header and sidebar brand;-lightskins get the light sidebar. The theme preview on the privilege form no longer wipes the layout classes. - Bootstrap 3 / AdminLTE 2 compatibility layer for views written against the old markup:
assets/css/bs3-compat.css(box,panel,col-xs-*, offsets,form-horizontal,btn-default,btn-flat,btn-xs,btn-block,label-*,input-group-addon/-btn,pull-*,hidden-*,help-block,has-error,dropdown-menu > li > a,nav-tabs > li.active,well,media, glyphicons, AdminLTE 2bg-*/text-*colours) andassets/js/bs3-compat.js(data-toggle/data-target/data-dismissmirrored todata-bs-*,.into.show,$(el).modal()and the other jQuery plugin calls available from page load,.boxcollapse/remove). - Login, forgot password, lock screen and 404 pages, the statistic builder, the datamodal browser and the public API documentation use AdminLTE 4 too, through the new
partials.theme_stylesandpartials.theme_scripts. The public API page and the login pages no longer load Bootstrap or Font Awesome from CDNs. - The WYSIWYG field and the API generator use Summernote Lite (same editor and API, no Bootstrap 3 dependency).
- Grid and datamodal pagination render with Laravel's Bootstrap pagination view, whatever the app's default.
- The admin stays in light mode: AdminLTE 4 would otherwise follow the OS dark-mode setting.
- jQuery 2.2.3, Font Awesome 4.7, Ionicons and the date, time, select2, DataTables and lightbox plugins are unchanged, so stored
fa fa-*module and menu icons and custom scripts keep working. The AdminLTE 2 files stay inassets/adminltefor custom views that load its plugins.
Native AdminLTE 4 markup in every view
The remaining views no longer rely on the Bootstrap 3 compatibility layer (it stays for apps' own views):
boxandpanelwrappers are AdminLTE 4 cards (card,card-header,card-body,card-footer,card-tools,card-primary card-outline, ...): forms, detail, child rows, import, menu management, privileges, settings, module generator, API generator and documentation, and the statistic builder widgets.- Tables use Bootstrap 5 classes (
table-sm,table-active,table-info, ...), labels arebadge text-bg-*, help text isform-text, input add-ons areinput-group-text,pull-*isfloat-*, and modals usebtn-closeanddata-bs-*attributes. Nav tabs usenav-item/nav-link. - Sidebar: the brand shows the favicon setting, or the app name's first letter, next to the name (it stays when the sidebar collapses), and the user panel follows the sidebar's light or dark theme.
- The section-header toggle and the statistic builder's drag handles select the new card classes as well as the old ones, so custom forms built on
box-bodykeep working. - Fixed: on the detail page, every cell of a child table was bold; the menu management form squeezed its labels into a narrow column (it now stacks them, via the new
cb-form-stackedform class).
Data grid
The module index page (default/index, default/table) is rebuilt on Bootstrap 5 / AdminLTE 4 markup:
- One toolbar: bulk actions, search (press
/to focus it), the filter button with a count of active filters, and a "Show" page-size picker. It wraps on small screens. - Active search, filters and sorting appear as chips; each one removes itself, and "Clear all" removes them all.
- Selecting rows: click anywhere on a row, shift-click for a range, a live "N selected" count, an indeterminate "select all" box, and highlighted selected rows. Bulk actions stay disabled until a row is selected, and the confirmation says how many rows it applies to.
- Sort headers show the current direction (and set
aria-sort); the duplicate header row under the table is gone. - An empty grid says why and offers "Clear all" (when filtered) or "Add Data".
- Row counts and pagination sit in the card footer. The filter and export dialogs use Bootstrap 5 forms; the export dialog can tick or clear every column at once.
- Row action dropdowns (
button_action_style = 'dropdown') are no longer cut off by the scrolling table. - Every id, class, field name and hook the grid had is kept (
#table_dashboard,#form-table,.checkbox,.selected-action,#btn_advanced_filter,#advanced_filter_modal,#export-data,table_row_color,hook_row_index, ...). - Fixed: a column's
stylewas printed as the text "1" on the wrong cell (removed Bladeorsyntax, and the bulk and numbering cells were not counted); the filter dialog offered no comparison operators for SQLite/PostgreSQLINTEGER/numericcolumns; the export dialog's script was bound twice.
Security: front-end libraries updated
Libraries with published advisories are updated to the newest release that works with the bundled plugins:
- jQuery 2.2.3 → 3.7.1, with jQuery Migrate 3.6.0 (muted) for jQuery 1/2 APIs that older plugins and custom scripts still call. Not 4.0, which removes APIs the date and time pickers use.
- Moment 2.13.0 → 2.31.0 (ReDoS fixes), same path.
- DataTables 1.10.7 → 2.3.8 with Bootstrap 5 styling, in
assets/datatables. - Select2 4.0.2 → 4.1.0, Lightbox2 2.9.0 → 2.12.0, Summernote (classic build) 0.8.1 → 0.9.1, all at their old paths so custom pages that load them get the fixes too.
- Statistic builder: jQuery UI 1.11.4 and Raphaël 2.1.0 from CDNs → 1.14.2 and 2.3.0, bundled.
- Docs site: Vite is pinned to 6.4.5 (
overridesinpackage.json) because VitePress 1.6.4 asks for Vite 5, which has no patched release.npm auditreports no vulnerabilities.
Fixed (front end)
- The grid's bulk-action confirmation never opened: a line break inside a JavaScript string stopped the whole script, which also held the responsive-table toggle.
- The statistic builder's widget panel came back empty after dropping a widget.
Changed
- A
headerfield's'collapsed' => truenow starts its section folded andfalsekeeps it open; both were inverted. Sections without the option still start open. - Saving a record, its child rows and its many-to-many links is one database transaction: when one fails, nothing is kept.
hook_after_add/hook_after_editrun after the commit. - Header and sidebar typography: one font stack, a light header shadow, uppercase section labels, tighter menu items and ellipsis for long names. Colours still come from each privilege's skin. Run
php artisan crudbooster:updateto republish the CSS. - The navbar, user menu and sidebar panel show the admin's initials instead of the profile photo, in AdminLTE 2.3.8's avatar slots and sizes.
Fixed
Found with the playground's Field Showcase module:
- A
moneyfield with cents (decimalsorpriceformat_parameters.centsLimit) saved "$ 2,345.67" as 234567, and its detail view rounded to whole units. The field's own format is now used for both. - Child rows were read with inner joins on their select and datamodal lookups, so a row with an empty lookup was hidden on the edit form and deleted on the next save. Unqualified columns also failed with "ambiguous column" when a lookup table shared a column name.
- A child row with an empty optional column failed the whole save ("all VALUES must have the same number of terms").
- In a child field,
selectaccepteddataenumonly as an array andradioonly as a"a;b"string; both accept either now, like the top-level fields. - A
headerwithout anamewas rendered as a hidden input, and one with anamewas saved as a column. - The
jsonfield's editor never started (its script ran before jQuery loaded), and its detail view was empty. The detail view now prints the formatted JSON.
- A
A
childform field submitted without rows inserted an empty child row (failed on NOT NULL columns).The
googlemapsform component crashed with "Array to string conversion".The
str_limitgrid column option crashed (str_limit()was removed in Laravel 6; the helper is back).Security: the
hide_forminput is unserialized with objects disallowed, and the select2 lookup's search term is bound instead of interpolated into SQL.XLS and CSV export failed when a cell contained
&or<, including the "no image" placeholder of every empty image column. Values are now exported as escaped text.A bare
uniquevalidation rule now checks the module's table.A string
$orderbywith a qualified column ("table.column,desc") and thewhere[...]index parameter built wrong column names.The forgot-password email showed a literal
[password]instead of the new password.Generating a module for a table with an
is_*column crashed with "Array to string conversion".A third module on the same table overwrote
Admin{Name}1Controller; a module generated without a name becameAdmin1Controller.Sending email with an unknown template slug now throws a clear
InvalidArgumentException.On MySQL, table columns were read in no guaranteed order, so the Module Generator could shuffle grid columns and form fields.
Security: API key, API generator, statistic builder and setting-file endpoints that a standard admin could call now require a superadmin, like the rest of those tools.
A module created with the Module Manager's add form had no permissions and an invisible menu group.
Deleting a module without a controller deleted every menu; deleting a module left its empty menu group.
The notification bell listed read broadcast notifications, and any user's notification could be marked read.
Saving the default export paper size did not refresh the cached setting.
Security: queued email attachments are unserialized with objects disallowed, and the sidebar menu queries bind the privilege id instead of interpolating it into SQL.
Documentation site built with VitePress and deployed to GitHub Pages: https://vicheanath.github.io/crudbooster-contrib/
v7.0.2
- Fields submitted empty skip validation again, as in 5.x. 7.0.0 validated required-but-empty fields, which broke the common pattern of a hidden
requiredfield (e.g.site_id) filled byhook_before_add/edit, so those forms could not be saved. - The edit form now requires update permission. Read-only roles could open it before (saving was already refused).
v7.0.1
- Delete confirmation uses SweetAlert2's
Swal.fire()on pages that load it. SweetAlert2 also takes over the globalswal, so the legacy call silently never deleted. Extra options via the newSWEETALERT2_DELETE_OPTIONSconfig key. - UPGRADE.md: apps that copied the CRUDBooster helper class into
autoload.filesmust remove it. - Contributor setup: committed e-commerce playground wired to the repo, optional Docker MySQL + Mailpit,
composer test:mysql, CONTRIBUTING.md and issue/PR templates. - Forgot-password email had no subject, and every
crudbooster:updateadded another copy of its template.
v7.0.0
First release as vicheanath/crudbooster: the classic CRUDBooster 5.x line on current PHP and Laravel. See UPGRADE.md.
Platform
- PHP 8.2 – 8.5, Laravel 12 / 13 (Laravel 7/8 and 11 dropped).
- DomPDF wrapper 3.x, Intervention Image 3.x (imagecache removed), Laravel Excel 3.1.70+ / 4.x, doctrine/dbal removed.
- CI on PHP 8.2–8.5 × Laravel 12/13 and MySQL 8.4, with about 85 feature tests covering every core module.
Fixed for PHP 8 / modern Laravel
DB::raw()insideDB::select()(fatal on Laravel 10+): export query, statistic widgets,dataqueryoptions, column type/nullability lookups.- Statistic dashboards crashed: SQL widgets passed session arrays to
str_replace(). - The sidebar crashed when no dashboard menu existed.
count(null)when saving an API without parameters.- Undefined key/property notices that PHP 8 turned into exceptions (compatibility handler, can be switched off).
- Dynamic properties in generated controllers (PHP 8.2).
Restored features
- Data import (it used the removed Laravel Excel 2.x
Excel::load()). crudbooster:install(seeder class resolution) andcrudbooster:update(crashed without laravel-filemanager, prompted onvendor:publish).- Email: CRUDBooster SMTP settings are applied again; the
mailqueuescommand deletes sent emails from the right table. - Push notifications via FCM HTTP v1 (the legacy API was shut down).
- Form fields and detail rows: component lookup no longer depends on the package's vendor path.
- Uploads follow the configured
localdisk root (Laravel 11+storage/app/private).
Other fixes
- API: tokens starting with
B,e,aorrwere randomly rejected (403).ltrim()stripped those characters along with theBearerprefix. - API token lifetime now matches the advertised 48 hours.
- Redirect helpers no longer
exit(Octane/test friendly). - The seeder links the admin user to the real Super Administrator privilege id.