October CMS 3.0 - Upgrade Guide

Release Note 30

The following is an instructional guide on how to upgrade your 2.x October CMS installation to the latest version 3.0

October CMS Version 3.0 is a technology upgrade and introduces the Tailor module. The foundation framework has been upgraded to Laravel 9 and the Tailor module is a new feature that brings improved content management features.

If you are upgrading from October CMS v1.0, please follow the October CMS 2.0 Upgrade Guide first before proceeding with these instructions. If you are upgrading from October CMS 2.0, we will perform the upgrade using your existing installation. As always, we recommend taking a backup before performing this major upgrade.

Table of Contents

Upgrading to 3.0 from 2.x

PHP Minimum Version

Make sure you are using the new minimum PHP version 8.0.3.

Composer Dependencies

Update the following dependencies in your application's composer.json file:

  • october/all to ^3.0
  • october/rain to ^3.0
  • laravel/framework to ^9.0

If you are including individual modules, these can be updated to be the same as the october/all package.

Once this file has been updated, you my run composer update to perform the upgrade. In most cases everything should update smoothly.

Note: For any errors or support with upgrading packages, you may reach out on the Community Discord channel.

Vendor Upgrade Guides

Laravel has been upgraded from version 6 to version 9:

Twig has been upgraded from version 2 to version 3:

For information on the changes between PHP versions:


New Feature Overview

New App Directory

New installs of October CMS now ship with an app directory located in the project base path. This directory is used for storing Tailor blueprints, but it also behaves exactly like a plugin. The Provider.php file is identical to a Plugin.php service provider. You may create any directory in here, such as models or controllers. These will be autoloaded in the App namespace. For example, the file app/models/Customer.php will be available in PHP as App\Models\Customer.

Please note this is feature considered experimental for this release. If you encounter any issues, feel free to contact us using email support. Read more about the directory structure in the documentation.

New Documentation

The October CMS v3.x Documentation has received a significant update. The previous documentation was created many years ago and is starting to show its age and it was difficult to follow with larger articles. The new documentation has been restructured to clearly show that October CMS is a platform with two major aspects:

  • October the Content Management System
  • October the PHP Framework

Each aspect now has its own space to develop and grow. We hope you enjoy the new structure and are always open to feedback on how we can improve. Check the "Edit this Page" link at the bottom of every page.

New ES6 Browser Requirement

The October CMS backend panel now requires ECMAScript 2015 (ES6) support in the web browser. We believe that October CMS is a simple PHP platform that should not need a core dependency on Node.js or npm to function. Requiring native ES6 in the browser allows developers to work with modern JavaScript patterns without the need for a build step. However, in the usual fashion, it is still open to bringing your own technology when developing themes and plugins.

New Twig Functions

Some new Twig functions have been added to the CMS system. When these functions are combined, it creates a powerful system for building API endpoints in the theme.

The response() function will override the page output and replace it with a custom response.

{% do response({ foo: 'bar' }) %}

The ajaxHandler() function will execute an AJAX handler and return an object containing the response values, including errors and redirects.

{% set result = ajaxHandler('onSignin') %}

The collect() function returns a new collection object and is useful for building arrays within Twig.

{% set result = collect() %}

{% for post in posts %}
    {% do result.push({}) %}
{% endfor %}

The pager() function takes a paginated collection and prepares an object that contains a links and meta property.

{% set pager = pager(posts) %}

{% do response({
    data: posts,
    links: pager.links,
    meta: pager.meta
}) %}

See the API Resources documentation to learn more.

New List Filters

The list filtering implementation has been rewritten from the ground up to reduce the amount of technical debt and allow the developer to build custom filter scopes. As a result, the some of the native scopes use a different underlying implementation. The following filter scopes have been converted to filter widgets.

  • Backend\FilterWidgets\Text replaces text
  • Backend\FilterWidgets\Number replaces number and numberrange
  • Backend\FilterWidgets\Group replaces group
  • Backend\FilterWidgets\Date replaces date and daterange

A best effort has been made to retain backwards compatibility using a special adapter to convert the legacy definitions to the newer scope definintions. Standard implementations should be unaffected by the transition. View the following articles in the documentation for the latest advice.

New Backend View Extensions

All backend views have been updated to use the .php file extension, as opposed to the .htm file extension. The previous format created problems when using syntax formatters. Now you can map the following extensions in your code editor.

  • php - PHP syntax
  • htm - Twig syntax

Note: The backend will continue to allow both the .php and .htm extensions for the forseeable future.

New afterUpdate AJAX Option

A new option called afterUpdate has been added to the AJAX framework. This compliments the beforeUpdate option and should be used to fix the stutter that occurs with the success function.

$.request('onSomething', {
    success: function() {
        // Must call success again for partials to update (stutter)
        this.success().done(function() {
            /*...*/
        });
    })
});

The new afterUpdate option is the equivalent to the above code except with less code.

$.request('onSomething', {
    afterUpdate: function() {
        /*...*/
    })
});

New Localization Updates

Localization has changed to primarily focus on JSON-based language files. This is a superior approach for larger applications when it comes to interoperability and performance. Please observe the documentation that has been rewritten to use the new approach.

In summary, the following changes can be observed:

  • October CMS prefers JSON-based translations for larger applications, however, PHP-based translations are still supported.
  • JSON-based keys can be used to override PHP-based keys.
  • The lang directory for custom translation overrides has moved to app/lang
  • Theme JSON files are loaded only for the active theme in the backend and frontend contexts.
  • Plugin JSON files are only loaded in the backend panel. This improves performance of the frontend.

The location of the custom app/lang files has changed. If you use custom language files, please move them to the new location.

  • Old location: lang/en/author/plugin/lang.php
  • New location: app/lang/author/plugin/en/lang.php

The translator.beforeResolve event has been removed for performance reasons. The Lang::set method has been introduced to handle this instead.

Old Code
Event::listen('translator.beforeResolve', function($key, $replace, $locale) {
    if ($key === 'validation.custom.username.required') {
        return 'Say hello (Username field required)';
    }
});
New Code
Lang::set('validation.custom.username.required', 'Say hello (Username field required)');

New List Toolbar Attributes

The list toolbar has been improved to include two new attributes. This reduces code complexity and eliminates the need to use the onclick event to eval code.

  • data-list-checked-trigger will enable the button if a checkbox has been selected, otherwise it will be disabled.
  • data-list-checked-request includes the checkbox values when an AJAX request is used.

Here is an example on how to upgrade your code.

Old Code
<button
    class="btn btn-danger oc-icon-trash-o"
    disabled="disabled"
    onclick="$(this).data('request-data', {
        checked: $('.control-list').listWidget('getChecked')
    })"
    data-request="onDelete"
    data-trigger-action="enable"
    data-trigger=".control-list input[type=checkbox]"
    data-trigger-condition="checked"
    data-request-success="$(this).prop('disabled', true)"
    data-stripe-load-indicator>
    Delete Selected
</button>
New code
<button
    class="btn btn-danger oc-icon-trash-o"
    data-request="onDelete"
    data-list-checked-trigger
    data-list-checked-request
    data-stripe-load-indicator>
    Delete Selected
</button>

For more details see the List Controller article.


Breaking Change Overview

Twig Conditions In For Loop

This breakage relates to the upgraded version of Twig and is significant enough that we should mention it here. The Twig v3 changelog mentions the following.

* removed the "if" condition support on the "for" tag

This means the syntax used in previous versions that allows and "if" statement to occur alongside a "for" statement is no longer possible.

{% for VALUE in ARRAY if VALUE not empty %}
    {# ... #}
{% endfor %}

It should be replaced with another node inside the for loop. See the following example:

{% for VALUE in ARRAY %}
    {% if VALUE not empty %}
        {# ... #}
    {% endif %}
{% endfor %}

New Twig Safe Mode Policy

An allow-list (aka white-list) Twig security policy has been introduced that makes the safe mode feature very strict (cms.safe_mode) for the CMS. This applies when using the safe mode configuration (disabled by default) or when using the system Twig engine (mail templates).

To call a method on an object in Twig (using safe mode), an object must specify if it allows methods to be called using these interfaces.

  • October\Contracts\Twig\CallsMethods
  • October\Contracts\Twig\CallsAnyMethod

If you enable safe mode and see this error in your codebase:

  • SecurityNotAllowedMethodError: Calling any method on "..." is blocked in "..."

You can switch back to the block-list (aka black-list) security policy with the CMS_SECURITY_POLICY_V1 environment variable.

CMS_SECURITY_POLICY_V1=true

Note: The "V1" block-list security policy is deprecated but will remain available for the forseeable future.

AJAX and Page Action Logic

The Backend Controller page action method is now called before every AJAX request. Previously it would only be called for AJAX handlers registered by widgets (widget::onDoSomething). Now all handlers will run the page cycle for better overall consistency.

You may need to search your code for $this->pageAction() and remove it from your AJAX handlers. This will prevent the action logic from being called twice.

function onDoSomething()
{
    $this->pageAction();
}

Http Interface Replaced

In Laravel 9 a Http facade has been introduced that replaces the October CMS Http facade. The two are similar on the surface but different under the hood. While the Ocober\Rain\Network\Http interface was never officially documented, some developers may have discovered it and used it in their plugins.

If you are using the Http interface then you have two options. Either replace Http with a reference to Ocober\Rain\Network\Http, for example.

// Replace this
use Http;

// With this
use Ocober\Rain\Network\Http;

Or, you can migrate to the Laravel Http interface.

Old Code
$result = Http::post('https://octobercms.com');
echo $result->body;                    // Outputs: <html><head><title>...
echo $result->code;                    // Outputs: 200
echo $result->headers['Content-Type']; // Outputs: text/html; charset=UTF-8
New Code
$result = Http::post('https://octobercms.com');
echo $result->body();                  // Outputs: <html><head><title>...
echo $result->status();                // Outputs: 200
echo $result->header('Content-Type');  // Outputs: text/html; charset=UTF-8

The HTTP Client is now included in the October CMS documentation:

Custom Maker Has Been Removed

In older versions of Laravel, the IoC container (App::make) would allow unresolvable constructor values to be passed as anonymous arguments. In a later versions, this logic was changed to use named arguments instead. October CMS solved this by retaining support for both argument types. However, this logic has been removed in v3.0 to stay in closer alignment with the Laravel ecosystem. This means the name of the arguments in a constructor matters when you resolve a class through the application container.

Note: Low Impact - If this change does not make sense to you, then you will likely be unaffected by it.

Inside October CMS, this change only impacts the Cms\Classes\ComponentBase class. Take note of the constructor of this class. It contains two values that are not resolvable by the container - $cmsObject and $properties. If you are extending this class in your CMS components and overriding the constructor. The constructor must use the same argument names otherwise it will not resolve correctly.

namespace Cms\Classes;

abstract class ComponentBase
{
    public function __construct($cmsObject = null, $properties = [])
    {
    }
}

Below is the functionality that was removed:

// Ability to resolve a class using anonymous arguments
App::make(SomeClass:class, ['valueForCmsObject', 'valueForProperties']);

Below is the new Laravel approach and that October CMS uses in v3.0 onwards:

// Arguments must be named and names must stay intact everywhere
App::make(SomeClass:class, [
    'cmsObject' => 'valueForCmsObject',
    'properties' => 'valueForProperties'
]);

Decorated Event Dispatcher

The Laravel event dispatcher removed ordering priority to listeners back in Laravel 5.5 as a breaking change to the October CMS platform. This important functionality is now preserved in October CMS using a seperate container instance that improves compatibility with third-party packages.

$app['events']; // Laravel's event dispatcher
$app['events.priority'] // October's event dispatcher

The events.priority instance is resolved from the Event facade by default and will forward call methods to the events container.

// Laravel's listen method
public function listen($events, $listener = null)

// October's listen method
public function listen($events, $listener = null, $priority = 0)

The October CMS event dispatcher supports basic callback listeners and include a numerical priority to determine their load order. If you use prioritized event listeners, make sure they are called from the Event facade or the events.priority container instance. Otherwise, all functionality should remain the same.

Fillable Classes

The following classes now prefer the fillable attribute instead of the guarded attribute. This means you must use the addFillable method to extend the attributes that support mass assignment.

  • October\Rain\Auth\Models\Group
  • October\Rain\Auth\Models\Role
  • October\Rain\Auth\Models\User
  • Backend\Models\User
\Backend\Models\User::extend(function($model) {
    $model->addFillable('my_custom_column');
});

Sunset Classes

The following classes were deprecated in the previous version and are no longer available after having been removed.

  • October\Rain\Database\DataFeed
  • October\Rain\Database\Attach\BrokenImage
  • October\Rain\Database\Attach\Resizer
  • October\Rain\Database\Behaviors\Purgeable
  • October\Rain\Database\Behaviors\Sortable
  • October\Rain\Database\Traits\SoftDeleting

Thank You

We hope you enjoy this release. Thanks for being a customer of October CMS - The Laravel-Based CMS Engineered For Simplicity.

comments powered by Disqus