Version 3 end of life
This version of Silverstripe CMS will not recieve any additional bug fixes or documentation updates. Go to documentation for the most recent stable version.

3.2.0 beta1

Contents

Major changes

  • Minimum PHP version raised to 5.3.3
  • Introduction of new parameterised ORM
  • Default support for PDO
  • Moved SS_Report and ReportAdmin out to a separate module. If you're using composer or downloading a release, this module should be included for you. Otherwise, you'll need to include the module yourself (https://github.com/silverstripe-labs/silverstripe-reports)
  • Moved SiteConfig also out to its own module. This will be included by default if you include the CMS module. (https://github.com/silverstripe/silverstripe-siteconfig)
  • Implementation of new "Archive" concept for page removal, which supercedes "delete from draft". Where deletion removed pages only from draft, archiving removes from both draft and live simultaneously.
  • Most of the Image manipulation methods have been renamed

Deprecated classes/methods removed

  • ToggleField was deprecated in 3.1, and has been removed. Use custom Javascript with ReadonlyField instead.
  • ExactMatchMultiFilter was deprecated in 3.1, and has been removed. Use ExactMatchFilter instead.
  • NegationFilter was deprecated in 3.1, and has been removed. Use ExactMatchFilter:not instead.
  • StartsWithMultiFilter was deprecated in 3.1, and has been removed. Use StartsWithFilter instead.
  • ScheduledTask and subclasses like DailyTask were deprecated in 3.1, and have been removed. Use custom code instead, or a module like silverstripe-crontask: https://github.com/silverstripe-labs/silverstripe-crontask
  • Cookie::forceExpiry() was removed. Use Cookie::force_expiry() instead
  • Object statics removal: get_static(), set_static(), uninherited_static(), combined_static(), addStaticVars() and add_static_var() removed. Use the Config methods instead.
  • GD methods removed: setGD(), getGD(), hasGD(). Use setImageResource(), getImageResource(), and hasImageResource() instead
  • DataExtension::get_extra_config() removed, no longer supports extraStatics or extraDBFields. Define your statics on the class directly.
  • DataList::getRange() removed. Use limit() instead.
  • SQLMap removed. Call map() on a DataList or use SS_Map directly instead.
  • Profiler removed. Use xhprof or xdebug for profiling instead.
  • Aggregate removed. Call aggregate methods on a DataList instead e.g. Member::get()->max('LastEdited')
  • MySQLDatabase::set_connection_charset() removed. Use MySQLDatabase.connection_charset config setting instead
  • SQLConditionalExpression/SQLQuery select(), limit(), orderby(), groupby(), having(), from(), leftjoin(), innerjoin(), where() and whereAny() removed. Use set*() and add*() methods instead.
  • Template <% control $MyList %> syntax removed. Use <% loop $MyList %> instead.
  • Removed Member.LastVisited and Member.NumVisits properties, see Howto: Track Member Logins to restore functionality as custom code

New and changed API

  • Implementation of a parameterised query framework eliminating the need to manually escape variables for use in SQL queries. This has been integrated into nearly every level of the database ORM.
  • Refactor of database connectivity classes into separate components linked together through dependency injection
  • Refactor of SQLQuery into separate objects for each query type: SQLSelect, SQLDelete, SQLUpdate and SQLInsert
  • PDO is now a standard connector, and is available for all database interfaces
  • DataObject::doValidate() method visibility added to access DataObject::validate externally
  • NumericField now uses HTML5 "number" type instead of "text"
  • UploadField "Select from files" shows files in all folders by default
  • UploadField won't display an overwrite warning unless Upload::replaceFile is true
  • HtmlEditorField no longer substitutes <blockquote /> for indented text
  • ClassInfo::dataClassesFor now returns classes which should have tables, regardless of whether those tables actually exist.
  • SS_Filterable, SS_Limitable and SS_Sortable now explicitly extend SS_List
  • Convert::html2raw no longer wraps text by default and can decode single quotes.
  • Mailer no longer calls xml2raw on all email subject line, and now must be passed in via plain text.
  • ErrorControlChain now supports reload on exceptions
  • FormField::validate now requires an instance of Validator
  • API: Removed URL routing by controller name
  • Security: The multiple authenticator login page should now be styled manually - i.e. without the default jQuery UI layout. A new template, Security_MultiAuthenticatorLogin.ss is available.
  • Security: This controller's templates can be customised by overriding the getTemplatesFor function.
  • API: Form and FormField ID attributes rewritten.
  • SearchForm::getSearchQuery no longer pre-escapes search keywords and must be cast in your template
  • Helper function DB::placeholders can be used to generate a comma separated list of placeholders useful for creating "WHERE ... IN (?,...)" SQL fragments
  • Implemented Convert::symbol2sql to safely encode database and table names and identifiers. E.g. Convert::symbol2sql('table.column') => '"table"."column"';
  • Convert::raw2sql may now quote the escaped value, as well as safely escape it, according to the current database adaptor's preference.
  • DB class has been updated and many static methods have been renamed to conform to coding convention.

    • Renamed API:

      • affectedRows -> affected_rows
      • checkAndRepairTable -> check_and_repair_table
      • createDatabase -> create_database
      • createField -> create_field
      • createTable -> create_table
      • dontRequireField -> dont_require_field
      • dontRequireTable -> dont_require_table
      • fieldList -> field_list
      • getConn -> get_conn
      • getGeneratedID -> get_generated_id
      • isActive -> is_active
      • requireField -> require_field
      • requireIndex -> require_index
      • requireTable -> require_table
      • setConn -> set_conn
      • tableList -> table_list
    • Deprecated API:

      • getConnect (Was placeholder for PDO connection string building code, but is made redundant after the PDOConnector being fully abstracted)
    • New API:

      • build_sql - Hook into new SQL generation code
      • get_connector (Nothing to do with getConnect)
      • get_schema
      • placeholders
      • prepared_query
  • SS_Database class has been updated and many functions have been deprecated, or refactored into the various other database classes. Most of the database management classes remain in the database controller, due to individual databases (changing, creating of, etc) varying quite a lot from API to API, but schema updates within a database itself is managed by an attached DBSchemaManager

    • Refactored into DBSchemaManager:

      • createTable
      • alterTable
      • renameTable
      • createField
      • renameField
      • fieldList
      • tableList
      • hasTable
      • enumValuesForField
      • beginSchemaUpdate and endSchemaUpdate -> Use schemaUpdate with a callback
      • cancelSchemaUpdate
      • isSchemaUpdating
      • doesSchemaNeedUpdating
      • transCreateTable
      • transAlterTable
      • transCreateField
      • transCreateField
      • transCreateIndex
      • transAlterField
      • transAlterIndex
      • requireTable
      • dontRequireTable
      • requireIndex
      • hasField
      • requireField
      • dontRequireField
    • Refactored into DBQueryBuilder

      • sqlQueryToString
    • Deprecated:

      • getConnect - Was intended for use with PDO, but was never implemented, and is now redundant, now that there is a stand-alone PDOConnector
      • prepStringForDB - Use quoteString instead
      • dropDatabase - Use dropSelectedDatabase
      • createDatabase - Use selectDatabase with the second parameter set to true instead
      • allDatabaseNames - Use databaseList instead
      • currentDatabase - Use getSelectedDatabase instead
      • addslashes - Use escapeString instead
  • LogErrorEmailFormatter now better displays SQL queries in errors by respecting line breaks
  • Installer has been majorly upgraded to handle the new database configuration options and additional PDO functionality.
  • Created SS_DatabaseException to emit database errors. Query information such as SQL and any relevant parameters may be used by error handling user code that catches this exception.
  • The SQLConditionGroup interface has been created to represent dynamically evaluated SQL conditions. This may be used to wrap a class that generates a custom SQL clause(s) to be evaluated at the time of execution.
  • DataObject constants CHANGE_NONE, CHANGE_STRICT, and CHANGE_VALUE have been created to provide more verbosity to field modification detection. This replaces the use of various magic numbers with the same meaning.
  • create_table_options now uses constants as API specific filters rather than strings. This is in order to promote better referencing of elements across the codebase. See FulltextSearchable->enable for example.
  • $FromEnd iterator variable now available in templates.
  • Support for multiple HtmlEditorConfigs on the same page.
  • Object::singleton() method for better type-friendly singleton generation
  • New Image methods CropWidth and CropHeight added
  • 'Max' versions of Image methods introduced to prevent up-sampling
  • Update Image method names in PHP code and templates

    • SetRatioSize -> Fit
    • CroppedImage -> Fill
    • PaddedImage -> Pad
    • SetSize -> Pad
    • SetWidth -> ScaleWidth
    • SetHeight -> ScaleHeight

Bugfixes

  • Reduced database regeneration chances on subsequent rebuilds after the initial dev/build
  • Elimination of various SQL injection vulnerability points
  • DataObject::writeComponents() now called correctly during DataObject::write()
  • Fixed missing theme declaration in installer
  • Fixed incorrect use of non-existing exception classes (e.g. HTTPResponse_exception)
  • GridState fixed to distinguish between check for missing values, and creation of nested state values, in order to prevent non-empty values being returned for missing keys. This was breaking DataObject::get_by_id by passing in an object for the ID.
  • Fixed order of File fulltext searchable fields to use same order as actual fields. This is required to prevent unnecessary rebuild of MS SQL databases when fulltext searching is enabled.
  • In the past E_RECOVERABLE_ERROR would be ignored, and now correctly appear as warnings.

Upgrading Notes

UploadField "Select from files" shows files in all folders by default

In order to list files in a single folder by default (previous default behaviour), use setDisplayFolderName() with a folder path relative to assets/:

:::php
UploadField::create('MyField')->setDisplayFolderName('Uploads');

UploadField won't display an overwrite warning unless Upload:replaceFile is true

The configuration setting UploadField:overwriteWarning is dependent on Upload:replaceFile which is set to false by default.

To display a warning before overwriting a file:

Via config:

::yaml
Upload:
  # Replace an existing file rather than renaming the new one.
  replaceFile: true
UploadField:
  # Warning before overwriting existing file (only relevant when Upload: replaceFile is true)
  overwriteWarning: true

Or per instance:

::php
$uploadField->getUpload()->setReplaceFile(true);
$uploadField->setOverwriteWarning(true);

File.allowed_extensions restrictions

Certain file types such as swf, html, htm, xhtml and xml have been removed from the list of allowable file uploads. If your application requires the ability to upload these, you will need to append these to the File.allowed_extensions config as necessary. Also if uploading other file types, it's necessary to ensure that File.allowed_extensions includes that extension, as extensions passed to UploadField will be filtered against this list.

Removed format detection in i18n::$date_format and i18n::$time_format

Localized dates cause inconsistencies in client-side vs. server-side formatting and validation, particularly in abbreviated month names. The default date format has been changed to "yyyy-MM-dd" (e.g. 2014-12-31). New users will continue to have the option for a localized date format in their profile (based on their chosen locale). If you have existing users with Member.DateFormat set to a format including "MMM" or "MMMM", consider deleting those formats to fall back to the global (and more stable) default.

Cookies set via Cookie::set() are now HTTP only by default

Cookies set through Cookie::set() now default to "HTTP only". This means that scripting languages like JavaScript won't be able to read them.

To set it back to be non-HTTP only, you need to set the $httpOnly argument to false when calling Cookie::set().

API: Removed URL routing by controller name

The auto-routing of controller class names to URL endpoints has been removed (rule: '$Controller//$Action/$ID/$OtherID': '*'). This increases clarity in routing since it makes URL entpoints explicit, and thereby simplifies system and security reviews.

Please access any custom controllers exclusively through self-defined routes. For controllers extending Page_Controller, simply use the provided page URLs.

:::php
class MyController extends Controller {
	static $allowed_actions = array('myaction');
	public function myaction($request) {
		// ...
	}
}

Create a new file mysite/_config/routes.yml (read more about the config format). Your controller is now available on http://yourdomain.com/my-controller-endpoint, after refreshing the configuration cache through ?flush=all.

:::yaml
---
Name: my-routes
After: framework/routes#coreroutes
---
Director:
  rules:
    'my-controller-endpoint//$Action' : 'MyController'

The auto-routing is still in place for unit tests, since its a frequently used feature there. Although we advise against it, you can reinstate the old behaviour through a director rule:

:::yaml
---
Name: my-routes
After: framework/routes#coreroutes
---
Director:
  rules:
    '$Controller//$Action/$ID/$OtherID':  '*'

API: Default Form and FormField ID attributes rewritten.

Previously the automatic generation of ID attributes throughout the Form API could generate invalid ID values such as Password[ConfirmedPassword] as well as duplicate ID values between forms on the same page. For example, if you created a field called Email on more than one form on the page, the resulting HTML would have multiple instances of #Email. ID should be a unique identifier for a single element within the document.

This rewrite has several angles, each of which is described below. If you rely on ID values in your CSS files, Javascript code or application unit tests you will need to update your code.

Conversion of invalid form ID values

ID attributes on Form and Form Fields will now follow the HTML specification. Generating ID attributes is now handled by the new FormTemplateHelper class.

Please test each of your existing site forms to ensure that they work correctly in particular, javascript and css styles which rely on specific ID values.

Invalid ID attributes stripped

ID attributes will now be run through Convert::raw2htmlid. Invalid characters are replaced with a single underscore character. Duplicate, leading and trailing underscores are removed. Custom ID attributes (set through setHTMLID) will not be altered.

Before:

:::html
<form id="MyForm[Form]"
	<div id="MyForm[Form][ID]">

Now:

:::html
<form id="MyForm_Form">
	<div id="MyForm_Form_ID">

Namespaced FormField ID's

Form Field ID values will now be namespaced with the parent form ID.

Before:

:::html
<div id="Email">

Now:

:::html
<div id="MyForm_Email">

FormField wrapper containers suffixed with _Holder

Previously both the container div and FormField tag shared the same ID in certain cases. Now, the wrapper div in the default FormField template will be suffixed with _Holder.

Before:

:::html
<div id="Email">
	<input id="Email" />

After:

:::html
<div id="MyForm_Email_Holder"
	<input id="MyForm_Email" />

Reverting to the old specification

If upgrading existing forms is not feasible, developers can opt out of the new specifications by using the FormTemplateHelper_Pre32 class rules instead of the default ones.

mysite/config/_config.yml:

:::yaml
Injector:
  FormTemplateHelper:
    class: FormTemplateHelper_Pre32

Update code that uses SQLQuery

SQLQuery has been changed. Previously this class was used for both selecting and deleting, but deletion is now handled by the new SQLDelete class.

Additionally, 3.2 now provides SQLUpdate and SQLInsert to generate parameterised query friendly data updates.

SQLQuery, SQLDelete and SQLUpdate all inherit from SQLConditionalExpression, which implements toSelect, toDelete, and toUpdate to generate basic transformations between query types.

In the past SQLQuery->setDelete(true) would be used to turn a select into a delete, although now a new SQLDelete object should be created from the original SQLQuery.

Before:

:::php
<?php
$query = new SQLQuery('*');
$query->setFrom('"SiteTree"');
$query->setWhere('"SiteTree"."ShowInMenus" = 0');
$query->setDelete(true);
$query->execute();

After:

:::php
<?php
$query = SQLDelete::create()
	->setFrom('"SiteTree"')
	->setWhere(array('"SiteTree"."ShowInMenus"' => 0));
$query->execute();

When working with SQLQuery passed into user code, it is advisable to strictly cast it into either a SQLSelect or SQLDelete. This can be done by using the new SQLQuery::toAppropriateExpression() method, which will automatically convert to the correct type based on whether the SQLQuery is set to delete or not.

If a SQLQuery is not converted, then the result of getWhere will not be parameterised. This is because user code written for 3.1 expects this list to be a flat array of strings. This format is inherently unsafe, and should be avoided where possible.

:::php
<?php
public function augmentSQL(SQLQuery &$query) {
	$query->getWhere(); // Will be flattened (unsafe 3.1 compatible format)
	$expression = $query->toAppropriateExpression(); // Either SQLSelect or SQLDelete
	$expression->getWhere(); // Will be parameterised (preferred 3.2 compatible format)
}

Alternatively:

:::php
<?php
$query = SQLQuery::create()
	->setFrom('"SiteTree"')
	->setWhere(array('"SiteTree"."ShowInMenus"' => 0))
	->setDelete(true)
	->toAppropriateExpression();
$query->execute();

Update code that interacts with SQL strings to use parameters

The Silverstripe ORM (object relation model) has moved from using escaped SQL strings to query the database, to a combination of parameterised SQL expressions alongside a related list of parameter values. As a result of this, it is necessary to assume that any SQLQuery object may, and will usually, have un-injected parameters.

All database queries performed through DataList, DataQuery and SQLQuery will continue to work, as will those through DataObject::get() (which returns a filterable DataList). However, any conditional expression that includes values escaped with Convert::raw2sql() should use the new standard syntax. This new querying standard method enforces a much higher level of security than was previously available, and all code using manual escaping should be upgraded.

See the security topic for details on why this is necessary, or the databamodel topic for more information.

As a result of this upgrade there are now very few cases where Convert::raw2sql needs to be used.

Examples of areas where queries should be upgraded are below:

1. Querying the database directly through DB, including non-SELECT queries

Before:

:::php
<?php

// Note: No deprecation notices will be caused here
DB::query("UPDATE \"SiteTree\" SET \"Title\" LIKE '%" . Convert::raw2sql($myTitle) . "%' WHERE \"ID\" = 1");
$myPages = DB::query(sprintf('SELECT "ID" FROM "MyObject" WHERE "Title" = \'%s\'', Convert::raw2sql($parentTitle)));

After:

:::php
<?php

DB::prepared_query(
	'UPDATE "SiteTree" SET "Title" LIKE ? WHERE "ID" = ?',
	array("%{$myTitle}%", 1)
);
$myPages = DB::prepared_query(
	'SELECT "ID" FROM "MyObject" WHERE "Title" = ?',
	array($parentTitle)
);

2. Querying the database through DataList, DataQuery, SQLQuery, and DataObject

Before:

:::php
<?php

$items = DataObject::get_one('MyObject', '"Details" = \''.Convert::raw2sql($details).'\'');
$things = MyObject::get()->where('"Name" = \''.Convert::raw2sql($name).'\'');
$list = DataList::create('Banner')->where(array(
	'"ParentID" IS NOT NULL',
	'"Title" = \'' . Convert::raw2sql($title) . '\''
);

After:

:::php
<?php

$items = DataObject::get_one('MyObject', array('"MyObject"."Details"' => $details));
$things = MyObject::get()->where(array('"MyObject"."Name" = ?' => $name));
$list = DataList::create('Banner')->where(array(
	'"ParentID" IS NOT NULL',
	'"Title" = ?', $title
);

3. Interaction with DataList::sql(), DataQuery::sql(), SQLQuery::sql(), or SQLQuery::getJoins() methods

The place where legacy code would almost certainly fail is any code that calls DataList::sql,DataQuery::sql,SQLQuery::sqlorSQLQuery::getJoins()`, as the api requires that user code passes in an argument here to retrieve SQL parameters by value.

User code that assumes parameterless queries will likely fail, and need to be updated to handle this case properly.

Before:

:::php
<?php

// Generate query
$argument = 'whatever';
$query = SQLQuery::create()
	->setFrom('"SiteTree"')
	->setWhere(array("\"SiteTree\".\"Title\" LIKE '" . Convert::raw2sql($argument) . "'"));

// Inspect elements of the query
$sql = $query->sql();
$sql = preg_replace('/LIKE \'(.+)\'/', 'LIKE \'%${1}%\'', $sql); // Adds %% around the argument

// Pass new query to database connector
DB::query($sql);

After:

:::php
<?php

// Generate query
$argument = 'whatever';
$query = SQLQuery::create()
	->setFrom('"SiteTree"')
	->setWhere(array('"SiteTree"."Title" LIKE ?' => $argument));

// Inspect elements of the query
$sql = $query->sql($parameters);
foreach($parameters as $key => $value) {
	// Adds %% around arguments
	$parameters[$key] = "%{$value}%";
}

// Pass new query to database connector
// Note that DB::query($sql) would fail, as it would contain ? with missing parameters
DB::prepared_query($sql, $parameters);

Also note that the parameters may not be a single level array, as certain values may be forced to be cast as a certain type (where supported by the current API).

E.g.

:::php
<?php

$parameters = array(
	10,
	array('value' => 0, 'type' => 'boolean') // May also contain other database API specific options
)
DB::prepared_query('DELETE FROM "MyObject" WHERE ParentID = ? OR IsValid = ?', $parameters);

4. Interaction with SQLSelect::getWhere() method

The SQLSelect class supercedes the old SQLQuery object for performing select queries. Although both implement the getWhere() method, the results returned by SQLSelect::getWhere() will be parameterised while SQLQuery::getWhere() will be a flattened array of strings.

SQLSelect::getWhere() returns a list of conditions, each of which is an associative array mapping the condition string to a list of parameters provided.

Before:

:::php
<?php

// Increment value of a single condition
$query = new SQLQuery(/*...*/);
$conditions = $query->getWhere();
$new = array();
foreach($conditions as $condition) {
	if(preg_match('/\"Count\" = (?<count>\d+)/', $condition, $matches)) {
		$condition = '"Count" = '.($matches['count'] + 1);
	}
	$new[] = $condition;
}
$query->setWhere($new);

After:

:::php
// Increment value of a single condition
$query = new SQLSelect(/*...*/);
$conditions = $query->getWhere();
$new = array();
foreach($conditions as $condition) {
	// $condition will be a single length array
	foreach($condition as $predicate => $parameters) {
		if('"Count" = ?' ===  $predicate) {
			$parameters[0] = $parameters[0] + 1;
		}
		$new[] = array($predicate => $parameters);
	}
}
$query->setWhere($new);

In cases where user code will manipulate the results of this value, it may be useful to replace this method call with the new getWhereParameterised($parameters) method, where applicable.

This method returns a manipulated form of the where conditions stored by the query, so that it matches the list of strings similar to the old 3.1 SQLQuery::getWhere() behaviour. Additionally, the list of parameters is safely extracted, flattened, and can be passed out through the $parameters argument which is passed by reference.

Before:

:::php
public function filtersOnColumn($query, $column) {
	$regexp = '/^(.*\.)?("|`)?' . $column . ' ("|`)?\s?=/';
	foreach($this->getWhere() as $predicate) {
		if(preg_match($regexp, $predicate)) return true;
	}
	return false;
}

After:

:::php
public function filtersOnColumn($query, $column) {
	$regexp = '/^(.*\.)?("|`)?' . $column . ' ("|`)?\s?=/';
	foreach($this->getWhereParameterised($parameters) as $predicate) {
		if(preg_match($regexp, $predicate)) return true;
	}
	return false;
}

5. Update code that interacts with the DB schema

Updating database schema is now done by updateSchema with a callback, rather than relying on user code to call beginSchemaUpdate and endSchemaUpdate around the call.

Since the schema management object is separate from the database controller you interact with it via DB::get_schema instead of DB::get_conn (previously named DB::getConn)

Before:

:::php
<?php
$conn = DB::getConn();
$conn->beginSchemaUpdate();
foreach($dataClasses as $dataClass) {
	singleton($dataClass)->requireTable();
}
$conn->endSchemaUpdate();

After:

:::php
<?php
$schema = DB::get_schema();
$schema->schemaUpdate(function() use($dataClasses){
	foreach($dataClasses as $dataClass) {
		singleton($dataClass)->requireTable();
	}
});

Also should be noted is that many functions have been renamed to conform better with coding conventions. E.g. DB::requireTable is now DB::require_table

Revert to legacy CMS page actions

By default "delete from live" and "delete" actions are deprecated in favour of "unpublish" and "archive". "unpublish" is an existing action which is functionally equivalent to "delete from live", and "archive" is a new feature which performs both unpublish and deletion simultaneously.

To restore "delete from live" add the following config to your site's config.yml.

:::yml
CMSMain:
  enabled_legacy_actions:
    - CMSBatchAction_DeleteFromLive

In order to remove the new "archive" action and restore the old "delete" action you can use the following config

:::yml
CMSMain:
  enabled_legacy_actions:
    - CMSBatchAction_Delete

Changelog

API Changes

  • 2015-06-16 f3e1472 Revert DataObject::validate to 3.1 method signature (protected) (Damian Mooyman)
  • 2015-06-16 58cc3da Revert DataObject::validate to 3.1 method signature (protected) (Damian Mooyman)
  • 2015-06-13 e766658 Allow HTTP Cache Headers to be customised (Jeremy Shipman)
  • 2015-06-12 8389260 New and renamed image functions (Jonathon Menz)
  • 2015-06-09 a8ace75 Support for multiple HTMLEditorConfig per page (Damian Mooyman)
  • 2015-05-15 b169823 Deprecate delete in favour of archive Remove "delete from live" duplicate action in favour of existing "unpublish" which is more consistent with current terminology Add pop-up verification to destructive actions Fix bug preventing side-by-side preview of archived pages Fix bug in reporting publishing of error pages Restoring a page without an available parent will restore to root (Damian Mooyman)
  • 2015-05-15 a72bd16 Deprecate delete in favour of archive Remove "delete from live" duplicate action in favour of existing "unpublish" which is more consistent with current terminology Add pop-up verification to destructive actions Fix bug in reporting publishing of error pages Restoring a page also restores parents (Damian Mooyman)
  • 2015-04-30 c5e0c8f Enable tree filter highlighting Decoupling of CMS / Framework (Damian Mooyman)
  • 2015-04-30 8863797 Enable tree filter highlighting Decoupling of CMS / Framework (Damian Mooyman)
  • 2015-04-29 e8d6f15 Use mysql buffered statements Avoids the usage of any MySQL Native Driver specific API (Damian Mooyman)
  • 2015-04-09 e91606e Introduce $FromEnd variable for iterators (Damian Mooyman)
  • 2015-03-31 95c162e Security better respects BackURL on login BUG Restore missing authentication message not appearing in the login form $Content area (regression from #1807) (Damian Mooyman)
  • 2015-03-05 9367fd2 enable PaginatedList to be disabled by setting page length to 0 (Damian Mooyman)
  • 2015-01-14 5d4c2c4 Adding default_classes to FormField (Daniel Hensby)
  • 2015-01-14 6d00027 Adding default_classes to Form (Daniel Hensby)
  • 2014-09-25 e478009 Mailer can be configured to use different encoding mechanisms, and added support for unicode quoted-string encoding API Mailer bounce email can now be configured API Mailer no longer calls Convert::xml2raw on all email subjects API Deprecate dead Mailer code and refactored duplicate or mis-documented code. (Damian Mooyman)
  • 2014-09-25 29e3347 Convert::html2raw no longer wraps text automatically BUG Convert::html2raw now correctly decodes single quotes (Damian Mooyman)
  • 2014-09-24 5631553 Cookies set via Cookie::set() are now HTTP only by default (Sean Harvey)
  • 2014-09-15 062ad8e Allow parameterised joins / subselects (Damian Mooyman)
  • 2014-08-15 2ba1c46 broken link hihglighting to write to database. (Mateusz Uzdowski)
  • 2014-08-14 784e292 Add a getter for customisedObject property. (Mateusz Uzdowski)
  • 2014-08-09 18d6c53 Extract siteconfig out to an external module. (Will Rossiter)
  • 2014-08-04 1759d5d Use "number" HTML5 type for NumericField by default (Sean Harvey)
  • 2014-07-30 26a0e91 SS_Filterable, SS_Limitable and SS_Sortable now explicitly extend SS_List (Damian Mooyman)
  • 2014-04-23 d16db2d tinymce editor no longer transforms paragraphs with margin-left into blockquotes This is legacy behaviour which does not often reflect the expected behaviour of the current editor. indent and outdent can (in some situations) prefer to use margin instead of padding. sapphiremce_cleanup faultily assumes that such indented text should be block quoted, and replaces this with a block quote element. This is not necessary, since the blockquote element can be placed explicitly by the user when necessary. (Damian Mooyman)
  • 2014-04-16 5f7ebd3 UploadField: move replaceFile to the front end config (Devlin)
  • 2014-04-11 5b55361 DateTime.Ago better infers significance of date units. BUG Fixes missing i18n translation in Date::TimeDiffIn BUG Fixes Date::TimeDiffIn not respecting mocked SS_Datetime::now This provides less vague date periods. I.e. "36 days" has a lot more relevance that "1 month" Reduced duplication of time period calculation code (ref: CWPBUG-141) (Damian Mooyman)
  • 2014-04-09 2e73dcb Remove swf,html,htm,xhtml,xml as default allowed upload able file types (Damian Mooyman)
  • 2014-04-04 bf4e9eb Singleton method allowing type inference This pattern improves over the current usage of singleton by allowing type inference. This also better supports refactor, code usage detection, and auto-completion of classes. (Damian Mooyman)
  • 2014-02-12 6906c9b Removed auto-detection for i18n date/time formats (Ingo Schommer)
  • 2014-01-17 973b967 Adding chaining to i18nTextCollector::write() (Daniel Hensby)
  • 2014-01-02 791ee71 Prevent large images from repeatedly crashing PHP on resize (Loz Calver)
  • 2013-12-23 5fff5af moved useTestTheme to base Sapphire test class so that it can be used elsewhere (eg CMS test) (micmania1)
  • 2013-12-19 6fc9db6 DataObject::validate() visibility changed to public (issue #1659) (Sean Harvey)
  • 2013-11-26 b88a095 Support string descriptors for unique indexes in Versioned (Fred Condo)
  • 2013-10-18 fee54c7 Change DropdownField::getSource() to not return the emptyString value. (Nathan J. Brauer)
  • 2013-10-18 1c983bc LookupField::Field now returns an HTMLText instance. (Will Rossiter)
  • 2013-10-17 52f6581 Better declaration of DataObject field change levels. Use of const named identifiers to represent each change level rather than numbers. (Damian Mooyman)
  • 2013-10-11 b6b3cd9 GridState_Data values can have default values specified during retrieval. Fixes issues with GridStata_Data being returned from various states when value types are necessary. Pruning of dead code from GridFieldAddExistingAutocompleter Documentation for GridState (Damian Mooyman)
  • 2013-10-09 b367dd6 Removed Member.LastVisited and Member.NumVisits (Ingo Schommer)
  • 2013-09-27 c7f656c Removed "PastMember" cookie and template getter (Ingo Schommer)
  • 2013-08-08 4385264 Make GridFieldConfig objects decoratable (unclecheese)
  • 2013-07-10 7c60c73 Polymorphic has_one behaviour (Damian Mooyman)
  • 2013-07-01 47147eb delete simplepie from framework thirdparty (carlos barberis)
  • 2013-06-21 a395c53 Move of codebase to parameterised query database abstraction layer API Renamed DB static methods to properly conform to naming convention (lowercase, underscored) API Replaced deprecated method (Damian Mooyman)
  • 2013-06-21 d8e9af8 New Database abstraction layer. Ticket #7429 Database abstraction broken up into controller, connector, query builder, and schema manager, each independently configurable via YAML / Injector Creation of new DBQueryGenerator for database specific generation of SQL Support for parameterised queries, move of code base to use these over escaped conditions Refactor of SQLQuery into separate query classes for each of INSERT UPDATE DELETE and SELECT Support for PDO Installation process upgraded to use new ORM SS_DatabaseException created to handle database errors, maintaining details of raw sql and parameter details for user code designed interested in that data. Renamed DB static methods to conform correctly to naming conventions (e.g. DB::getConn -> DB::get_conn) 3.2 upgrade docs Performance Optimisation and simplification of code to use more concise API API Ability for database adapters to register extensions to ConfigureFromEnv.php (Damian Mooyman)
  • 2013-05-31 0c4ec47 Using $HolderID for form field container templates (Ingo Schommer)
  • 2013-05-26 ca87b8b Form Field ID attribute should follow HTML specification (Will Rossiter)
  • 2013-05-22 cb1f95e Remove AjaxUniqueTextField, since its operation is very limited (#1947) (Ingo Schommer)
  • 2013-01-29 957469d Removed auto-routing of controller name (Ingo Schommer)
  • 2013-01-17 56346a5 moved reports API to separate module (Will Rossiter)

Features and Enhancements

  • 2015-06-03 a9d22f1 Files can be uploaded directly in the 'Insert Link' form (scott1702)
  • 2015-05-29 44b1ff1 Configurable file version prefix (Jonathon Menz)
  • 2015-05-11 ce5a8f2 Cookie names with dots are now handled more gracefully (Daniel Hensby)
  • 2015-03-31 ae8dbe3 - Added maximum upload file size by type (Turnerj)
  • 2015-03-24 16f0e7b ViewableData_Debugger implements __toString (Daniel Hensby)
  • 2015-03-03 835ee69 Only validate DataObject model definitions during a build (Loz Calver)
  • 2015-02-24 8ee9130 CMS site tree status icons (Jonathon Menz)
  • 2015-02-08 5f31983 updateAttributes hook in FormField (Ingo Schommer)
  • 2015-01-23 3f1805b Support multiple many_manys between the same classes (closes #1377) (Josh)
  • 2014-12-15 6ad8f7c Subject line for email links in HtmlEditorField (Loz Calver)
  • 2014-11-12 41ea83b add validation to form field subclasses (Stevie Mayhew)
  • 2014-10-17 dc7bc46 Text - Limit characters to closest word (Anton Smith)
  • 2014-10-03 23fc498 Allow 'null' limit for database queries (closes #3487) (Loz Calver)
  • 2014-05-04 3b9056f Cookie_Backend for managing cookie state (Daniel Hensby)
  • 2013-10-17 e8287cd Hook for Member::registerFailedLogin (Thomas Speak)
  • 2013-08-23 7d7c754 Track broken anchors (Russell Michell)
  • 2013-06-05 60333f6 UploadField lists all files, shows path info (Ingo Schommer)
  • 2013-06-03 2a91d27 use Injector pattern to create ValidationResult in validate (Will Morgan)
  • 2013-05-26 736bde8 Add Convert::raw2htmlid() (Will Rossiter)
  • 2013-03-26 64349fe Allow setting of ASSETS_DIR in _ss_environment.php (Loz Calver)

Bugfixes

  • 2015-06-16 6169bf2 No longer caching has_one after ID change (Daniel Hensby)
  • 2015-06-16 ce3b5a5 Fix major segfault on PDOConnector after any DDL BUG Fix issue in PDOQuery::first() Refactor previewWrite and benchmarkQuery into SS_Database (Damian Mooyman)
  • 2015-06-11 6be0488 TreeDropdownField doesnt change label on unselect (Daniel Hensby)
  • 2015-06-09 24a268a Image test cleanup (Jonathon Menz)
  • 2015-06-09 07c21e2 Fix deletion of orphaned versioned records when a parent _versions table has been deleted (Damian Mooyman)
  • 2015-06-08 acf19b7 Fix false values for many_many_ExtraFields not being saved Fixes #4067 (Damian Mooyman)
  • 2015-06-05 a819bcf explicitly call get functions for site tree checks (Stevie Mayhew)
  • 2015-05-29 0319f78 Incorrect env setting in 3.1.13 (Damian Mooyman)
  • 2015-05-22 68d8df4 DropdownField didn't consider disabled items (Loz Calver)
  • 2015-05-22 e0710ae Fix DirectorTest failing when run with sake (Damian Mooyman)
  • 2015-05-20 94f6a13 Fixed setting LastEdited for DataObject with class ancestry (Gregory Smirnov)
  • 2015-05-20 869e69a Clicking icon in site tree link fails (Jonathon Menz)
  • 2015-05-20 f9bdf61 Fixed handling of numbers in certain locales (Gregory Smirnov)
  • 2015-05-19 dbe2ad4 Folder expansion icons (Jonathon Menz)
  • 2015-05-19 a56d08b TreeDropdownField Folder expansion (Jonathon Menz)
  • 2015-05-16 c6bcfea FieldList::changeFieldOrder() leftovers discarded (Jonathon Menz)
  • 2015-05-11 9e8a5c9 remove validation type constraint from form fields for 3.2 release (Stevie Mayhew)
  • 2015-02-14 bee642a make class loader classExists check interface_exists as per docs (Daniel Hensby)
  • 2015-02-08 6212b4b Versioned not ignoring obsolete fields (Benjamin R. White)
  • 2015-01-31 e724d6f notice level error when value is not set on CreditCardField (Will Rossiter)
  • 2015-01-07 cee7adc . Placeholder isn't completely translated (Elvinas L)
  • 2014-12-15 c358ac6 How to folder on forms (Cam Findlay)
  • 2014-12-09 bdb3b7f Feedback to name the fields section to "field types" to make it clearer what the section is about. (Cam Findlay)
  • 2014-12-09 aba9667 use GFMD code blocks to fix code formatting consistency. (Cam Findlay)
  • 2014-11-03 51337ac Image backend ignoring config. (Michael Strong)
  • 2014-10-26 ec0c259 Reinstate tab and form focus states (fixes CMS #732 and #817) (Naomi Guyer)
  • 2014-10-25 28be51c Config state leaking between unit tests (Loz Calver)
  • 2014-09-26 db0cad4 ErrorControlChain now supports exception handling (Damian Mooyman)
  • 2014-09-20 bbc1cb8 #3458 iframe transport multi file upload FIX #3343, FIX #3148 (Thierry François)
  • 2014-09-02 c140459 Fix versioned Versioned is not writing Version to _version tables for subclasses of Version dataobjects which have their own DB fields - Fix disjoint of ID / RecordID (which should be the same) - Fix calculation of new record version - Fix use of empty vs !isset to check for existing version (Damian Mooyman)
  • 2014-09-01 3644110 Ensure that columns are unique within a gridfield (Will Rossiter)
  • 2014-08-01 b0239f4 Fix PDOConnector issues Travis support for PDO ATTR_EMULATE_PREPARES = false breaks some test cases Enable username sans password Remove unnecessary semicolons delimiting queries (Damian Mooyman)
  • 2014-07-25 81c0a34 Remove caching of statements due to risk of instability This would cause segfaults in rare situations where statements are reused (Damian Mooyman)
  • 2014-07-15 0433ba1 Revert some changes to ManyManyList BUG Fix incompatibility in Member_GroupList Fix regressions in merges from 3.1 BUG Fix Security failing on test classes BUG Fix postgresql compatibility Clarify sql encoding of table names (Damian Mooyman)
  • 2014-05-22 3213630 fix listview not working with IE9 (Igor)
  • 2014-05-09 8335de4 remove redundant DB name switch in TestRunner (Will Morgan)
  • 2014-05-02 9cbfd14 TemplateManifest prevent cache collision (Will Morgan)
  • 2014-04-30 5dd0583 Fix encoding of SearchForm::getSearchQuery This made it awkward for user code to extract the query value for use in other applications; This would otherwise have to be xml decoded again. Casting has been promoted to the templating level via DBField::create_field and a SearchForm.casting config setting. (Damian Mooyman)
  • 2014-04-08 438fe02 change action variable source to getViewer (Will Morgan)
  • 2014-03-28 cf5d524 Fix regressions from #2206 in hasValue and dbObject (Damian Mooyman)
  • 2014-03-25 4b87b2e Fix ContentControllerTest (Damian Mooyman)
  • 2014-02-28 ab52b67 Log out current member when forgotten password (Daniel Hensby)
  • 2014-02-20 f6b72a2 Fixed regression in ContentController template selection. (Sam Minnee)
  • 2014-02-14 d0a4fc2 Fix failover to index template in ContentController::getViewer() (Sam Minnee)
  • 2014-02-04 cd213ab Fixed handing of false values in GridState_Data API Added ability to unset values (Damian Mooyman)
  • 2014-01-31 6df276c GridState_Data doesn't hold falsey values (Daniel Hensby)
  • 2013-10-30 4102cc6 Issues with CMSForm not consistently respecting new form naming scheme. Fixes for failing CMSFormTest cases (Damian Mooyman)
  • 2013-10-23 8534982 Debug error handler breaks error_get_last (Damian Mooyman)
  • 2013-10-19 ab10c2e An enum field in the search panel model admin misses an option to not filter on that field (Nico Haase)
  • 2013-10-17 d22ca62 FailedLoginCount reset (Thomas Speak)
  • 2013-10-02 fb5bb64 Fixed cross-platform issues with test cases and file utilities (Damian Mooyman)
  • 2013-05-30 c7468ca Generate Form::FormName() through (Will Rossiter)
  • 2013-05-26 831a507 Update references to ID values from 79c9433 (Will Rossiter)
  • 2013-05-17 3728907 allow children to be accessed via template (Will Morgan)
  • 2013-01-23 60c4d99 PHPUnit latest not working with composer installed builds (Hamish Friedlander)
  • 2012-12-13 31255fc Set visibility on login form methods to public. (Justin Martin)
  • 2012-12-12 379b561 RSSFeed now sets the Content-Type on the current HTTPResponse (Simon Welsh)