mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
b694829084
API Enable DataList::sort to support composite field names
464 lines
18 KiB
Markdown
464 lines
18 KiB
Markdown
# 4.0.0 (unreleased)
|
||
|
||
## Overview
|
||
|
||
### Framework
|
||
|
||
* Minimum PHP version raised to 5.5.0
|
||
* Deprecate `SQLQuery` in favour `SQLSelect`
|
||
* `DataList::filter` by null now internally generates "IS NULL" or "IS NOT NULL" conditions appropriately on queries
|
||
* `DataObject::database_fields` now returns all fields on that table.
|
||
* `DataObject::db` now returns composite fields.
|
||
* `DataObject::ClassName` field has been refactored into a `DBClassName` type field.
|
||
* Image manipulations have been moved into a new `[api:ImageManipulation]` trait.
|
||
* `CMSFileAddController` removed.
|
||
* UploadField::setAllowedFileCategories('image') now excludes non-resizeable images. 'unresizeable_image' is
|
||
can be used to validate these types.
|
||
* `Image_Backend` API now loads and saves from `AssetContainer` instances rather than local files.
|
||
* The following File categories have been renamed: 'zip' to 'archive', 'doc' to 'document', and 'mov' to 'video'
|
||
* `File::updateLinks` no longer takes urls as parameters. All file links are now identified either by
|
||
the `DataObject::ID` in a `data-fileid` property, or via shortcodes. This is necessary because file
|
||
urls are no longer able to identify assets.
|
||
* Extension point `HtmlEditorField::processImage` has been removed, and moved to `Image::regenerateImageHTML`
|
||
* `Upload::load` now stores assets directly without saving into a `File` dataobject.
|
||
|
||
## New API
|
||
|
||
* New filesystem abstraction including new `DBFile` database field to hold file references.
|
||
* `ShortcodeHandler` interface to help generate standard handlers for HTML shortcodes in the editor.
|
||
* `AssetNameGenerator` interface, including a `DefaultAssetNameGenerator` implementation, which is used to generate
|
||
renaming suggestions based on an original given filename in order to resolve file duplication issues.
|
||
* `GeneratedAssetHandler` API now used to store and manage generated files (such as those used for error page
|
||
cache or combined files).
|
||
* `Requirements_Minifier` API can be used to declare any new mechanism for minifying combined required files.
|
||
By default this api is provided by the `JSMinifier` class, but user code can substitute their own.
|
||
* `AssetField` formfield to provide an `UploadField` style uploader for the new `DBFile` database field.
|
||
|
||
## Deprecated classes/methods
|
||
|
||
### ORM
|
||
|
||
* `DataList::getRelation` is removed, as it was mutable. Use `DataList::applyRelation` instead, which is immutable.
|
||
|
||
### ErrorPage
|
||
|
||
* `ErrorPage.static_filepath` config has been removed.
|
||
* `ErrorPage::get_filepath_for_errorcode` has been removed
|
||
* `ErrorPage::alternateFilepathForErrorcode` extension point has been removed
|
||
|
||
See notes below on upgrading extensions to the ErrorPage class
|
||
|
||
### Assets and Filesystem
|
||
|
||
The following image manipulations previously deprecated has been removed:
|
||
|
||
* `Image::SetRatioSize` superceded by `Fit`
|
||
* `Image::SetWidth` superceded by `ScaleWidth`
|
||
* `Image::SetHeight` superceded by `ScaleHeight`
|
||
* `Image::SetSize` superceded by `Pad`
|
||
* `Image::PaddedImage` superceded by `Pad`
|
||
* `Image::CroppedImage` superceded by `Fill`
|
||
* `Image::AssetLibraryPreview` superceded by `PreviewThumbnail`
|
||
* `Image::AssetLibraryThumbnail` superceded by `CMSThumbnail`
|
||
|
||
The following `File` methods have been removed. Since there is no longer any assumed local path for any file,
|
||
methods which dealt with such paths may no longer be relied on.
|
||
|
||
* `File::deletedatabaseOnly`
|
||
* `File::link_shortcode_handler` renamed to `handle_shortcode`
|
||
* `File::setParentID`
|
||
* `File::getFullPath`
|
||
* `File::getRelativePath`
|
||
* `File::Content` database field is removed
|
||
|
||
Image manipulations have been moved out of Image.php and now available to any File or DBFile which has the
|
||
appropriate mime types. The following file manipulations classes and methods have been removed:
|
||
|
||
* `CleanImageManipulationCache` class
|
||
* `Image_Cached` class
|
||
* `Image::regenerateFormattedImages`
|
||
* `Image::getGeneratedImages`
|
||
* `Image::deleteFormattedImages`
|
||
* `AssetAdmin::deleteunusedthumbnails`
|
||
* `AssetAdmin::getUnusedThumbnails`
|
||
|
||
Many `Folder` methods have also been removed:
|
||
|
||
* `Folder::syncChildren`
|
||
* `Folder::constructChild`
|
||
* `Folder::addUploadToFolder`
|
||
|
||
The following filesystem synchronisation methods are also removed
|
||
|
||
* `Filesystem::sync`
|
||
* `AssetAdmin::doSync`
|
||
|
||
The Spyc YAML library has been removed from /thirdparty. Please load it yourself, or use the Symfony YAML component that’s automatically installed by composer.
|
||
|
||
### Requirements
|
||
|
||
The following methods and properties on `Requirements_Backend` have been renamed:
|
||
|
||
* `Requirements_Backund::$combine_files` made protected and renamed `$combinedFiles`
|
||
* `Requirements_Backend::$combine_js_with_min` made protected and renamed `$minifyCombinedFiles`
|
||
* `Requirements_Backend::$write_header_comments` made protected and renamed `$writeHeaderComment`
|
||
* `Requirements_Backend::$write_js_to_body` made protected and renamed to `$writeJavascriptToBody`
|
||
* `Requirements_Backend::$force_js_to_bottom` renamed to `$forceJSToBottom`
|
||
* `get_combined_files_enabled` renamed to `getCombinedFilesEnabled`
|
||
* `set_combined_files_enabled` renamed to `setCombinedFilesEnabled`
|
||
* `get_suffix_requirements` renamed to `getSuffixRequirements`
|
||
* `set_suffix_requirements` renamed to `setSuffixRequirements`
|
||
* `get_custom_scripts` renamed to `getCustomScripts`
|
||
* `unblock_all` renamed to `unblockAll`
|
||
* `include_in_response` renamed to `includeInResponse`
|
||
* `combine_files` renamed to `combineFiles`
|
||
* `get_combine_files` renamed to `getCombinedFiles`
|
||
* `clear_combined_files` renamed to `clearCombinedFiles`
|
||
* `process_combined_files` renamed to `processCombinedFiles`
|
||
* `set_write_js_to_body` renamed to `setWriteJavascriptToBody`
|
||
* `set_force_js_to_bottom` renamed to `setForceJSToBottom`
|
||
|
||
New methods on `Requirements` are added to access these:
|
||
|
||
* `get_minify_combined_js_files`
|
||
* `set_minify_combined_js_files`
|
||
* `get_force_js_to_bottom`
|
||
* `get_write_js_to_body`
|
||
|
||
And some methods on `Requirements` and `Requirements_Backend` have been removed as they are obsolete.
|
||
|
||
* `delete_combined_files` (both classes)
|
||
|
||
A new config `Requirements_Backend.combine_in_dev` has been added in order to allow combined files to be
|
||
forced on during development. If this is off, combined files is only enabled in live environments.
|
||
|
||
## Upgrading
|
||
|
||
### Update code that uses SQLQuery
|
||
|
||
Where your code once used SQLQuery you should now use SQLSelect in all cases, as this has been removed.
|
||
|
||
See the [3.2.0](3.2.0) upgrading notes for details on how existing code should be upgraded.
|
||
|
||
### New asset storage mechanism
|
||
|
||
File system has been abstracted into an abstract interface. By default, the out of the box filesystem
|
||
uses [Flysystem](http://flysystem.thephpleague.com/) with a local storage mechanism (under the assets directory).
|
||
|
||
Because the filesystem now uses the sha1 of file contents in order to version multiple versions under the same
|
||
filename, the default storage paths in 4.0 will not be the same as in 3.
|
||
|
||
In order to retain existing file paths in line with framework version 3 you should set the
|
||
`\SilverStripe\Filesystem\Flysystem\FlysystemAssetStore.legacy_paths` config to true.
|
||
Note that this will not allow you to utilise certain file versioning features in 4.0.
|
||
|
||
|
||
:::yaml
|
||
\SilverStripe\Filesystem\Flysystem\FlysystemAssetStore:
|
||
legacy_paths: true
|
||
|
||
|
||
See [/developer_guides/files/file_management] for more information on how the new system works.
|
||
|
||
### Migrating File DataObject from 3.x to 4.0
|
||
|
||
Since the structure of `File` dataobjects has changed, a new task `MigrateFileTask` has been added to assist
|
||
in migration of legacy files. Migration can be invoked by either this task, or can be configured to automatically
|
||
run during dev build by setting the `File.migrate_legacy_file` config to true. However, it's recommended that
|
||
this task is run manually during an explicit migration process, as this process could potentially consume
|
||
large amounts of memory and run for an extended time.
|
||
|
||
|
||
:::yaml
|
||
File:
|
||
migrate_legacy_file: true
|
||
|
||
|
||
### Upgrade code which acts on `Image`
|
||
|
||
As all image-specific manipulations has been refactored from `Image` into an `ImageManipulations` trait, which
|
||
is applied to both `File` and `DBFile`. These both implement a common interface `AssetContainer`, which
|
||
has the `getIsImage()` method. In some cases, it may be preferable to invoke this method to detect
|
||
if the asset is an image or not, rather than checking the subclass, as the asset may also be a `DBFile` with
|
||
an image filter applied, rather than an instance of the `Image` dataobject.
|
||
|
||
In addition, a new file category `image/supported` has been added, which is a subset of the `image` category.
|
||
This is the subset of all image types which may be assigned to the `[api:Image]` dataobject, and may have
|
||
manipulations applied to it. This should be used as the file type restriction on any `[api:UploadField]` which
|
||
is intended to upload images for manipulation.
|
||
|
||
Before:
|
||
|
||
|
||
:::php
|
||
if($file instanceof Image) {
|
||
$upload = new UploadField();
|
||
$upload->setAllowedFileCategories('image');
|
||
}
|
||
|
||
|
||
After:
|
||
|
||
|
||
:::php
|
||
if($file->getIsImage()) {
|
||
$upload = new UploadField();
|
||
$upload->setAllowedFileCategories('image/supported');
|
||
}
|
||
|
||
|
||
In cases where image-only assets may be assigned to relationships then your datamodel should specify explicitly
|
||
an `Image` datatype, or refer to `DBFile('image/supported')`.
|
||
|
||
E.g.
|
||
|
||
|
||
:::php
|
||
class MyObject extends DataObject {
|
||
private static $has_one = array(
|
||
"ImageObject" => "Image"
|
||
);
|
||
private static $db = array(
|
||
"ImageField" => "DBFile('image/supported')"
|
||
);
|
||
}
|
||
|
||
|
||
### Upgrading code that writes to `File` dataobjects, or writes files to the 'assets' folder
|
||
|
||
In the past all that was necessary to write a `File` dataobject to the database was to ensure a physical file
|
||
existed in the assets folder, and that the Filename of the dataobject was set to the same location.
|
||
|
||
Since the storage of physical files is no longer a standard location, it's necessary to delegate the writing of such
|
||
files to the asset persistence layer. As a wrapper for an individual file, you can use any of the `setFrom*`
|
||
methods to assign content from a local (e.g. temporary) file, a stream, or a string of content.
|
||
|
||
You would need to upgrade your code as below.
|
||
|
||
Before:
|
||
|
||
|
||
:::php
|
||
function importTempFile($tmp) {
|
||
copy($tmp, ASSETS_PATH . '/imported/' . basename($tmp));
|
||
$file = new File();
|
||
$file->setFilename('assets/imported/'.basename($tmp));
|
||
$file->write();
|
||
}
|
||
|
||
|
||
After:
|
||
|
||
|
||
:::php
|
||
function importTempFile($tmp) {
|
||
$file = new File();
|
||
$file->setFromLocalFile($tmp, 'imported/'.basename($tmp));
|
||
$file->write();
|
||
}
|
||
|
||
|
||
Note that 'assets' is no longer present in the new code, and the path beneath what was once assets is now
|
||
used to generate the 'filename' value. This is because there is no longer an assumption that files are
|
||
stored in the assets folder.
|
||
|
||
There are other important considerations in working with File dataobjects which differ from legacy:
|
||
|
||
* Deleting File dataobjects no longer removes the physical file directly. This is because any file could be referenced
|
||
from DBFile fields, and deleting these could be a potentially unsafe operation.
|
||
* File synchronisation is no longer automatic. This is due to the fact that there is no longer a 1-to-1 relationship
|
||
between physical files and File dataobjects.
|
||
* Moving files now performs a file copy rather than moving the underlying file, although only a single DataObject
|
||
will exist, and will reference the destination path.
|
||
* Folder dataobjects are now purely logical dataobjects, and perform no actual filesystem folder creation on write.
|
||
|
||
### Upgrading code performs custom image manipulations
|
||
|
||
As file storage and handling has been refactored into the abstract interface, many other components which were
|
||
once specific to Image.php have now been moved into a shared `ImageManipulation` trait. Manipulations of file content,
|
||
which are used to generate what are now called "variants" of assets, is now a generic api available to both `File`
|
||
and `DBFile` classes through this trait.
|
||
|
||
Custom manipulations, applied via extensions, must be modified to use the new API.
|
||
|
||
For instance, code which sizes images to a fixed width should be updated as below:
|
||
|
||
Before:
|
||
|
||
|
||
:::php
|
||
// in MyImageExtension.php
|
||
class MyImageExtension extends DataExtension {
|
||
public function GalleryThumbnail($height) {
|
||
return $this->getFormattedImage('GalleryThumbnail', $height);
|
||
}
|
||
public function generateGalleryThumbnail(Image_Backend $backend, $height) {
|
||
return $backend->paddedResize(300, $height);
|
||
}
|
||
}
|
||
|
||
// in _config.php
|
||
Image::add_extension('MyImageExtension');
|
||
|
||
|
||
Now image manipulations are implemented with a single method via a callback generator.
|
||
|
||
After:
|
||
|
||
|
||
:::php
|
||
// in MyImageExtension.php
|
||
class MyImageExtension extends Extension {
|
||
public function GalleryThumbnail($height) {
|
||
// Generates the manipulation key
|
||
$variant = $this->owner->variantName(__FUNCTION__, $height);
|
||
|
||
// Instruct the backend to search for an existing variant with this key,
|
||
// and include a callback used to generate this image if it doesn't exist
|
||
return $this->owner->manipulateImage($variant, function(Image_Backend $backend) use ($height) {
|
||
return $backend->paddedResize(300, $height);
|
||
});
|
||
}
|
||
}
|
||
|
||
// in _config.php
|
||
File::add_extension('MyImageExtension');
|
||
DBFile::add_extension('MyImageExtension');
|
||
|
||
|
||
There are a few differences in this new API:
|
||
|
||
* The extension is no longer specific to dataobjects, so it uses the generic 'Extension' class instead of 'DataExtension'
|
||
* This extension is added to both `DBFile` and `File`, or order to make this manipulation available to non-dataobject
|
||
file references as well, but it could be applied to either independently.
|
||
* A helper method `variantName` is invoked in order to help generate a unique variant key. Custom code may use another
|
||
generation mechanism.
|
||
* Non-image files may also have manipulations, however the specific `manipulateImage` should not be used in this case.
|
||
A generic `manipulate` method may be used, although the callback for this method both is given, and should return,
|
||
an `AssetStore` instance and file tuple (Filename, Hash, and Variant) rather than an Image_Backend.
|
||
|
||
### Upgrading code that uses composite db fields.
|
||
|
||
`CompositeDBField` is now an abstract class, not an interface. In many cases, custom code that handled
|
||
saving of content into composite fields can be removed, as it is now handled by the base class.
|
||
|
||
The below describes the minimum amount of effort required to implement a composite DB field.
|
||
|
||
:::php
|
||
<?
|
||
class MyAddressField extends CompositeDBField {
|
||
|
||
private static $composite_db = array(
|
||
'Street' => 'Varchar(200)',
|
||
'Suburb' => 'Varchar(100)',
|
||
'City' => 'Varchar(100)',
|
||
'Country' => 'Varchar(100)'
|
||
);
|
||
|
||
public function scaffoldFormField($title = null) {
|
||
new AddressFormField($this->getName(), $title);
|
||
}
|
||
}
|
||
|
||
|
||
### Upgrading code that references `DataObject::database_fields` or `DataObject::db`
|
||
|
||
These methods have been updated to include base fields (such as ID, ClassName, Created, and LastEdited), as
|
||
well as composite DB fields.
|
||
|
||
`DataObject::database_fields` does not have a second parameter anymore, and can be called directly on an object
|
||
or class. E.g. `Member::database_fields()`
|
||
|
||
If user code requires the list of fields excluding base fields, then use custom_database_fields instead, or
|
||
make sure to call `unset($fields['ID']);` if this field should be excluded.
|
||
|
||
`DataObject:db()` will return all logical fields, including foreign key ids and composite DB Fields, alongside
|
||
any child fields of these composites. This method can now take a second parameter $includesTable, which
|
||
when set to true (with a field name as the first parameter), will also include the table prefix in
|
||
`Table.ClassName(args)` format.
|
||
|
||
|
||
### Update code that uses SQLQuery
|
||
|
||
SQLQuery is still implemented, but now extends the new SQLSelect class and has some methods
|
||
deprecated. Previously this class was used for both selecting and deleting, but these
|
||
have been superceded by the specialised SQLSelect and SQLDelete classes.
|
||
|
||
Take care for any code or functions which expect an object of type `SQLQuery`, as
|
||
these references should be replaced with `SQLSelect`. Legacy code which generates
|
||
`SQLQuery` can still communicate with new code that expects `SQLSelect` as it is a
|
||
subclass of `SQLSelect`, but the inverse is not true.
|
||
|
||
### Update implementations of augmentSQL
|
||
|
||
Since this method now takes a `SQLSelect` as a first parameter, existing code referencing the deprecated `SQLQuery`
|
||
type will raise a PHP error.
|
||
|
||
E.g.
|
||
|
||
Before:
|
||
|
||
:::php
|
||
function augmentSQL(SQLQuery &$query, DataQuery &$dataQuery = null) {
|
||
$locale = Translatable::get_current_locale();
|
||
if(!preg_match('/("|\'|`)Locale("|\'|`)/', implode(' ', $query->getWhere()))) {
|
||
$qry = sprintf('"Locale" = \'%s\'', Convert::raw2sql($locale));
|
||
$query->addWhere($qry);
|
||
}
|
||
}
|
||
|
||
After:
|
||
|
||
:::php
|
||
function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null) {
|
||
$locale = Translatable::get_current_locale();
|
||
if(!preg_match('/("|\'|`)Locale("|\'|`)/', implode(' ', $query->getWhereParameterised($parameters)))) {
|
||
$query->addWhere(array(
|
||
'"Locale"' => $locale
|
||
));
|
||
}
|
||
}
|
||
|
||
|
||
### Update code that modifies the behaviour of ErrorPage
|
||
|
||
ErrorPage has been updated to use a configurable asset backend, similar to the `AssetStore` described above.
|
||
This replaces the `ErrorPage.static_filepath` config that was used to write local files.
|
||
|
||
As a result, error pages may be cached either to a local filesystem, or an external Flysystem store
|
||
(which is configured via setting a new Flysystem backend with YAML).
|
||
|
||
`ErrorPage::get_filepath_for_errorcode()` has been removed, because the local path for a specific code is
|
||
no longer assumed. Instead you should use `ErrorPage::get_content_for_errorcode` which retrieves the
|
||
appropriate content for that error using one of the methods above.
|
||
|
||
In order to retrieve the actual filename (which is used to identify an error page regardless of base
|
||
path), you can use `ErrorPage::get_error_filename()` instead. Unlike the old `get_filepath_for_errorcode`
|
||
method, there is no $locale parameter.
|
||
|
||
In case that user code must customise this filename, such as for extensions which provide a locale value
|
||
for any error page, the extension point `updateErrorFilename` can be used. This extension point should
|
||
also be used to replace any `alternateFilepathForErrorcode` used.
|
||
|
||
E.g.
|
||
|
||
|
||
:::php
|
||
class MyErrorPageExtension extends SiteTreeExtension {
|
||
public function updateErrorFilename(&$name, &$statuscode) {
|
||
if($this->owner->exists()) {
|
||
$locale = $this->Locale;
|
||
} else {
|
||
$locale = Translatable::get_current_locale();
|
||
}
|
||
$name = "error-{$statusCode}-{$locale}.html";
|
||
}
|
||
}
|
||
|
||
|
||
:::yaml
|
||
ErrorPage:
|
||
extensions:
|
||
- MyErrorPageExtension
|
||
|