Version 4.1 of October CMS contains feature improvements to the platform, including better Twig support in the CMS.
If updating via the administrative area, you may need to click the Update button multiple times for the process to complete. This is due to changes in the AJAX protocol introduced in this release.
Table of Contents
- Larajax: AJAX Framework 2.0
- New Cache Twig Tag
- Improved Resizer Implementation
- Themes Seeding Media Files
- Dashboard Data Source Improvements
- Notable Minor Changes
Larajax: AJAX Framework 2.0
October CMS v4.1 introduces Larajax, a rewritten and rebranded version of the AJAX framework that has been extracted into a standalone Laravel package. This evolution brings a cleaner architecture, improved performance, and better developer experience while maintaining full backwards compatibility with the previous version.
The framework has been restructured and now supports native JavaScript promises, replacing the jQuery-style promises from before.
const response = await oc.ajax('onSubscribe');
On the backend, a new fluent response builder is available. The ajax() helper returns an AjaxResponse instance with chainable methods like data(), update(), redirect(), error(), and flash().
public function onSubscribe()
{
return ajax()
->data(['success' => true])
->update(['#subscription-status' => 'Complete!'])
->flash('success', 'Subscribed successfully!');
}
The asset loading methods js(), css(), and img() now support custom HTML attributes, enabling ES modules and other script configurations.
return ajax()->js('/assets/app.js', ['type' => 'module']);
- Read the Larajax Framework documentation to learn more.
New Cache Twig Tag
A new {% cache %} Twig tag has been included to cache the output of view content within CMS themes.
{% cache "blog_post;v1;" ~ post.id ~ ";" ~ post.updated_at %}
<!-- Expensive Blog Post Contents Here -->
{% endcache %}
This allows you to use a cheap cache key lookup (e.g. updated timestamp on a model) to cache more expensive contents inside.
- Read the Cache Twig Tag documentation to learn more.
Improved Resizer Implementation
The Resizer implementation used to handle resizing images has been reworked to use the Intervention Image v3 composer package, bringing modern image processing capabilities and improved performance.
The crop resize mode now supports an offset option to specify exact crop coordinates. When an offset is provided, the image will remain at its original size and crop from the specified position. Without an offset, crop continues to behave as before - resizing and cropping to fill the specified dimensions.
A new cover mode (with fit as an alias) has been introduced that explicitly implements this resize-and-crop strategy.
A new force option has been added that performs the resize immediately instead of deferring it to a redirect. This is useful for meta tags (OpenGraph, Twitter Cards) where social media bots may cache the deferred URL before it's processed.
<meta property="og:image" content="{{ page.image|media|resize(1200, 630, { force: true }) }}">
Without the force option, the resizer returns a /resize/... URL that redirects to the final image when accessed. With force: true, the image is processed during page render and the final URL is returned directly.
Themes Seeding Media Files
Themes can now seed media files to the media library using the Media\Models\MediaLibraryItemImport model.
-
name: Media File Data
class: Media\Models\MediaLibraryItemImport
file: seeds/data/media-files.json
attributes:
file_format: json
The data file contains files and folder references that should be imported to the media library when the theme is seeded. Existing files are skipped and will not be overwritten.
[
{
"type": "folder",
"path": "my-theme/announcements",
"source": "seeds/media/announcements"
},
{
"type": "file",
"path": "my-theme/banner.png",
"source": "seeds/media/banner.png"
}
]
- Read the Seeding Themes documentation to learn more.
Dashboard Data Source Improvements
A new ReportQueryBuilder class has been introduced that provides a fluent interface for building dashboard report queries. This replaces the previous ReportDataQueryBuilder which required up to 15 constructor parameters.
use Dashboard\Classes\ReportQueryBuilder;
return ReportQueryBuilder::table('analytics_data')
->dimension($dimension)
->metrics($metrics)
->dateColumn('recorded_at')
->dateRange($startDate, $endDate)
->groupByMonth()
->orderByDimension()
->get();
The builder includes semantic methods for date grouping (groupByDay(), groupByWeek(), groupByMonth(), groupByQuarter(), groupByYear()) and a fromFetchData() factory method for easy migration from existing data sources:
return ReportQueryBuilder::fromFetchData($data, 'my_table')
->dateColumn('created_at')
->get();
The previous ReportDataQueryBuilder class remains available for backwards compatibility but is now deprecated.
Notable Minor Changes
New Peer Management Configuration
A new configuration item has been added that allows peer management for backend users. By default, administrators can only manage users that have a role below them. This new feature allows them to manage other administrators at the same level in addition.
To enable user peer management, set the user_peer_management in config/backend.php to true..
'user_peer_management' => true
Keep in mind, this allows administrators to demote permissions only at the same level, yet also they can create permissions at the same level.
Multisite CMS Statistics
The internal traffic statistics used by the CMS has support for multisite. This means you can capture individual statistics for each site definition. The feature is not enabled by default and can be enabled with the features.dashboard_traffic_statistics configuration found in the config/multisite.php file.
'features' => [
'dashboard_traffic_statistics' => true,
],
If you don't have the multisite configuration file, you can create it using this source file found on GitHub.
Postback Handlers Removed in Backend
For simplicity and improved security, "postback" AJAX handlers are removed from the backend module. Postback AJAX handlers are still available in the CMS module. This mostly obscure feature allowed backend AJAX handlers to be requested by submitting a _handler postback value and responded with a traditional page load.
For this upgrade, check to see if the following appears in your backend controllers.
<?= Form::open(['handler' => 'onDoSomething']) ?>
The above code can be replaced by the following true AJAX equivalent. Alternatively, the controller action logic can handle it explicitly.
<?= Form::ajax(['handler' => 'onDoSomething']) ?>
Since the backend environment always has the AJAX library available, postback handlers fall outside the AJAX framework specification. They were originally included to be functionally equivalent with the CMS controller, which introduces it as an extension of the specification.
Tooltip Support for Form Fields
Form fields now support tooltips, matching the existing functionality available for list columns. An informational icon appears next to the field label, displaying a tooltip on hover.
status:
label: Status
type: dropdown
tooltip: Select the current status of this item
Advanced configuration supports HTML content, custom placement, and icons:
status:
label: Status
type: dropdown
tooltip:
title: "<b>Draft</b> = work in progress<br><b>Published</b> = visible"
isHtml: true
placement: bottom
icon: icon-question-circle
List columns also now support the isHtml option for HTML content in tooltips.
Turbo Router Activation via Meta Tag
The turbo router is now activated using a meta tag instead of being enabled by default. To enable turbo navigation on your pages, add the following meta tag to your layout's head section:
<meta name="turbo-visit-control" content="enable" />
Without this meta tag, page navigation will perform full page refreshes instead of turbo-powered transitions.
MediaFinder and FileUpload JavaScript API Changes
The MediaFinder and FileUpload form widgets have been migrated to the new Hot Controls pattern. If you are accessing these control instances via JavaScript, the API has changed.
// Old way (no longer works)
$('.element').mediaFinder('getValue');
$('.element').data('oc.mediaFinder').someMethod();
$('.element').data('oc.fileUpload').someMethod();
// New way
oc.observeControl(element, 'mediafinder').getValue();
oc.fetchControl(element, 'mediafinder').someMethod();
oc.fetchControl(element, 'fileupload').someMethod();
The control functionality remains the same, only the method of accessing the instance has changed.
This is the end of the document, you may read the announcement blog post or visit the changelog for more information.