Skip to content


Subversion checkout URL

You can clone with
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

475 lines (352 sloc) 23.207 kB

3.0.0 (unreleased)


  • New template engine
  • New CMS interface design
    • "Page Content" and "Page Settings" are split into two interfaces
    • Image/Link insertion moved into a modal dialog instead of a sidebar
    • "Add pages" dropdown moved to a more descriptive modal dialog
  • Renaming of sapphire to SilverStripe framework
  • Allow usage of SilverStripe framework without the "cms" module
  • CMS JavaScript moved to jQuery.entwine
  • CMS stylesheets are generated by SCSS to provide more flexible and robust styling


sapphire renamed to framework

sapphire has been renamed to framework.

Please ensure the framework now resides in the new folder when upgrading.

Here's a list of steps to check:

  • Remove your existing sapphire directory, and replace with framework from the new SilverStripe 3.0 package
  • Rename references of sapphire to framework in .htaccess, web.config and /usr/bin/sake (the last is only necessary if you use sake)
  • Find and replace any references to sapphire in your custom code to framework. In your PHP code, you can use the constant FRAMEWORK_DIR, which points to the framework directory, and in the templates you can use $ModulePath(framework)

New ORM: More flexible and expressive querying via DataList

The new "fluent" syntax to retrieve ORM records allows for a more expressive notation (instead of unnamed arguments).

// before
DataObject::get('Member', '"FirstName" = \'Sam'\', '"Surname" ASC");
// after
DataList::create('Member')->filter(array('FirstName' => 'Sam'))->sort('Surname');

The underlying record retrieval and management is rewritten from scratch, and features lazy loading which fetches only the records it needs, as late as possible. In order to retrieve all ORM records manually (as the previous ORM would've done), please use DataList->toArray().

The old getters (DataObject::get(), DataObject:;get_one(), DataObject::get_by_id()) are now deprecated, but continue to operate. Instead of a DataObjectSet, they'll now return a DataList.

// before
DataObject::get_one('Member', '"Email" = \'\'');
// after
DataList::create('Member')->filter('Email', '')->First();

// before
DataObject::get_by_id('Member', 5);
// after

Note that they will return a DataList even if they're empty, so if you want to check for the presence of records, please call the count() method on the DataList:

// before
if(!DataObject::get('SiteTree', '"ParentID" = 5')) echo "Page 5 has no children";
// after
if(!DataObject::get('SiteTree', '"ParentID" = 5')->count()) echo "Page 5 has no children";

See the "datamodel" documentation for more details.

New ORM: Changes to manipulation of SQL queries

In the 2.4 ORM it was sometimes necessary to bypass the ORM for performance reasons. For example, this command would have been intolerably slow:


The 3.0 ORM is more intelligent gives you tools you need to create high-performance code without bypassing the ORM:

// before
echo DB::query("SELECT COUNT(*) FROM \"SiteTree\"")->value();
// after
echo DataList::create('SiteTree')->count()

Both extendedSQL() and buildSQL() have been deprecated. There is not currently any way of overriding the query generation code equivalent to overriding buildSQL() in 2.4, but this facility was error prone. If you need to access the SQLQuery object, you can call ->dataQuery()->query() on any DataList. Note that modifications to this query will not be passed back into the DataList.

// before
$query = singleton('SiteTree')->extendedSQL('ParentID = 5');
// after
$query = DataList::create('SiteTree')->filter('ParentID', 5)->dataQuery()->query();

We advise that you keep this kind of code to a minimum and that you use the DataList wherever possible. If you find yourself needing to bypass the ORM in SilverStripe 3, we suggest you raise this as a discussion topic on, as we may want to add more tools to the ORM to help you.

New ORM: Better encapsulation of relationship queries with RelationList

The abstract RelationList class and its implementations ManyManyList and HasManyList are replacing the ComponentSet API, which is only relevant if you have instanciated these manually. Relations are retrieved through the same way (e.g. $myMember->Groups()).

InnoDB driver for existing and new tables on MySQL (instead of MyISAM)

SilverStripe has traditionally created all MySQL tables with the MyISAM storage driver, mainly to ensure a fulltext search based on MySQL works out of the box. Since then, the framework has gained many alternatives for fulltext search (sphinx, solr, etc.), and relies more on database transactions and other features not available in MyISAM.

This change convert tables on existing databases when dev/build is called, unless the FullTextSearch feature is enabled. In order to disable this behaviour, you have to add the following code to your _config.php BEFORE running a dev/build:

DataObject::$create_table_options['MySQLDatabase] = 'ENGINE=MyISAM';

As with any SilverStripe upgrade, we recommend database backups before calling dev/build. See for details on the conversion. Note: MySQL has made InnoDB the default engine in its 5.5 release.

Convert::json2array() changes

Convert JSON functions have been changed to use built-in json PHP functions json_decode() and json_encode()

Because json_decode() will convert nested JSON structures to arrays as well, this has changed the way it worked, as before nested structures would be converted to an object instead.

So, given the following JSON input to Convert::json2array():


Here's the output from SilverStripe 2.4, with nested JSON as objects:

    'Joe' => 'Bloggs'
    'Tom' => 'Jones',
    'My' => stdObject(
        Complicated => 'Structure' // property on object

Now in SilverStripe 3.x, nested structures are arrays:

    'Joe' => 'Bloggs',
    'Tom' => 'Jones',
    'My' => array(
        'Complicated' => 'Structure' // key value on nested array

GridField: Replacement for TableListField and ComplexTableField

We have a new component for managing lists of objects: The [GridField](/topics/grid-field). It's a substantial rewrite of the features previously captured by TableListField, ComplexTableField, HasManyComplexTableField and ManyManyComplexTableField.

The legacy fields remain operational for now, although a switch to GridField is strongly encouraged, for stability, interface and performance reasons. The HasManyComplexTableField and ManyManyComplexTableField are no longer maintained, for those you do have to make the switch. The TableField class will be deprecated soon, but we don't have an adequate replacement for it yet.

Upgrade example: Record listing

// before
$field = new TableListField('Companies', 'Company');
// after
$field = new GridField('Companies', null, DataList::create('Company'));

Upgrade example: Record listing with view/edit interface

// before
$field = new ComplexTableField($myController, 'Companies', 'Company');
// after
$field = new GridField('Companies', null, DataList::create('Company'), GridFieldConfig_RecordEditor::create());

Upgrade example: Relationship editing

// before
$field = new HasManyComplexTableField($myController, 'MyRelation', 'MyRelationObject');
// after
$field = new GridField('MyRelation', null, $myRecord->MyRelation(), GridFieldConfig_RelationEditor::create());

More information is available in the GridField documentation.

New template engine

The template engine has been completely rewritten, and although it is generally backward compatible, there are new features and some features have been deprecated. See the template upgrading guide and the template reference for more information.

Removed view-specific accessors from ViewableData

Several methods in ViewableData that were originally added to expose values to the template language were moved, in order to stop polluting the namespace. These were sometimes called by project-specific PHP code too, and that code will need re-working.

See the template upgrading guide for a list of methods and their replacements.

New user interface for CMS

Most aspects of the interface have been redesigned, which necessitated a substantial redevelopment of the underlying logic and presentation. If you have customized the admin interface in any way, please review the detailed changelog for this release. Many interface components have changed completely, unfortunately there is no clear upgrade path for every interface detail. As a starting point, have a look at the new templates in cms/templates and framework/admin/templates, as well as the new jQuery.entwine based JavaScript logic. Have a look at the new "Extending the CMS" guide, "CSS" guide, "JavaScript" guide and "CMS Architecture" guide to get you started.

New tree library

The page tree moved from a bespoke tree library to JSTree, which required changes to markup of the tree and its JavaScript architecture. This includes changes to TreeDropdownField and TreeMultiSelectField.

Settings-related fields move from SiteTree->getCMSFields() to new SiteTree->getSettingsFields()

The fields and tabs are now split into two separate forms, which required a structural change to the underlying class logic. In case you have added or removed fields in the "Behaviour" or "Access" tab, please move these customizations to a new getSettingsFields() method. In case of SiteTree extension through updateCMSFields() and a decorator/extension, please use the new updateSettingsFields() instead.

We've also removed the $params attribute on DataObject->getCMSFields() which could be used as a shortcut for customizations to FormScaffolder, in order to achieve E_STRICT compliance. Please use FormScaffolder directly.

New SiteTree::$description field to describe purpose of a page type

Please use this static property to describe the purpose of your page types, which will help users understand the new "Add page" dialog. For example, a TeamPage type could be described as "Lists all team members, linking to their profiles". Note: This property is optional (defaults to an empty string), but its usage is highly encouraged.

New ModelAdmin interface, removed sub-controllers

ModelAdmin has been substanially rewritten to natively support the [api:GridField] API for more flexible data presentation (replacing [api:ComplexTableField]), and the [api:DataList] API for more expressive querying.

If you have overwritten any methods in the class, customized templates, or implemented your own $collection_controller_class/$record_controller_class controllers, please refer to the new ModelAdmin documentation on details for how to achieve the same goals in the new class.

Stylesheet preprocessing via SCSS and the "compass" module

CSS files in the cms and framework/admin modules are now generated through the "compass" SilverStripe module, which uses the "Compass" framework and the "SCSS" language. This allows us to build more flexible and expressive stylesheets as a foundation for any extensions to the CMS interface.

The "compass" module is only required if core stylesheets are modified, not when simply using the CMS or developing other CMS functionality. If you want to extend the CMS stylesheets for your own projects without SCSS, please create a new CSS file and link it into the CMS via [api:LeftAndMain::require_css()].

Built-in Javascript validation removed

Built-in client-side form validation using Validator.js and behaviour.js has been removed, and is no longer supported. Server-side validation remains. Developers are encouraged to use custom Javascript validation on their forms if requiring client-side validation. You don't need to explicitly disable JS validation through Validator::set_javascript_validation_handler() any longer (the method is deprecated).

FormField consistently adds classes to HTML elements

The [api:FormField] API has been refactored to use SilverStripe templates for constructing the field HTML, as well as new accessors for HTML attributes. This change makes the HTML a bit more predictable, but it also means that you need to check any code (CSS, JavaScript, etc) relying on the old inconsistencies. Particularly, CSS class names applied through [api:FormField->addExtraClass()] and the "type" class are now consistently added to the container <div> as well as the HTML form element itself.

Before (abbreviated):
<div class="field checkbox extraClass"...>
    <input type="checkbox".../>

After (abbreviated):
<div class="field checkbox extraClass"...>
    <input type="checkbox" class="checkbox extraClass".../>

FormField constructor argument changes

In order to enforce a consistent parameter order in core [api:FormField] subclasses, its no longer possible to set the following optional attributes via constructor arguments: $form, $maxLength, $rightTitle, $rows/$cols (for TextareaField and HtmlEditorField) and $folderName (for FileField and SimpleImageField). Please use the appropriate setters on the form field instance instead.

Restructured files and folders

In order to make the SilverStripe framework useable without the cms module, we've moved some files around.

CMS base functionality which is not directly related to content pages (SiteTree) has been moved from the cms module into a new "sub-module" located in framework/admin. This includes generic management interfaces like "Files & Images" (AssetAdmin), "Security" (SecurityAdmin) and the ModelAdmin class. On the other hand, SiteTree related features were moved from framework to the cms module.

Due to the built-in PHP class autoloader, this usually won't have any effect on your own code (unless you're including direct file paths). For any other files (CSS files, templates, images, JavaScript) which might be referenced by their path, please doublecheck that their path is still valid.

Removed prototype.js and and behaviour.js dependencies from most core components

This will only affect you if you used either of those libraries, or by extension on the globals set in prototype_improvements.js and jquery_improvements.js. The $$() shorthand for document.getElementsBySelector() is no longer globally bound, but rather just defined when used through other components. The $() shorthand had two meanings, based on context: Either document.getElementsById() through prototype.js, or as an alias for the jQuery() method. In general, we recommend not to rely on the $() global in SilverStripe, as we unset it via [jQuery.noConflict()]( Use a custom alias via function scope if possible.

Moved Translatable extension into new 'translatable' module

If you are translating your SiteTree or DataObject classes with the Translatable extension, please install the new module from The following settings can be removed from your own _config.php, as they're automatically included through translatable/_config.php:

Object::add_extension('SiteTree', 'Translatable');
Object::add_extension('SiteConfig', 'Translatable');

Moved Group->IPRestrictions into a new 'securityextras' module

IP restrictions for group memberships in the "Security" section were a rarely used feature, and cluttered up the interface. We've decided to move it to a separate module called securityextras. To continue using these restrictions, just install the module - no data migration required.

Moved comments system into new 'comments' module

This affects websites which have comments enabled, through the $Comments placeholder and the PageComment class. See the 'comments' module. To continue using comments, simply install the module - no data migration required.

Moved SiteTree->HomepageForDomain into a new 'homepagefordomain' module

The setting determines difference homepages at arbitrary locations in the page tree, and was rarely used in practice - so we moved it to a "homepagefordomain" module.

Default translation source in YML instead of PHP $lang array, using Zend_Translate

This allows for a more flexible handling of translation sources in various formats. Your own translations can be converted automatically via the "i18n_yml_converter" module. Any modifications to the $lang global variable need to be re-applied via YML, or directly to the new translation adapters.

    'content' => array('My.Entity' => 'My Translation'),
    'locale' => 'en_US',
    'usetranslateadapter' => true

In order to keep backwards compatibility for PHP-based translations (from modules or your own code) without conversion, please add the following to your _config.php. Note that its just necessary if SilverStripe is used in a language other than the default (en_US).

    new Zend_Translate(array(
        'adapter' => 'i18nSSLegacyAdapter',
        'locale' => i18n::default_locale(),
        'disableNotices' => true,
    9 // priority lower than standard translator

Removed "auto-merging" of member records from Member->onBeforeWrite()

Due to security reasons. Please use DataObject->merge() explicitly if this is desired behaviour.

Unit tests require definition of used DataObject and Extension classes

This change was necessary in order to improve performance of the test framework, and avoid rebuilding the database where no database access is required. It also won't build any DataObject subclasses implementing the TestOnly interface, unless these are explicitly noted.

  • SapphireTest->extraDataObjects: List all TestOnly classes relevant to your test class here.
  • SapphireTest->requiredExtensions: Maps DataObject classes to an array of required extensions. Example: array("MyTreeDataObject" => array("Versioned", "Hierarchy"))
  • SapphireTest->illegalExtensions: Does the reverse of requiredExtensions: Removes already applied extensions.

Alternatively, you can enforce database usage by setting SapphireTest->usesDatabase to TRUE in your test class.


Breadcrumbs have been altered to be their own template. In the process of this, the common static SiteTree::$breadcrumbs_delimiter has been removed. To customise breadcrumbs now, create a template from cms/template to your theme or application.

Deprecation API

There is a new deprecation API that generates deprecation notices. Calls to Deprecated methods will only produce errors if the API was deprecated in the release equal to or earlier than the "notification version".

framework/_config.php currently contains a call to throw notices calls to all methods deprecated in 3.0.


If you change the notification version to 3.0.0-dev, then only methods deprecated in older versions (e.g. 2.4) will trigger notices, and the other methods will silently pass. This can be useful if you don't yet have time to remove all calls to deprecated methods.


Deprecated Classes

  • ComplexTableField: Use GridField instead
  • DataObjectDecorator: Use DataExtension instead
  • DataObjectSet: Use DataList instead
  • FileIframeField: Use UploadField
  • HasManyComplexTableField: Use GridField instead
  • ImageField: Use UploadField with $myField->allowedExtensions = array('jpg', 'gif', 'png')
  • ManyManyComplexTableField: Use GridField instead
  • SimpleImageField: Use FileField or UploadField with setAllowedExtensions()
  • TableListField: Use GridField instead

Renamed Classes

  • CMSMainMarkingFilter: Use CMSSiteTreeFilter_Search instead *
  • DataObjectDecorator: Use DataExtension instead (the class doesn't implement the GOF "Decorator" pattern)
  • MySQLFulltextSearchable: Use FulltextSearchable instead

Removed Classes

  • AdvancedSearchForm: Use SearchForm and extend to get similar functionality.
  • Archive, TarballArchive: If you make use of these, copy the classes from 2.4 into your project.
  • AssetTableField: Use GridField with GridFieldConfig_RelationEditor instead
  • ComponentSet: Use ManyManyList or HasManyList
  • CustomRequiredFields: Use RequiredFields
  • DataObjectLog: There is no replacement for this.
  • DataObjectSet: Use ArrayList or DataList instead
  • FieldSet: Use FieldList instead
  • GeoIP: Moved to separate "geoip" module
  • MemberTableField: Use GridField with GridFieldConfig_RelationEditor instead
  • Notifications: If you make use of this, copy the classes from 2.4 into your project.
  • NZGovtPasswordValidator: Moved to "securityextras" module
  • QueuedEmail, QueuedEmailDispatchTask: If you make use of these, copy the classes from 2.4 into your project.
  • RestrictedTextField, UniqueTextField, UniqueRestrictedTextField, AutocompleteTextField, ConfirmedFormAction: Use custom fields instead
  • SQLMap: Use SS_Map instead
  • TreeSelectorField: Use TreeDropdownField instead.
  • XML: Use PHP's built-in SimpleXML instead
Jump to Line
Something went wrong with that request. Please try again.