Version 5 supported

5.3.0 (unreleased)

Overview

Security considerations

Three security fixes that were previously released in the July security release are mentioned in the Silverstripe CMS security patches July 2024 blog post are listed below.

Review the individual vulnerability disclosure for more detailed descriptions of each security fix. We highly encourage upgrading your project to include the latest security patches.

We have provided a severity rating of the vulnerabilities below based on the CVSS score. Note that the impact of each vulnerability could vary based on the specifics of each project. You can read the severity rating definitions in the Silverstripe CMS release process.

Features and enhancements

Changes to TinyMCEConfig

In order to facilitate fixing a bug related to the sanitisation of HTML content via the HTMLEditorSanitiser class, some changes had to be made to the TinyMCEConfig class. Those changes are as follows:

  • If valid_elements and extended_valid_elements are both empty, all HTML elements will be stripped out of the HTML content.
  • A default set of valid_elements has been defined for all TinyMCEConfig instances. If you use custom TinyMCEConfig definitions and have not explicitly set the valid_elements option, you may have more elements permitted than you were expecting.
  • There is a new TinyMCEConfig.default_options configuration property which allows you to define the default options for all TinyMCEConfig instances.

If you use custom TinyMCEConfig definitions, we strongly recommend double checking if they include a definition of valid_elements, and if they don't, validate whether the default set defined in TinyMCEConfig.default_options is suitable for you.

You can either change the TinyMCEConfig.default_options configuration value to affect the options for all TinyMCEConfig definitions, or explicitly define valid_elements for your specific configuration instances. See setting options for more details.

See sanitisation of HTML for more information about the bug that was fixed.

High-level API for converting files

There is now a high-level API for converting files from one format to another. This builds on top of the low-level API which was added in 5.2.0.

Files can be converted both in PHP code and in templates:

// Convert an image to webp format and apply the FitMax manipulation to the result
$this->MyImage()->Convert('webp')->FitMax(100, 100);
<%-- Convert an image to webp format and apply the FitMax manipulation to the result --%>
$MyImage.Convert('webp').FitMax(100, 100)

Out of the box silverstripe/assets provides the new InterventionImageFileConverter class which converts images to other image formats using Intervention Image. You can add your own FileConverter implementations in projects or modules for any file convertions you might need.

See convert a file to a different format for more details.

Improve customisability of rendered images

We've aligned how images added to an HTMLEditorField and images invoked in templates are rendered. The markup up for both <img> tags are now generated from the same template file. This makes it easier to customise how images are rendered on your site.

The template for this is templates/SilverStripe/Assets/Storage/DBFile_Image.ss. Add a file with that path to your Silverstripe CMS theme if you want to override it.

This opens many interesting possibilities. For example, this code snippet will provide a WebP version of any image rendered on your site.

<picture>
    <source srcset="$Convert('webp').Link" type="image/webp">
    <img $AttributesHTML >
</picture>

You can also choose to have different rendering logic for HTMLEditorField images and for images invoked in templates by overriding different templates.

  • Add a SilverStripe/Assets/Shortcodes/ImageShortcodeProvider_Image.ss to your theme to control images added to an HTMLEditorField.
  • Add a DBFile_Image.ss file to the root of your theme to control only images invoked in templates.

Validation for inline-editable elemental blocks

Elemental content blocks now support validation when saving or publishing individual content blocks using the "three-dot" context menu in the top-right of the block.

Validation can be added to a content block using standard Model Validation and Constraints by implementing a DataObject::validate() method on a content block, or by using Form Validation where there are several options available.

Elemental data is no longer sent when saving the parent DataObject (usually a Page) that contains an ElementalAreaField. Instead, when saving the parent DataObject, all the child inline-editable elements that have unsaved changes are inline saved at the same time. This change was done to consolidate the saving process down to a single code path. The code that was previously used to process any element data sent with the parent data has been removed.

FormField scaffolding for DataObject models

We've made a few improvements to how form fields are scaffolded for DataObject::getCMSFields().

Define scaffolded form fields for relations

Most DataObject classes will rely on some amount of automatic scaffolding of form fields in their getCMSFields() implementations. However, it's common for modules to provide a specialised form field which is intended to always be used with a specific DataObject class. In those cases, even though you always want to use that form field with that class, you have to copy some boilerplate code from the module's documentation to set it up.

You can now define what form fields should be used when scaffolding form fields for has_one, has_many, and many_many relations. This is defined on the class on the child-side of the relationship. For example, for the below has_one relation you would implement scaffoldFormFieldForHasOne() on the MyChild class.

namespace App\Model;

use SilverStripe\ORM\DataObject;

class MyParent extends DataObject
{
    // ...
    private static array $has_one = [
        'MyChild' => MyChild::class,
    ];
}
namespace App\Model;

use SilverStripe\Forms\FormField;
use SilverStripe\ORM\DataObject;

class MyChild extends DataObject
{
    // ...

    public function scaffoldFormFieldForHasOne(
        string $fieldName,
        ?string $fieldTitle,
        string $relationName,
        DataObject $ownerRecord
    ): FormField {
        return /* instantiate some FormField here */;
    }
}

This means modules can pre-define the form field that should be used for their custom models, which reduces the amount of boilerplate code developers need to include in their getCMSFields() implementations.

For more information see scaffolding for relations.

FormScaffolder options

The FormScaffolder class (which is responsible for scaffolding form fields for getCMSFields()) has some new options:

optiondescription
mainTabOnlyOnly set up the "Root.Main" tab, but skip scaffolding actual form fields or relation tabs. If tabbed is false, the FieldList will be empty.
ignoreFieldsDeny list of field names. If populated, database fields and fields representing has_one relations which are in this array won't be scaffolded.
ignoreRelationsDeny list of field names. If populated, form fields representing has_many and many_many relations which are in this array won't be scaffolded.
restrictRelationsAllow list of field names. If populated, form fields representing has_many and many_many relations not in this array won't be scaffolded.

In particular, the ignoreFields and ignoreRelations options are useful ways to prevent form fields from being scaffolded at all. You can use this instead of calling $fields->removeFieldByName(), for example if you are adding database fields that you don't want to be edited with form fields in the CMS.

These options are now also configurable using the new DataObject.scaffold_cms_fields_settings configuration property.

See the scaffolding section for more details.

Support for JOIN in SQL UPDATE

The SQLUpdate class now supports all of the same JOIN operations (using the same methods) that SQLSelect does.

This is particularly helpful if you need to update columns in one table to match values from another table.

Autologin token regeneration changes

The Autologin ('remember me') feature stores cookies in the user's browser to allow recreation of their session when it expires. Currently, one of the cookies is regenerated whenever a user's session is recreated. This can cause unexpected logouts in certain situations, and has minimal value from a security standpoint.

In 5.3, the current behaviour is retained, but can be disabled via configuration:

SilverStripe\Security\RememberLoginHash:
  replace_token_during_session_renewal: false

This will cause the token to be generated once during login, and not be regenerated during session renewal.

From 6.0 onwards, tokens will never be regenerated during session renewal, and this configuration will be removed.

Other new features

  • silverstripe/linkfield now has improved accessibility support for screen readers and keyboard navigation. Focus states have also been made consistent between keyboard and mouse interaction.
  • silverstripe/graphql-devtools contains a new GraphQLSchemaInitTask to help you initialise a basic GraphQL schema.
  • GridFieldDetailForm_ItemRequest now uses a PjaxResponseNegotiator to handle PJAX responses for the save and publish actions. This aligns it with responses from other form submissions in the CMS.
  • Primitive types inside an iterable object will now be automatically converted to relevant DBField types so can be rendered in a template. This allows the use of return ArrayList::create(['lorem', 123]); from a ContentController method that is then looped over in a template.
  • A new TabSet::changeTabOrder() method has been added that allows you to change the order of tabs in a TabSet. See tabbed forms for details.

API changes

  • Passing a non-array $fields argument to both FieldList::addFieldsToTab() and FieldList::removeFieldsFromTab() has been deprecated.
  • The BaseElement::getDescription() method has been deprecated. To update or get the CMS description of elemental blocks, use the description configuration property and the localisation API.
  • The RememberLoginHash::renew() method has been deprecated without replacement, since the associated behaviour will be removed in 6.0.

    • The onAfterRenewToken extension point within this method will likely be replaced with a new extension point in 6.0.
  • The RememberLoginHash.replace_token_during_session_renewal configuration property has been added to allow disabling token regeneration during session renewal. This property will be removed in 6.0.
  • Code for the CMS GraphQL admin schema which provided endpoints for the CMS has been deprecated and will be removed in CMS 6. Functionality which the GraphQL endpoints are currently responsible for will be replaced with regular Silverstripe controller endpoints instead. Extension hooks will be added to the new controller endpoints that return data to allow for customisation. Frontend schemas, such the default schema, will continue to work in CMS 6.
  • IPUtils has been deprecated and its usage has been replaced with the IPUtils class from symfony/http-foundation which is now included as a composer dependency in silverstripe/framework.
  • Code in silverstripe/blog which supported integration with silverstripe/widgets has been deprecated and will be removed in CMS 6.0.
  • DataExtension, SiteTreeExtension, and LeftAndMainExtension have been deprecated and will be removed in CMS 6.0. If you subclass any of these classes, you should now subclass Extension directly instead.

Bug fixes

  • If you use <% loop %> in your templates without telling it what to loop, previously the behaviour was to fail silently. This will now loop over what is currently in scope. See looping over lists for more details.
  • Reordering elemental blocks used to update the published sort order immediately, without pressing the publish button. That wasn't consistent with the way any other sort order updates happen in the CMS. We've changed this so that the sort order won't update in your published site until you publish one of the blocks in the elemental area that the reordered blocks live in. This is consistent with what happens when you reorder pages in the site tree.

This release includes a number of bug fixes to improve a broad range of areas. Check the change logs for full details of these fixes split by module. Thank you to the community members that helped contribute these fixes as part of the release!

Sanitisation of HTML

When you save content in a HTMLEditorField, the HTMLEditorSanitiser class is responsible for ensuring the HTML content is safe and matches the valid_elements and extended_valid_elements options you've defined.

There was a bug that resulted in HTMLEditorSanitiser using the 'active' HTMLEditorConfig instance rather than the instance which was defined for the field. In many cases this goes unnoticed because the default active instance is very permissive, and TinyMCE does a lot of this work on the client-side, but it was possible to bypass the defined allowed HTML elements by sending requests directly to the server.

This bug has been fixed, but some additional changes were required to facilitate it. See changes to TinyMCEConfig for more details about those changes.

Restore batch action removed

The "Restore" batch action was removed from the batch actions dropdown menu in the CMS as it has not functioned correctly for a long time. There is no intention at this stage to reintroduce this feature.