Skip to content

Latest commit

 

History

History
361 lines (237 loc) · 25.3 KB

5.2.0.md

File metadata and controls

361 lines (237 loc) · 25.3 KB
title
5.2.0 (unreleased)

5.2.0 (unreleased)

Overview

Security considerations {#security-considerations}

This release includes several security fixes. 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

New ORM features {#new-orm-features}

This release comes jampacked with new ORM features, granting you access to some new abstractions for more powerful and efficient queries.

Multi-relational has_one relations

Traditionally, if you wanted to have multiple has_many relations for the same class, you would have to include a separate has_one relation for each has_many relation.

This release includes a new has_one syntax to declare that your has_one should be allowed to handle multiple reciprocal has_many relations. The syntax for that is as follows:

[hint] Multi-relational has_one relations must be polymorphic. [/hint]

namespace App\Model;

use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DataObjectSchema;

class MyExample extends DataObject
{
    // ...

    private static array $has_one = [
        'MyMultiRelationalRelation' => [
            // The class here is the class for the has_one - it must be polymorphic.
            'class' => DataObject::class,
            // Setting this to true is what defines this has_one relation as multi-relational
            DataObjectSchema::HAS_ONE_MULTI_RELATIONAL => true,
        ],
    ];
}

Multiple has_many relations on a single class can point to the above has_one relation, and they will be correctly saved and resolved when you get the relation list.

[warning] This new feature means sometimes the value in the associative has_one configuration array will be an array, rather than a class name. If you are relying on fetching this configuration to find the class names of has_one relations, consider using DataObject::hasOne() or DataObjectSchema::hasOneComponent() instead. [/warning]

See multi-relational has_one in the relations docs for more details about this relation type.

UNION clause {#orm-union-clause}

Abstractions for the SQL UNION clause have been added to SQLSelect and DataQuery.

To add a UNION clause to an SQLSelect, call the SQLSelect::addUnion() method and pass in the SQLSelect query you want to combine using a union.

For the second argument, you can leave it blank for a default union (which is functionality equivalent to a distinct union in MySQL) - or you can pass in one of the SQLSelect::UNION_ALL or SQLSelect::UNION_DISTINCT constants for a UNION ALL or UNION DISTINCT clause respectively.

$baseQuery = SQLSelect::create()->setFrom($tableName)->addWhere(...);
$anotherQuery = SQLSelect::create()->setFrom($tableName)->addWhere(...);
$baseQuery->addUnion($anotherQuery, SQLSelect::UNION_DISTINCT);

To add a UNION clause to an DataQuery, call the DataQuery::union() method and pass in either another DataQuery or an SQLSelect query you want to combine using a union. The same constants used for SQLSelect can be passed in here as well.

$baseQuery = DataQuery::create(MyClass::class)->where(...);
$anotherQuery = DataQuery::create(MyClass::class)->where(...);
$baseQuery->union($anotherQuery, SQLSelect::UNION_ALL);

Common table expressions (CTE aka the WITH clause) {#orm-with-clause}

Abstractions for Common Table Expressions (aka the SQL WITH clause) have been added to SQLSelect and DataQuery.

Common Table Expressions are a powerful tool both for optimising complex queries, and for creating recursive queries. This functionality is abstracted in the SQLSelect::addWith() and DataQuery::with() methods.

Older database servers don't support this functionality, and the core implementation is only valid for MySQL, so if you are using this functionality in an open source module or a project that you can't guarantee the type and version of database being used, you should wrap the query in a condition checking if CTEs are supported. You can do that by calling DB::get_conn()->supportsCteQueries().

Check out the SQL Queries and Data Model and ORM documentation for usage details and examples.

RIGHT JOIN clause {#orm-right-join}

Abstractions for RIGHT JOIN have been added with the new DataList::rightJoin(), DataQuery::rightJoin(), and SQLConditionalExpression::addRightJoin() methods. The signature for these methods is identical to their LEFT JOIN and INNER JOIN counterparts.

Support for multiple (or no) tables in the FROM clause {#orm-from-clause}

Previously the SQLConditionalExpression abstraction couldn't handle multiple table names being passed into its FROM clause. This restriction has been removed, so you can now have queries selecting from multiple tables so long as your database supports it. If you were working around that limitation by adding an explicit comma to subsequent tables in the FROM clause for your queries, you'll need to remove the comma.

You can also now choose to not have a FROM clause in an SQLSelect query, which can be useful for setting up simple queries to be used in unit tests.

Better support for custom column selections in DataQuery {#orm-custom-columns}

When using DataQuery, it is possible to use collations and other raw SQL field statements as part of the query's SELECT clause. If these have an alias that matches the name of an existing database column, this results in an exception being thrown.

You can choose to allow those conflicts to be resolved via a CASE statement. In that scenario, if the value in the database column is null, the value for your custom field statement will be used. This is enabled per query by passing true to the new DataQuery::setAllowCollidingFieldStatements() method.

$query = new DataQuery(MyClass::class);
$query->selectField('"my custom title" AS "Title"');
$query->setAllowCollidingFieldStatements(true);

GridField components now work with arbitrary data {#gridfield-arbitrary-data}

It has historically been difficult to use a GridField to display data that isn't represented by DataObject records - and even more difficult to edit that data.

We have removed several barriers to using the GridField to display arbitrary data. Descriptive exceptions will be thrown when specific information cannot be dynamically identified, such as which columns to display and what form fields to use when viewing or editing data. Note that these new exceptions do not break backwards compatibility. Any scenario that will throw an exception now would have already done so - but the old exception would not have been sufficiently descriptive to quickly understand what changes are required to get a functioning GridField.

This applies to all GridFieldComponent classes in silverstripe/framework except for GridFieldAddExistingAutocompleter and GridFieldLevelup, which both explicitly require the model class for the associated GridField to be a subclass of DataObject.

See using GridField with arbitrary data for more information.

ErrorPage allowed codes configuration

By default, all available error codes are present in the dropdown in the CMS. This can be overwhelming and there are a few (looking at you, 418) that can be confusing. To that end, you can limit the codes in the dropdown with the config value allowed_error_codes like so:

SilverStripe\ErrorPage\ErrorPage:
  allowed_error_codes:
    - 400
    - 403
    - 404
    - 500

Create random passwords for new users

If a new user is created in the security section of the CMS with an empty password, then a strong random password will now be automatically assigned to the user which will not be visible to the person creating the user. The user will need to click the "I've forgotten my password" link in order to receive a password reset email so they can then enter a new password.

This is a behavioural change from the change introduced in Silverstripe CMS 5.1 where blank passwords were disallowed when creating a new user. This change in 5.1 meant the administrator setting up the user could know what the users password was until the user changed it. Prior to 5.1, blank passwords were allowed though users were unable to log in using them when the default Silverstripe member authenticator was used, thus forcing the user to reset their password.

This only applies to users created through the security section of the CMS. Users created programatically with an empty password will not have a random password automatically assigned to them. This is the current behaviour and is often used for non-production purposes such as unit-testing.

Buttons to select all files and deselect all files {#bulk-action-buttons}

The files section of the CMS now has buttons to select and deselect all files and folders currently on the screen, which is useful when you want to perform bulk operations. These buttons show at the bottom of the screen in the bulk actions toolbar. The deselect all button also shows the number of currently selected items. As these buttons are on the bulk actions toolbar they are only visible if at least one item has been selected.

select all functionality in the CMS

New searchable dropdown fields {#searchable-dropdown-field}

A pair of new dropdown form fields have been added which are particularly useful for dropdowns with a large number of options.

SearchableDropdownField is similar to DropdownField which allows selecting a single option, and SearchableMultiDropdownField is similar to ListboxField which allows selecting multiple options.

Both of these fields include a setIsLazyLoaded() method which will load a limited number of options at a time using an AJAX request matching what a user has typed in. There are quite a few options to customise these, including optionally using SearchContext to power the lazy-loaded search functionality. We encourage you to look at the API documentation for these new classes to see what you can do with them.

Note that these are both powered by react components, and are only intended to be used within the CMS. If you want to use them on the front-end of your project you will need to provide your own templates and JavaScript implementation for them.

New UrlField {#urlfield}

A new UrlField has been added which is a subclass of TextField with some additional validation rules. It will validate that the value entered is a valid absolute URL with a protocol and a host.

Auto scaffolding of has_one relations

A SearchableDropdownField will now be used when automatically scaffolding has_one relations on a form in the CMS. Previously a DropdownField was used, and when there were over 100 items a NumericField was used which was not user friendly.

Previously the DBForeignKey.dropdown_field_threshold config property was used as the threshold of the number of options to decide when to switch between auto-scaffolding a DropdownField and a NumericField. This configuration property is now used as the threshold of the number of options to decide when to start using lazy-loading for the SearchableDropdownField.

Create file variants with different extensions {#file-variants}

A low-level API has been added which allows the creation of file variants with a different extension than the original extension. A file variant is a manipulated version of the original file - for example if you resize an image or convert a file to another format, this will generate a variant (leaving the original file intact).

Some examples of when you might want this are:

  • Generating thumbnails for videos, documents, etc
  • Converting images to .webp for faster page load times
  • Converting documents to .pdf so downloaded documents are more portable

See file manipulation for details about how to use this new API.

More nuanced permissions for /dev/* routes {#dev-route-permissions}

Previously, all /dev/* routes registered with DevelopmentAdmin (for example /dev/tasks/MyBuildTask) could only be access by administrator users, and this couldn't be configured.

Now, all of the controllers which handle these routes that come packaged in a core or supported module have a new init_permissions configuration property (e.g. TaskRunner.init_permissions). This new configuration can be used to grant non-administrative users access to these routes.

You can also now optionally implement a canView() method on your BuildTask implementations to restrict accessed for specific tasks even further. This means you can grant access to some tasks to specific users or groups without granting access to all tasks.

Generic typehints {#generics}

Typehints using PHPStan-style generic types have been added to PHPDocs in many areas of the codebase of supported modules. The primary goal of this is to improve the developer experience by correctly reporting to your IDE what types it should expect, for example when looping through a DataList. In many cases your IDE will now know what types to expect without needing you to prompt it with @var annotation comments.

[info] There are some cases where this goal conflicts with having types that are correctly identified by PHPStan itself (or other static analysis tools). For example conditional return types aren't supported as widely in IDEs as generics themselves are, so we opted to not use conditional return types even when those would result in a more accurate type for static analysis tools. [/info]

While you should see some improvements immediately after updating, there are some changes you can make to your own codebase to best use the new generic type hints.

See Generics By Examples | PHPStan and Generics in PHP using PHP DocComments | DEVSENSE for more information about PHP generic typehints.

Generic typehints when returning lists {#generics-return-lists}

In your project code, any time you return an instance of SS_List (such as a DataList or ArrayList), you can add a generic typehint to declare what kind of object the returned list contains. This example will hint to the IDE that it returns a DataList containing CarouselItem records:

use App\Model\CarouselItem;
use SilverStripe\ORM\DataList;

/**
 * @return DataList<CarouselItem>
 */
function getCarouselItems(): DataList
{
    return CarouselItem::get();
}

Generic typehints in Extension subclasses {#generics-extensions}

The generic typing on the Extension class can be used to tell your IDE what type to expect for the $owner property and getOwner() method.

For this to be useful, you need to tell your IDE that your subclass @extends the Extension class, and tell it what type the owner should be.

[warning] Don't forget to include a use statement, even if you're not explicitly referencing the type anywhere in your actual code. Your IDE needs the use statement to resolve the FQCN for the class you're referencing in the typehint. [/warning]

namespace App\Extension;

use SilverStripe\Core\Extension;
use SilverStripe\SiteConfig\SiteConfig;

/**
 * @extends Extension<SiteConfig>
 */
class SiteConfigExtension extends Extension
{
    // ...
}

This is also a useful way to indicate to developers at a glance what type(s) the extension is designed to be applied to.

For example you might have an extension that can apply to any SiteTree class, or to LeftAndMain and GridFieldDetailForm_ItemRequest classes, which you can indicate using a union typehint: @extends Extension<LeftAndMain|GridFieldDetailForm_ItemRequest>.

Generic typehints in ContentController subclasses {#generics-contentcontroller}

If you use the data() method or the $dataRecord property in your page controllers, you may find it useful for your IDE to know specifically what page class that data represents.

For this to work, you need to make sure your base PageController class has a @template type to extend. Any time you use @extends, the class being extended needs to have a @template type so that your IDE knows what the type you're passing in is going to be used for.

namespace {

    use SilverStripe\CMS\Controllers\ContentController;

    /**
     * @template T of Page
     * @extends ContentController<T>
     */
    class PageController extends ContentController
    {
        // ...
    }
}
namespace App\PageType;

use PageController;

/**
 * @extends PageController<HomePage>
 */
class HomepageController extends PageController
{
    // ...
}

New exception in react forms {#react-forms-exception}

A LogicException is now thrown by FormSchema::getSchema() if a react component was not found for a field type.

[warning] If your project or a module you're using is currently trying to include a field which doesn't have a react component into a react-rendered form, it will have been silently failing. The form will have been rendering everything except for the field(s) which have no react component.

This will now fail by throwing an exception, which means your form won't render at all until you remove or replace the field(s). [/warning]

Other new features

  • A new SiteTree.hide_pagetypes configuration property has been added. Unlike SiteTree.hide_ancestor (which has now been deprecated), this is an array. This allows you to define all page types that should be hidden in a single configuration property in your YAML configuration.
  • A new DBText.summary_sentence_separators configuration property has been added. This is used to split sentences in the DBText::Summary() method. The default configuration value includes ., !, and ?. Previously, only . was used to split sentences.
  • You can now upload Braille ASCII file format (.brf) files to the CMS without needing to modify the allowed file types.
  • The assets admin section now respects the Upload_Validator.default_max_file_size configuration if AssetAdmin.max_upload_size has not been explicitly set for your project. Previously, asset admin would ignore Upload_Validator.default_max_file_size and just use the PHP upload_max_filesize ini configuration by default.
  • Redirector pages with a link to a missing or unpublished page/file will now return a 404 response. You can revert to the previous behaviour by setting the RedirectorPageController.missing_redirect_is_404 configuration property to false.

API changes

silverstripe/framework {#api-silverstripe-framework}

The following legacy subclasses of PasswordEncryptor have been deprecated, and will be removed in a future major release. If you are using one of these password encryptors in your projects, we strongly recommend swapping to one that has not been deprecated (PasswordEncryptor_Blowfish is the current recommendation, and is the default encryptor for passwords in new installations). Note that changing the password encryptor will also require that all of your members reset their passwords.

  • PasswordEncryptor_None
  • PasswordEncryptor_LegacyPHPHash
  • PasswordEncryptor_MySQLOldPassword
  • PasswordEncryptor_MySQLPassword

The following unused API have been deprecated and will be removed in a future major release:

  • Configuration property DataObject.subclass_access
  • Public static method DataObject::disable_subclass_access()
  • Public static method DataObject::enable_subclass_access()

The ViewableData::getIterator() method has been deprecated and will be removed in a future major release.

silverstripe/cms {#api-silverstripe-cms}

The SiteTree.hide_ancestor configuration property has been deprecated. Use SiteTree.hide_pagetypes instead.

silverstripe/versioned {#api-silverstripe-versioned}

The $having parameter in the Versioned::Versions() method has been deprecated. This parameter was never used, and has been removed from the method signature.

Bug fixes

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!