# 3.0.0 (unreleased) # ## Overview ## * New template engine * New CMS interface design * Image/Link insertion moved into a modal dialog instead of a sidebar * Batch actions on site tree moved to an "Edit Tree" view * "Add pages" dropdown now an "Add new" button which goes to a more descriptive page * Renaming of sapphire to SilverStripe framework * FormField classes now have their own HTML templates * Allow usage of SilverStripe framework without the "cms" module * CMS JavaScript moved to [jQuery.entwine](https://github.com/hafriedlander/jquery.entwine) * CMS stylesheets are generated by SCSS to provide more flexible and robust styling ## Upgrading ## ### sapphire renamed to framework {#sapphire-rename} `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)` ### Object static functions replaced with new Config class {#new-config} Static functions for getting a static variable on the `Object` class have been deprecated, in favour of using the new `Config` class instead. * `Object::set_static('MyClass', 'myvar')` becomes `Config::inst()->update('MyClass', 'myvar', 'myval')` instead. * `Object::addStaticVars('MyClass', array('myvar' => 'myval'))` should be replaced with individual calls to `Config::inst()->update()` instead. * `Object::add_static_var('MyClass', 'myvar', 'myval')` becomes `Config::inst()->update('MyClass', 'myvar', 'myval')` instead. * `Object::set_uninherited('MyClass', 'myvar', 'myval')` becomes `Config::inst()->update('MyClass', 'myvar', 'myval')` instead. Any arrays you pass as values to `update()` will be automatically merged. To replace the variable, call `remove()` first, then call `update()`. * `Object::get_static('MyClass', 'myvar')` becomes `Config::inst()->get('MyClass', 'myvar', Config::FIRST_SET)` * `Object::uninherited_static('MyClass', 'myvar')` becomes `Config::inst()->get('MyClass', 'myvar', Config::UNINHERITED)` * `Object::combined_static('MyClass', 'myvar')` becomes `Config::inst()->get('MyClass', 'myvar')` (no option as third argument) Note the different options for the third parameter of `get()`: * `Config::INHERITED` will only get the configuration set for the specific class, not any of it's parents. * `Config::FIRST_SET` will inherit configuration from parents, but stop on the first class that actually provides a value. * `Config::EXCLUDE_EXTRA_SOURCES` will not use additional static sources (such as those defined on extensions) If you don't set an option, it will get all the values for the static, including inherited ones. This was previously known as `Object::combined_static()`. ### Director static functions deprecated, Director::redirect() and Director::redirectBack() in particular `Director::redirect()` and `Director::redirectBack()` are now marked as deprecated. If you have a `Controller` instance and need to redirect, call `redirect()` or `redirectBack()` on the instance instead, e.g. `$controller->redirect()` or `$controller->redirectBack()`. Most of the time, form action handler methods on a controller need only call `$this->redirect()` or `$this->redirectBack()`. Use `Controller::curr()->redirect()` and `Controller::curr()->redirectBack()` if you need to redirect in contexts where a controller might not be immediately available. ### DataExtension and deprecated extraStatics on extension classes {#extensions} `DataObjectDecorator` has been renamed to `DataExtension`. Any classes that extend `DataObjectDecorator` should now extend `DataExtension` instead. `extraStatics()` on extensions is now deprecated. Instead of using `extraStatics()`, you can simply define static variables on your extension directly. If you need custom logic, e.g. checking for a class before applying the statics on the extension, you can use `add_to_class()` as a replacement to `extraStatics()`. Given the original `extraStatics` function: array( 'Title' => 'Varchar' ); ); } } This would now become a static function `add_to_class`, and calls `update()` with an array instead of returning it. It also needs to call `parent::add_to_class()`: update($class, 'db', array( 'Title' => 'Varchar' )); } parent::add_to_class($class, $extensionClass, $args); } Alternatively, you can define statics on the extension directly, like this: 'Varchar' ); ### New ORM: More flexible and expressive querying via `DataList` {#new-orm-datalist} The new "fluent" syntax to retrieve ORM records allows for a more expressive notation (instead of unnamed arguments). :::php // before DataObject::get('Member', '"FirstName" = \'Sam'\', '"Surname" ASC"); // after Member::get()->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`. :::php // before DataObject::get_one('Member', '"Email" = \'someone@example.com\''); // after Member::get()->filter('Email', 'someone@example.com')->First(); :::php // before DataObject::get_by_id('Member', 5); // after Member::get()->byID(5); 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`: :::php // 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](../../topics/datamodel) for more details. ### New ORM: Changes to manipulation of SQL queries {#new-orm-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: :::php SiteTree::get()->count(); The 3.0 ORM is more intelligent gives you tools you need to create high-performance code without bypassing the ORM: :::php // before echo DB::query("SELECT COUNT(*) FROM \"SiteTree\"")->value(); // after echo SiteTree::get()->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. :::php // before $query = singleton('SiteTree')->extendedSQL('ParentID = 5'); // after $query = SiteTree::get()->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 silverstripe-dev@groups.google.com, 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()`). ### Aggregate changes for partial caching in templates ### `DataObject::Aggregate()` and `DataObject::RelationshipAggregate()` are now deprecated. To replace your deprecated aggregate calls in PHP code, you should query with something like `Member::get()->max('LastEdited')`, that is, calling the aggregate on the `DataList` directly. The same concept applies for replacing `RelationshipAggregate()`, just call the aggregate method on the relationship instead, so something like `Member::get()->Groups()->max('LastEdited')`. For partial caching in templates, the syntax `<% cached Aggregate(Page).Max(LastEdited) %>` has been deprecated. The new syntax is similar, except you use `List()` instead of `Aggregate()`, and the aggregate call `Max()` is now lowercase, as in `max()`. An example of the new syntax is `<% cached List(Page).max(LastEdited) %>`. Check `DataList` class for more aggregate methods to use. ### `SQLQuery` changes ### `SQLQuery` has been changed so direct access to internal properties `$from`, `$select`, `$orderby` is now deprecated. Instead, there are now methods you can call which allow you to get and set SQL clauses instead. * `$from` getter is `getFrom()` and setters `setFrom()` and `addFrom()` * `$select` getter is `getSelect()` and setters `setSelect()` and `addSelect()` * `$where` getter is `getWhere()` and setter `setWhere()` and `addWhere()` * `$orderby` getter is `getOrderBy()` and setter `setOrderBy()` and `addOrderBy()` * `$groupby` getter is `getGroupBy()` and setter `getGroupBy()` and `addGroupBy()` * `$having` getter is `getHaving()` and setter `setHaving()` and `addHaving()` * `$limit` getter is `getLimit()` and setter `setLimit()` * `$distinct` getter is `getDistinct()` and setter `setDistinct()` * `$delete` getter is `getDelete()` and setter `setDelete()` * `$connective` getter is `getConnective()` and settter `setConnective()` * `innerJoin()` has been renamed to `addInnerJoin()` * `leftJoin()` has been renamed to `addLeftJoin()` ### TinyMCE upgraded to 3.5 ### TinyMCE has been upgraded to version 3.5. This change should be transparent to most people upgrading, but if you're using custom plugins for TinyMCE, please ensure they are still working correctly with the new version. If you're upgrading from an SS 3.0 beta, TinyMCE HTML source editor and other popups might be blank. This is caused by the TinyMCE compressor leaving stale cache files in the system temp folder from an earlier version. To resolve this problem, simply delete the `{hash}.gz` files within your temp location (defined by `sys_get_temp_dir()` in PHP.) These cache files will be regenerated next time the CMS is opened. ### InnoDB driver for existing and new tables on MySQL (instead of MyISAM) [innodb]### 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](https://github.com/silverstripe/silverstripe-sphinx), [solr](https://github.com/nyeholt/silverstripe-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`: :::php DataObject::$create_table_options['MySQLDatabase] = 'ENGINE=MyISAM'; As with any SilverStripe upgrade, we recommend database backups before calling `dev/build`. See [mysql.com](http://dev.mysql.com/doc/refman/5.5/en/converting-tables-to-innodb.html) for details on the conversion. Note: MySQL has made InnoDB the default engine in its [5.5 release](http://dev.mysql.com/doc/refman/5.5/en/innodb-storage-engine.html). ### Convert::json2array() changes [raw2json]### 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()`: {"Joe":"Bloggs","Tom":"Jones","My":{"Complicated":"Structure"}} Here's the output from SilverStripe 2.4, with nested JSON as objects: array( 'Joe' => 'Bloggs' 'Tom' => 'Jones', 'My' => stdObject( Complicated => 'Structure' // property on object ) ) Now in SilverStripe 3.x, nested structures are arrays: array( 'Joe' => 'Bloggs', 'Tom' => 'Jones', 'My' => array( 'Complicated' => 'Structure' // key value on nested array ) ) ### GridField: Replacement for TableListField and ComplexTableField [gridfield]### 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 :::php // before $field = new TableListField('Companies', 'Company'); $field->setPageSize(20); // after $field = new GridField('Companies', null, Company::get()); $field->getConfig()->getComponentByType('GridFieldPaginator')->setItemsPerPage(20); Upgrade example: Record listing with view/edit interface :::php // before $field = new ComplexTableField($myController, 'Companies', 'Company'); // after $field = new GridField('Companies', null, Company::get(), GridFieldConfig_RecordEditor::create()); Upgrade example: Relationship editing :::php // 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](/topics/grid-field). ### New template engine [templates]### 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](/reference/templates-upgrading-guide) and the [template reference](/reference/templates) 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](/reference/templates-upgrading-guide) for a list of methods and their replacements. ### New user interface for CMS [ui]### 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](https://github.com/hafriedlander/jquery.entwine) based JavaScript logic. Have a look at the new ["Extending the CMS" guide](../howto/extending-the-cms), ["CSS" guide](../topics/css), ["JavaScript" guide](../topics/javascript) and ["CMS Architecture" guide](/reference/cms-architecture) to get you started. ### New tree library [tree]### The page tree moved from a bespoke tree library to [JSTree](http://jstree.com), 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() [getcmsfields]### 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. ### Changed tab paths in SiteTree->getCMSFields() {#tab-paths} In order to simplify the interface, the `SiteTree->getCMSFields` method now only has one rather than two levels of tabs. This changes the tab paths, affecting any fields you might have added. We have also moved all fields from the "Metadata" tab into the "Main Content" tab. :::php // 2.4 $fields->addFieldToTab('Root.Content.Main', $myField); $fields->addFieldToTab('Root.Content.Metadata', $myOtherField); // 3.0 $fields->addFieldToTab('Root.Main', $myField); $fields->addFieldToTab('Root.Main', $myOtherField); ![Tab paths in 2.4](_images/tab-paths-before.png) ![Tab paths in 3.0](_images/tab-paths-after.png) The old paths are rewritten automatically, but will be deprecated in the next point release. If you are working with tab objects directly in your `FieldSet`, you'll need to update the tab names manually: :::php // 2.4 $fields->fieldByName('Root')->fieldByName('Content')->fieldByName('Main')->push($myField); // 3.0 $fields->fieldByName('Root')->fieldByName('Main')->push($myField); If only a single tab is found in any CMS tabset, it is hidden by default to reduce UI clutter. You still need to address it through the usual tabset methods, as the underlying object structure doesn't change. Once you add more tabs, e.g. to the "Root.Main" tab in `SiteTree`, the tab bar automatically shows. ![Tab paths in 3.0 with a custom tab](_images/tab-paths-customtab.png) ### New `SiteTree::$description` field to describe purpose of a page type [sitetree-description]### 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] 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](/reference/modeladmin) on details for how to achieve the same goals in the new class. ### Stylesheet preprocessing via SCSS and the "compass" module [scss]### CSS files in the `cms` and `framework/admin` modules are now generated through the ["compass" SilverStripe module](http://silverstripe.org/compass-module), which uses the ["Compass" framework](http://compass-style.org/) and the ["SCSS" language](http://sass-lang.com/). 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 {#js-validation} 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 [formfield-classes]### 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 `