silverstripe-framework/docs/en/04_Changelogs/4.0.0.md
2016-08-16 11:09:20 +12:00

43 KiB
Raw Blame History

4.0.0 (unreleased)

Overview

Framework

  • Minimum PHP version raised to 5.5.0
  • Minimum CMS browser requirement raised from Internet Explorer 8 to Internet Explorer 10
  • Deprecate SQLQuery in favour SQLSelect
  • DataList::filter by null now internally generates "IS NULL" or "IS NOT NULL" conditions appropriately on queries
  • DataObject constructor now has an additional parameter, which must be included in subclasses.
  • 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.
  • Controller::init visibility changed to protected. Use Controller::doInit() instead.
  • 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.
  • Protected file storage is now a core Framework API. See [/developer_guides/files/file_security] for more information.
  • Object::useCustomClass has been removed. You should use the config API with Injector instead.
  • Upgrade of TinyMCE to version 4.
  • File is now versioned, and should be published before they can be used on the frontend. See section on Migrating File DataObject from 3.x to 4.0 below for upgrade notes.
  • Removed RegenerateCachedImagesTask
  • Removed dev/tests/ controller in favour of standard vendor/bin/phpunit command
  • Updated PHPUnit from 3.7 to 4.8 (upgrade notes). Please remove any PHPUnit related require_once() calls (e.g. in FeatureContext definitions of the behat-extension module). Run composer require --dev 'phpunit/phpunit:~4.8' on existing projects to pull in the new dependency.
  • Object::invokeWithExtensions now has the same method signature as Object::extend and behaves the same way.
  • CMSMain model actions have been changed:
    • CMSBatchAction_Delete and CMSMain::delete are no longer deprecated.
    • CMSBatchAction_DeleteFromLive is removed.
    • CMSMain.enabled_legacy_actions config is removed.
  • DataObject::can has new method signature with $context parameter.
  • SiteTree.alternatePreviewLink is deprecated. Use updatePreviewLink instead.
  • Injector dependencies no longer automatically inherit from parent classes.
  • default_cast is now enforced on all template variables. See upgrading notes below.
  • HTMLText no longer enables shortcodes by default. You can specify the db option for html fields as HTMLText(['whitelist=meta,link']), or use a ShortcodeHTMLText as a shorthand substitute.
  • FormField->dontEscape has been removed. Escaping is now managed on a class by class basis.
  • HeaderField requires a $name constructor argument (new HeaderField('MyName', 'My Title')
  • DBString->LimitWordCountXML removed. Use LimitWordCount for XML safe version.
  • $module parameter in themedCSS and themedJavascript removed.
  • Theme selector has been removed from SiteConfig. Please use SSViewer.themes config instead.
  • CMSMain::buildbrokenlinks() action is removed.
  • Folder_UnusedAssetsField is removed.
  • UpgradeSiteTreePermissionSchemaTask is removed.
  • $action parameter to Controller::Link() method is standardised.
  • Removed UpgradeSiteTreePermissionSchemaTask

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.
  • AssetControlExtension is applied by default to all DataObjects, in order to support the management of linked assets and file protection.
  • ProtectedFileController class is used to serve up protected assets.
  • Object has been broken up into various traits, each of which can be added to other objects independently:
    • Configurable Provides Config API helper methods
    • Injectable Provides Injector API helper methods
    • Extensible Allows extensions to be applied
  • Removed ability to run tests via web requests (http://mydomain.com/dev/tests), use the standard CLI command instead (vendor/bin/phpunit)
  • Removed dev/jstests/ controller (no replacement)
  • Moved test database cleanup task from sake dev/tests/cleanupdb to sake dev/tasks/CleanupTestDatabasesTask
  • Removed TestRunner and JSTestRunner APIs
  • Removed PhpUnitWrapper, PhpUnitWrapper_3_4, PhpUnitWrapper_3_5, PhpUnitWrapper_Generic, SapphireTestSuite APIs
  • Removed SapphireTest->skipTest, use markTestSkipped() in a setUp() method instead
  • HtmlEditorConfig is now an abstract class, with a default implementation TinyMCEConfig for the built in TinyMCE editor.
  • HtmlEditorField::setEditorConfig may now take an instance of a HtmlEditorConfig class, as well as a standard config identifier name.
  • A lot of standard versioned API has been refactored from SiteTree into Versioned extension. Now all versioned DataObjects have canPublish(), canArchive(), canUnpublish(), doPublish(), doArchive() doUnpublish(), isPublished() and isonDraft() out of the box. However, do*() methods will no longer automatically check can*() permissions, and must be done by usercode before invocation.
  • GridField edit form now has improved support for versioned DataObjects, with basic publishing actions available when editing records.
  • Versioned API has some breaking changes:
    • Versioned constructor now only allows a single string to declare whether staging is enabled or not. The number of names of stages are no longer able to be specified. See below for upgrading notes for models with custom stages.
    • reading_stage is now set_stage and throws an error if setting an invalid stage.
    • current_stage is now get_stage
    • getVersionedStages is gone.
    • get_live_stage is removed. Use the Versioned::LIVE constant instead.
    • getDefaultStage is removed. Use the Versioned::DRAFT constant instead.
    • $versionableExtensions is now private static instead of protected static
    • hasStages is addded to check if an object has a given stage.
    • stageTable is added to get the table for a given class and stage.
    • Any extension declared via versionableExtensions config on Versioned dataobject must now VersionableExtension interface at a minimum. Translatable has been removed from default versionableExtensions
  • ChangeSet and ChangeSetItem have been added for batch publishing of versioned dataobjects.
  • FormAction::setValidationExempt can be used to turn on or off form validation for individual actions
  • DataObject.table_name config can now be used to customise the database table for any record.
  • DataObjectSchema class added to assist with mapping between classes and tables.
  • Changes to DBString formatting:
    • NoHTML is renamed to Plain
    • LimitWordCountXML is removed. Use LimitWordCount instead.
    • BigSummary is removed. Use Summary instead.
    • Most limit methods on DBHTMLText now plain text rather than attempt to manipulate the underlying HTML.
    • FormField::Title and FormField::RightTitle are now cast as plain text by default (but can be overridden).
  • PopoverField added to provide popup-menu behaviour in react forms (currently not available for non-react forms).
  • Admin URL can now be configured via custom Director routing rule

Front-end build tooling for CMS interface

  • Management of CMS JavaScript dependencies via npm
  • ES6 is now available in all core JavaScript files via Babel
  • Bundling of core JavaScript via Browserify
  • CMS build tasks managed by Gulp

If you're planning to contribute to SilverStripe core, you should learn how to use the build tooling and work with client-side dependencies. Note that you don't need any of these tools for running SilverStripe or developing your own website.

Added new front-end UI libraries

Compiling CSS

Compass has been removed and css/sprite compilation is now handled with gulp (see Markup and Style Conventions).

Bootstrap CSS

Bootstrap 4 Alpha.2 CSS is available in admin/css/bootstrap.

Changed paths of static assets

All static files (images, javascript, stylesheets, fonts) used for the CMS and forms interfaces in framework and cms have moved locations. These assets are now placed in a client/ subfolder, to account for a structural change where both javascript and styles are co-located in component-specific folders. This will affect you if you have used Requirements::block() on files in the framework/ or cms/ folder. Care should also be taken when referencing images in these folders from your own stylesheets.

framework/javascript       => framework/client/dist/
framework/javascript/lang  => framework/client/lang/
framework/images           => framework/client/dist/images/
framework/css              => framework/client/dist/css/
framework/scss             => framework/client/src/styles/
admin/javascript/          => admin/client/src/
admin/javascript/src/      => admin/client/src/legacy/ (mostly)
admin/javascript/lang/     => admin/client/lang/
admin/scss/                => admin/client/styles/legacy/
admin/css/                 => admin/client/dist/css/
admin/css/screen.css       => admin/client/dist/css/bundle.css
admin/images/              => admin/client/dist/images/
admin/images/sprites/src/  => admin/client/src/sprites/
admin/images/sprites/dist/ => admin/client/dist/sprites/
admin/font/                => admin/client/dist/font/

Deprecated classes/methods

Core

  • Added the following npm libraries:

    • Page.js
  • Removed the following JavaScript libraries:

    • History.js
  • debugmethods querystring argument has been removed from debugging.

  • The following ClassInfo methods are now deprecated:

    • ClassInfo::baseDataClass - Use DataObject::getSchema()->baseDataClass() instead.
    • ClassInfo::table_for_object_field - Use DataObject::getSchema()->tableForField() instead

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

Member

  • Member Field 'RememberLoginToken' removed, replaced with 'RememberLoginHashes' has_many relationship

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 thats 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

Some methods on Requirements have had their method signatures changed:

  • includeInHTML has had the first parameter $template removed as it was previously deprecated.

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.

In addition, a new API for javascript files has been added to support front-end tools such as browserify that pre-combine scripts. You can specify a 'provides' option to the second parameter of Requirements::javascript to declare included files.

E.g.

:::php
Requirements::javascript('mysite/js/dist/bundle.js', ['provides' => [
    'mysite/js/jquery.js'
    'mysite/js/src/main.js',
    'mysite/js/src/functions.js'
]]);

RestfulService

  • RestfulService has been removed. Use Guzzle instead. See Upgrading notes.

Oembed

  • Our self-maintained Oembed implementation has been removed, in favour of introducing oscarotero/Embed as a dependency.

Upgrading

Explicit text casting is now enforced on all template variables

Now whenever a $Variable is used in a template, regardless of whether any casts or methods are suffixed to the reference, it will be cast to either an explicit DBField for that field, or the value declared by the default_cast on the parent object.

The default value of default_cast is Text, meaning that now many cases where a field was left un-uncoded, this will now be safely encoded via Convert::raw2xml. In cases where un-cast fields were used to place raw HTML into templates, this will now encode this until explicitly cast for that field.

You can resolve this in your model by adding an explicit cast to HTML for those fields.

Before:

:::ss
<div>
$SomeHTML
</div>


:::php
class MyObject extends ViewableData {
	public function getSomeHTML {
		$title = Convert::raw2xml($this->Title);
		return "<h1>{$title}</h1>";
	}
}

After:

:::ss
<div>
$SomeHTML
</div>


:::php
class MyObject extends ViewableData {
	private static $casting = [
		'SomeHTML' => 'HTMLText'
	];
	
	public function getSomeHTML {
		$title = Convert::raw2xml($this->Title);
		return "<h1>{$title}</h1>";
	}
}

If you need to encode a field (such as HTMLText) for use in html attributes, use .ATT instead, or if used in an actual XML file use .CDATA.

See the Template casting section for specific details.

Automatically upgrading

4.0 moves many classes to namespaces, and renames many methods. A tool is available to (semi-)automatically update your 3.x code to the new 4.0 names.

See https://github.com/silverstripe/silverstripe-upgrader/ for the latest documentation on how to upgrade your code. Typically the process will be:

composer global require silverstripe/upgrader
cd ~/Project/Root
~/.composer/vendor/bin/upgrade-code upgrade ./mysite --write

If you want to do a dry-run, omit the --write option to see a preview of a diff of all changed project files.

This will resolve the majority of upgrading work, but for specific changes that will require manual intervention, please see the below upgrading notes.

Make sure templates are in correct locations

Templates are now much more strict about their locations. You can no longer put a template in an arbitrary folder and have it be found. Case is now also checked on case-sensitive filesystems.

Either include the folder in the template name (renderWith('Field.ss') => renderWith('forms/Field.ss')), move the template into the correct directory, or both.

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 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 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

This task will also support migration of existing File DataObjects to file versioning. Any pre-existing File DataObjects will be automatically published to the live stage, to ensure that previously visible assets remain visible to the public site.

If additional security or visibility rules should be applied to File dataobjects, then make sure to correctly extend canView via extensions.

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) {
    Versioned::reading_stage('Stage');
	$file = new File();
	$file->setFromLocalFile($tmp, 'imported/'.basename($tmp));
	$file->write();
	$file->doPublish();
}

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:

  • 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.
  • Folder DataObjects are now purely logical DataObjects, and perform no actual filesystem folder creation on write.
  • All Files are versioned, which means that by default, new File records will not be visibile to the public site. You will need to make sure to invoke ->doPublish() on any File DataObject you wish visitors to be able to see.

You can disable File versioning by adding the following to your _config.php

:::php
File::remove_extension('Versioned');

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');
SilverStripe\Filesystem\Storage\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.

The CompositeDBField interface has been replaced with an abstract class, DBComposite. 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 DBComposite {

	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 code that references table names

A major change in 4.0.0 is that now tables and class names can differ from model to model. In order to fix a table name, to prevent it being changed (for instance, when applying a namespace to a model) the table_name config can be applied to any DataObject class.

:::php
namespace SilverStripe\BannerManager;
class BannerImage extends \DataObject {
	private static $table_name = 'BannerImage';
}

In order to ensure you are using the correct table for any class a new [api:DataObjectSchema] service is available to manage these mappings.

:::php
public function countDuplicates($model, $fieldToCheck) {
	$table = DataObject::getSchema()->tableForField($model, $field);
	$query = new SQLSelect();
	$query->setFrom("\"{$table}\"");
	$query->setWhere(["\"{$table}\".\"{$field}\"" => $model->$fieldToCheck]);
	return $query->count();
}

See versioned documentation for more information.

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

Upgrading asset web.config, .htaccess, or other server configuration

Server configuration files for /assets are no longer static, and are regenerated via a set of standard silverstripe templates on flush. These templates include:

  • Assets_HTAccess.ss: Template for public permissions on the Apache server.
  • Assets_WebConfig.ss: Template for public permissions on the IIS server.
  • Protected_HTAccess.ss: Template for the protected store on the Apache server (should deny all requests).
  • Protected_WebConfig.ss: Template for the protected store on the IIS server (should deny all requests).

You will need to make sure that these files are writable via the web server, and that any necessary configuration customisation is done via overriding these templates.

If upgrading from an existing installation, make sure to invoke ?flush=all at least once.

See [/developer_guides/files/file_security] for more information.

ListboxField is now multiple-only

Previously, this field would operate as either a single select (default) or multi-select by setting setMultiple to either true or false.

Now this field should only be used for multi-selection. Single-selection should be done using a regular DropdownField.

GroupedDropdownField::setDisabled now only accepts a list of values.

Where previously you could specify a list of grouped values in the same way as setSource, this method now only accepts either a non-associative array of values (not titles) or an SS_List of items to disable.

Upgrading Requirements file paths

Requirements now throws an exception then a file is not found, rather than failing silently, so check your Requirements are pointing to files that exist.

Core JavaScript files paths have changed, so if you're referencing these, you'll have to update your file paths. Files previously in admin/javascript are now located in admin/javascript/dist and files previously located in javascript are now locatied in javascript/dist.

If you're not doing this already, we suggest looking into a JavaScript bundler like Browserify, to combine JavaScript files. SilverStripe core is moving away from Requirements::combine_files in favour of Browserify as of 4.0 and Requirements::combine_files is being considered for deprecation in future versions.

Upgrading TinyMCE to 4.0

Please see the tinymce upgrading guide to assist with upgrades to customisations to tinymce 3.

In Framework 4.0 the user interface for TinyMCE has been trimmed down considerably, with certain toolbar buttons removed from the default cms configuration. These include:

  • Strikethrough
  • Styles dropdown
  • Block quotes
  • Horizontal Rule
  • Undo / Redo
  • Cut / Paste as word
  • Select all
  • Fullscreen

However, these function may be enabled on a case by case basis through modifification of the default tinymce config, or by creating custom configurations.

The optional ss_macron plugin for inserting Māori diacritical marks has been removed from core. You can configure the built-in charmap plugin instead:

:::php
$editor = HtmlEditorConfig::get('cms');
$editor->enablePlugins('charmap');
$editor->addButtonsToLine(1, 'charmap');
$editor->setOption('charmap_append', [
    ['256','A - macron'],
    ['274','E - macron'],
    ['298','I - macron'], 
    ['332','O - macron'], 
    ['362','U - macron'], 
    ['257','a - macron'], 
    ['275','e - macron'], 
    ['299','i - macron'], 
    ['333','o - macron'], 
    ['363','u - macron']
]);

For more information on available options and plugins please refer to the tinymce documentation

Upgrading DataObjects with the Versioned extension

In most cases, versioned models with the default versioning parameters will not need to be changed. However, there are now additional restrictions on the use of custom stage names.

Rather than declaring the list of stages a model has, the constructor for Versioned will take a single mode parameter, which declares whether or not the model is versioned and has a draft and live stage, or alternatively if it only has versioning without staging.

For instance:

:::php
/**
 * This model has staging and versioning. Stages will be "Stage" and "Live"
 */
class MyStagedModel extends DataObject {
	private staic $extensions = array(
		"Versioned('StagedVersioned')"
	);
}

/**
 * This model has versioning only, and will not has a draft or live stage, nor be affected by the current stage.
 */
class MyVersionedModel extends DataObject {
	private static $extensions = array(
		"Versioned('Versioned')"
	);
}

Additionally, the following api methods have been added:

  • Versioned::publishRecursive Publishes this object, and all owned objects
  • Versioned::publishSingle Publishes this object, but not owned objects
  • Versioned::copyVersionToStage Replaces the old publish method.

These methods are deprecated:

  • Versioned::publish Replaced by Versioned::copyVersionToStage
  • Versioned::doPublish Replaced by Versioned::publishRecursive

Implementation of ownership API

In order to support the recursive publishing of dataobjects, a new API has been developed to allow developers to declare dependencies between objects. See the versioned documentation for more information.

By default all versioned dataobjects will automatically publish objects that they own.

ChangeSet batch publishing

ChangeSet objects have been added, which allow groups of objects to be published in a single atomic transaction.

This API will utilise the ownership API to ensure that changes to any object include all necessary changes to owners or owned entities within the same changeset.

New [image] shortcode in HTMLText fields

The new Ownership API relies on relationships between objects. Many of these relationships are already made explicit through has_one, has_many and many_many. Images inserted into HTMLText fields (through a WYSIWYG editor) need to be tracked as well. Instead of <img> tags, the field will insert [image] shortcodes which point to the database identifier of the Image record rather than its path on the filesystem. The shortcode will be automatically replaced when the field is rendered. Newly inserted images will automatically receive the shortcode and ownership tracking, and existing <img> will continue to work.

Upgrading references to DBField and subclasses

A major change in 4.0 is the introduction of namespaced DBField subclasses. Now as a standard, all DBField subclasses have a DB prefix, are namespaced, and have an associative alias which omits the DB prefix.

This means that for the most part, code that worked in 3.0 won't need to be changed, although if you have any hard class literals which reference the old classes, they will need to be updated to point to the new namespaced classes.

An exception to this is any classes which once had the SS_ prefix, which will now be instead prefixed with DB, and have an un-aliased prefix. For example SS_Datetime is now DBDateTime, and has the alias DateTime which may be used in config.

For example:

class MyObject extends DataObject {
    private static $db = array(
        'Number' => 'Int',
        'Time' => 'SS_Datetime'
    );

    /**
     * @param Int $val
     * @return Varchar
     */
    public function TextNumber() {
        return new Varchar('TextNumber', 'Number is ' . $this->Number);
    }
}

Will become:

use SilverStripe\ORM\FieldType\DBVarchar;

class MyObject extends DataObject {
    private static $db = array(
        'Number' => 'Int',
        'Time' => 'Datetime'
    );

    /**
     * @param Int $val
     * @return Varchar
     */
    public function TextNumber() {
        return new DBVarchar('TextNumber', 'Number is ' . $this->Number);
    }
}

Note that string references to SS_Datetime passed to injector, or used in config values, will still work, and will refer to the updated class names.

Upgrading from deprecated RestfulService

Install Guzzle to get an API consuming library. composer require guzzlehttp/guzzle or add guzzlehttp/guzzle: "^6.0" to your composer.json.

For information on how to use Guzzle, please see the extensive Guzzle documentation

In case you want to keep using RestfulService, you can use Firesphere/silverstripe-restfulservice, but it is unmaintained and deprecated.

Upgrading from deprecated Oembed

Instead of Oembed, the framework now relies on oscarotero/Embed to handle getting the shortcode-data for embedding.

If you have custom embedding-code relying on Oembed, please refer to the documentation provided by oscarotero.

Admin URL can now be configured via custom Director routing rule

The default admin/ URL to access the CMS interface can now be changed via a custom Director routing rule for AdminRootController. If your website or module has hard coded admin URLs in PHP, templates or JavaScript, make sure to update those with the appropriate function or config call. See CMS architecture for language specific functions.