# 3.1.0 (unreleased) ## Overview ## ### CMS * "Split view" editing with side-by-side preview of the edited website * Resizing of preview to common screen widths ("desktop", "tablet" and "smartphone") * Decluttered "Edit Page" buttons by moving minor actions into a "more options" panel * Auto-detect CMS changes and highlight the save button for better informancy * Display "last edited" and "last published" data for pages in CMS * CMS form fields now support help text through `setDescription()`, both inline and as tooltips * Removed SiteTree "MetaTitle" and "MetaKeywords" fields * More legible and simplified tab and menu styling in the CMS ### Framework * `DataList` and `ArrayList` are now immutable, they'll return cloned instances on modification * Behaviour testing support through [Behat](http://behat.org), with CMS test coverage (see the [SilverStripe Behat Extension]() for details) * Removed legacy table APIs (e.g. `TableListField`), use GridField instead * Deny URL access if `Controller::$allowed_actions` is undefined * Removed support for "*" rules in `Controller::$allowed_actions` * Removed support for overriding rules on parent classes through `Controller::$allowed_actions` * Editing of relation table data (`$many_many_extraFields`) in `GridField` * Optional integration with ImageMagick as a new image manipulation backend * Support for PHP 5.4's built-in webserver * Support for [Composer](http://getcomposer.org) dependency manager (also works with 3.0) ## Upgrading ### Deny URL access if `Controller::$allowed_actions` is undefined or empty array In order to make controller access checks more consistent and easier to understand, the routing will require definition of `$allowed_actions` on your own `Controller` subclasses if they contain any actions accessible through URLs, or any forms. :::php class MyController extends Controller { // This action is now denied because no $allowed_actions are specified public function myaction($request) {} } Please review all rules governing allowed actions in the ["controller" topic](/topics/controller). ### Removed support for "*" rules in `Controller::$allowed_actions` The wildcard ('*') character allowed to define fallback rules in case they weren't explicitly defined. This caused a lot of confusion, particularly around inherited rules. We've decided to remove the feature, you'll need to specificy each accessible action individually. :::php class MyController extends Controller { public static $allowed_actions = array('*' => 'ADMIN'); // Always denied because not explicitly listed in $allowed_actions public function myaction($request) {} // Always denied because not explicitly listed in $allowed_actions public function myotheraction($request) {} } Please review all rules governing allowed actions in the ["controller" topic](/topics/controller). ### Removed support for overriding rules on parent classes through `Controller::$allowed_actions` Since 3.1, the `$allowed_actions` definitions only apply to methods defined on the class they're also defined on. Overriding inherited access definitions is no longer possible. :::php class MyController extends Controller { public static $allowed_actions = array('myaction' => 'ADMIN'); public function myaction($request) {} } class MySubController extends MyController { // No longer works public static $allowed_actions = array('myaction' => 'CMS_ACCESS_CMSMAIN'); } This also applies for custom implementations of `handleAction()` and `handleRequest()`, which now have to be listed in the `$allowed_actions` specifically. It also restricts `Extension` classes applied to controllers, which now can only grant or deny access or methods they define themselves. New approach with the [Config API](/topics/configuration) :::php class MySubController extends MyController { public function init() { parent::init(); Config::inst()->update('MyController', 'allowed_actions', array('myaction' => 'CMS_ACCESS_CMSMAIN') ); } } Please review all rules governing allowed actions in the ["controller" topic](/topics/controller). ### Grouped CMS Buttons The CMS buttons are now grouped, in order to hide minor actions by default and declutter the interface. This required changing the form field structure from a simple `FieldList` to a `FieldList` which contains a `CompositeField` for all "major actions", and a `TabSet` with a single tab for all "minor actions". If you have previously added, removed or altered built-in CMS actions in any way, you'll need to adjust your code. :::php class MyPage extends Page { function getCMSActions() { $actions = parent::getCMSActions(); // Inserting a new toplevel action (old) $actions->push(new FormAction('MyAction')); // Inserting a new toplevel action (new) $actions->insertAfter(new FormAction('MyAction'), 'MajorActions'); // Removing an action, both toplevel and nested (no change required) $actions->removeByName('action_unpublish'); // Inserting a new minor action (new) $actions->addFieldToTab( 'Root.ActionMenus.MoreOptions', new FormAction('MyMinorAction') ); // Finding a toplevel action (no change required) $match = $actions->dataFieldByName('action_save'); // Finding a nested action (new) $match = $actions->fieldByName('ActionMenus.MoreOptions') ->fieldByName('action_MyMinorAction'); return $actions; } } ### GridField and ModelAdmin Permission Checks `GridFieldDetailForm` now checks for `canEdit()` and `canDelete()` permissions on your model. `GridFieldAddNewButton` checks `canCreate()`. The default implementation requires `ADMIN` permissions. You'll need to loosen those permissions if you want other users with CMS access to interact with your data. Since `GridField` is used in `ModelAdmin`, this change will affect both classes. Example: Require "CMS: Pages section" access :::php class MyModel extends DataObject { public function canView($member = null) { return Permission::check('CMS_ACCESS_CMSMain', 'any', $member); } public function canEdit($member = null) { return Permission::check('CMS_ACCESS_CMSMain', 'any', $member); } public function canDelete($member = null) { return Permission::check('CMS_ACCESS_CMSMain', 'any', $member); } public function canCreate($member = null) { return Permission::check('CMS_ACCESS_CMSMain', 'any', $member); } You can also implement [custom permission codes](/topics/permissions). For 3.1.0 stable, we aim to further simplify the permission definitions, in order to reduce the boilerplate code required to get a model editable in the CMS. Note: GridField is already relying on the permission checks performed through the CMS controllers, providing a simple level of security. ### Other * `TableListField`, `ComplexTableField`, `TableField`, `HasOneComplexTableField`, `HasManyComplexTableField` and `ManyManyComplexTableField` have been removed from the core and placed into a module called "legacytablefields" located at https://github.com/silverstripe-labs/legacytablefields * `prototype.js` and `behaviour.js` have been removed from the core, they are no longer used. If you have custom code relying on these two libraries, please update your code to include the files yourself * `Object::has_extension()` and `Object::add_extension()` deprecated in favour of using late static binding, please use `{class}::has_extension()` and `{class}::add_extension()` instead, where {class} is the class name of your DataObject class. * Removed `SiteTree.MetaKeywords` since they are irrelevant in terms of SEO ([seomoz article](http://www.mattcutts.com/blog/keywords-meta-tag-in-web-search/)) and general page informancy * Removed `SiteTree.MetaTitle` as a means to customize the window title, use `SiteTree.Title` instead * Deprecated `Profiler` class, use third-party solutions like [xhprof](https://github.com/facebook/xhprof/) * Removed defunct or unnecessary debug GET parameters: `debug_profile`, `debug_memory`, `profile_trace`, `debug_javascript`, `debug_behaviour` * Removed `Member_ProfileForm`, use `CMSProfileController` instead * `SiteTree::$nested_urls` enabled by default. To disable, call `SiteTree::disable_nested_urls()`. * Removed CMS permission checks from `File->canEdit()` and `File->canDelete()`. If you have unsecured controllers relying on these permissions, please override them through a `DataExtension`. * Moved email bounce handling to new ["emailbouncehandler" module](https://github.com/silverstripe-labs/silverstripe-emailbouncehandler), including `Email_BounceHandler` and `Email_BounceRecord` classes, as well as the `Member->Bounced` property. * Deprecated global email methods `htmlEmail()` and `plaintextEmail`, as well as various email helper methods like `encodeMultipart()`. Use the `Email` API, or the `Mailer` class where applicable. * Removed non-functional `$inlineImages` option for sending emails * Removed support for keyed arrays in `SelectionGroup`, use new `SelectionGroup_Item` object to populate the list instead (see [API docs](api:SelectionGroup)). * `FormField->setDescription()` now renders in a `` by default, rather than a `title` attribute * Removed `Form->Name()`: Use getName() * Removed `FormField->setContainerFieldSet()`: Use setContainerFieldList() * Removed `FormField->rootFieldSet()`: Use rootFieldList() * Removed `Group::map()`: Use DataList::("Group")->map() * Removed `Member->generateAutologinHash()`: Tokens are no longer saved directly into the database in plaintext. Use the return value of the Member::generateAutologinTokenAndHash to get the token * Removed `Member->sendInfo()`: use Member_ChangePasswordEmail or Member_ForgotPasswordEmail directly * Removed `SQLMap::map()`: Use DataList::("Member")->map() * Removed `SQLMap::mapInGroups()`: Use Member::map_in_groups() * Removed `PasswordEncryptor::register()/unregister()`: Use config system instead * Methods on DataList and ArrayList that used to both modify the existing list & return a new version now just return a new version. Make sure you change statements like `$list->filter(...)` to $`list = $list->filter(...)` for these methods: - `ArrayList#reverse` - `ArrayList#sort` - `ArrayList#filter` - `ArrayList#exclude` - `DataList#where` - `DataList#limit` - `DataList#sort` - `DataList#addFilter` - `DataList#applyFilterContext` - `DataList#innerJoin` - `DataList#leftJoin` - `DataList#find` - `DataList#byIDs` - `DataList#reverse` * `DataList#dataQuery` has been changed to return a clone of the query, and so can't be used to modify the list's query directly. Use `DataList#alterDataQuery` instead to modify dataQuery in a safe manner. * `ScheduledTask`, `QuarterHourlyTask`, `HourlyTask`, `DailyTask`, `MonthlyTask`, `WeeklyTask` and `YearlyTask` are deprecated, please extend from `BuildTask` or `CliController`, and invoke them in self-defined frequencies through Unix cronjobs etc.