3.1.0
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
- New context action "Show children as list" on tree for better management on large sites
- 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
- Dropped support for Internet Explorer 7
- Added support for Internet Explorer 10 (in "classic"/desktop mode)
Framework
- Security: Require ADMIN for
?flush=1
(stop denial of service attacks) (#1692) - Static properties are immutable and private, you must use Config API
- Statics in custom Page classes need to be "private"
$default_cast
is nowText
instead ofHTMLText
, to secure templates from XSS by default- Shortcodes are no longer supported in template files (still works in DB fields and through HTMLText casting)
DataList
andArrayList
are now immutable, they'll return cloned instances on modification- 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
RestfulService
verifies SSL peers by default- UploadField functions on new records
- Editing of relation table data (
$many_many_extraFields
) inGridField
- Optional integration with ImageMagick as a new image manipulation backend
- Support for PHP 5.4's built-in webserver
- Support for Composer dependency manager (also works with 3.0)
- Added support for filtering incoming HTML from TinyMCE (disabled by default, see security)
- Behaviour testing support through Behat, with CMS test coverage (see the SilverStripe Behat Extension for details)
Details
Security: Require ADMIN for ?flush=1
Flushing the various manifests (class, template, config) is performed through a GET
parameter (flush=1
). Since this action requires more server resources than normal requests,
it can facilitate denial-of-service attacks.
To prevent this, main.php now checks and only allows the flush parameter in the following cases:
- The environment is in "dev mode"
- A user is logged in with ADMIN permissions
- An error occurs during startup
This applies to both flush=1
and flush=all
(technically we only check for the existence of any parameter value)
but only through web requests made through main.php - CLI requests, or any other request that goes through
a custom start up script will still process all flush requests as normal.
Upgrading
Statics in custom Page classes need to be "private"
Requires action on every SilverStripe installation.
Typical error message: Access level to ErrorPage::$db must be public
Related to the configuration change described above, many statics in core are now
marked with private
visibility. While PHP allows making variables more visible
(e.g. from "private" to "public"), it complains if you try to restrict visibility in subclasses.
The core framework extends from the Page
class in your own codebase (mysite/
),
which means you need to change those statics to private
yourself.
The same rules apply to controllers subclassd from Page_Controller
.
Before:
:::php
<?php
class Page extends SiteTree {
static $db = array('MyVar' => 'Text');
}
class Page_Controller extends ContentController {
static $allowed_actions = array('myaction');
}
After:
:::php
<?php
class Page extends SiteTree {
private static $db = array('MyVar' => 'Text');
}
class Page_Controller extends ContentController {
private static $allowed_actions = array('myaction');
}
Most statics defined in SiteTree
and DataObject
are affected, for example:
$db
, $has_one
, $has_many
, $many_many
, $defaults
, $allowed_children
.
The same goes for statics defined in ContentController
, e.g. $allowed_actions
.
Classes which are not further extended by the core (e.g. all custom DataObject
subclasses)
are not affected by this change, although we recommend to mark those inherited statics
as private
as well, to make it clear that they should be accessed through the Config API.
default_cast is now Text
In order to reduce the chance of accidentally allowing XSS attacks, the value of default_cast
has been changed in 3.1 from HTMLText to Text. This means that any values used in a template
that haven't been explicitly cast as safe will be escaped (<
replaced with <
etc).
When upgrading, if methods return HTML fragments they need to explicitly cast them as such. This can either be done by returning an HTMLText object, like:
:::php
return DBField::create_field('HTMLText', '<div></div>');
or by defining the casting of the accessor method, like:
:::php
class Page extends SiteTree {
private static $casting = array(
'MyDiv' => 'HTMLText'
)
function MyDiv() {
return '<div></div>';
}
}
SSViewer#process (and as a result ViewableData#renderWith) have been changed to already return explicitly cast HTMLText instances, so functions that return the result of these methods won't have to do any additional casting.
Note that this change means that if code was testing the result via is_string, that is no longer reliable.
Static properties are immutable and private, you must use Config API.
A common SilverStripe pattern is to use a static variable on a class to define a configuration parameter. The configuration system added in SilverStripe 3.0 builds on this by using this static variable as a way of defining the default value.
In SilverStripe 3.0, it was possible to edit this value at run-time and have the change propagate into the
configuration system. This is no longer the case, for performance reasons. We've marked all "configurable"
statics as private
, so you can't set or retrieve their value directly.
When using static setters or getters, the system throws a deprecation warning.
Notable exceptions to this rule are all static setters which accept objects, such as SS_Cache::add_backend()
.
Please change all run-time manipulation of configuration to use Config::inst()->update()
or
$this->config()->update()
. You can keep using procedural configuration through _config.php
through this new notation, although its encouraged to use the (faster) YAML config wherever possible.
For this purpose, we have added a mysite/_config/config.yml
file.
Here's an example on how to rewrite a common _config.php
configuration:
:::php
<?php
global $project;
$project = 'mysite';
global $database;
$database = 'SS_mydb';
require_once('conf/ConfigureFromEnv.php');
SSViewer::set_theme('simple');
if(class_exists('SiteTree')) SiteTree::enable_nested_urls();
if(Director::isLive()) Email::setAdminEmail('support@mydomain.com');
if(is_defined('MY_REDIRECT_EMAILS')) Email::send_all_emails_to('developer@mydomain.com');
SS_Log::add_writer(new SS_LogFileWriter(BASE_PATH . '/mylog.log'), SS_Log::WARN);
if(strpos('Internet Explorer', $_SERVER['HTTP_USER_AGENT']) !== false) {
SSViewer::set_theme('basic');
}
Object::add_extension('Member', 'MyMemberExtension');
The upgraded _config.php
:
:::php
<?php
global $project;
$project = 'mysite';
global $database;
$database = 'SS_mydb';
require_once('conf/ConfigureFromEnv.php');
// Removed SiteTree::enable_nested_urls() since its configured by default
// Requires PHP objects, keep in PHP config
SS_Log::add_writer(new SS_LogFileWriter(BASE_PATH . '/mylog.log'), SS_Log::WARN);
// Non-trivial conditional, keep in PHP config
if(strpos('Internet Explorer', $_SERVER['HTTP_USER_AGENT']) !== false) {
// Overwrites any earlier YAML config
Config::inst()->update('SSViewer'. 'theme', 'basic');
}
The upgraded config.yml
:
:::yml
---
Name: mysite
After: 'framework/*','cms/*'
---
SSViewer:
theme: 'simple'
Member:
extensions:
- MyMemberExtension
---
Only:
environment: 'live'
---
Email:
admin_email: 'support@mydomain.com'
Some examples of changed notations (not exhaustive, there's over a hundred in total):
SSViewer::set_theme()
: UseSSViewer.theme
insteadSecurityAdmin::$hidden_permissions
: UsePermission.hidden_permissions
insteadDirector::setBaseFolder
: UseDirector.alternate_base_folder
insteadDirector::setBaseURL
: UseDirector.alternate_base_url
insteadSSViewer::setOption('rewriteHashlinks', ...)
: UseSSViewer.rewrite_hashlinks
instead
[warning]
Please remember to upgrade the installer project as well, particularly
your .htaccess
or web.config
files. Web access to these sensitive YAML configuration files
needs to be explicitly denied through these configuration files (see the 3.0.5 security release)
for details.
[/warning]
For more information about how to use the config system, see the "Configuration" topic.
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.
:::php
class MyController extends Controller {
// This action is now denied because no $allowed_actions are specified
public function myaction($request) {}
}
You can overwrite the default behaviour on undefined $allowed_actions
to allow all actions,
by setting the RequestHandler.require_allowed_actions
config value to false
(not recommended).
This applies to anything extending RequestHandler
, so please check your Form
and FormField
subclasses as well. Keep in mind, action methods as denoted through FormAction
names should NOT
be mentioned in $allowed_actions
to avoid CSRF issues.
Please review all rules governing allowed actions in the "controller" topic.
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.
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
:::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.
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. 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.
RestfulService verifies SSL peers by default
This makes the implementation "secure by default", by removing
the call to curl_setopt(CURLOPT_SSL_VERIFYPEER, false)
.
Failing to validate SSL peers makes HTTP requests vulnerable to man in the middle attacks.
The underlying curl
library relies on the operating system for the resulting CA certificate
verification. On some systems (mainly Windows), these certificates are not available on
a standard PHP installation, and need to be added manually through CURLOPT_CAINFO
.
Although it is not recommended, you can restore the old insecure behaviour with
the following configuration: RestfulService::set_default_curl_option(CURLOPT_SSL_VERIFYPEER, false)
.
Deprecation API
The Deprecation
API generates deprecation notices to help you future-proof your code.
Calls to ceprecated methods will only produce errors if the API was deprecated in the
release equal to or earlier than the "notification version" (currently set to "3.1.0").
If you change the notification version to 3.1.0-dev, then only methods deprecated in older versions (e.g. 3.0) 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.
Deprecation::notification_version('3.1.0-dev');
On the other hand, if you want to identify which APIs will be removed in the next minor release (3.2.0), you can enable those warnings and future-proof your code already.
Deprecation::notification_version('3.2.0');
Other
TableListField
,ComplexTableField
,TableField
,HasOneComplexTableField
,HasManyComplexTableField
andManyManyComplexTableField
have been removed from the core and placed into a module called "legacytablefields" located at https://github.com/silverstripe-labs/legacytablefieldsprototype.js
andbehaviour.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- Removed
SiteTree.MetaKeywords
since they are irrelevant in terms of SEO (seomoz article) and general page informancy - Removed
SiteTree.MetaTitle
as a means to customise the window title, useSiteTree.Title
instead - Deprecated
Profiler
class, use third-party solutions like xhprof - Removed defunct or unnecessary debug GET parameters:
debug_profile
,debug_memory
,profile_trace
,debug_javascript
,debug_behaviour
- Removed
Member_ProfileForm
, useCMSProfileController
instead SiteTree::$nested_urls
enabled by default. To disable, callSiteTree::disable_nested_urls()
.- Removed CMS permission checks from
File->canEdit()
andFile->canDelete()
. If you have unsecured controllers relying on these permissions, please override them through aDataExtension
. - Moved email bounce handling to new
"emailbouncehandler" module,
including
Email_BounceHandler
andEmail_BounceRecord
classes, as well as theMember->Bounced
property. - Deprecated global email methods
htmlEmail()
andplaintextEmail
, as well as various email helper methods likeencodeMultipart()
. Use theEmail
API, or theMailer
class where applicable. - Removed non-functional
$inlineImages
option for sending emails - Removed support for keyed arrays in
SelectionGroup
, use newSelectionGroup_Item
object to populate the list instead (see API docs). FormField->setDescription()
now renders in a<span class="description">
by default, rather than atitle
attribute * RemovedForm->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. UseDataList#alterDataQuery
instead to modify dataQuery in a safe manner.ScheduledTask
,QuarterHourlyTask
,HourlyTask
,DailyTask
,MonthlyTask
,WeeklyTask
andYearlyTask
are deprecated, please extend fromBuildTask
orCliController
, and invoke them in self-defined frequencies through Unix cronjobs etc.i18n::$common_locales
andi18n::$common_languages
are now accessed via the Config API, and contain associative rather than indexed arrays. Before:array('de_DE' => array('German', 'Deutsch'))
, After:array('de_DE' => array('name' => 'German', 'native' => 'Deutsch'))
.SSViewer::current_custom_theme()
has been replaced with theSSViewer.theme_enabled
configuration setting. Please use it to toggle theme behaviour rather than relying on the custom theme being set in the (now deprecated)SSViewer::set_theme()
call.- Scaffolded
DateField
,TimeField
andDatetimeField
form field instances automatically include formatting hints as placeholders and description text below the field itself. If you change the date/time format of those fields, you need to adjust the hints. To remove the hints, usesetDescription(null)
andsetAttribute('placeholder', null)
. - Changed the way FreeStrings in
SSTemplateParser
are recognized, they will now also break on inequality operators (<
,>
). If you use inequality operators in free strings in comparisions like<% if Some<String == Some>Other>String %>...<% end_if %>
you have to replace them with explicitly markes strings like<% if "Some<String" == "Some>Other>String" %>...<% end_if %>
. This change was necessary in order to support inequality operators in comparisons in templates - Hard limit displayed pages in the CMS tree to
500
, and the number of direct children to250
, to avoid excessive resource usage. Configure throughHierarchy.node_threshold_total
andHierarchy.node_threshold_leaf
. Set to0
to show tree unrestricted. Object
now hasbeforeExtending
andafterExtending
to inject behaviour around method extension.DataObject
also hasbeforeUpdateCMSFields
to insert fields between automatic scaffolding and extension byupdateCMSFields
. See the DataExtension Reference for more information.- Magic quotes is now deprecated. Will trigger user_error on live sites, as well as an error on new installs
- Support for Apache 1.x is removed.
- Forms created in the CMS should now be instances of a new
CMSForm
class, and have the CMS controller's response negotiator passed into them. Example:$form = new CMSForm(...); $form->setResponseNegotiator($this->getResponseNegotiator());