Merge branch '4' into 4

This commit is contained in:
Ingo Schommer 2019-03-11 21:31:22 +13:00 committed by GitHub
commit b803a174ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
146 changed files with 4468 additions and 1641 deletions

View File

@ -47,18 +47,10 @@ matrix:
- DB=MYSQL
- PHPUNIT_TEST=cms
- php: 7.3.0RC1
- php: 7.3
env:
- DB=MYSQL
- PHPUNIT_TEST=framework
sudo: required
dist: xenial
addons:
apt:
packages:
- libzip4
services:
- mysql
before_script:
# Extra $PATH

View File

@ -1284,6 +1284,16 @@ warnings:
message: 'Removed SilverStripe\ORM\FieldType\DBDate->day_before()'
'days_between()':
message: 'Removed SilverStripe\ORM\FieldType\DBDate->days_between()'
'SilverStripe\Security\Permission::get_declared_permissions_list()':
message: 'Deprecated'
'SilverStripe\Security\Permission::get_label_for_permission()':
message: 'Deprecated'
'SilverStripe\Security\Permission::traverse_declared_permissions()':
message: 'Deprecated'
'SilverStripe\Control\Session::get_all()':
message: 'Session can not be accessed statically and `get_all()` is now called `getAll()'
'SilverStripe\Control\Session::clear_all()':
message: 'Session can not be accessed statically and `clear_all()` is now called `clearAll()'
props:
'class':
message: '$this->class access has been removed'
@ -1315,6 +1325,12 @@ warnings:
replacement: 'writeJavascriptToBody'
'SilverStripe\Forms\Formfield->dontEscape':
message: 'FormField::$dontEscape has been removed. Escaping is now managed on a class by class basis.'
'SilverStripe\Security\LoginForm->authenticator_class':
message: 'authenticator_class is deprecated. Use getAuthenticatorClass/setAuthenticatorClass.'
'SilverStripe\Security\Permission::$declared_permissions':
message: 'Deprecated'
'SilverStripe\Security\Permission::$declared_permissions_list':
message: 'Deprecated'
functions:
'file_get_contents()':
message: 'Use new asset abstraction'
@ -1368,3 +1384,148 @@ warnings:
'THIRDPARTY_DIR':
message: 'Path constants have been deprecated. Use the Requirements and ModuleResourceLoader APIs'
url: 'https://docs.silverstripe.org/en/4/changelogs/4.0.0#module-paths'
'SilverStripe\Core\Manifest\ManifestFileFinder::RESOURCES_DIR':
message: 'Use global const RESOURCES_DIR'
url: 'https://docs.silverstripe.org/en/4/changelogs/4.4.0#resources-dir'
replacement: 'RESOURCES_DIR'
renameWarnings:
- Form
visibilities:
'SilverStripe\ORM\DataObject::db':
visibility: private
'SilverStripe\ORM\DataObject::singular_name':
visibility: private
'SilverStripe\ORM\DataObject::plural_name':
visibility: private
'SilverStripe\ORM\DataObject::api_access':
visibility: private
'SilverStripe\ORM\DataObject::default_classname':
visibility: private
'SilverStripe\ORM\DataObject->destroyed':
visibility: public
'SilverStripe\ORM\DataObject->record':
visibility: protected
'SilverStripe\ORM\DataObject->joinRecord':
visibility: protected
'SilverStripe\ORM\DataObject->changed':
visibility: private
'SilverStripe\ORM\DataObject->changeForced':
visibility: private
'SilverStripe\ORM\DataObject->original':
visibility: protected
'SilverStripe\ORM\DataObject->brokenOnDelete':
visibility: protected
'SilverStripe\ORM\DataObject->brokenOnWrite':
visibility: protected
'SilverStripe\ORM\DataObject::validation_enabled':
visibility: private
'SilverStripe\ORM\DataObject::_cache_get_one':
visibility: protected
'SilverStripe\ORM\DataObject::_cache_field_labels':
visibility: protected
'SilverStripe\ORM\DataObject::fixed_fields':
visibility: private
'SilverStripe\ORM\DataObject::table_name':
visibility: private
'SilverStripe\ORM\DataObject->components':
visibility: protected
'SilverStripe\ORM\DataObject->unsavedRelations':
visibility: protected
'SilverStripe\ORM\DataObject::cascade_deletes':
visibility: private
'SilverStripe\ORM\DataObject::cascade_duplicates':
visibility: private
'SilverStripe\ORM\DataObject::sourceQueryParams':
visibility: private
'SilverStripe\ORM\DataObject::subclass_access':
visibility: private
'SilverStripe\ORM\DataObject::casting':
visibility: private
'SilverStripe\ORM\DataObject::create_table_options':
visibility: private
'SilverStripe\ORM\DataObject::indexes':
visibility: private
'SilverStripe\ORM\DataObject::defaults':
visibility: private
'SilverStripe\ORM\DataObject::default_records':
visibility: private
'SilverStripe\ORM\DataObject::has_one':
visibility: private
'SilverStripe\ORM\DataObject::belongs_to':
visibility: private
'SilverStripe\ORM\DataObject::has_many':
visibility: private
'SilverStripe\ORM\DataObject::many_many':
visibility: private
'SilverStripe\ORM\DataObject::many_many_extraFields':
visibility: private
'SilverStripe\ORM\DataObject::belongs_many_many':
visibility: private
'SilverStripe\ORM\DataObject::default_sort':
visibility: private
'SilverStripe\ORM\DataObject::searchable_fields':
visibility: private
'SilverStripe\ORM\DataObject::field_labels':
visibility: private
'SilverStripe\ORM\DataObject::summary_fields':
visibility: private
'SilverStripe\Control\Director::rules':
visibility: private
'SilverStripe\Control\Director::current_page':
visibility: private
'SilverStripe\Control\Director::alternate_base_folder':
visibility: private
'SilverStripe\Control\Director::alternate_public_dir':
visibility: private
'SilverStripe\Control\Director::default_base_url':
visibility: private
'SilverStripe\Forms\GridField\GridFieldPaginator::default_items_per_page':
visibility: private
'SilverStripe\View\SSViewer::themes':
visibility: private
'SilverStripe\View\SSViewer::current_themes':
visibility: protected
'SilverStripe\View\SSViewer::theme':
visibility: private
'SilverStripe\View\SSViewer::theme_enabled':
visibility: private
'SilverStripe\View\SSViewer::global_key':
visibility: private
'SilverStripe\View\SSViewer::source_file_comments':
visibility: private
'SilverStripe\View\SSViewer::rewrite_hash_links':
visibility: private
'SilverStripe\View\SSViewer::current_rewrite_hash_links':
visibility: protected
'SilverStripe\View\SSViewer::rewriteHashlinks':
visibility: protected
'SilverStripe\View\SSViewer::template_cache_flushed':
visibility: private
'SilverStripe\View\SSViewer::cacheblock_cache_flushed':
visibility: private
'SilverStripe\View\SSViewer::topLevel':
visibility: protected
'SilverStripe\View\SSViewer::templates':
visibility: protected
'SilverStripe\View\SSViewer::chosen':
visibility: protected
'SilverStripe\View\SSViewer::subTemplates':
visibility: protected
'SilverStripe\View\SSViewer::includeRequirements':
visibility: protected
'SilverStripe\View\SSViewer::parser':
visibility: protected
'SilverStripe\View\SSViewer::partialCacheStore':
visibility: protected
classToTraits:
SS_Object:
'SilverStripe\Core\Extensible': 'Extensible'
'SilverStripe\Core\Injector\Injectable': 'Injectable'
'SilverStripe\Core\Config\Configurable': 'Configurable'
Object:
'SilverStripe\Core\Extensible': 'Extensible'
'SilverStripe\Core\Injector\Injectable': 'Injectable'
'SilverStripe\Core\Config\Configurable': 'Configurable'

View File

@ -19,7 +19,7 @@ and [installation from source](https://doc.silverstripe.org/framework/en/install
## Bugtracker ##
Bugs are tracked on [github.com](https://github.com/silverstripe/silverstripe-framework/issues).
Please read our [issue reporting guidelines](https://doc.silverstripe.org/framework/en/misc/contributing/issues).
Please read our [issue reporting guidelines](https://docs.silverstripe.org/en/4/contributing/issues_and_bugs/).
## Development and Contribution ##

View File

@ -18,20 +18,17 @@ SilverStripe\Core\Injector\Injector:
factory: SilverStripe\Core\Cache\CacheFactory
constructor:
namespace: "VersionProvider_composerlock"
args:
disable-container: true
disable-container: true
Psr\SimpleCache\CacheInterface.RateLimiter:
factory: SilverStripe\Core\Cache\CacheFactory
constructor:
namespace: 'ratelimiter'
args:
disable-container: true
disable-container: true
Psr\SimpleCache\CacheInterface.InheritedPermissions:
factory: SilverStripe\Core\Cache\CacheFactory
constructor:
namespace: "InheritedPermissions"
args:
disable-container: true
disable-container: true
Psr\SimpleCache\CacheInterface.ThemeResourceLoader:
factory: SilverStripe\Core\Cache\CacheFactory
constructor:

View File

@ -8,8 +8,16 @@ SilverStripe\Core\Manifest\VersionProvider:
Name: httpconfig-dev
Only:
environment: dev
After:
- 'requestprocessors'
---
# Set dev level to disabled with a higher forcing level
SilverStripe\Control\Middleware\HTTPCacheControlMiddleware:
defaultState: 'disabled'
defaultForcingLevel: 3
SilverStripe\Core\Injector\Injector:
SilverStripe\Control\Director:
properties:
Middlewares:
ExecMetricMiddleware: '%$SilverStripe\Control\Middleware\ExecMetricMiddleware'

6
_config/gridfield.yml Normal file
View File

@ -0,0 +1,6 @@
---
Name: gridfieldconfig
---
SilverStripe\Core\Injector\Injector:
SilverStripe\Forms\GridField\FormAction\StateStore:
class: SilverStripe\Forms\GridField\FormAction\SessionStore

View File

@ -34,7 +34,7 @@
"psr/container-implementation": "1.0.0",
"silverstripe/config": "^1@dev",
"silverstripe/assets": "^1@dev",
"silverstripe/vendor-plugin": "^1.0",
"silverstripe/vendor-plugin": "^1.4",
"swiftmailer/swiftmailer": "~5.4",
"symfony/cache": "^3.3@dev",
"symfony/config": "^3.2",

View File

@ -20,6 +20,7 @@ Especially be aware of [accidental php-execution](https://nealpoole.com/blog/201
* It does not cover serving securely over HTTPS.
* It uses the new filesystem layout (with `public` directory) introduced in version 4.1.0. If your installation has been upgraded to 4.1+ from an older version and you have not [upgraded to the public folder](/changelogs/4.1.0.md), see the version of this documentation for version 4.0.
* The regular expression for allowed file types must be manually updated if the File.allowed_extensions list is updated.
* The error pages for 502 (Bad Gateway) and 503 (Service Unavailable) need to be manually created and published in the CMS (assuming use of the silverstripe/errorpage module).
```nginx
@ -47,11 +48,33 @@ server {
error_page 502 /assets/error-500.html;
error_page 503 /assets/error-500.html;
location ^~ /assets/ {
sendfile on;
try_files $uri =404;
# Support assets & resources #
# Never serve .gitignore, .htaccess, or .method
location ~ /\.(gitignore|htaccess|method)$ {
return 403;
}
# Handle allowed file types (see caveats)
# Pass unfound files to SilverStripe to check draft images
location ~ ^/assets/.*\.(?i:css|js|ace|arc|arj|asf|au|avi|bmp|bz2|cab|cda|csv|dmg|doc|docx|dotx|flv|gif|gpx|gz|hqx|ico|jpeg|jpg|kml|m4a|m4v|mid|midi|mkv|mov|mp3|mp4|mpa|mpeg|mpg|ogg|ogv|pages|pcx|pdf|png|pps|ppt|pptx|potx|ra|ram|rm|rtf|sit|sitx|tar|tgz|tif|tiff|txt|wav|webm|wma|wmv|xls|xlsx|xltx|zip|zipx)$ {
sendfile on;
try_files $uri /index.php?$query_string;
}
# Allow the error pages. Fail with 404 Not found.
location ~ ^/assets/error-\d\d\d\.html$ {
try_files $uri =404;
}
# Fail all other assets requests as 404 Not found
# Could also use 403 Forbidden or 444 (nginx drops the connection)
location ~ ^/assets/ {
return 404;
}
# End of assets & resources support #
location /index.php {
fastcgi_buffer_size 32k;
fastcgi_busy_buffers_size 64k;

View File

@ -11,7 +11,7 @@ Directory | Description
--------- | -----------
`public/` | Webserver public webroot
`public/assets/` | Images and other files uploaded via the SilverStripe CMS. You can also place your own content inside it, and link to it from within the content area of the CMS.
`public/resources/` | Exposed public files added from modules. Folders within this parent will match that of the source root location.
`public/_resources/` | Exposed public files added from modules. Folders within this parent will match that of the source root location (this can be altered by configuration).
`vendor/` | SilverStripe modules and other supporting libraries (the framework is in `vendor/silverstripe/framework`)
`themes/` | Standard theme installation location

View File

@ -67,7 +67,7 @@ check our [community help options](https://www.silverstripe.org/community/).
## Related Lessons
* [Up and running](https://www.silverstripe.org/learn/lessons/v4/up-and-running-setting-up-a-local-silverstripe-dev-environment-1)
* [Creating your first theme](https://www.silverstripe.org/learn/lessons/v4/creating-your-first-theme-1)
* [Creating your first project](https://www.silverstripe.org/learn/lessons/v4/creating-your-first-project)
* [Migrating static templates into your theme](https://www.silverstripe.org/learn/lessons/v4/migrating-static-templates-into-your-theme-1)
* [Working with multiple templates](https://www.silverstripe.org/learn/lessons/v4/working-with-multiple-templates-1)

View File

@ -1,25 +1,28 @@
title: Lessons
introduction: The lessons take a step by step look at how to build a SilverStripe application.
* [How to set up a local development environment in SilverStripe](https://vimeo.com/108861537)
* [Lesson 1: Creating your first theme](http://www.silverstripe.org/learn/lessons/creating-your-first-theme)
* [Lesson 2: Migrating static templates into your theme](http://www.silverstripe.org/learn/lessons/migrating-static-templates-into-your-theme)
* [Lesson 3: Adding dynamic content](http://www.silverstripe.org/learn/lessons/adding-dynamic-content)
* [Lesson 4: Working with multiple templates](http://www.silverstripe.org/learn/lessons/working-with-multiple-templates)
* [Lesson 5: The holder/page pattern](http://www.silverstripe.org/learn/lessons/the-holderpage-pattern)
* [Lesson 6: Adding Custom Fields to a Page](http://www.silverstripe.org/learn/lessons/adding-custom-fields-to-a-page)
* [Lesson 7: Working with Files and Images](http://www.silverstripe.org/learn/lessons/working-with-files-and-images)
* [Lesson 8: Introduction to the ORM](http://www.silverstripe.org/learn/lessons/introduction-to-the-orm)
* [Lesson 9: Data Relationships - $has_many](http://www.silverstripe.org/learn/lessons/working-with-data-relationships-has-many)
* [Lesson 10: Introduction to the ORM](http://www.silverstripe.org/learn/lessons/working-with-data-relationships-many-many)
* [Lesson 11: Introduction to frontend forms](http://www.silverstripe.org/learn/lessons/introduction-to-frontend-forms)
* [Lesson 12: Data Extensions and SiteConfig](http://www.silverstripe.org/learn/lessons/data-extensions-and-siteconfig)
* [Lesson 13: Introduction to ModelAdmin](http://www.silverstripe.org/learn/lessons/introduction-to-modeladmin)
* [Lesson 14: Controller Actions/DataObjects as Pages](http://www.silverstripe.org/learn/lessons/controller-actions-dataobjects-as-pages)
* [Lesson 15: Building a Search Form](http://www.silverstripe.org/learn/lessons/building-a-search-form)
* [Lesson 16: Lists and Pagination](http://www.silverstripe.org/learn/lessons/lists-and-pagination)
* [Lesson 17: Ajax Behaviour and Viewable Data](http://www.silverstripe.org/learn/lessons/ajax-behaviour-and-viewabledata)
* [Lesson 18: Dealing with Arbitrary Template Data](http://www.silverstripe.org/learn/lessons/dealing-with-arbitrary-template-data)
* [How to set up a local development environment in SilverStripe](https://www.silverstripe.org/learn/lessons/v4/up-and-running-setting-up-a-local-silverstripe-dev-environment-1)
* [Lesson 1: Creating your first project](https://www.silverstripe.org/learn/lessons/v4/creating-your-first-project)
* [Lesson 2: Migrating static templates into your theme](https://www.silverstripe.org/learn/lessons/v4/migrating-static-templates-into-your-theme-1)
* [Lesson 3: Adding dynamic content](https://www.silverstripe.org/learn/lessons/v4/adding-dynamic-content-1)
* [Lesson 4: Working with multiple templates](https://www.silverstripe.org/learn/lessons/v4/working-with-multiple-templates-1)
* [Lesson 5: The holder/page pattern](https://www.silverstripe.org/learn/lessons/v4/the-holderpage-pattern-1)
* [Lesson 6: Adding Custom Fields to a Page](https://www.silverstripe.org/learn/lessons/v4/adding-custom-fields-to-a-page-1)
* [Lesson 7: Working with Files and Images](https://www.silverstripe.org/learn/lessons/v4/working-with-files-and-images-1)
* [Lesson 8: Introduction to the ORM](https://www.silverstripe.org/learn/lessons/v4/introduction-to-the-orm-1)
* [Lesson 9: Data Relationships - $has_many](https://www.silverstripe.org/learn/lessons/v4/working-with-data-relationships-has-many-1)
* [Lesson 10: Data Relationships - $many_many](https://www.silverstripe.org/learn/lessons/v4/working-with-data-relationships-many-many-1)
* [Lesson 11: Introduction to frontend forms](https://www.silverstripe.org/learn/lessons/v4/introduction-to-frontend-forms-1)
* [Lesson 12: Data Extensions and SiteConfig](https://www.silverstripe.org/learn/lessons/v4/data-extensions-and-siteconfig-1)
* [Lesson 13: Introduction to ModelAdmin](https://www.silverstripe.org/learn/lessons/v4/introduction-to-modeladmin-1)
* [Lesson 14: Controller Actions/DataObjects as Pages](https://www.silverstripe.org/learn/lessons/v4/controller-actions-dataobjects-as-pages-1)
* [Lesson 15: Building a Search Form](https://www.silverstripe.org/learn/lessons/v4/building-a-search-form-1)
* [Lesson 16: Lists and Pagination](https://www.silverstripe.org/learn/lessons/v4/lists-and-pagination-1)
* [Lesson 17: Ajax Behaviour and Viewable Data](https://www.silverstripe.org/learn/lessons/v4/ajax-behaviour-and-viewabledata-1)
* [Lesson 18: Dealing with Arbitrary Template Data](https://www.silverstripe.org/learn/lessons/v4/dealing-with-arbitrary-template-data-1)
* [Lesson 19: Creating Filtered Views](https://www.silverstripe.org/learn/lessons/v4/creating-filtered-views-1)
* [Lesson 20: Beyond the ORM: Building Custom SQL](https://www.silverstripe.org/learn/lessons/v4/beyond-the-orm-building-custom-sql-1)
* [Lesson 21: Advanced Environment Configuration](https://www.silverstripe.org/learn/lessons/v4/advanced-environment-configuration-1)
## Help: If you get stuck

View File

@ -91,7 +91,7 @@ class Car extends DataObject
private static $db = [
'Wheels' => 'Int(4)',
'Condition' => 'Enum(array("New","Fair","Junk"), "New")',
'Make' => 'Varchar(["default" => "Honda"]),
'Make' => 'Varchar(["default" => "Honda"])',
);
}
```

View File

@ -143,6 +143,27 @@ $MetaTags(false)
<title>$Title - Bob's Fantasy Football</title>
```
### Modifying Meta Tags
You can override the `MetaComponents()` method on your `SiteTree` sub-classes or make use of the `MetaComponents` extension point to manipulate the underlying data that is rendered by `$MetaTags`. Example (for `Page` class):
```php
public function MetaComponents()
{
$tags = parent::MetaComponents();
// Override the content of the Title tag (needs to be html)
if ($this->MetaTitle) {
$tags['title']['content'] = $this->obj('MetaTitle')->forTemplate();
}
// Provide a default Meta Description
if (!$tags['description']['attributes']['content']) {
// provide raw text as attributes will be escaped later
$tags['description']['attributes']['content'] = $this->dbObject('Content')->LimitCharactersToClosestWord(300);
}
return $tags;
}
```
## Links
```ss

View File

@ -11,6 +11,69 @@ The examples below are using certain folder naming conventions (CSS files in `cs
SilverStripe core modules like `cms` use a different naming convention (CSS and JavaScript files in `client/src/`).
The `Requirements` class can work with arbitrary file paths.
## Exposing static assets
Before requiring static asset files in PHP code or in a template, those assets need to be "exposed". This process allows SilverStripe projects and SilverStripe modules to make static asset files available via the web server from locations that would otherwise be blocked from web server access, such as the `vendor` folder.
### Configuring your project "exposed" folders
Exposed assets are made available in your web root in a dedicated "resources" directory. Prior to SilverStripe 4.4, the name of this directory was hardcoded to `resources`. In SilverStripe 4.4 and above, the name of the resources directory can be configured by defining the `extra.resources-dir` key in your `composer.json`. SilverStripe projects created from `silverstripe/installer` 4.4 and above will automatically be configured to use `_resources` as their resource directory.
Each folder that needs to be exposed must be entered under the `extra.expose` key in your `composer.json` file. Module developers should use a path relative to the root of their module (don't include the "vendor/package-developer/package-name" path).
This is a sample SilverStripe project `composer.json` file configured to expose some assets.
```json
{
"name": "app/myproject",
"type": "silverstripe-project",
"require": {
"silverstripe/recipe-cms": "4.4.x-dev"
},
"extra": {
"resources-dir": "_resources",
"expose": [
"app/client/dist",
"app/images"
]
}
}
```
Files contained inside the `app/client/dist` and `app/images` will be made publicly available under the `_resources` directory.
SilverStripe projects should not track the "resources" directory in their source control system.
### Exposing assets in the web root
SilverStripe projects ship with `silverstripe/vendor-plugin`. This Composer plugin automatically tries to expose assets from your project and installed modules after installation, or after an update.
Developers can explicitly expose static assets by calling `composer vendor-expose`. This is necessary after updating your `resources-dir` or `expose` configuration in your `composer.json` file.
`composer vendor-expose` accepts an optional `method` argument (e.g.: `composer vendor-expose auto`). This controls how the files are exposed in the "resources" directory:
* `none` disables all symlink / copy
* `copy` copies the exposed files
* `symlink` create symbolic links to the exposed folder
* `junction` uses a junction (Windows only)
* `auto` creates symbolic links (or junctions on Windows), but fails over to copy.
### Referencing exposed assets
When referencing exposed static assets, use either the project file path (relative to the project root folder) or a module name and relative file path to that module's root folder. E.g.:
```php
// When referencing project files, use the same path defined in your `composer.json` file.
Requirements::javascript('app/client/dist/bundle.js');
// When referencing theme files, use a path relative to the root of your project
Requirements::javascript('themes/simple/javascript/script.js');
// When referencing files from a module, you need to prefix the path with the module name.
Requirements::javascript('silverstripe/admin:client/dist/js/bundle.js');
```
When rendered in HTML code, these URLs will be rewritten to their matching path inside the "resources" directory.
## Template Requirements API
**<my-module-dir>/templates/SomeTemplate.ss**
@ -179,7 +242,7 @@ is not appropriate. Normally a single backend is used for all site assets, so a
replaced. For instance, the below will set a new set of dependencies to write to `app/javascript/combined`
```yaml
```yml
---
Name: myrequirements
---

View File

@ -16,12 +16,14 @@ $field = new TextField(..);
$field->setTemplate('MyCustomTextField');
```
Both `MyCustomTemplate.ss` and `MyCustomTextField.ss` should be located in **app/templates/** or the same directory as the core.
To override the template for CMS forms, the custom templates should be located in **/app/templates**. Front-end form templates can be located in **/app/templates** or in the active theme's **/templates** directory.
<div class="notice" markdown="1">
It's recommended to copy the contents of the template you're going to replace and use that as a start. For instance, if
you want to create a `MyCustomFormTemplate` copy the contents of `Form.ss` to a `MyCustomFormTemplate.ss` file and
modify as you need.
*The default Form.ss can be found in `/vendor/silverstripe/framework/templates/SilverStripe/Forms/Includes/`*
</div>
By default, Form and Fields follow the SilverStripe Template convention and are rendered into templates of the same

View File

@ -472,11 +472,29 @@ functionality. See [How to Create a GridFieldComponent](../how_tos/create_a_grid
## Saving the GridField State
`GridState` is a class that is used to contain the current state and actions on the `GridField`. It's transfered
`GridState` is a class that is used to contain the current state and actions on the `GridField`. It's transferred
between page requests by being inserted as a hidden field in the form.
The `GridState_Component` sets and gets data from the `GridState`.
## Saving GridField_FormAction state
By default state used for performing form actions is saved in the session and tagged with a key like `gf_abcd1234`. In
some cases session may not be an appropriate storage method. The storage method can be configured:
```yaml
Name: mysitegridfieldconfig
After: gridfieldconfig
---
SilverStripe\Core\Injector\Injector:
SilverStripe\Forms\GridField\FormAction\StateStore:
class: SilverStripe\Forms\GridField\FormAction\AttributeStore
```
The `AttributeStore` class configures action state to be stored in the DOM and sent back on the request that performs
the action. Custom storage methods can be created and used by implementing the `StateStore` interface and configuring
`Injector` in a similar fashion.
## API Documentation
* [GridField](api:SilverStripe\Forms\GridField\GridField)

View File

@ -61,7 +61,7 @@ Note that SilverStripe modules have the following distinct characteristics:
- Any folder which should be exposed to the public webroot must be declared in the `extra.expose` config.
These paths will be automatically rewritten to public urls which don't directly serve files from the `vendor`
folder. For instance, `vendor/my-vendor/my-module/client` will be rewritten to
`resources/my-vendor/my-module/client`.
`_resources/my-vendor/my-module/client`.
- Any module which uses the folder expose feature must require `silverstripe/vendor-plugin` in order to
support automatic rewriting and linking. For more information on this plugin you can see the
[silverstripe/vendor-plugin github page](https://github.com/silverstripe/vendor-plugin).

View File

@ -38,6 +38,16 @@ Check the PHPUnit manual for all available [command line arguments](http://www.p
On Linux or OSX, you can avoid typing the full path on every invocation by adding `vendor/bin`
to your `$PATH` definition in the shell profile (usually `~/.profile`): `PATH=./vendor/bin:$PATH`
## Caching
Just like on web requests, SilverStripe caches metadata about the execution context.
This cache can get stale, e.g. when you change YAML configuration or add certain types of PHP code.
In order to flush the cache, use the `flush=1` CLI parameter:
```
vendor/bin/phpunit vendor/silverstripe/framework/tests '' flush=1
```
## Generating a Coverage Report
PHPUnit can generate a code coverage report ([docs](http://www.phpunit.de/manual/current/en/code-coverage-analysis.html))

View File

@ -28,7 +28,8 @@ session variables, used templates and much more.
| isDev | | 1 | | Put the site into [development mode](../), enabling debugging messages to the browser on a live server. For security, you'll be asked to log in with an administrator log-in. Will persist for the current browser session. |
| isTest | | 1 | | See above. |
| debug | | 1 | | Show a collection of debugging information about the director / controller operation |
| debug_request | | 1 | | Show all steps of the request from initial [HTTPRequest](api:SilverStripe\Control\HTTPRequest) to [Controller](api:SilverStripe\Control\Controller) to Template Rendering |
| debug_request | | 1 | | Show all steps of the request from initial [HTTPRequest](api:SilverStripe\Control\HTTPRequest) to [Controller](api:SilverStripe\Control\Controller) to Template Rendering |
| execmetric | | 1 | | Display the execution time and peak memory usage for the request |
## Classes and Objects

View File

@ -39,6 +39,13 @@ SilverStripe\Core\Injector\Injector:
namespace: "myCache"
```
<div class="alert" markdown="1">
Please note that if you have the `silverstripe/versioned` module installed (automatically installed by the
`silverstripe/cms` module), caches will automatically be segmented by current “stage”. This ensures that
any content written to the cache in the _draft_ reading mode isnt accidentally exposed in the _live_ reading mode.
Please read the [versioned cache segmentation](#versioned-cache-segmentation) section for more information.
</div>
Cache objects are instantiated through a [CacheFactory](SilverStripe\Core\Cache\CacheFactory),
which determines which cache adapter is used (see "Adapters" below for details).
This factory allows us you to globally define an adapter for all cache instances.
@ -209,6 +216,42 @@ SilverStripe\Core\Injector\Injector:
SilverStripe\Core\Cache\CacheFactory: '%$MemcachedCacheFactory'
```
## Versioned cache segmentation
`SilverStripe\Core\Cache\CacheFactory` now maintains separate cache pools for each versioned stage (if you have the
`silverstripe/versioned` module installed). This prevents developers from caching draft data and then
accidentally exposing it on the live stage without potentially required authorisation checks. Unless you
rely on caching across stages, you don't need to change your own code for this change to take effect. Note
that cache keys will be internally rewritten, causing any existing cache items to become invalid when this
change is deployed.
```php
// Before:
$cache = Injector::inst()->get(CacheInterface::class . '.myapp');
Versioned::set_stage(Versioned::DRAFT);
$cache->set('my_key', 'Some draft content. Not for public viewing yet.');
Versioned::set_stage(Versioned::LIVE);
$cache->get('my_key'); // 'Some draft content. Not for public viewing yet'
// After:
$cache = Injector::inst()->get(CacheInterface::class . '.myapp');
Versioned::set_stage(Versioned::DRAFT);
$cache->set('my_key', 'Some draft content. Not for public viewing yet.');
Versioned::set_stage(Versioned::LIVE);
$cache->get('my_key'); // null
```
Data that is not content sensitive can be cached across stages by simply opting out of the segmented cache
with the `disable-container` argument.
```yaml
SilverStripe\Core\Injector\Injector:
Psr\SimpleCache\CacheInterface.myapp:
factory: SilverStripe\Core\Cache\CacheFactory
constructor:
namespace: "MyInsensitiveData"
disable-container: true
```
## Additional Caches
Unfortunately not all caches are configurable via cache adapters.

View File

@ -229,4 +229,20 @@ SilverStripe\Control\HTTP:
```
Note that if you use `Director::is_ajax()` on cached pages
then you should add `X-Requested-With` to the vary header.
then you should add `X-Requested-With` to the vary header.
## Testing
HTTP Cache headers are disabled in developer environments by default to prevent any confusion around content not updating. To enable HTTP Cache Headers in dev mode you can add the following in yml config.
```yml
---
Name: 'app_httpconfig'
After: '#httpconfig-dev'
Only:
environment: dev
---
SilverStripe\Control\Middleware\HTTPCacheControlMiddleware:
defaultState: 'disabled'
defaultForcingLevel: 0
```

View File

@ -198,6 +198,22 @@ $email->getSwiftMessage()->getHeaders()->addTextHeader('HeaderName', 'HeaderValu
See this [Wikipedia](http://en.wikipedia.org/wiki/E-mail#Message_header) entry for a list of header names.
</div>
## Disabling Emails
If required, you can also disable email sending entirely. This is useful for testing and staging servers where
you do not wish to send emails out.
```yaml
---
Name: myemailconfig
Only:
Environment: dev
---
SilverStripe\Core\Injector\Injector:
Swift_Transport:
class: Swift_NullTransport
```
## SwiftMailer Documentation
For further information on SwiftMailer, consult their docs: http://swiftmailer.org/docs/introduction.html

View File

@ -53,14 +53,15 @@ The simplest way to use [CsvBulkLoader](api:SilverStripe\Dev\CsvBulkLoader) is t
```php
use SilverStripe\Admin\ModelAdmin;
use SilverStripe\Dev\CsvBulkLoader;
class PlayerAdmin extends ModelAdmin
{
private static $managed_models = [
'Player'
Player::class
];
private static $model_importers = [
'Player' => 'CsvBulkLoader',
'Player' => CsvBulkLoader::class,
];
private static $url_segment = 'players';
}

View File

@ -414,6 +414,11 @@ Requirements::javascript('silverstripe/admin:client/dist/js/i18n.js');
Requirements::add_i18n_javascript('<my-module-dir>/javascript/lang');
```
You can also include the language files from the public resources folder with the resource syntax:
```php
Requirements::add_i18n_javascript('vendor/module:path/to/lang');
```
### Translation Tables in JavaScript
Translation tables are automatically included as required, depending on the configured locale in `i18n::get_locale()`.

View File

@ -120,6 +120,20 @@ As with storage, there are also different ways of loading the content (or proper
| `File::getAbsoluteURL` | Gets the absolute URL to this resource |
| `File::getMimeType` | Get the mime type of this file |
| `File::getMetaData` | Gets other metadata from the file as an array |
| `File::getFileType` | Return the type of file for the given extension |
### Additional file types
SilverStripe has a pre-defined list of common file types. `File::getFileType` will return "unknown" for files outside that list.
You can add your own file extensions and its description with the following configuration.
```yml
SilverStripe\Assets\File:
file_types:
ai: 'Adobe Illustrator'
psd: 'Adobe Photoshop File'
```
## Modifying files

View File

@ -284,7 +284,7 @@ SilverStripe\Filesystem\Flysystem\FlysystemAssetStore:
### Configuring: Archive behaviour
By default, the default extension `AssetControlExtension` will control the disposal of assets
attached to objects when those objects are archived. For example, unpublished versioned objects
attached to objects when those objects are archived or replaced. For example, unpublished versioned objects
will automatically have their attached assets moved to the protected store. The archive of
draft or (or deletion of unversioned objects) will have those assets permanantly deleted
(along with all variants).

View File

@ -23,6 +23,9 @@ that previously visible assets remain visible to the public site.
If additional security or visibility rules should be applied to File dataobjects, then
make sure to correctly extend `canView` via extensions.
*IMPORTANT*: There is a [known bug](https://github.com/silverstripe/silverstripe-versioned/issues/177)
which breaks existing direct links to asset URLs unless `legacy_filenames` is set to `true` (see below).
## Automatic migration
Migration can be invoked by either this task, or can be configured to automatically run during dev build
@ -63,17 +66,58 @@ are incompatible with core file security.
## Support existing paths
Because the filesystem now uses the sha1 of file contents in order to version multiple versions under the same
Because the filesystem now uses the hash of file contents in order to version multiple versions under the same
filename, the default storage paths in 4.0 will not be the same as in 3.
Although it is not recommended, it is possible to configure the backend to omit this SHA1 url segment,
Although it is not recommended, it is possible to configure the backend to omit this hash url segment,
meaning that file paths and urls will not be modified during the upgrade.
This is done by setting this config:
This configuration needs to be chosen before starting the file migration,
and can't be changed after migration.
```yaml
SilverStripe\Assets\Flysystem\FlysystemAssetStore:
legacy_filenames: true
```
Note that this will not allow you to utilise certain file versioning features in 4.0.
This setting will still allow creation of protected (draft) files before publishing them.
It'll also keep track of changes to file metadata (e.g. title and description).
But it won't keep track of replaced file contents (not compatible with `keep_archived_assets=true`).
When replacing an already published file, the new file will be public right away (no draft stage).
## Migrating substantial number of files
The time it takes to run the file migration will depend on the number of files and their size. The generation of thumbnails will depend on the number and dimension of your images.
If you are migrating a substantial number of files, you should run file migration task either as a queued job or on the command line. If the migration task fails or times out, you can start it again and it will pick up where it left off.
If your environement supports the _Imagick_ PHP library, you may want to use that library instead of _GD_. Imagick is considerably faster when resizing images. You can switch back to _GD_ after running the file migration task.
[Changing the image manipulation driver to Imagick](images#changing-the-manipulation-driver-to-imagick)
If your project hosts big images (e.g. 4K images), this can also affect the amount of memory used to generate the thumbnails. The file migration task assumes that it will have at least 512MB of memory available.
By default the file migration task will not generate thumbnails for files greater than 9MB to avoid exhausting the available memory. To increase this limit, add the following code to your YML configuration:
```yml
SilverStripe\Core\Injector\Injector:
SilverStripe\AssetAdmin\Helper\ImageThumbnailHelper:
constructor:
0: '100MB'
```
You can also set this to `0` to disable the limit.
## System Requirements
The approach to running your file migration depends on your system and how many files you are migrating.
Use the following estimates to decide how you will run your file migration:
| Number of files | Method | Expected Execution Time | Approximate Memory Usage |
| --- | --- | --- | --- |
| < 150 | Web Request | 30 seconds | 6 MB |
| < 500 | Queued Job | 120 seconds | 8 MB |
| < 10000 | Command Line | 10000 seconds | 950 MB |
| 10000+ | Command Line or contact support | n/a | n/a |
Your exact experience will vary based on your host server, the size of your files and other conditions. If your site is hosted on a managed environement (e.g.: [Common Web Platform](https://www.cwp.govt.nz/service-desk) or [SilverStripe Platform](https://docs.platform.silverstripe.com/support/)), you may not have access to the command line to manually run the migration task. Contact your hosting provider's helpdesk if that's your case.

View File

@ -89,3 +89,22 @@ __'Scheduled To Publish'__ status. The look of the page node will be changed
from ![Normal Page Node](../../../_images/page_node_normal.png) to ![Scheduled Page Node](../../../_images/page_node_scheduled.png). The getStatusFlags has an `updateStatusFlags()`
extension point, so the flags can be modified through `DataExtension` rather than
inheritance as well. Deleting existing flags works by simply unsetting the array key.
## Customising page icons
The page tree in the CMS is a central element to manage page hierarchies, hence its display of pages can be customised as well. You can specify a custom page icon to make it easier for CMS authors to identify pages of this type, when navigating the tree or adding a new page:
```php
class HomePage extends Page
{
private static $icon_class = 'font-icon-p-home';
}
```
The CMS uses an icon set from [Fontastic](http://fontastic.me/). New icons may be [requested](https://github.com/silverstripe/silverstripe-admin/issues/new) and added to the [core icon set](https://silverstripe.github.io/silverstripe-admin/?selectedKind=Admin%2FIcons&selectedStory=Icon%20reference&full=0&addons=1&stories=1&panelRight=0&addonPanel=storybook%2Factions%2Factions-panel). The benefit of having icons added to the core set is that you can use icons more consistently across different modules allowing every module to use a different icon with the same style.
You can also add your own icon by specifying an image path to override the Fontastic icon set:
```php
private static $icon = 'app/images/homepage-icon.svg';
```

View File

@ -24,10 +24,10 @@ the common `Page` object (a new PHP class `MyPage` will look for a `MyPage.ss` t
We can use this to create a different base template with `LeftAndMain.ss`
(which corresponds to the `LeftAndMain` PHP controller class).
Copy the template markup of the base implementation at `templates/SilverStripe/Admin/Includes/LeftAndMain_Menu.ss`
Copy the template markup of the base implementation at `templates/SilverStripe/Admin/Includes/LeftAndMain_MenuList.ss`
from the `silverstripe/admin` module
into `app/templates/Includes/LeftAndMain_Menu.ss`. It will automatically be picked up by
the CMS logic. Add a new section into the `<ul class="cms-menu-list">`
into `app/templates/SilverStripe/Admin/Includes/LeftAndMain_MenuList.ss`. It will automatically be picked up by
the CMS logic. Add a new section into the `<ul class="cms-menu__list">`
```ss
@ -69,26 +69,42 @@ SilverStripe\Admin\LeftAndMain:
- app/css/BookmarkedPages.css
```
In order to let the frontend have the access to our `css` files, we need to `expose` them in the `composer.json`:
```javascript
"extra": {
...
"expose": [
"app/css"
]
},
```
Then run `composer vendor-expose`. This command will publish all the `css` files under the `app/css` folder to their public-facing paths.
> Note: don't forget to `flush`.
## Create a "bookmark" flag on pages
Now we'll define which pages are actually bookmarked, a flag that is stored in
the database. For this we need to decorate the page record with a
`DataExtension`. Create a new file called `app/code/BookmarkedPageExtension.php`
`DataExtension`. Create a new file called `app/src/BookmarkedPageExtension.php`
and insert the following code.
```php
use SilverStripe\Forms\CheckboxField;
use SilverStripe\Forms\FieldList;
use SilverStripe\ORM\DataExtension;
class BookmarkedPageExtension extends DataExtension
class BookmarkedPageExtension extends DataExtension
{
private static $db = [
'IsBookmarked' => 'Boolean'
];
public function updateCMSFields(FieldList $fields)
public function updateCMSFields(FieldList $fields)
{
$fields->addFieldToTab('Root.Main',
new CheckboxField('IsBookmarked', "Show in CMS bookmarks?")
@ -117,7 +133,7 @@ pages from the database into the template we've already created (with hardcoded
links)? Again, we extend a core class: The main CMS controller called
`LeftAndMain`.
Add the following code to a new file `app/code/BookmarkedLeftAndMainExtension.php`;
Add the following code to a new file `app/src/BookmarkedLeftAndMainExtension.php`;
```php
@ -143,12 +159,12 @@ SilverStripe\Admin\LeftAndMain:
```
As the last step, replace the hardcoded links with our list from the database.
Find the `<ul>` you created earlier in `app/admin/templates/LeftAndMain.ss`
Find the `<ul>` you created earlier in `app/templates/SilverStripe/Admin/Includes/LeftAndMain_MenuList.ss`
and replace it with the following:
```ss
<ul class="cms-menu-list">
<ul class="cms-menu__list">
<!-- ... -->
<% loop $BookmarkedPages %>
<li class="bookmarked-link $FirstLast">
@ -168,7 +184,7 @@ The following conventions apply:
* New actions can be added by redefining `getCMSActions`, or adding an extension
with `updateCMSActions`.
* It is required the actions are contained in a `FieldSet` (`getCMSActions`
* It is required the actions are contained in a `FieldList` (`getCMSActions`
returns this already).
* Standalone buttons are created by adding a top-level `FormAction` (no such
button is added by default).

View File

@ -0,0 +1,1188 @@
title: Upgrading to SilverStripe 4
introduction: Upgrade your project SilverStripe 4 and keep it up to date with the latest fixes, security patches and new features.
# Upgrading a SilverStripe 3 project to SilverStripe 4
SilverStripe applications should be kept up to date with the latest security releases. Usually an update or upgrade to
your SilverStripe installation means overwriting files, flushing the cache and updating your database schema.
## Understanding and planning your upgrade {#planning}
How easy will it be to update my project? It's a fair question, and sometimes a difficult one to answer.
* SilverStripe follows _semantic versioning_ (see our [release process](/contributing/release_process) for details).
* "Major" releases introduce API changes that may break your application.
* "Minor" releases (x.y) introduce API changes in a backwards compatible way and can mark some API as deprecated.
* "Patch" releases (x.y.z) fix bugs without introducing any API changes.
* If you've made custom branches of SilverStripe core, or any thirdparty module, upgrades are going to be more complex.
* More custom features will mean more work to re-test all of those features, and adapt to API changes in core.
* Customisations of a well defined type - such as custom page types or custom blog widgets -
are going to be easier to upgrade than customisations that modify deep system internals like rewriting SQL queries.
### Overview of changes
There are some fundamental changes in SilverStripe 4:
* PHP 5.6 is now the minimum required version and up to PHP 7.2 is supported.
* SilverStripe is now even more modular which allows you to remove functionality your project might not need.
* Common functionality sets can now be installed via SilverStripe _recipes_.
* SilverStripe modules can now be installed in the `vendor/` folder along with your regular PHP packages.
* All classes are namespaced: You have to use these, but can decide if you namespace your project code.
* PHP _traits_ replace a few core classes (e.g. `Object`) and make it easy to apply common patterns
* Public files can now be served from a `public/` webroot for added security.
* Versioning is more powerful through an "ownership" concept, and available for all DataObject classes.
* Changes across objects can be collected in a "campaign" for batch publication.
* GraphQL is now the favourite way of creating web services with SilverStripe.
* Asset management has been completely redone with a brand new React-based UI, protected draft files and versioning.
* Parts of the CMS UI are now built with React and Bootstrap instead of Entwine and custom CSS.
* PSR-4 auto-loading is supported for modules and for your project code.
[Learn more about major API changes introduced by SilverStripe 4](#list-of-major-api-changes),
and dig into the changelogs for [4.0.0](/changelogs/4.0.0), [4.1.0](/changelogs/4.1.0), [4.2.0](/changelogs/4.2.0), and [4.3.0](/changelogs/4.3.0).
### Using recipes instead of requiring individual modules
The SilverStripe CMS and SilverStripe Framework are becoming more modular. Many of the secondary features contained in SilverStripe CMS 3 and SilverStripe Framework 3 have been moved to separate modules.
SilverStripe 4 introduces the concept of _recipes_. Recipes are a combination of modules to achieve a common pattern.
Read the [Switching to recipes](#switching-to-recipes) section of this guide for more information about how recipes work.
### Automating your upgrades using the SilverStripe Upgrader tool
We've developed [an upgrader tool](https://github.com/silverstripe/silverstripe-upgrader) which you can use to help
with the upgrade process. The upgrader is unlikely to completely upgrade your project, however it can take care of the most tedious part of the upgrade.
It can also be used to upgrade your existing SilverStripe 4 project to a newer minor release.
## Step 0 - Pre-requisites and background work {#step0}
Before you begin the upgrade process, make sure you meet these pre-requisites.
### Back up your files and database
* Set up your codebase in your development environment.
* Backup your database content.
* Backup your codebase (use version control if possible).
<div class="warning" markdown="1">
Never update a website on the live server. Get it working on a development copy first!
</div>
### Install composer
[Composer](http://getcomposer.org) is a tool for managing PHP dependencies. SilverStripe 4 requires composer version _1.1_ or greater. Read the [SilverStripe _Getting started_ guide](/getting_started/composer) for more details.
We recommend using `recipe-cms` in your `composer.json` file to help you keep up to date.
```json
{
"require": {
"silverstripe/recipe-cms": "^4"
}
}
```
Running `composer update` will install additional dependencies, such as the `admin`, `asset-admin`, `reports`, `errorpage`, and `siteconfig` modules.
If you want more granular control over what gets installed,
check out the [recipe plugin repository](https://github.com/silverstripe/recipe-plugin)
as well as the `composer.json` files in [recipe-core](https://github.com/silverstripe/recipe-core) and
[recipe-cms](https://github.com/silverstripe/recipe-cms).
For a description on how to handle issues with pre-existing composer installs or upgrading other modules, read
through the [Step 1 - Upgrade your dependencies](#step1) section.
### Install or update the upgrader tool
Using the upgrader is not mandatory, but it can speed up the process. Although SilverStripe 4 can run in both PHP 5.6 and PHP 7, the upgrader itself requires PHP 7.1.
The upgrader is available as a phar executable.
To install the PHAR executable:
1. [Download the upgrader as a PHAR executable](https://silverstripe.github.io/silverstripe-upgrader/upgrade-code.phar) or `wget https://silverstripe.github.io/silverstripe-upgrader/upgrade-code.phar`
2. Make the file executable `chmod +x upgrade-code.phar`
3. Move the file to a folder in your path, for example `sudo mv upgrade-code.phar /usr/local/bin/upgrade-code`
When starting a new upgrade project, it's a good idea to check if you are using the latest release of the upgrader. Releases from 1.4 and above ship with a `self-update` command and will warn you if you are using an outdated version. If you are upgrading from a prior version, follow the regular installation instructions and override your existing executable.
You can run `upgrade-code help` to get more information about the upgrader or `upgrade-code help command-name` to information about a specific command.
Each command in the upgrader has somewhat different arguments. However, most of them accept these two options:
* `--write` which tells the upgrader to apply changes to your code base
* `--root-dir` which can be use to explicitly specify the root of your project. If this is not specified then the current working directory is assumed to be the root of the project.
<div class="info" markdown="1">
Sample upgrader commands in this guide assume your working directory is the root of your SilverStripe project. You'll need to use the `--root-dir` flag if that's not the case.
</div>
#### Install the upgrader globally with composer
You can install the upgrader globally with composer. This can make it easier to update to newer releases, however you can get dependency conflicts if you have other packages installed globally.
To install the upgrader globally run this command.
```bash
composer global require silverstripe/upgrader
```
Add your global composer bin directory to your path. On \*nix system, this directory is normally located at `$HOME/.composer/vendor/bin`. On Windows system, this directory is normally located at `C:\Users\<COMPUTER NAME>\AppData\Roaming\Composer\vendor\bin`. You can find the exact location by running this command:
```bash
composer global config bin-dir
```
On \*nix system, the following command will add your global composer bin directory to your path if `bash` is your default shell environment:
```bash
echo 'export PATH=$PATH:~/.composer/vendor/bin/' >> ~/.bash_profile
source ~/.bash_profile
```
### Running all the upgrader commands in this guide with one line
The upgrader comes with an `all` command. This command will attempt to run all the upgrader commands in the same order as this guide. This is unlikely to work on your first try, but can be a good way to get started without going through this entire guide.
```bash
upgrade-code all --namespace="App\\Web" --psr4
```
* `--recipe-core-constraint` defines your SilverStripe release version (optional, will default to the most recent stable release).
* `--cwp-constraint` can be used instead `--recipe-core-constraint` when upgrading a CWP project.
* `--namespace` allows you to specify how your project will be namespaced (optional).
* `--psr4` allows you to specify that your project structure respects the PSR-4 standard and to automatically use sub-namespaces.
* `--skip-add-namespace` allows you to skip the `add-namespace` command.
* `--skip-reorganise` allows you to skip the `reorganise` command.
* `--skip-webroot` allows you to skip the `webroot` command.
### Branching your project
Creating a dedicated branch in your source version control system to track your upgrade work can help you manage your upgrade. If you're upgrading a big project, you should consider creating individual branches or commits for each step.
## Step 1 - Upgrade your dependencies {#step1}
The first step is to update your dependencies' constraints in your `composer.json` file to require the latest version of modules.
### Automatically upgrade dependencies with the `recompose` upgrader command
If you've installed the upgrader, you can use the `recompose` command to help you upgrade your dependencies. This command will try to:
* upgrade your PHP constraint
* upgrade core SilverStripe modules to their version 4 equivalent
* switch to recipes where possible
* find SilverStripe 4 compatible versions of third party modules.
Take for example the following SilverStripe 3 `composer.json` file.
```json
{
"name": "app/cms-website",
"description": "The Example website project.",
"license": "BSD-3",
"require": {
"php": ">=5.3.3",
"silverstripe/cms": "3.6.5@stable",
"silverstripe/framework": "3.6.5@stable",
"silverstripe/reports": "3.6.5@stable",
"silverstripe/siteconfig": "3.6.5@stable",
"dnadesign/silverstripe-elemental": "^1.8.0"
}
}
```
You can upgrade the `composer.json` file with this command:
```bash
upgrade-code recompose --write
```
You can add a `--recipe-core-constraint` flag to target a specific version of `silverstripe/recipe-core`. By default, the project will be upgraded to the latest stable version. If you are upgrading a CWP project, you can use `--cwp-constraint` instead to target a specific version of `cwp/cwp-core`.
The upgrader uses [caret version constraint](https://getcomposer.org/doc/articles/versions.md#caret-version-range-) by default. This will cause composer to install compatible minor releases. You can use the `--strict` option if you want to use the more conservative [tilde version constraints](https://getcomposer.org/doc/articles/versions.md#tilde-version-range-).
Omit the `--write` flag to preview your changes.
Your upgraded `composer.json` file will look like this.
```json
{
"name": "app/cms-website",
"description": "The Example website project.",
"license": "BSD-3",
"require": {
"dnadesign/silverstripe-elemental": "^4.0",
"php": ">=5.6",
"silverstripe/recipe-cms": "^4.3"
}
}
```
If the `recompose` command can't find a compatible version for one of your modules, it will keep this dependency in your `composer.json` file with its existing constraint.
[Continue to Step 2](#step2)
### Manually upgrading your dependencies
The instructions in this section assume that you'll be editing your `composer.json` file in a text editor.
#### Switching to recipes
Where possible, we recommend you use recipes.
If your SilverStripe 3 project requires the `silverstripe/cms` module, replace that dependency with `silverstripe/recipe-cms`. The version constraint for `silverstripe/recipe-cms` must match your targeted version of SilverStripe:
* `~4.0.0` to upgrade to SilverStripe 4.0
* `~4.1.0` to upgrade to SilverStripe 4.1
* `~4.2.0` to upgrade to SilverStripe 4.2
* and so on.
If your SilverStripe 3 project requires the `silverstripe/framework` module without `silverstripe/cms`, replace `silverstripe/framework` with `silverstripe/recipe-core`. The version constraint for `silverstripe/recipe-core` must match your targeted version of SilverStripe:
* `~4.0.0` to upgrade to SilverStripe 4.0
* `~4.1.0` to upgrade to SilverStripe 4.1
* `~4.2.0` to upgrade to SilverStripe 4.2
* and so on.
The following modules are implicitly required by `silverstripe/recipe-core`. They can be removed from your `composer.json` dependencies if you are using `silverstripe/recipe-core` or `silverstripe/recipe-cms`.
* `silverstripe/framework`
* `silverstripe/config`
* `silverstripe/assets`
The following modules are implicitly required by `silverstripe/recipe-cms`. They can be removed from your `composer.json` dependencies if you are using `silverstripe/recipe-cms`.
* `silverstripe/admin`
* `silverstripe/asset-admin`
* `silverstripe/campaign-admin`
* `silverstripe/cms`
* `silverstripe/errorpage`
* `silverstripe/reports`
* `silverstripe/graphql`
* `silverstripe/siteconfig`
* `silverstripe/versioned`
* `silverstripe/recipe-core`
Take for example the following SilverStripe 3 `composer.json`.
```json
{
"name": "app/cms-website",
"require": {
"silverstripe/cms": "3.6.5@stable",
"silverstripe/framework": "3.6.5@stable",
"silverstripe/reports": "3.6.5@stable",
"silverstripe/siteconfig": "3.6.5@stable"
}
}
```
After switching to SilverStripe 4 recipes, the `composer.json` file should look like this.
```json
{
"name": "app/cms-website",
"require": {
"silverstripe/recipe-cms": "~4.1.0"
}
}
```
#### Explicitly defining your dependencies
If you would rather explicitly define your dependencies, you can do so. Update the `silverstripe/framework` constraint and `silverstripe/cms` constraint to match your targeted minor version of SilverStripe 4. If you use `silverstripe/reports` and `silverstripe/siteconfig`, update their constraints as well.
In most cases, you'll also want to require the same modules as the equivalent recipes. If you don't, your users will likely lose some features after the upgrade is completed.
Take for example the following SilverStripe 3 `composer.json`.
```json
{
"name": "app/cms-website",
"require": {
"silverstripe/cms": "3.6.5@stable",
"silverstripe/framework": "3.6.5@stable",
"silverstripe/reports": "3.6.5@stable",
"silverstripe/siteconfig": "3.6.5@stable"
}
}
```
After switching to SilverStripe 4 and explicitly defining your dependencies, the `composer.json` file should look like this.
```json
{
"name": "app/cms-website",
"require": {
"silverstripe/cms": "~4.1.0",
"silverstripe/framework": "~4.1.0",
"silverstripe/reports": "~4.1.0",
"silverstripe/siteconfig": "~4.1.0",
"silverstripe/admin": "~1.1.0",
"silverstripe/asset-admin": "~1.1.0",
"silverstripe/campaign-admin": "~1.1.0",
"silverstripe/errorpage": "~1.1.0",
"silverstripe/graphql": "~1.1.0",
"silverstripe/versioned": "~1.1.0"
}
}
```
#### Updating third party dependencies
If you project requires third party modules, you'll need to adjust their associated constraint. This will allow you to retrieve a SilverStripe 4 compatible version of the module.
[Look up the module on Packagist](https://packagist.org/) to see if a SilverStripe 4 version is provided.
Take for example the following SilverStripe 3 `composer.json`.
```json
{
"name": "app/cms-website",
"require": {
"silverstripe/framework": "3.6.5@stable",
"silverstripe/cms": "3.6.5@stable",
"dnadesign/silverstripe-elemental": "^1.8.0"
}
}
```
Looking at the [Packagist entry for `dnadesign/silverstripe-elemental`](https://packagist.org/packages/dnadesign/silverstripe-elemental#2.0.0), you can see that versions 2.0.0 and above of this module are compatible with SilverStripe 4. So you can update that constraint to `^2.0.0`.
Alternatively, you can set a very permissive constraint and let composer find a SilverStripe 4 compatible version. After you're done updating your dependencies, make sure you adjust your constraints to be more specific.
Once you've updated your third-party modules constraints, try updating your dependencies by running `composer update`. If composer can't resolve all your dependencies it will throw an error.
### Resolving conflicts
You'll likely have some conflicts to resolve, whether you've updated your dependencies with the upgrader or manually.
Running a `composer update` will tell you which modules are conflicted and suggested alternative combinations of modules that might work.
The most typical reason for a conflict is that the maintainer of a module hasn't released a version compatible with SilverStripe 4.
If the maintainer of the module is in the process of upgrading to SilverStripe 4, a development version of the module might be available. In some cases, it can be worthwhile to look up the repository of the module or to reach out to the maintainer.
<div class="info" markdown="1">
If you're going to install development version of third party modules, you should consider adding the following entries to your `composer.json` file.
```json
{
// ...
"minimum-stability": "dev",
"prefer-stable": true,
// ...
}
```
</div>
To resolve a conflict you can either:
* fork the affected module and upgrade it yourself. Don't forget to send a pull request to the original module!
* Integrate the affected module into your project's codebase
* Remove the module from your project, if it is not essential
To integrate a third party module in your project, remove it from your `composer.json` file and from your `.gitignore` file. Then track the module's codebase in your project source control. You'll need to upgrade the module's code to be compatible with SilverStripe 4.
<div class="info" markdown="1">
If you're taking the time to upgrade a third party module, consider doing a pull request against the original project so other developers can benefit from your work or releasing your fork as a seperate module.
[Learn about how to publish a SilverStripe module](/developer_guides/extending/how_tos/publish_a_module)
</div>
### Finalising your dependency upgrade
Once you've resolved all conflicts in your `composer.json` file, `composer update` will be able to run without errors.
This will install your new dependencies. You'll notice many of the folders in the root of your project will disappear. That's because SilverStripe 4 modules can be installed in the vendor folder like generic PHP packages.
If you've decided to use recipes, some generic files will be copied from the recipe into your project. The `extra` attribute in your `composer.json` file will be updated to keep track of those new files.
This is a good point to commit your changes to your source control system before moving on to the next step.
## Step 2 - Update your environment configuration {#env}{#step2}
The php configuration `_ss_environment.php` file has been replaced with a non-executable
`.env` file. It follows a syntax similar to a `.ini` file for key/value pair assignment. Your `.env` file may be placed in your project root, or one level above your project root.
Read the [Environment management](/getting_started/environment_management/) documentation to learn more about configuring your project's environment.
### Automatically convert `_ss_environment.php` to `.env`
If you have installed the upgrader tool, you can use the `environment` command to generate a valid `.env` file from your existing `_ss_environment.php` file.
```bash
upgrade-code environment --write
```
If your `_ss_environment.php` file contains unusual logic (conditional statements or loops), you will get a warning. `upgrade-code` will still try to convert the file, but you should double-check the output. Omit the `--write` flag to do a dry-run.
[Continue to "Cleaning up `mysite/_config.php`"](#env-config-cleanup)
### Manually convert `_ss_environment.php` to `.env`
Create a `.env` file in the root of your project. Replace `define` statements from `_ss_environment.php` with `KEY=VALUE` pairs in `.env`.
Most SilverStripe 3 environment variables have been carried over to SilverStripe 4. See [Environment Management docs](/getting_started/environment_management/) for the full list of available variables. Your `.env` file can contain environment variables specific to your project as well.
The global array `$_FILE_TO_URL_MAPPING` has been removed and replaced with the `SS_BASE_URL` environment variable. `SS_BASE_URL` expects an absolute url with an optional protocol. The following are values would be valid entries for `SS_BASE_URL`:
* `http://localhost/`
* `https://localhost/`
* `//localhost/`
For example, take the following `_ss_environment.php` file.
```php
<?php
// Environment
define('SS_ENVIRONMENT_TYPE', 'dev');
define('SS_DEFAULT_ADMIN_USERNAME', 'admin');
define('SS_DEFAULT_ADMIN_PASSWORD', 'password');
$_FILE_TO_URL_MAPPING[__DIR__] = 'http://localhost';
// Database
define('SS_DATABASE_CHOOSE_NAME', true);
define('SS_DATABASE_CLASS', 'MySQLDatabase');
define('SS_DATABASE_USERNAME', 'root');
define('SS_DATABASE_PASSWORD', '');
define('SS_DATABASE_SERVER', '127.0.0.1');
```
The equivalent `.env` file will look like this.
```bash
## Environment
SS_ENVIRONMENT_TYPE="dev"
SS_DEFAULT_ADMIN_USERNAME="admin"
SS_DEFAULT_ADMIN_PASSWORD="password"
SS_BASE_URL="http://localhost/"
## Database
SS_DATABASE_CHOOSE_NAME="true"
SS_DATABASE_CLASS="MySQLDatabase"
SS_DATABASE_USERNAME="root"
SS_DATABASE_PASSWORD=""
SS_DATABASE_SERVER="127.0.0.1"
```
### Cleaning up `mysite/_config.php` after your environment configuration upgrade {#env-config-cleanup}
Regardless if you've used the automated or manual path,
you'll need to clean up your `mysite/_config.php` file after upgrading your environment file.
The global values `$database` and `$databaseConfig` have been deprecated. Your database configuration details should be stored in your `.env` file. If you want to keep your database configuration in `_config.php`, you can use the new `DB::setConfig()` api, however this is discouraged.
Requiring `conf/ConfigureFromEnv.php` is is no longer necessary. You should remove any references to it in `_config.php`.
The removal of the `_ss_environment.php` file means that conditional logic is no longer available in the environment
variable set-up process. This encouraged bad practice and should be avoided. If you still require conditional logic early in the bootstrap, this is best placed in the `_config.php` files.
To access environment variables, use the `SilverStripe\Core\Environment::getEnv()` method. To define environment variables, use the `SilverStripe\Core\Environment::setEnv()` method.
### Finalising your environment upgrade
It's inadvisable to track your `.env` file in your source control system as it might contain sensitive information.
You should ignore the `.env` file by adding an entry to your `.gitignore` file. You can create a sample environment configuration by duplicating your `.env` file as `.env.sample`, and removing sensitive information from it. You can safely delete your legacy `_ss_environment.php` if you want.
This is a good point to commit your changes to your source control system before moving on to the next step.
## Step 3 - Namespacing your project (optional) {#step3}
Namespacing your code is an optional step. It is recommended and will help future-proof your code base.
Read more about [PHP Namespaces](http://php.net/manual/en/language.namespaces.php)
and the [PSR-4 Autoloader Standard](https://www.php-fig.org/psr/psr-4/).
### Before you start namespacing your codebase
You need to choose a root namespace for your project. We recommend following the `Vendor\Package` pattern.
The `Page` and `PageController` classes *must* be defined in the global namespace (or without a namespace).
If you want your codebase to comply with the PSR-4 standard, make sure sub-directories of your code folder are using the _UpperCamelCase_ naming convention. For example, `mysite/code/page_types` should be renamed to `mysite/code/PageTypes`.
### Automatically namespacing your codebase with the upgrader
The `add-namespace` command of the [upgrader tool](https://github.com/silverstripe/silverstripe-upgrader/) provides a feature
to namespace your codebase and to automatic update references to those classes.
```bash
upgrade-code add-namespace "App\\Web" ./mysite/code --recursive --write
```
This task will do the following:
* Add the given namespace to all files in the code folder, and subdirectories
* All references to classes in any namespaced files will be safely retained with additional `use` directives added as necessary
* A `mysite/.upgrade.yml` file will be created/updated to record the new fully qualified name of each class.
This will be used in later steps to update references to the outdated class names in your own project code.
By default, the same namespace will be applied to all your classes regardless of which directory they are in. If you want to apply different namespaces to different folders to be compliant with PSR-4, combine the `--recursive` option with the `--psr4` option. Your folder structure must be PSR-4 compliant for this to work. If you want to do a dry-run, omit the `--write` option to see a preview of all changed project files.
[Continue to Step 4](#step4)
### Manually namespacing your codebase
Go through each PHP file under `mysite/code` and add a `namespace` statement at the top, *with the exception of the files for `Page` or `PageController`*.
Take for example this SilverStripe 3 file located at `mysite/code/Products/ExplosiveTennisBall.php`.
```php
<?php
class ExplosiveTennisBall extends DataObject
{
// ...
}
```
Assuming your root namespace is `App\Web`, the equivalent namespaced file will look like this.
```php
<?php
namespace App\Web\Products;
class ExplosiveTennisBall extends DataObject
{
// ...
}
```
If you intend to use the upgrader to update references to your namespaced classes, you'll need to create a `mysite/.upgrade.yml` file.
```yaml
mappings:
ExplosiveTennisBall: App\Web\Products\ExplosiveTennisBall
```
If you intend to manually update references to your namespaced classes, you'll need to go through each of your file to add `use` statements.
For example, if `mysite/code/ProductService.php` is using the `ExplosiveTennisBall` class, you'll need to add a use statement at the top of the file just after it's own namespace definition.
```php
<?php
namespace App\Web;
use App\Web\Products\ExplosiveTennisBall;
class ProductService
{
// ...
}
```
### Enable PSR-4 auto-loading in your `composer.json` file
If you have namespaced your project and followed the PSR-4 convention, you have the option to enable PSR-4 auto-loading in your composer.json file.
Enabling PSR-4 auto-loading is optional. It will provide better auto-loading of your classes in your development environment and will future proof your code.
For example, let's say you have defined the following namespaces for the following folders:
* `App\Web` for your main application logic contained in `mysite/code`
* `App\SubModule` for a secondary module contained in `sub-module/code`
* `App\Web\Tests` for your application test suite contained in `mysite/tests`.
Your `autoload` section in your `composer.json` file should look like this:
```json
{
// ...
"autoload": {
"psr-4": {
"App\\Web\\": "mysite/code",
"App\\SubModule\\": "sub-module/code"
}
},
"autoload-dev": {
"psr-4": {
"App\\Web\\Tests\\": "mysite/tests"
}
},
// ...
}
```
Read the [Composer schema autoload documentation](https://getcomposer.org/doc/04-schema.md#autoload) for more information about configuring auto-loading in your project.
### Finalise your project namespacing
All your classes should now be fully namespaced.
Note that applying a namespace to your project will also affect which template file SilverStripe tries to load when rendering certain objects.
For example, pretend you have a `RoadRunnerPage` class that extends `Page`. In SilverStripe 3, you would define a template for this page in `themes/example/templates/Layout/RoadRunnerPage.ss`. If you decide to move `RoadRunnerPage` to `App\Web\RoadRunnerPage`, you'll need to move the template to `themes/example/templates/App/Web/Layout/RoadRunnerPage.ss`.
This is a good point to commit your changes to your source control system before moving on to the next step.
## Step 4 - Update codebase with references to newly namespaced classes {#step4}
All core PHP classes in SilverStripe 4 have been namespaced. For example, `DataObject` is now called `SilverStripe\ORM\DataObject`. Your project codebase, config files and language files need be updated to reference those newly namespaced classes. This will include explicit references in your PHP code, but also string that contain the name of a class.
If you've opted to namespace your own code in the previous step, those references will need to be updated as well.
### Automatically update namespaced references with the `upgrade` command
If you've installed the upgrader, you can use the `upgrade` command to update references to namespaced classes.
The `upgrade` command will update PHP files, YML configuration files, and YML language files.
Each core SilverStripe 4 module includes a `.upgrade.yml` that defines the equivalent fully qualified name of each class. Most third party SilverStripe modules that have been upgraded to be compatible with SilverStripe 4, also include a `.upgrade.yml`. If you've namespaced your own project, you'll need to provide your own `.upgrade.yml` file . If you've used the upgrader to namespace your project, that file will have been created for you.
The `upgrade` command will try to update some strings that reference the old name of some classes. In some cases this might not be what you want. You can tell the upgrader to skip specific strings by using the `@skipUpgrade` flag in your PHPDoc comment. For example:
```PHP
/** @skipUpgrade */
return Injector::inst()->get('ProductService');
```
Execute the upgrade command with this command.
```bash
upgrade-code upgrade ./mysite/ --write
```
If you omit the `--write` flag you will get a preview of what change the upgrader will apply to your codebase. This can be helpful if you are tweaking your `.upgrade.yml` or if you are trying to identify areas where you should add a `@skipUpgrade` statement,
You can also tweak which rules to apply with the `--rule` flag: `code`, `config`, and `lang`. For example, the following command will only upgrade `lang` and `config` files:
```bash
upgrade-code upgrade ./mysite/ --rule=config --rule=lang
```
The `upgrade` command can alter big chunks of your codebase. While it works reasonably well in most use cases, you should not trust it blindly. You should take time to review all changes applied by the `upgrade` command and confirm you are happy with them.
[Continue to "Finalising namespace updates"](#namespace-finalise)
#### Rename Warnings
You can also show extra warnings for potentially ambiguous mappings with the `renameWarnings` property:
```yaml
renameWarnings:
- File
- Image
```
An example of an ambiguous rename would be:
```PHP
private static $has_one = [
'Image' => 'Image',
];
```
Add the `--prompt` flag to manually approve ambiguous class renames.
### Manually update namespaced references
If you decide to update your namespace references by hand, you'll need to go through the entire code base and update them all from the old non-namespaced SilverStripe classes to the new namespaced equivalent. If you are referencing classes from third party modules that have been namespaced, you'll need to update those as well.
#### Update explicit references to classes in your code
Wherever your code explicitly references a SilverStripe class, it will need to be updated to the new namespaced equivalent. You can either update the reference to use the fully qualified name of the class or you can add a `use` statement to your file.
For example take the following SilverStripe 3 class. `DataObject` and `FieldList` need to point to their namespace equivalents.
```php
<?php
namespace App\Web\Products;
class ExplosiveTennisBall extends DataObject
{
public function getCMSFields()
{
return FieldList::create([]);
}
}
```
You can add `use` statements at the top of your file to reference the fully qualified name of `DataObject` and `FieldList`.
```php
<?php
namespace App\Web\Products;
use SilverStripe\ORM\DataObject;
use SilverStripe\Forms\FieldList;
class ExplosiveTennisBall extends DataObject
{
// ...
```
Alternatively, you can update the references to the fully qualified names.
```php
<?php
namespace App\Web\Products;
class ExplosiveTennisBall extends SilverStripe\ORM\DataObject
{
public function getCMSFields()
{
return SilverStripe\Forms\FieldList::create([]);
}
}
```
#### Update string references to classes
In many cases, SilverStripe expects to be provided the name of a class as a string. Typical scenarios include:
* defining an `has_one` or `has_many` relationship on a DataObject
* requesting an instance of class via the Injector
* specifying managed models for a `ModelAdmin`.
Those string need to use the fully qualified name of their matching classes. Take for example the following class.
```php
<?php
namespace App\Web\Products;
use SilverStripe\ORM\DataObject;
class ExplosiveTennisBall extends DataObject
{
private static $has_one = [
'Thumbnail' => 'Image'
];
private static $has_many = [
'Tags' => 'BlogPost'
];
public function getShippingCost()
{
return Injector::inst('ProductService')->calculateCost($this);
}
}
```
`Image`, `BlogPost`, and `ProductService` represent classes. Those strings need to be updated to specify the full namespace.
The best way of achieving this is to use the [`::class` PHP magic class constant](http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.class.class) which will return the fully qualified name of a class.
Our example could be update to:
```php
<?php
namespace App\Web\Products;
use SilverStripe\ORM\DataObject;
use SilverStripe\Assets\Image;
use SilverStripe\Blog\Model\BlogPost;
use App\Web\ProductService;
class ExplosiveTennisBall extends DataObject
{
private static $has_one = [
'Thumbnail' => Image::class
];
private static $has_many = [
'Tags' => BlogPost::class
];
public function getShippingCost()
{
return Injector::inst(ProductService::class)->calculateCost($this);
}
}
```
Alternatively, you can spell out the full qualified name of each class in a string. For example, `'Image'` would become `'SilverStripe\\Assets\\Image'`. Note the use of the _double backslash_ — this is necessary because the backslash is an escape character.
#### Update references to classes in your YML config
YAML configuration files can reference SilverStripe classes. Those references also need to use the fully qualified name of each class.
Take for example the following SilverStripe 3 YAML configuration file.
```yaml
Injector:
ProductService:
properties:
RoadRunnerSpeed: 99999999
CoyoteSpeed: 1
BlogPost:
extensions:
- HasOneExplosiveTennisBallExtension
Email:
admin_email: no-reply@example.com
```
In SilverStripe 4, this will become:
```yaml
SilverStripe\Core\Injector\Injector:
App\Web\ProductService:
properties:
RoadRunnerSpeed: 99999999
CoyoteSpeed: 1
SilverStripe\Blog\Model\BlogPost:
extensions:
- App\Web\Extensions\HasOneExplosiveTennisBallExtension
SilverStripe\Control\Email\Email:
admin_email: no-reply@example.com
```
#### Update references to classes in your language files
Translation keys are normally tied to classes. If you override SilverStripe's default translation or if you are localising your own project, you'll need to update those references to use the fully qualified name of each class.
For example, let's say you had the following translation file in `mysite/lang/eng.yml`.
```yaml
en:
Member:
SINGULARNAME: Coyote
RoadRunner:
SALUTATION: Beep Beep
```
In SilverStripe 4, it would become:
```yaml
en:
SilverStripe\Security\Member:
SINGULARNAME: Coyote
App\Web\RoadRunner:
SALUTATION: Beep Beep
```
### Finalising namespace updates {#namespace-finalise}
You'll need to perform the following steps manually, even if you've used the automated rewrite of namespaces.
DataObject database tables will default to use a namespaced name. For example, if you have a class under `App\Web\Products\ExplosiveTennisBall` that extends `DataObject`, the matching table in your database will be called `App_Web_Products_ExplosiveTennisBall`.
You can define a `private static $table_name` property on your DataObjects to use more convenient table names.
For example, `private static $table_name = 'ExplosiveTennisBall';`.
In your PHP code, calls to the `_t()` method should be updated to use the full namespace of the target class.
```php
<?php
# Old SilverStripe 3 way
$translation = _t('CMSMain.ACCESS', "Access to ''{title}'' section", ['title' => 'Products']);
# New SilverStripe 4
use SilverStripe\CMS\Controllers\CMSMain;
// ...
$translation = _t(CMSMain::class . '.ACCESS', "Access to '{title}' section", ['title' => 'Products']);
```
If you're calling `_t()` to retrieve a translation for the current class, you can also use `__CLASS__` or `self::class`. For example:
```php
<?php
namespace App\Web\Services;
class ProductService
{
public function getTranslation()
{
# Those two lines are equivalent.
$translation = _t(__CLASS__ . '.PRODUCT', 'Product');
$translation = _t(self::class . '.PRODUCT', 'Product');
return $translation;
}
}
```
<div class="warning" markdown="1">
Avoid using `static::class` or `parent::class` to retrieve translated string. It will retrieve unpredictable values bases on the class inheritance.
</div>
If your template files contain translatable strings, they also need to be updated to referenced the namespaced classes.
For example, `<%t Member.SINGULARNAME 'Member' %>` would become `<%t SilverStripe\Security\Member.SINGULARNAME 'Member' %>`.
Your codebase should now be referencing valid SilverStripe 4 classes. This means that your classes can be loaded at runtime. However, your codebase will still be using an outdated API.
This is a good point to commit your changes to your source control system before moving on to the next step.
## Step 5 - Updating your codebase to use SilverStripe 4 API {#step5}
This is the most intricate and potentially time-consuming part of the upgrade. It involves going through your entire codebase to remove references to deprecated APIs and update your project logic.
### Automatically update deprecated API references with the `inspect` command
The upgrader has an `inspect` command that can flag deprecated API usage, and in some cases, update your codebase to the SilverStripe 4 equivalent. This does require you to carefully review each change and warning to make sure the updated logic still work as intended. Even so, it is a huge time-saver compared to reviewing your code base manually.
Note that the `inspect` command loads your files with PHP interpreter. So basic syntax errors — for example, extending a class that does not exists — will cause an immediate failure. For this reason, you need to complete [Step 4 - Update codebase with references to newly namespaced classes](#step4) before running the `inspect` command.
```bash
upgrade-code inspect ./mysite/ --write
```
You can omit the `--write` flag if you just want to view the proposed changes without applying them. You can run the command on a specific subdirectory or file. This can be more manageable if you have a big project to go through.
Like the `upgrade` command, `inspect` gets its list of API changes from `.upgrade.yml` files. So you may get upgrade suggestions and substitution from third party modules. You can even include your own project specific changes in your `.upgrade.yml` if you want.
#### Sample output of the `inspect` command
Here's some sample output of what you might get back from the `inspect` command.
```bash
upgrade-code inspect ./mysite/Models/Coyote.php
Running post-upgrade on "/var/www/SS_example/mysite/code/Models/Coyote.php"
[2018-06-06 13:35:38] Applying ApiChangeWarningsRule to Coyote.php...
modified: Coyote.php
@@ -68,7 +68,7 @@
{
// Getting a reference to Coyote's list of crazy ideas
- $manyManyRelation = $this->manyManyComponent('CrazyIdeas');
+ $manyManyRelation = $this->getSchema()->manyManyComponent('CrazyIdeas');
return $manyManyRelation;
}
Warnings for Coyote.php:
- Coyote.php:20 SS_Cache: Using symfony/cache now (https://docs.silverstripe.org/en/4/changelogs/4.0.0#cache)
- Coyote.php:42 SilverStripe\Control\Director::setUrlParams(): Method removed
- Coyote.php:71 SilverStripe\ORM\DataObject->manyManyComponent(): DataObject->manyManyComponent() moved to DataObjectSchema. Access through getSchema(). You must manually add static::class as the first argument to manyManyComponent()
Changes not saved; Run with --write to commit to disk
```
### Manually update deprecated API references
SilverStripe 4 introduces many API changes. To update deprecated API references manually, you have to go through each one of your project files.
Read the changelogs for [4.0.0](/changelogs/4.0.0/) and for [subsequent minor releases](/changelogs)
### Finalising the deprecated API update
At this stage, your site should be using only SilverStripe 4 API logic.
You still have some minor clean up tasks and configuration tweaks to apply, but you're almost done.
This is a good point to commit your changes to your source control system before moving on to the next step.
## Step 6 - Update your entry point {#step6}
The location of SilverStripe's _entry file_ has changed. Your project and server environment will need
to adjust the path to this file from `framework/main.php` to `index.php`.
### Update your `index.php` file
You can get a copy of the SilverStripe 4 `index.php` file at
`vendor/silverstripe/recipe-core/public/index.php`.
If you've created your own `index.php` or modified version of `main.php`,
you'll need to reconcile those changes with the `index.php` file you got from `recipe-core`.
Otherwise, just use the generic `index.php` file `recipe-core` provides.
Copy your new `index.php` to your project's web root. Unlike SilverStripe 3, `index.php` must be present in your web root.
### Update your server configuration
If you're using a `.htaccess` file or `web.config` file to handle your server configuration, you can get the generic SilverStripe 4 version of those file from
`vendor/silverstripe/recipe-core/public`.
Just like `index.php`, if you've modified your server configuration file from the one that shipped with SilverStripe 3, you'll need to reconcile your changes into the version retrieve from `recipe-core`.
[Refer to the installation instruction for your platform](/getting_started/installation/) if your server configuration is not managed via a `.htaccess` or `web.config` file.
### Finalising the entry point upgrade
At this stage, you could in theory run your project in SilverStripe 4.
This is a good point to commit your changes to your source control system before moving on to the next step.
## Step 7 - Update project structure (optional) {#step7}
SilverStripe 4 introduces a new recommended project structure ([details](/changelogs/4.2.0#app-folder-name)).
Adopting the recommended project structure is optional, but will become mandatory in SilverStripe 5.
[Skip to Step 8](#step8)
### Automatically switch to the new structure with the `reorganise` command
The reorganise command can automatically update your project to use the new recommended structure.
It will search your code and find any occurrence of `mysite`. It won't replace those occurrences with `app` however.
```bash
upgrade-code reorganise --write
```
Omit the `--write` flag if you just want to preview your changes
### Manually switch to the new structure
Simply rename your `mysite` fold to `app`. Then rename `app/code` to `app/src`.
### Finalising the reorganise structure
If you've implemented the new PSR-4 auto-loading logic in your `composer.json` file you'll need to update your namespace mapping.
For example, let's say you had the following autoload attribute in your `composer.json`.
```json
{
// ...
"autoload": {
"classmap": [
"mysite/code/Page.php",
"mysite/code/PageController.php"
],
"psr-4": {
"App\\Web\\": "mysite/code/"
}
},
// ...
}
```
It will become this:
```json
{
// ...
"autoload": {
"classmap": [
"app/src/Page.php",
"app/src/PageController.php"
],
"psr-4": {
"App\\Web\\": "app/src/"
}
},
// ...
}
```
You'll need to update the `project` attribute for your `ModuleManifest` in your `app/src/mysite.yml` file. It should now look something like this:
```yaml
SilverStripe\Core\Manifest\ModuleManifest:
project: app
```
At this stage, your project should be functional with the recommended project structure.
Note that if you've explicitly referenced any static assets (images, css, js) under `mysite`, you'll need to rewrite those references.
This is a good point to commit your changes to your source control system before moving on to the next step.
## Step 8 - Switch to public web-root (optional){#step8}
SilverStripe 4.1 introduces the concept of _public web-root_ this allows you to move
all publicly accessible assets under a `public` folder ([details](/changelogs/4.1.0#public-folder)).
This has security benefits as it minimises the possibility that files that are not meant to be access directly get accidentally exposed.
This step is optional and requires SilverStripe 4.1 or greater. It will become mandatory in SilverStripe 5.
[Skip to Step 9](#step9)
### Automatically switch to the public web root
The `webroot` upgrader command will automatically move your files for you.
```bash
upgrade-code webroot --write
```
Omit the `--write` flag if you want to preview the change.
If you are using a modified `index.php`, `.htaccess`, or `web.config`, you will get a warning.
### Manually switch to using the public web root
* Create a `public` folder in the root of your project
* Move the following files and folder to your new public folder
* `index.php`
* `.htaccess`
* `webconfig.php`
* `assets`
* Any `favicon` files
* Other common files that should be accssible in your project webroot (example: `robots.txt`)
* Delete the root `resources` or `_resources` directories if present.
* Run the following command `composer vendor-expose` to make static assets files accessible via the `public` directory.
If you are upgrading from SilverStripe 4.0 to SilverStripe 4.1 (or above), you'll need to update `index.php` before moving it to the public folder. You can get a copy of the generic `index.php` file from `vendor/silverstripe/recipe-core/public`. If you've made modifications to your `index.php` file, you'll need to replicate those into the new `public/index.php` file.
### Finalising the web root migration
You'll need to update your server configuration to point to the public directory rather than the root of your project.
Update your `.gitignore` file so `assets` and `_resources` (or `resources` if using a pre SilverStripe 4.4 release) are still ignored when located under the `public` folder.
Your project should still be functional, although you may now be missing some static assets.
This is a good point to commit your changes to your source control system before moving on to the next step.
## Step 9 - Move away from hardcoded paths for referencing static assets {#step9}
SilverStripe 4 introduces a new way to reference static assets like images and css.
This enables innovations like moving the SilverStripe module [vendor folder](/changelogs/4.0.0#vendor-folder) or the [public web root](/changelogs/4.1.0#public-folder).
This change is mandatory if you've completed either
[step 7](#step7) (update project structure) or [step 8](#step8) (switch to public web-root).
If you have skipped these steps, it is strongly recommended, but not mandatory.
### Exposing your project static assets
If you have folders under `app` or `mysite` that need to be accessible for your project's web root, you need to say so in your `composer.json` file by adding an entry under `extra.expose`.
For example, let's say you have `scripts`, `images` and `css` folders under `app`. You can expose them by adding this content to your `composer.json` file:
```json
{
// ...
"extra": {
"branch-alias": {
"4.x-dev": "4.2.x-dev"
},
"expose": [
"app/scripts",
"app/images",
"app/css"
]
},
// ...
}
```
For the change to take affect, run the following command: `composer vendor-expose`.
### Referencing static assets in your PHP code
Wherever you would have use a hardcoded path, you can now use the `path/to/file.css` syntax. To reference a static file from a module, prefix the path with the module's name (e.g.: `silverstripe/admin:client/dist/js/bundle.js`).
To add some javascript and css files to your requirements from your PHP code, you could use this syntax:
```php
use SilverStripe\View\Requirements;
# Load your own style and scripts
Requirements::css('app/css/styles.css');
Requirements::script('app/scripts/client.css');
# Load some assets from a module.
Requirements::script('silverstripe/blog: js/main.bundle.js');
```
You can `SilverStripe\Core\Manifest\ModuleResourceLoader` to get the web path of file.
```php
ModuleResourceLoader::singleton()->resolveURL('app/images/road-runner.jpg');
ModuleResourceLoader::singleton()->resolveURL('silverstripe/blog: js/main.bundle.js');
```
You can use `SilverStripe\View\ThemeResourceLoader` to access files from your theme:
```php
$cssResourcePath = ThemeResourceLoader::inst()->findThemedResource('css/layout.css');
$relativeUrl = ModuleResourceLoader::singleton()->resolveURL($cssResourcePath);
```
For classes that expect icons, you can specify them with:
```php
class ListingPage extends \Page
{
private static $icon = 'app/images/sitetree_icon.png';
}
class MyCustomModelAdmin extends \SilverStripe\Admin\ModelAdmin
{
private static $menu_icon = 'app/images/modeladmin_icon.png';
}
```
### Referencing static assets in template files
SilverStripe template files accept a similar format for referencing static assets. You will need to go through your assets files and remove hardcoded references.
```html
<img src="$resourceURL(app/images/coyote.png)" />
<% require css("app: css/styles.css") %>
```
### Finalising removal of hardcoded paths for referencing static assets
All your assets should be loading properly now.
This is a good point to commit your changes to your source control system before moving on to the next step.
## Step 10 - Update database class references {#step10}
If you've updated your class names to use namespaces you will need to reflect those changes in any existing database fields. For example, if you've renamed your `HomePage` class to `App\HomePage` then the database `ClassName` column needs to be updated to point to the `App\HomePage` class, otherwise the CMS will tell you that the page is obsolete. This also applies to polymorphic relationships.
There is no automated way to do this, but you can use the list generated in .upgrade.yml and copy it to `app/_config/legacy.yml`, removing any classes that don't extend DataObject.
```
SilverStripe\ORM\DatabaseAdmin:
classname_value_remapping:
HomePage: App\HomePage
```
This will automatically update affected columns when you first build the database.
## Step 11 - Running your upgraded site for the first time {#step11}
You're almost across the finish line.
### Run a dev build
Run a `dev/build` either on the command line or in your browser.
```bash
./vendor/bin/sake dev/build
```
This should migrate your existing data (non-destructively) to the new SilverStripe 4 structure.
#### Migrating files
Since the structure of the `File` DataObject has changed, a new task `MigrateFileTask`
has been added to assist in migration of legacy files (see [file migration documentation](/developer_guides/files/file_migration)).
```bash
./vendor/bin/sake dev/tasks/MigrateFileTask
```
### Any other script that needs running.
Some third party modules may include their own migration tasks. Take a minute to consult the release notes of your third party dependencies to make sure you haven't missed anything.

View File

@ -0,0 +1,331 @@
title: Upgrading a module
introduction: Upgrade your module to be compatible with SilverStripe 4 and make it easy for your users to upgrade.
# Upgrading a module to be compatible with SilverStripe 4
This guide will help you upgrade a SilverStripe 3 module to be compatible with SilverStripe 4.
You should be familiar with [Upgrading a project to SilverStripe 4](Upgrading_project) before reading this guide. The process for upgrading a SilverStripe module is very similar to the process for Upgrading a SilverStripe project. This guide focuses on highlighting ways in which upgrading a module differs from upgrading a regular project.
## Improving the upgrade experience of your users with a`.upgrade.yml` file
Making your module compatible with SilverStripe 4 is only one part of the process. As a module maintainer, you also want to provide a good upgrade experience for your users. Your module can integrate with the [SilverStripe upgrader](https://github.com/silverstripe/silverstripe-upgrader) just like the SilverStripe core modules.
Your SilverStripe 4 module should ship with a `.upgrade.yml` file. This file is read by the upgrader and will define new APIs introduced by the upgraded version of your module. Each step in this guide details what entry you should add to your module's `.upgrade.yml` file.
## Step 0 - Branching off your project
You'll want to run your module upgrade on a dedicated development branch. While it's possible to upgrade a module from within a SilverStripe project, it's usually cleaner and easier to clone your module and work directly on it.
```bash
# We're assumming that the default branch of you module is the latest SS3 compatible branch
git clone git@github.com:example-user/silverstripe-example-module.git
cd silverstripe-example-module
git checkout -b pulls/ss4-upgrade
git push origin pulls/ss4-upgrade --set-upstream
```
If you're planning to keep supporting the SilverStripe 3 version of your module, consider creating a dedicated SilverStripe 3 branch.
To require the development branch of your module in a SilverStripe 4 project, you can use composer and prefix the name the name of your branch with `dev-`.
```bash
composer require example-user/silverstripe-example-module dev-pulls/ss4-upgrade
```
If the development branch is hosted on a different Git remote than the one used to publish your module, you'll need to add a VCS entry to your test project's `composer.json` file.
```json
{
"name": "example-user/test-project",
"type": "project",
"require": {
"example-user/silverstripe-example-module": "dev-pulls/ss4-upgrade"
},
"repositories": [
{
"type": "vcs",
"url": "git@github.com:alternative-user/silverstripe-example-module.git"
}
]
}
```
You will not be able to install your development branch in a SilverStripe 4 project until you've adjusted your module's dependencies.
## Step 1 - Upgrade your dependencies
Before you can install your module in a SilverStripe 4 project, you must update your module's `composer.json` file to require SilverStripe 4 compatible dependencies. In most cases, you'll be better off updating your module's composer file manually, especially if your module only requires a small number of dependencies. You can use upgrader's `recompose` command if you want, but you'll need to carefully validate the resulting `composer.json` file.
### Update module's type
SilverStripe 4 modules are now installed inside the vendor directory. To get your module installed in the vendor directory, you'll need to update its `type` to `silverstripe-vendormodule`. You'll also need to add a dependency to `silverstripe/vendor-plugin`.
```diff
{
"name": "example-user/silverstripe-example-module",
- "type": "silverstripe-module",
+ "type": "silverstripe-vendormodule",
"require": {
+ "silverstripe/vendor-plugin": "^1",
+ "silverstripe/framework": "^3"
}
}
```
### Prefer specific modules over recipes
When upgrading a project, it is recommended to require recipes rather than modules. However, when upgrading a module, you want to limit the number of additional packages that gets installed along with your module. You should target specific packages that your module depends on.
For example, let's say your module adds a ModelAdmin to the SilverStripe administration area without interacting with the CMS directly. In this scenario, the main module you need is `silverstripe/admin` which contains the `ModelAdmin` class and related administration functionality. If you update your `composer.json` file to require `silverstripe/recipe-cms`, you'll force your users to install a lot of modules they may not need like `silverstripe/cms`, `silverstripe/campaign-admin`, `silverstripe/asset-admin`, `silverstripe/versioned-admin`.
### Avoid rigid constraints
Choose constraints based on the minimum version of SilverStripe 4 you are planning on supporting and allow your module to work with future releases.
For example, if your module requires an API that got introduced with the 4.1 release of `silverstripe/framework`, then that's the version you should target. You should use the caret symbol (`^`) over the tilde (`~`) so your module works with more recent releases. In this scenario, your constraint should look like `"silverstripe/framework": "^4.1"`.
### Avoid tracking unnecessary files
If you run composer commands from your module's folder, a lock file will be created and dependencies will be installed in a vendor folder. You may also get `project-files` and `public-files` entries added under the `extra` key in your composer.json.
While these changes may be useful for testing, they should not be part of the final release of your module.
### Finalising the module's dependency upgrade
You should commit the changes to your module's `composer.json` and push them to your remote branch.
By this point, your module should be installable in a test SilverStripe 4 project. It will be installed under the vendor directory (e.g.: `vendor/example-user/silverstripe-example-module`). However, it will throw exceptions if you try to run it.
From this point, you can either work from a test project or you can keep working directly on your module.
## Step 2 - Update your environment configuration
As a module maintainer, you shouldn't be shipping any environment file with your module. So there's no need for you to run the upgrader `environment` command. If your module requires environment variables, you should update your documentation accordingly, but otherwise you can move on to the next step.
## Step 3 - Namespacing your module
Namespacing your module is mandatory to get it working with SilverStripe 4. You can use the `add-namespace` upgrader command to achieve this.
```bash
# If you are working from a test project, you need to specify the `--root-dir` parameter
upgrade add-namespace --root-dir vendor/example-user/silverstripe-example-module \
"ExampleUser\\SilverstripeExampleModule" \
vendor/example-user/silverstripe-example-module/code/
# If you are working directly from the module, you can ommit `--root-dir` parameter
upgrade add-namespace "ExampleUser\\SilverstripeExampleModule" code/
```
If your module codebase is structured in folders, you can use the `--psr4` and `--recursive` flag to quickly namespace your entire module in one command. This command will recursively go through the `code` directory and namespace all files based on their position relative to `code`.
```bash
upgrade add-namespace --recursive --psr4 "ExampleUser\\SilverstripeExampleModule" code/
```
### Configuring autoloading
You need to update your `composer.json` file with an autoload entry, so composer knows what folder maps to what namespace.
You can do this manually:
```diff
{
"name": "example-user/silverstripe-example-module",
"type": "silverstripe-vendormodule",
"require": {
"silverstripe/framework": "^4",
"silverstripe/vendor-plugin": "^1"
- }
+ },
+ "autoload": {
+ "psr4": {
+ "ExampleUser\\SilverstripeExampleModule\\": "code/",
+ "ExampleUser\\SilverstripeExampleModule\\Tests\\": "tests/"
+ }
+ }
}
```
Alternatively, you can use the `--autoload` parameter when calling `add-namespace` to do this for you.
```bash
upgrade add-namespace --recursive --psr4 --autoload "ExampleUser\\SilverstripeExampleModule" code/
upgrade add-namespace --recursive --psr4 --autoload "ExampleUser\\SilverstripeExampleModule\\Tests" tests
```
[Learn more about configuring autoloading](https://getcomposer.org/doc/04-schema.md#autoload) in your `composer.json` file.
### Preparing your `.upgrade.yml` file
`add-namespace` will create a `.upgrade.yml` file that maps your old class names to their new namespaced equivalent. This will be used by the `upgrade` command in the next step.
Depending on the nature of your module, you may have some class names that map to other common names. When the `upgrade` command runs, it will try to substitute any occurrence of the old name with the namespaced one. This can lead to accidental substitution. For example, let's say you have a `Link` class in your module. In many project the word `Link` will be used for other purposes like a field label or property names. You can manually update your `.upgrade.yml` file to define a `renameWarnings` section. This will prompt users upgrading to confirm each substitution.
```yml
mappings:
# Prompt user before replacing references to Link
Link: ExampleUser\SilverstripeExampleModule\Model\Link
# No prompt when replacing references to ExampleModuleController
ExampleModuleController: ExampleUser\SilverstripeExampleModule\Controller
renameWarnings:
- Link
```
Make sure to commit this file and to ship it along with your upgraded module. This will allow your users to update references to your module's classes if they use the upgrader on their project.
### Finalising your namespaced module
By this point:
* all your classes should be inside a namespace
* your `composer.json` file should have an autoload definition
* you should have a `.upgrade.yml` file.
However, your codebase is still referencing SilverStripe classes by their old non-namespaced names. Commit your changes before proceeding to the next step.
## Step 4 - Update codebase with references to newly namespaced classes
This part of the process is identical for both module upgrades and project upgrades.
```bash
# If upgrading from inside a test project
upgrade-code upgrade --root-dir vendor/example-user/silverstripe-example-module \
vendor/example-user/silverstripe-example-module/
# If upgrading the module directly
upgrade-code upgrade ./
```
All references to the old class names will be replaced with namespaced class names.
By this point, you should be able to load your module with PHP. However, your module will be using deprecated APIs.
## Step 5 - Updating your codebase to use SilverStripe 4 API
This step will allow you to update references to deprecated APIs. If you are planning on making changes to your own module's API, take a minute to define those changes in your `.upgrade.yml`:
* this will help you with updating your own codebase
* your users will be warned when using your module's deprecated APIs.
You can define warnings for deprecated APIs along with a message. If there's a one-to-one equivalent for the deprecated API, you can also define a replacement. e.g.:
```yml
warnings:
classes:
'ExampleUser\SilverstripeExampleModule\Controller':
message: 'This warning message will be displayed to your users'
url: 'https://github.com/example-users/silverstripe-example-module/en/4/changelogs/#object-replace'
methods:
'ExampleUser\SilverstripeExampleModule\AmazingClass::deprecatedMethod()':
message: 'Replace with a different method'
replacement: 'newBetterMethod'
props:
'ExampleUser\SilverstripeExampleModule\AmazingClass->oldProperty':
message: 'Replace with a different property'
replacement: 'newProperty'
```
When you are done updating your `.upgrade.yml` file, you can run the `inspect` command to search for deprecated APIs.
```bash
# If upgrading from inside a test project
upgrade-code inspect --root-dir vendor/example-user/silverstripe-example-module \
vendor/example-user/silverstripe-example-module/code/
# If upgrading the module directly
upgrade-code inspect code/
```
## Step 6 - Update your entry point
Module do not have an entry point. So there's nothing to do here.
## Step 7 - Update project structure
This step is optional. We recommend renaming `code` to `src`. This is only a convention and will not affect how your module will be executed.
If you do rename this directory, do not forget to update your `autoload` configuration in your `composer.json` file.
## Step 8 - Switch to public web-root
The public web root does not directly affect module. So you can skip this step.
## Step 9 - Move away from hardcoded paths for referencing static assets
While SilverStripe 4 projects can get away with directly referencing static assets under some conditions, modules must dynamically expose their static assets. This is necessary to move modules to the vendor folder and to enable the public web root.
### Exposing your module's static assets
You'll need to update your module's `composer.json` file with an `extra.expose` key.
```diff
{
"name": "example-user/silverstripe-example-module",
"type": "silverstripe-vendormodule",
"require": {
"silverstripe/framework": "^4",
"silverstripe/vendor-plugin": "^1"
},
"autoload": {
"psr4": {
"ExampleUser\\SilverstripeExampleModule\\": "code/"
}
},
"autoload-dev": {
"psr4": {
"ExampleUser\\SilverstripeExampleModule\\Tests\\": "tests/"
}
- }
+ },
+ "extra": {
+ "expose": [
+ "images",
+ "styles",
+ "javascript"
+ ]
+ }
}
```
### Referencing static assets
This process is essentially the same for projects and modules. The only difference is that module static asset paths must be prefix with the module's name as defined in their `composer.json` file.
```diff
<?php
- Requirements::css('silverstripe-example-module/styles/admin.css');
+ Requirements::css('example-user/silverstripe-example-module: styles/admin.css');
$pathToImage =
- 'silverstripe-example-module/images/logo.png';
+ ModuleResourceLoader::singleton()->resolveURL('example-user/silverstripe-example-module: images/logo.png');
```
## Step 10 - Update database class references {#step10}
Just like projects, your module must define class names remapping for every DataObject child.
```
SilverStripe\ORM\DatabaseAdmin:
classname_value_remapping:
ExampleModuleDummyDataObject: ExampleUser\SilverstripeExampleModule\Models\DummyDataObject
```
On the first `dev/build` after a successful upgrade, the `ClassName` field on each DataObject table will be substituted with the namespaced classname.
## Extra steps
You've been through all the steps covered in the regular project upgrade guide. These 2 additional steps might not be necessary.
### Create migration tasks
Depending on the nature of your module, you might need to perform additional tasks to complete the upgrade process. For example, the `framework` module ships with a file migration task that converts files from the old SilverStripe 3 structure to the new structure required by SilverStripe 4.
Extend [BuildTask](api:SilverStripe/Dev/BuildTask)s and create your own migration task if your module requires post-upgrade work. Document this clearly for your users so they know they need to run the task after they're done upgrading their project.
### Keep updating your `.upgrade.yml`
The upgrader can be run on projects that have already been upgraded to SilverStripe 4. As you introduce new API and deprecate old ones, you can keep updating your `.upgrade.yml` file to make it easy for your users to keep their code up to date. If you do another major release of your module aimed at SilverStripe 4, you can use all the tools in the upgrader to make the upgrade process seamless for your users.

View File

@ -1,1168 +1,6 @@
title: Upgrading
introduction: Upgrade your project SilverStripe 4 and keep it up to date with the latest fixes, security patches and new features.
introduction: The following guides will help you upgrade your project or module to SilverStripe 4.
# Upgrading to SilverStripe 4
The following guides will help you upgrade your project or module to SilverStripe 4. Upgrading a module is very similar to upgrading a Project. The module upgrade guide assumes familiarity with the project upgrade guide.
SilverStripe applications should be kept up to date with the latest security releases. Usually an update or upgrade to
your SilverStripe installation means overwriting files, flushing the cache and updating your database schema.
## Understanding and planning your upgrade {#planning}
How easy will it be to update my project? It's a fair question, and sometimes a difficult one to answer.
* SilverStripe follows _semantic versioning_ (see our [release process](/contributing/release_process) for details).
* "Major" releases introduces API change that may break your application.
* "Minor" releases (x.y) introduces API changes in a backward compatible way and can mark some API as deprecated.
* "Patch" releases (x.y.z) fix bugs without introducing any API changes.
* If you've made custom branches of SilverStripe core, or any thirdparty module, upgrades are going to be more complex.
* More custom features mean more work to re-test all of those features, and adapt to API changes in core.
* Customisations of a well defined type - such as custom page types or custom blog widgets -
are going to be easier to upgrade than customisations that modify deep system internals like rewriting SQL queries.
### Overview of changes
There are some fundamental changes in SilverStripe 4:
* PHP 5.6 is now the minimum required version and up to PHP 7.2 is supported.
* SilverStripe is now even more modular which allows you to remove functionality your project might not need.
* Common functionality sets can now be installed via SilverStripe _recipes_.
* SilverStripe modules can now be installed in the `vendor/` folder along with your regular PHP packages.
* All classes are namespaced: You have to use these, but can decide if you namespace your project code.
* PHP _traits_ replace a few core classes (e.g. `Object`) and make it easy to apply common patterns
* Public files can now be served from a `public/` webroot for added security.
* Versioning is more powerful through an "ownership" concept, and available for all DataObject classes.
* Changes across objects can be collected in a "campaign" for batch publication.
* GraphQL is now the favourite way of creating web services with SilverStripe.
* Asset management has been completely redone with a brand new React-based UI, protected draft files and versioning.
* Parts of the CMS UI are now build in React and Bootstrap instead of Entwine and custom CSS.
* PSR-4 auto-loading is supported for modules and for your project code.
[Learn more about major API changes introduced by SilverStripe 4](#list-of-major-api-changes),
and dig into the changelogs for [4.0.0](/changelogs/4.0.0), [4.1.0](/changelogs/4.1.0) and [4.2.0](/changelogs/4.2.0).
### Using recipes instead of requiring individual modules
The SilverStripe CMS and SilverStripe Framework are becoming more modular. Many of the secondary features contained in SilverStripe CMS 3 and SilverStripe Framework 3 have been moved to separate modules.
SilverStripe 4 introduces the concept of _recipes_. Recipes are a combination of modules to achieve a common pattern.
Read the [Switching to recipes](#switching-to-recipes) section of this guide for more information about how recipes work.
### Automating your upgrades using the SilverStripe Upgrader tool
We've developed [an upgrader tool](https://github.com/silverstripe/silverstripe-upgrader) which you can use to help
with the upgrade process. The upgrader is unlikely to completely upgrade your project, however it can take care of the most tedious part of the upgrade.
It can also be use to upgrade your existing SilverStripe 4 project to a newer minor release.
## Step 0 - Pre-requisites and background work {#step0}
Before you begin the upgrade process, make sure you meet these pre-requisites.
### Back up your files and database
* Set up your codebase in your development environment.
* Backup your database content.
* Backup your codebase (use version control if possible).
<div class="warning" markdown="1">
Never update a website on the live server. Get it working on a development copy first!
</div>
### Install composer
[Composer](http://getcomposer.org) is a tool for managing PHP dependencies. SilverStripe 4 requires composer version _1.1_ or greater. Read the [SilverStripe _Getting started_ guide](/getting_started/composer) for more details.
We recommend using `recipe-cms` in your `composer.json` file to help you keep up to date and run `composer update`.
```json
{
"require": {
"silverstripe/recipe-cms": "^4"
}
}
```
This will also add extra dependencies, such as the `admin`, `asset-admin`, `reports`, `errorpage` and `siteconfig`
modules.
If you want more granular control over what gets installed,
check out the [recipe plugin repository](https://github.com/silverstripe/recipe-plugin)
as well as the `composer.json` files in [recipe-core](https://github.com/silverstripe/recipe-core) and
[recipe-cms](https://github.com/silverstripe/recipe-cms).
For a description on how to handle issues with pre-existing composer installs or upgrading other modules, read
through the [Step 1 - Upgrade your dependencies](#step1) section.
### Install the upgrader tool (optional)
Using the upgrader is not mandatory, but it can speed up the process. Although SilverStripe 4 can run in both PHP 5.6 and PHP 7, the upgrader itself requires PHP 7.1.
The upgrader is available as a phar executable.
To install the PHAR executable:
1. [Download the upgrader as a PHAR executable](https://silverstripe.github.io/silverstripe-upgrader/upgrade-code.phar) or `wget https://silverstripe.github.io/silverstripe-upgrader/upgrade-code.phar`
2. Make the file executable `chmod +x upgrade-code.phar`
3. Move the file to a folder in your path, for example `sudo mv upgrade-code.phar /usr/local/bin/upgrade-code`
Each command in the upgrader has somewhat different arguments. However, most of them accept these two options:
* `--write` which tells the upgrader to apply changes to your code base
* `--root-dir` which can be use to explicitly specify the root of your project — if not specified the current working directory is assume to be the root of the project.
You can run `upgrade-code help` to get more information about the upgrader or `upgrade-code help command-name` to information about a specific command.
<div class="info" markdown="1">
Sample upgrader commands in this guide assume your working directory is the root of your SilverStripe project. You'll need to use the `--root-dir` flag if that's not the case.
</div>
#### Install the upgrader globally with composer
You can install the upgrader globally with composer. This can make it easier to update to newer releases, however you can get dependency conflicts if you have other packages installed globally.
To install the upgrader globally run this command.
```bash
composer global require silverstripe/upgrader
```
Add your global composer bin directory to your path. On *nix system, this directory is normally located at `$HOME/.composer/vendor/bin`. On Windows system, this directory is normally located at `C:\Users\<COMPUTER NAME>\AppData\Roaming\Composer\vendor\bin`. You can find the exact location by running this command:
```bash
composer global config bin-dir
```
On *nix system, the following command will add your global composer bin directory to your path if `bash` is your default shell environment:
```bash
echo 'export PATH=$PATH:~/.composer/vendor/bin/' >> ~/.bash_profile
source ~/.bash_profile
```
### Running all the upgrader commands in this guide in on line
The upgrader comes with an `all` command. This command will attempt to run all the upgrader commands in the same order as this guide. This is unlikely to work on your first try, but can be a good way to get started without going through this entire guide.
```bash
upgrade-code all --namespace="App\\Web" --psr4
```
* `--recipe-core-constraint` defines your SilverStripe release version (optional, will default to the most recent stable release).
* `--namespace` allows you to specify how your project will be namespaced (optional).
* `--psr4` allows you to specify that your project structure respect the PSR-4 standard and to use sub-namespaces.
* `--skip-add-namespace` allows you to skip the `add-namespace` command.
* `--skip-reorganise` allows you to skip the `reorganise` command.
* `--skip-webroot` allows you to skip the `webroot` command.
### Branching your project
Setting a dedicated branch in your source control system to track your upgrade work can help you manage your upgrade. If you're upgrading a big project, you should consider creating individual branches or commits for each step.
## Step 1 - Upgrade your dependencies {#step1}
The first step is to update your dependencies' constraints in your `composer.json` file to require the latest version of modules.
### Automatically upgrade dependencies with the `recompose` upgrader command
If you've installed the upgrader, you can use the `recompose` command to help you upgrade your dependencies. This command will try to:
* upgrade your PHP constraint
* upgrade core SilverStripe modules to their version 4 equivalent
* switch to recipes where possible
* find SilverStripe 4 compatible versions of third party modules.
Take for example the following SilverStripe 3 `composer.json` file.
```json
{
"name": "app/cms-website",
"description": "The Example website project.",
"license": "BSD-3",
"require": {
"php": ">=5.3.3",
"silverstripe/cms": "3.6.5@stable",
"silverstripe/framework": "3.6.5@stable",
"silverstripe/reports": "3.6.5@stable",
"silverstripe/siteconfig": "3.6.5@stable",
"dnadesign/silverstripe-elemental": "^1.8.0"
}
}
```
You can upgrade the `composer.json` file with this command:
```bash
upgrade-code recompose --write
```
You can add a `--recipe-core-constraint` flag to target a specific version of `silverstripe/recipe-core`. By default, the project will be upgraded to the latest stable version. You can use the `--strict` option if you want to use more conservative version constraints. Omit the `--write` flag to preview your changes.
Your upgraded `composer.json` file will look like this.
```json
{
"name": "app/cms-website",
"description": "The Example website project.",
"license": "BSD-3",
"require": {
"dnadesign/silverstripe-elemental": "^2.1",
"php": ">=5.6",
"silverstripe/recipe-cms": "^4.1"
}
}
```
If the `recompose` command can't find a compatible version for one of your module, it will keep this dependency in your `composer.json` file with its existing constraint.
[Continue to Step 2](#step2)
### Manually upgrading your dependencies
The instruction in this section assumed you'll be editing your `composer.json` file in a text editor.
#### Switching to recipes
Where possible, we recommend you use recipes.
If your SilverStripe 3 project requires the `silverstripe/cms` module, replace that dependency with `silverstripe/recipe-cms`. The version constraint for `silverstripe/recipe-cms` must match your targeted version of SilverStripe:
* `~4.0.0` to upgrade to SilverStripe 4.0
* `~4.1.0` to upgrade to SilverStripe 4.1
* `~4.2.0` to upgrade to SilverStripe 4.2
* and so on.
If your SilverStripe 3 project requires the `silverstripe/framework` module without `silverstripe/cms`, replace `silverstripe/framework` with `silverstripe/recipe-core`. The version constraint for `silverstripe/recipe-core` must match your targeted version of SilverStripe:
* `~4.0.0` to upgrade to SilverStripe 4.0
* `~4.1.0` to upgrade to SilverStripe 4.1
* `~4.2.0` to upgrade to SilverStripe 4.2
* and so on.
The following modules are implicitly required by `silverstripe/recipe-core`. They can be removed from your `composer.json` dependencies if you are using `silverstripe/recipe-core` or `silverstripe/recipe-cms`.
* `silverstripe/framework`
* `silverstripe/config`
* `silverstripe/assets`
The following modules are implicitly required by `silverstripe/recipe-cms`. They can be removed from your `composer.json` dependencies if you are using `silverstripe/recipe-cms`.
* `silverstripe/admin`
* `silverstripe/asset-admin`
* `silverstripe/campaign-admin`
* `silverstripe/cms`
* `silverstripe/errorpage`
* `silverstripe/reports`
* `silverstripe/graphql`
* `silverstripe/siteconfig`
* `silverstripe/versioned`
* `silverstripe/recipe-core`
Take for example the following SilverStripe 3 `composer.json`.
```json
{
"name": "app/cms-website",
"require": {
"silverstripe/cms": "3.6.5@stable",
"silverstripe/framework": "3.6.5@stable",
"silverstripe/reports": "3.6.5@stable",
"silverstripe/siteconfig": "3.6.5@stable"
}
}
```
After switching to SilverStripe 4 recipes, the `composer.json` file should look like this.
```json
{
"name": "app/cms-website",
"require": {
"silverstripe/recipe-cms": "~4.1.0"
}
}
```
#### Explicitly defining your dependencies
If you would rather explicitly define your dependencies, you can do so. Update the `silverstripe/framework` constraint and `silverstripe/cms` constraint to match your targeted minor version of SilverStripe 4. If you use `silverstripe/reports` and `silverstripe/siteconfig`, update their constraints as well.
In most cases, you'll also want to require the same modules as the equivalent recipes. If you don't, your users will likely lose some features after the upgrade is completed.
Take for example the following SilverStripe 3 `composer.json`.
```json
{
"name": "app/cms-website",
"require": {
"silverstripe/cms": "3.6.5@stable",
"silverstripe/framework": "3.6.5@stable",
"silverstripe/reports": "3.6.5@stable",
"silverstripe/siteconfig": "3.6.5@stable"
}
}
```
After switching to SilverStripe 4 and explicitly defining your dependencies, the `composer.json` file should look like this.
```json
{
"name": "app/cms-website",
"require": {
"silverstripe/cms": "~4.1.0",
"silverstripe/framework": "~4.1.0",
"silverstripe/reports": "~4.1.0",
"silverstripe/siteconfig": "~4.1.0",
"silverstripe/admin": "~1.1.0",
"silverstripe/asset-admin": "~1.1.0",
"silverstripe/campaign-admin": "~1.1.0",
"silverstripe/errorpage": "~1.1.0",
"silverstripe/graphql": "~1.1.0",
"silverstripe/versioned": "~1.1.0"
}
}
```
#### Updating third party dependencies
If you project requires third party modules, you'll need to adjust their associated constraint. This will allow you to retrieve a SilverStripe 4 compatible version of the module.
[Look up the module on Packagist](https://packagist.org/) to see if a SilverStripe 4 version is provided.
Take for example the following SilverStripe 3 `composer.json`.
```json
{
"name": "app/cms-website",
"require": {
"silverstripe/framework": "3.6.5@stable",
"silverstripe/cms": "3.6.5@stable",
"dnadesign/silverstripe-elemental": "^1.8.0"
}
}
```
Looking at the [Packagist entry for `dnadesign/silverstripe-elemental`](https://packagist.org/packages/dnadesign/silverstripe-elemental#2.0.0), you can see that versions 2.0.0 and above of this module are compatible with SilverStripe 4. So you can update that constraint to `^2.0.0`.
Alternatively, you can set a very permissive constraint and let composer find a SilverStripe 4 compatible version. After you're done updating your dependencies, make sure you adjust your constraints to be more specific.
Once you've updated your third-party modules constraints, try updating your dependencies by running `composer update`. If composer can't resolve all your dependencies it will throw an error.
### Resolving conflicts
You'll likely have some conflicts to resolve, whether you've updated your dependencies with the upgrader or manually.
Running a `composer update` will tell you which modules are conflicted and suggested alternative combinations of modules that might work.
The most typical reason for a conflict is that the maintainer of a module hasn't released a version compatible with SilverStripe 4.
If the maintainer of the module is in the process of upgrading to SilverStripe 4, a development version of the module might be available. In some cases, it can be worthwhile to look up the repository of the module or to reach out to the maintainer.
<div class="info" markdown="1">
If you're going to install development version of third party modules, you should consider adding the following entries to your `composer.json` file.
```json
{
// ...
"minimum-stability": "dev",
"prefer-stable": true,
// ...
}
```
</div>
To resolve a conflict you can either:
* fork the affected module and upgrade it yourself. Don't forget to send a pull request to the original module!
* Integrate the affected module into your project's codebase
* Remove the module from your project, if it is not essential
To integrate a third party module in your project, remove it from your `composer.json` file and from your `.gitignore` file. Then track the module's codebase in your project source control. You'll need to upgrade the module's code to be compatible with SilverStripe 4.
<div class="info" markdown="1">
If you're taking the time to upgrade a third party module, consider doing a pull request against the original project so other developers can benefit from your work or releasing your fork as a seperate module.
[Learn about how to publish a SilverStripe module](/developer_guides/extending/how_tos/publish_a_module)
</div>
### Finalising your dependency upgrade
Once you've resolved all conflicts in your `composer.json` file, `composer update` will be able to run without errors.
This will install your new dependencies. You'll notice many of the folders in the root of your project will disappear. That's because SilverStripe 4 modules can be installed in the vendor folder like generic PHP packages.
If you've decided to use recipes, some generic files will be copied from the recipe into your project. The `extra` attribute in your `composer.json` file will be updated to keep track of those new files.
This is a good point to commit your changes to your source control system before moving on to the next step.
## Step 2 - Update your environment configuration {#env}{#step2}
The php configuration `_ss_environment.php` file has been replaced with a non-executable
`.env` file. It follows a syntax similar to a `.ini` file for key/value pair assignment. Your `.env` file may be placed in your project root, or one level above your project root ([details](/getting_started/environment_management/))
### Automatically convert `_ss_environment.php` to `.env`
If you have installed the upgrader tool, you can use the `environment` command to generate a valid `.env` file from your existing `_ss_environment.php` file.
```bash
upgrade-code environment --write
```
If your `_ss_environment.php` file contains unusual logic (conditional statements or loops), you will get a warning. `upgrade-code` will still try to convert the file, but you should double-check the output. Omit the `--write` flag to do a dry-run.
[Continue to "Cleaning up `mysite/_config.php`"](#env-config-cleanup)
### Manually convert `_ss_environment.php` to `.env`
Create a `.env` file in the root of your project. Replace `define` statements from `_ss_environment.php` with `KEY=VALUE` pairs in `.env`.
Most SilverStripe 3 environment variables have been carried over to SilverStripe 4. See [Environment Management docs](/getting_started/environment_management/) for the full list of available variables. Your `.env` file can contain environment variables specific to your project as well.
The global array `$_FILE_TO_URL_MAPPING` has been removed and replaced with the `SS_BASE_URL` environment variable. `SS_BASE_URL` expects an absolute url with an optional protocol. The following are values would be valid entries for `SS_BASE_URL`:
* `http://localhost/`
* `https://localhost/`
* `//localhost/`
For example, take the following `_ss_environment.php` file.
```php
<?php
// Environment
define('SS_ENVIRONMENT_TYPE', 'dev');
define('SS_DEFAULT_ADMIN_USERNAME', 'admin');
define('SS_DEFAULT_ADMIN_PASSWORD', 'password');
$_FILE_TO_URL_MAPPING[__DIR__] = 'http://localhost';
// Database
define('SS_DATABASE_CHOOSE_NAME', true);
define('SS_DATABASE_CLASS', 'MySQLDatabase');
define('SS_DATABASE_USERNAME', 'root');
define('SS_DATABASE_PASSWORD', '');
define('SS_DATABASE_SERVER', '127.0.0.1');
```
The equivalent `.env` file will look like this.
```bash
## Environment
SS_ENVIRONMENT_TYPE="dev"
SS_DEFAULT_ADMIN_USERNAME="admin"
SS_DEFAULT_ADMIN_PASSWORD="password"
SS_BASE_URL="http://localhost/"
## Database
SS_DATABASE_CHOOSE_NAME="true"
SS_DATABASE_CLASS="MySQLDatabase"
SS_DATABASE_USERNAME="root"
SS_DATABASE_PASSWORD=""
SS_DATABASE_SERVER="127.0.0.1"
```
### Cleaning up `mysite/_config.php` after your environment configuration upgrade {#env-config-cleanup}
Regardless if you've used the automated or manual path,
you'll need to clean up your `mysite/_config.php` file after upgrading your environment file.
The global values `$database` and `$databaseConfig` have been deprecated. Your database configuration details should be stored in your `.env` file. If you want to keep your database configuration in `_config.php`, you can use the new `DB::setConfig()` api, however this is discouraged.
Requiring `conf/ConfigureFromEnv.php` is is no longer necessary. You should remove any references to it in `_config.php`.
The removal of the `_ss_environment.php` file means that conditional logic is no longer available in the environment
variable set-up process. This encouraged bad practice and should be avoided. If you still require conditional logic early in the bootstrap, this is best placed in the `_config.php` files.
To access environment variables, use the `SilverStripe\Core\Environment::getEnv()` method. To define environment variables, use the `SilverStripe\Core\Environment::setEnv()` method.
### Finalising your environment upgrade
It's inadvisable to track your `.env` file in your source control system as it might contain sensitive information.
You should ignore the `.env` file by adding an entry to your `.gitignore` file. You can create a sample environment configuration by duplicating your `.env` file as `.env.sample`, and removing sensitive information from it. You can safely delete your legacy `_ss_environement.php` if you want.
This is a good point to commit your changes to your source control system before moving on to the next step.
## Step 3 - Namespacing your project (optional) {#step3}
Namespacing your code is an optional step. It is recommended and will help future-proof your code base.
Read more about [PHP Namespaces](http://php.net/manual/en/language.namespaces.php)
and the [PSR-4 Autoloader Standard](https://www.php-fig.org/psr/psr-4/).
### Before you start namespacing your codebase
You need to choose a root namespace for your project. We recommend following the `Vendor\Package` pattern.
The `Page` and `PageController` classes *must* be defined in the global namespace (or without a namespace).
If you want your codebase to comply with the PSR-4 standard, make sure sub-directories of your code folder are using the _UpperCamelCase_ naming convention. For example, `mysite/code/page_types` should be renamed to `mysite/code/PageTypes`.
### Automatically namespacing your codebase with the upgrader
The `add-namespace` command of the [upgrader tool](https://github.com/silverstripe/silverstripe-upgrader/) provides a feature
to namespace your codebase and to automatic update references to those classes.
```bash
upgrade-code add-namespace "App\\Web" ./mysite/code --recursive --write
```
This task will do the following:
* Add the given namespace to all files in the code class, and subdirectories
* All references to classes in any namespaced files will be safely retained with additional `use` directives added as necessary
* A `mysite/.upgrade.yml` file will be created/updated to record the new fully qualified name of each class.
This will be used in later steps to update references to the outdated class names in your own project code.
By default, the same namespace will be applied to all your classes regardless of which directory they are in. If you want to apply different namespaces to different folders to be compliant with PSR-4, combine the `--recursive` with the `--psr4`. Your folder structure has to be PSR4 compliant for this to work. If you want to do a dry-run, omit the `--write` option to see a preview of all changed project files.
[Continue to Step 4](#step4)
### Manually namespacing your codebase
Go through each PHP file under `mysite/code` and add a `namespace` statement at the top, *with the exception of the files for `Page` or `PageController`*.
Take for example this SilverStripe 3 file located at `mysite/code/Products/ExplosiveTennisBall.php`.
```php
<?php
class ExplosiveTennisBall extends DataObject
{
// ...
}
```
Assuming your root namespace is `App\Web`, the equivalent namespaced file will look like this.
```php
<?php
namespace App\Web\Products;
class ExplosiveTennisBall extends DataObject
{
// ...
}
```
If you intend to use the upgrader to update references to your namespaced classes, you'll need to create a `mysite/.upgrade.yml` file.
```yaml
mappings:
ExplosiveTennisBall: App\Web\Products\ExplosiveTennisBall
```
If you intend to manually update references to your namespaced classes, you'll need to go through each of your file to add `use` statements.
For example, if `mysite/code/ProductService.php` is using the `ExplosiveTennisBall` class, you'll need to add a use statement at the top of the file just after it's own namespace definition.
```php
<?php
namespace App\Web;
use App\Web\Products\ExplosiveTennisBall;
class ProductService
{
// ...
}
```
### Enable PSR-4 auto-loading in your `composer.json` file
If you have namespaced your project and followed the PSR-4 convention, you have the option to enable PSR-4 auto-loading in your composer.json file.
Enabling PSR-4 auto-loading is optional. It will provide better auto-loading of your classes in your development environment and will future proof your code.
For example, let's say you have defined the following namespaces for the following folders:
* `App\Web` for your main application logic contained in `mysite/code`
* `App\SubModule` for a secondary module contained in `sub-module/code`
* `App\Web\Tests` for your application test suite contained in `mysite/tests`.
Your `autoload` section in your `composer.json` file should look like this:
```json
{
// ...
"autoload": {
"psr-4": {
"App\\Web\\": "mysite/code",
"App\\SubModule\\": "sub-module/code"
}
},
"autoload-dev": {
"psr-4": {
"App\\Web\\Tests\\": "mysite/tests"
}
},
// ...
}
```
Read the [Composer schema autoload documentation](https://getcomposer.org/doc/04-schema.md#autoload) for more information about configuring auto-loading in your project.
### Finalise your project namespacing
All your classes should now be fully namespaced.
Note that applying a namespace to your project will also affect which template file SilverStripe tries to load when rendering certain objects.
For example, pretend you have a `RoadRunnerPage` class that extends `Page`. In SilverStripe 3, you would define a template for this page in `themes/example/templates/Layout/RoadRunnerPage.ss`. If you decide to move `RoadRunnerPage` to `App\Web\RoadRunnerPage`, you'll need to move the template to `themes/example/templates/App/Web/Layout/RoadRunnerPage.ss`.
This is a good point to commit your changes to your source control system before moving on to the next step.
## Step 4 - Update codebase with references to newly namespaced classes {#step4}
All core PHP classes in SilverStripe 4 have been namespaced. For example, `DataObject` is now called `SilverStripe\ORM\DataObject`. Your project codebase, config files and language files need be updated to reference those newly namespaced classes. This will include explicit references in your PHP code, but also string that contain the name of a class.
If you've opted to namespace your own code in the previous step, those references will need to be updated as well.
### Automatically update namespaced references with the `upgrade` command
If you've installed the upgrader, you can use the `upgrade` command to update references to namespaced classes.
The `upgrade` command will update PHP files, YML configuration files, and YML language files.
Each core SilverStripe 4 module includes a `.upgrade.yml` that defines the equivalent fully qualified name of each class. Most third party SilverStripe modules that have been upgraded to be compatible with SilverStripe 4, also include a `.upgrade.yml`. If you've namespaced your own project, you'll need to provide your own `.upgrade.yml` file . If you've used the upgrader to namespace your project, that file will have been created for you.
The `upgrade` command will try to update some strings that reference the old name of some classes. In some cases this might not be what you want. You can tell the upgrader to skip specific strings by using the `@skipUpgrade` flag in your PHPDoc comment. For example:
```PHP
/** @skipUpgrade */
return Injector::inst()->get('ProductService');
```
Execute the upgrade command with this command.
```bash
upgrade-code upgrade ./mysite/ --write
```
If you omit the `--write` flag you will get a preview of what change the upgrader will apply to your codebase. This can be helpful if you if you are tweaking your `.upgrade.yml` or if you are trying to identify areas where you should add a `@skipUpgrade` statement,
You can also tweak which rules to apply with the `--rule` flag: `code`, `config`, and `lang`. For example, the following command will only upgrade `lang` and `config` files:
```bash
upgrade-code upgrade ./mysite/ --rule=config --rule=lang
```
The `upgrade` command can alter big chunks of your codebase. While it works reasonably well in most use case, you should not trust it blindly. You should take time to review all changes applied by the `upgrade` command and confirm you are happy with them.
[Continue to "Finalising namespace updates"](#namespace-finalise)
### Manually update namespaced references
If you decide to update your namespace references by hand, you'll need to go through the entire code base and update them all from the old non-namespaced SilverStripe classes to the new namespaced equivalent. If you are referencing classes from third party modules that have been namespaced, you'll need to update those as well.
#### Update explicit references to classes in your code
Wherever your code explicitly references a SilverStripe class, it will need to be updated to the new namespaced equivalent. You can either update the reference to use the fully qualified name of the class or you can add a `use` statement to your file.
For example take the following SilverStripe 3 class. `DataObject` and `FieldList` need to point to their namespace equivalents.
```php
<?php
namespace App\Web\Products;
class ExplosiveTennisBall extends DataObject
{
public function getCMSFields()
{
return FieldList::create([]);
}
}
```
You can add `use` statements at the top of your file to reference the fully qualified name of `DataObject` and `FieldList`.
```php
<?php
namespace App\Web\Products;
use SilverStripe\ORM\DataObject;
use SilverStripe\Forms\FieldList;
class ExplosiveTennisBall extends DataObject
{
// ...
```
Alternatively, you can update the references to the fully qualified names.
```php
<?php
namespace App\Web\Products;
class ExplosiveTennisBall extends SilverStripe\ORM\DataObject
{
public function getCMSFields()
{
return SilverStripe\Forms\FieldList::create([]);
}
}
```
#### Update string references to classes
In many cases, SilverStripe expects to be provided the name of a class as a string. Typical scenarios include:
* defining an `has_one` or `has_many` relationship on a DataObject
* requesting an instance of class via the Injector
* specifying managed models for a `ModelAdmin`.
Those string need to use the fully qualified name of their matching classes. Take for example the following class.
```php
<?php
namespace App\Web\Products;
use SilverStripe\ORM\DataObject;
class ExplosiveTennisBall extends DataObject
{
private static $has_one = [
'Thumbnail' => 'Image'
];
private static $has_many = [
'Tags' => 'BlogPost'
];
public function getShippingCost()
{
return Injector::inst('ProductService')->calculateCost($this);
}
}
```
`Image`, `BlogPost`, and `ProductService` represent classes. Those strings need to be updated to specify the full namespace.
The best way of achieving this is to use the [`::class` PHP magic class constant](http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.class.class) which will return the fully qualified name of a class.
Our example could be update to:
```php
<?php
namespace App\Web\Products;
use SilverStripe\ORM\DataObject;
use SilverStripe\Assets\Image;
use SilverStripe\Blog\Model\BlogPost;
use App\Web\ProductService;
class ExplosiveTennisBall extends DataObject
{
private static $has_one = [
'Thumbnail' => Image::class
];
private static $has_many = [
'Tags' => BlogPost::class
];
public function getShippingCost()
{
return Injector::inst(ProductService::class)->calculateCost($this);
}
}
```
Alternatively, you can spell out the full qualified name of each class in a string. For example, `'Image'` would become `'SilverStripe\\Assets\\Image'`. Note the use of the _double backslash_ — this is necessary because the backslash is an escape character.
#### Update references to classes in your YML config
YAML configuration files can reference SilverStripe classes. Those references also need to use the fully qualified name of each class.
Take for example the following SilverStripe 3 YAML configuration file.
```yaml
Injector:
ProductService:
properties:
RoadRunnerSpeed: 99999999
CoyoteSpeed: 1
BlogPost:
extensions:
- HasOneExplosiveTennisBallExtension
Email:
admin_email: no-reply@example.com
```
In SilverStripe 4, this will become:
```yaml
SilverStripe\Core\Injector\Injector:
App\Web\ProductService:
properties:
RoadRunnerSpeed: 99999999
CoyoteSpeed: 1
SilverStripe\Blog\Model\BlogPost:
extensions:
- App\Web\Extensions\HasOneExplosiveTennisBallExtension
SilverStripe\Control\Email\Email:
admin_email: no-reply@example.com
```
#### Update references to classes in your language files
Translation keys are normally tied to classes. If you override SilverStripe's default translation or if you are localising your own project, you'll need to update those references to use the fully qualified name of each class.
For example, let's say you had the following translation file in `mysite/lang/eng.yml`.
```yaml
en:
Member:
SINGULARNAME: Coyote
RoadRunner:
SALUTATION: Beep Beep
```
In SilverStripe 4, it would become:
```yaml
en:
SilverStripe\Security\Member:
SINGULARNAME: Coyote
App\Web\RoadRunner:
SALUTATION: Beep Beep
```
### Finalising namespace updates {#namespace-finalise}
You'll need to perform the following steps manually, even if you've used the automated rewrite of namespaces.
DataObject database tables will default to use a namespaced name. For example, if you have a class under `App\Web\Products\ExplosiveTennisBall` that extends `DataObject`, the matching table in your database will be called `App_Web_Products_ExplosiveTennisBall`.
You can define a `private static $table_name` property on your DataObjects to use more convenient table names.
For example, `private static $table_name = 'ExplosiveTennisBall';`.
In your PHP code, calls to the `_t()` method should be updated to use the full namespace of the target class.
```php
<?php
# Old SilverStripe 3 way
$translation = _t('CMSMain.ACCESS', "Access to ''{title}'' section", ['title' => 'Products']);
# New SilverStripe 4
use SilverStripe\CMS\Controllers\CMSMain;
// ...
$translation = _t(CMSMain::class .'.ACCESS', "Access to ''{title}'' section", ['title' => 'Products']);
```
If you're calling `_t()` to retrieve a translation for the current class, you can also use `__CLASS__` or `self::class`. For example:
```php
<?php
namespace App\Web\Services;
class ProductService
{
public function getTranslation()
{
# Those two lines are equivalent.
$translation = _t(__CLASS__ . '.PRODUCT', 'Product');
$translation = _t(self::class . '.PRODUCT', 'Product');
return $translation;
}
}
```
<div class="warning" markdown="1">
Avoid using `static::class` or `parent::class` to retrieve translated string. It will retrieve unpredictable values bases on the class inheritance.
</div>
If your template files contain translatable strings, they also need to be updated to referenced the namespaced classes.
For example, `<%t Member.SINGULARNAME 'Member' %>` would become `<%t SilverStripe\Security\Member.SINGULARNAME 'Member' %>`.
Your codebase should now be referencing valid SilverStripe 4 classes. This means that your classes can be loaded at runtime. However, your codebase will still be using an outdated API.
This is a good point to commit your changes to your source control system before moving on to the next step.
## Step 5 - Updating your codebase to use SilverStripe 4 API {#step5}
This is the most intricate and potentially time-consuming part of the upgrade. It involves going through your entire codebase to remove references to deprecated APIs and update your project logic.
### Automatically update deprecated API references with the `inspect` command
The upgrader has an `inspect` command that can flag deprecated API usage, and in some cases, update your codebase to the SilverStripe 4 equivalent. This does require you to carefully review each change and warning to make sure the updated logic still work as intended. Even so, it is a huge time-saver compared to reviewing your code base manually.
Note that the `inspect` command loads your files with PHP interpreter. So basic syntax errors — for example, extending a class that does not exists — will cause an immediate failure. For this reason, you need to complete [Step 4 - Update codebase with references to newly namespaced classes](#step4) before running the `inspect` command.
```bash
upgrade-code inspect ./mysite/ --write
```
You can omit the `--write` flag if you just want to view the proposed changes without applying them. You can run the command on a specific subdirectory or file. This can be more manageable if you have a big project to go through.
Like the `upgrade` command, `inspect` gets its list of API changes from `.upgrade.yml` files. So you may get upgrade suggestions and substitution from third party modules. You can even include your own project specific changes in your `.upgrade.yml` if you want.
#### Sample output of the `inspect` command
Here's some sample output of what you might get back the `inspect` command.
```bash
upgrade-code inspect ./mysite/Models/Coyote.php
Running post-upgrade on "/var/www/SS_example/mysite/code/Models/Coyote.php"
[2018-06-06 13:35:38] Applying ApiChangeWarningsRule to Coyote.php...
modified: Coyote.php
@@ -68,7 +68,7 @@
{
// Getting a reference to Coyote's list of crazy ideas
- $manyManyRelation = $this->manyManyComponent('CrazyIdeas');
+ $manyManyRelation = $this->getSchema()->manyManyComponent('CrazyIdeas');
return $manyManyRelation;
}
Warnings for Coyote.php:
- Coyote.php:20 SS_Cache: Using symfony/cache now (https://docs.silverstripe.org/en/4/changelogs/4.0.0#cache)
- Coyote.php:42 SilverStripe\Control\Director::setUrlParams(): Method removed
- Coyote.php:71 SilverStripe\ORM\DataObject->manyManyComponent(): DataObject->manyManyComponent() moved to DataObjectSchema. Access through getSchema(). You must manually add static::class as the first argument to manyManyComponent()
Changes not saved; Run with --write to commit to disk
```
### Manually update deprecated API references
SilverStripe 4 introduces many API changes. To update deprecated API references manually, you have to go through each one of your project files.
Read the changelogs for [4.0.0](/changelogs/4.0.0/), [4.1.0](/changelogs/4.1.0/) and [4.2.0](/changelogs/4.2.0/)
for a comprehensive overview.
### Finalising the deprecated API update
At this stage, your site should be using only SilverStripe 4 API logic.
You still have some minor clean up tasks and configuration tweaks to apply, but you're almost done.
This is a good point to commit your changes to your source control system before moving on to the next step.
## Step 6 - Update your entry point {#step6}
The location of SilverStripe's _entry file_ has changed. Your project and server environment will need
to adjust the path to this file from `framework/main.php` to `index.php`.
### Update your `index.php` file
You can get a copy of the SilverStripe 4 `index.php` file at
`vendor/silverstripe/recipe-core/public/index.php`.
If you've created your own `index.php` or modified version of `main.php`,
you'll need to reconcile those changes with the `index.php` file you got from `recipe-core`.
Otherwise, just use the generic `index.php` file `recipe-core` provides.
Copy your new `index.php` to your project's web root. Unlike SilverStripe 3, `index.php` must be present in your web root.
### Update your server configuration
If you're using a `.htaccess` file or `web.config` file to handle your server configuration, you can get the generic SilverStripe 4 version of those file from
`vendor/silverstripe/recipe-core/public`.
Just like `index.php`, if you've modified your server configuration file from the one that shipped with SilverStripe 3, you'll need to reconcile your changes into the version retrieve from `recipe-core`.
[Refer to the installation instruction for your platform](/getting_started/installation/) if your server configuration is not managed via a `.htaccess` or `web.config` file.
### Finalising the entry point upgrade
At this stage, you could in theory run your project in SilverStripe 4.
This is a good point to commit your changes to your source control system before moving on to the next step.
## Step 7 - Update project structure (optional) {#step7}
SilverStripe 4 introduces a new recommended project structure ([details](/changelogs/4.2.0#app-folder-name)).
Adopting the recommended project structure is optional, but will become mandatory in SilverStripe 5.
[Skip to Step 8](#step8)
### Automatically switch to the new structure with the `reorganise` command
The reorganise command can automatically update your project to use the new recommended structure.
It will search your code and find any occurrence of `mysite`. It won't replace those occurrence with `app` however.
```bash
upgrade-code reorganise --write
```
Omit the `--write` flag if you just want to preview your changes
### Manually switch to the new structure
Simply rename your `mysite` fold to `app`. Then rename `app/code` to `app/src`.
### Finalising the reorganise structure
If you've implemented the new PSR-4 auto-loading logic in your `composer.json` file you'll need to update your namespace mapping.
For example, let's say you had the following autoload attribute in your `composer.json`.
```json
{
// ...
"autoload": {
"classmap": [
"mysite/code/Page.php",
"mysite/code/PageController.php"
],
"psr-4": {
"App\\Web\\": "mysite/code/"
}
},
// ...
}
```
It will become this:
```json
{
// ...
"autoload": {
"classmap": [
"app/src/Page.php",
"app/src/PageController.php"
],
"psr-4": {
"App\\Web\\": "app/src/"
}
},
// ...
}
```
You'll need to update the `project` attribute for your `ModuleManifest` in your `app/src/mysite.yml` file. It should now look something like this:
```yaml
SilverStripe\Core\Manifest\ModuleManifest:
project: app
```
At this stage, your project should be functional with the recommended project structure.
Note, that if you've explicitly reference any static assets (images, css, js) under `mysite`, you'll need to rewrite those references.
This is a good point to commit your changes to your source control system before moving on to the next step.
## Step 8 - Switch to public web-root (optional){#step8}
SilverStripe 4.1 introduces the concept of _public web-root_ this allows you to move
all publicly accessible assets under a `public` folder ([details](/changelogs/4.1.0#public-folder)).
This has security benefits as it minimises the possibility that files that are not meant to be access directly get accidentally exposed.
This step is optional and requires SilverStripe 4.1 or greater. It will become mandatory in SilverStripe 5.
[Skip to Step 9](#step9)
### Automatically switch to the public web root
The `webroot` upgrader command will automatically move your files for you.
```bash
upgrade-code webroot --write
```
Omit the `--write` flag if you want to preview the change.
If you are using a modified `index.php`, `.htaccess`, or `web.config`, you will get a warning.
### Manually switch to using the public web root
* Create a `public` folder in the root of your project
* Move the following files and folder to your new public folder
* `index.php`
* `.htaccess`
* `webconfig.php`
* `assets`
* Any `favicon` files
* Other common files that should be accssible in your project webroot (example: `robots.txt`)
* Delete the root `resources` directory if present.
* Run the following command `composer vendor-expose` to make static assets files accessible via the `public` directory.
If you are upgrading from SilverStripe 4.0 to SilverStripe 4.1 (or above), you'll need to update `index.php` before moving it to the public folder. You can get a copy of the generic `index.php` file from `vendor/silverstripe/recipe-core/public`. If you've made modifications to your `index.php` file, you'll need to replicate those into the new `public/index.php` file.
### Finalising the web root migration
You'll need to update your server configuration to point to the public directory rather than the root of your project.
Update your `.gitignore` file so `assets` and `resources` are still ignored when located under the `public` folder.
Your project should still be functional, although you may now be missing some static assets.
This is a good point to commit your changes to your source control system before moving on to the next step.
## Step 9 - Move away from hardcoded paths for referencing static assets {#step9}
SilverStripe 4 introduces a new way to reference static assets like images and css.
This enable innovations like moving SilverStripe module [vendor folder](/changelogs/4.0.0#vendor-folder) or the [public web root](/changelogs/4.1.0#public-folder).
This change is mandatory if you've completed either
[step 7](#step7) (update project structure) or [step 8](#step8) (switch to public web-root).
If you have skipped these steps, it is strongly recommended, but not mandatory.
### Exposing your project static assets
If you have folders under `app` or `mysite` that need to be accessible for your project's web root, you need to say so in your `composer.json` file by adding an entry under `extra.expose`.
For example, let's say you have `scripts`, `images` and `css` folders under `app`. You can expose them by adding this content to your `composer.json` file:
```json
{
// ...
"extra": {
"branch-alias": {
"4.x-dev": "4.2.x-dev"
},
"expose": [
"app/scripts",
"app/images",
"app/css"
]
},
// ...
}
```
For the change to take affect, run the following command: `composer vendor-expose`.
### Referencing static assets in your PHP code
Wherever you would have use an hardcoded path, you can now use the `projectname: path/to/file.css` syntax.
`projectname` is controlled by the `project` property of `SilverStripe\Core\Manifest\ModuleManifest` in your YML configuration. This configuration file should look like this:
```yaml
SilverStripe\Core\Manifest\ModuleManifest:
project: app
```
To add some javascript and css files to your requirements from your PHP code, you could use this syntax:
```php
use SilverStripe\View\Requirements;
# Load your own style and scripts
Requirements::css('app: css/styles.css');
Requirements::script('app: scripts/client.css');
# Load some assets from a module.
Requirements::script('silverstripe/blog: js/main.bundle.js');
```
You can `SilverStripe\Core\Manifest\ModuleLoader` to get the web path of file.
```php
ModuleLoader::getModule('app')->getResource('images/road-runner.jpg')->getRelativePath();
```
You can use `SilverStripe\View\ThemeResourceLoader` to access files from your theme:
```php
$themeFolderPath = ThemeResourceLoader::inst()->getPath('simple');
$themesFilePath = ThemeResourceLoader::inst()->findThemedResource('css/styles.css');
```
For classes that expect icons, you can specify theme with:
```php
class ListingPage extends \Page
{
private static $icon = 'app: images/sitetree_icon.png';
}
class MyCustomModelAdmin extends \SilverStripe\Admin\ModelAdmin
{
private static $menu_icon = 'app: images/modeladmin_icon.png';
}
```
### Referencing static assets in template files
SS template files accept a similar format for referencing static assets. Go through your assets files and remove hardcoded references.
```html
<img src="$ModulePath(app)/images/coyote.png" />
<% require css("app: css/styles.css") %>
```
### Finalising removal of hardcoded paths for referencing static assets
All your assets should be loading properly now.
This is a good point to commit your changes to your source control system before moving on to the next step.
## Step 10 - Update database class references {#step10}
If you've updated your class names to use namespaces you will need to reflect those changes in any existing database fields. For example, if you've renamed your `HomePage` class to `App\HomePage` then the database `ClassName` column needs to be updated to point to the `App\HomePage` class, otherwise the CMS will tell you that the page is obsolete. This also applies to polymorphic relationships.
There is no automated way to do this, but you can use the list generated in .upgrade.yml and copy it to `app/_config/legacy.yml`, removing any classes that don't extend DataObject.
```
SilverStripe\ORM\DatabaseAdmin:
classname_value_remapping:
HomePage: App\HomePage
```
This will automatically update affected columns when you first build the database.
## Step 11 - Running your upgraded site for the first time {#step11}
You're almost across the finish line.
### Run a dev build
Run a `dev/build` either on the command line or in your browser.
```bash
./vendor/bin/sake dev/build
```
This should migrate your existing data (non-destructively) to the new SilverStripe 4 structure.
#### Migrating files
Since the structure of the `File` DataObject has changed, a new task `MigrateFileTask`
has been added to assist in migration of legacy files (see [file migration documentation](/developer_guides/files/file_migration)).
```bash
./vendor/bin/sake dev/tasks/MigrateFileTask
```
### Any other script that needs running.
Some third party modules may include their own migration tasks. Take a minute to consult the release notes of your third party dependencies to make sure you haven't missed anything.
[CHILDREN]

View File

@ -0,0 +1,21 @@
# 3.6.7
<!--- Changes below this line will be automatically regenerated -->
## Change Log
### Security
* 2019-01-10 [c44f06cdf](https://github.com/silverstripe/silverstripe-framework/commit/c44f06cdf10387a987e4efb096ff06b3bb4495ef) Patch SQL Injection vulnerability when arrays are assigned to DataObject Fields (Aaron Carlino) - See [ss-2018-021](https://www.silverstripe.org/download/security-releases/ss-2018-021)
* 2018-09-26 [598edd913](https://github.com/silverstripe/silverstripe-framework/commit/598edd91341f389d7b919ec1201e03d2aba4d284) Add confirmation token to dev/build (Loz Calver) - See [ss-2018-019](https://www.silverstripe.org/download/security-releases/ss-2018-019)
### Bugfixes
* 2019-01-23 [746c0679a](https://github.com/silverstripe/silverstripe-framework/commit/746c0679ad1d6ceac03d2adf167367f0ca2259cd) Injector may instantiate prototypes as if they're singletons (fixes #8567) (Loz Calver)
* 2018-11-15 [86701b8cd](https://github.com/silverstripe/silverstripe-framework/commit/86701b8cd0cd5f8de813a7c9347e7c8055d878f4) Redirect loop with multiple URL tokens (fixes #8607) (Loz Calver)
* 2018-06-04 [41e601a03](https://github.com/silverstripe/silverstripe-framework/commit/41e601a036307065d9ea2ba8862f67be738d402f) Regression from #8009 (Daniel Hensby)
* 2018-05-29 [1cbf27e0f](https://github.com/silverstripe/silverstripe-framework/commit/1cbf27e0f47c3547914b03193d0f5f77c87ff8d5) PHP 5.3 compat for referencing $this in closure, and make method public for same reason (Robbie Averill)
* 2018-04-17 [af3a9f3ec](https://github.com/silverstripe/silverstripe-framework/commit/af3a9f3ec8a5465f841c5aa8ee1faf40c1b76bf4) Duplicating many_many relationships looses the extra fields (fixes #7973) (UndefinedOffset)
* 2018-03-15 [d17d93f7](https://github.com/silverstripe/silverstripe-cms/commit/d17d93f784a6e01f3d396c55adc623d69a90261a) Remove SearchForm results() function from allowed_actions (Steve Dixon)
* 2018-02-16 [86addea1d](https://github.com/silverstripe/silverstripe-framework/commit/86addea1d2a7b2e28ae8115279ae358bcb46648a) Split HTML manipulation to onadd, so elements are not accidentally duplicated (Christopher Joe)
* 2018-02-13 [c767e472d](https://github.com/silverstripe/silverstripe-framework/commit/c767e472dc494408460ef47c27b8d34475da4ac6) DataObject singleton creation (Jonathon Menz)

View File

@ -0,0 +1,16 @@
# 3.7.3
<!--- Changes below this line will be automatically regenerated -->
## Change Log
### Security
* 2019-01-10 [6bf9542d6](https://github.com/silverstripe/silverstripe-framework/commit/6bf9542d664ac7935691c8055505b7ad8ea26e9a) Patch SQL Injection vulnerability when arrays are assigned to DataObject Fields (Maxime Rainville) - See [ss-2018-021](https://www.silverstripe.org/download/security-releases/ss-2018-021)
### Bugfixes
* 2019-01-30 [8e6e70335](https://github.com/silverstripe/silverstripe-framework/commit/8e6e70335895063c6e6cfd99cfcfb50c6f9c2ad9) Block Manifest of the compatibility class Object and lean on model/fieldtypes/compat/autoload.php (UndefinedOffset)
* 2019-01-23 [746c0679a](https://github.com/silverstripe/silverstripe-framework/commit/746c0679ad1d6ceac03d2adf167367f0ca2259cd) Injector may instantiate prototypes as if they're singletons (fixes #8567) (Loz Calver)
* 2019-01-11 [16a837d6a](https://github.com/silverstripe/silverstripe-framework/commit/16a837d6a093115755cd821c63be1e3be088645b) fix [Warning] on count() with PHP &gt;= 7.2 (lerni)
* 2018-11-15 [86701b8cd](https://github.com/silverstripe/silverstripe-framework/commit/86701b8cd0cd5f8de813a7c9347e7c8055d878f4) Redirect loop with multiple URL tokens (fixes #8607) (Loz Calver)

View File

@ -746,13 +746,13 @@ Because the filesystem now uses the sha1 of file contents in order to version mu
filename, the default storage paths in 4.0 will not be the same as in 3.
In order to retain existing file paths in line with framework version 3 you should set the
`\SilverStripe\Filesystem\Flysystem\FlysystemAssetStore.legacy_paths` config to true.
`\SilverStripe\Filesystem\Flysystem\FlysystemAssetStore.legacy_filenames` config to true.
Note that this will not allow you to utilise certain file versioning features in 4.0.
```yml
SilverStripe\Filesystem\Flysystem\FlysystemAssetStore:
legacy_paths: true
legacy_filenames: true
```

View File

@ -0,0 +1,19 @@
# 4.0.6
<!--- Changes below this line will be automatically regenerated -->
## Change Log
### Security
* 2018-12-10 [0e841aa](https://github.com/silverstripe/silverstripe-graphql/commit/0e841aabb7372d9fa78108e4819e151608ddec0f) Apply CSRF middlware API
* 2018-11-07 [48bd33564](https://github.com/silverstripe/silverstripe-framework/commit/48bd335648188df9dae72be1e5f9c808f3fe1e77) Ensure that table names are escaped to prevent possible SQL injection (Robbie Averill) - See [ss-2018-020](https://www.silverstripe.org/download/security-releases/ss-2018-020)
### Bugfixes
* 2018-11-29 [59221e8](https://github.com/silverstripe/silverstripe-assets/commit/59221e8d74ac5e07b86a741e2709e0676130f7b4) Cache key cannot contain : chars, will happen when viewing from archive (Robbie Averill)
* 2018-11-21 [9ce6d91b7](https://github.com/silverstripe/silverstripe-framework/commit/9ce6d91b76e525a6fc81e02023e9e53cdf82e047) / TreeMultiselectField::objectForKey handles list of IDs correctly (Serge Latyntcev)
* 2018-11-16 [35c3a8c6](https://github.com/silverstripe/silverstripe-cms/commit/35c3a8c68db2660838dcd2ae5abd2bd1c3214af4) 'Search' text in default search form should be a placeholder (Robbie Averill)
* 2018-11-15 [b5bae137b](https://github.com/silverstripe/silverstripe-framework/commit/b5bae137bd341eeda3f4886f45fc8f8d657a9c4c) Redirect loop with multiple confirmation tokens present (fixes #8607) (Loz Calver)
* 2018-11-12 [15aaf9db9](https://github.com/silverstripe/silverstripe-framework/commit/15aaf9db9fe1679cf8b01b74fce3eee841278495) Fix a code style typo (Serge Latyntcev)
* 2018-11-08 [4b4fbabed](https://github.com/silverstripe/silverstripe-framework/commit/4b4fbabed5d70bf577e4b0d6fdbc9dab9da80451) TreeMultiselectField passes value 'unchanged' as null to ORM for 'ID' column key (Serge Latyntcev)

View File

@ -0,0 +1,24 @@
# 4.0.7
<!--- Changes below this line will be automatically regenerated -->
## Change Log
### Security
* 2018-12-18 [95505db7d](https://github.com/silverstripe/silverstripe-framework/commit/95505db7d666a75f249f65cb1af74dca01d39add) Fix potential SQL vulnerability in non-scalar value hyrdation (Maxime Rainville) - See [ss-2018-021](https://www.silverstripe.org/download/security-releases/ss-2018-021)
### Features and Enhancements
* 2018-11-27 [a8f4f23c6](https://github.com/silverstripe/silverstripe-framework/commit/a8f4f23c660474e965b510ba1bb04bf7a145d5b0) Add visibility updates to `upgrade.yml`. (bergice)
### Bugfixes
* 2019-01-31 [cda9eef](https://github.com/silverstripe/silverstripe-assets/commit/cda9eef992b70fd46377be6d4459260d31ea2215) Fix for issue #212 (Joe Madden)
* 2019-01-29 [f918dcd36](https://github.com/silverstripe/silverstripe-framework/commit/f918dcd36a177adb8abee751d27a809f25a0afab) Escape wildcard characters when matching database name in databaseExists (Guy Marriott)
* 2019-01-28 [dc9d1b9cc](https://github.com/silverstripe/silverstripe-framework/commit/dc9d1b9cc0d3b14929fe2675282980a2750fb4cf) GridFieldPrintButton no longer assumes that children of GridField would implement their own print template (Robbie Averill)
* 2019-01-25 [0797ab7](https://github.com/silverstripe/silverstripe-asset-admin/commit/0797ab7762a4a64f2dc89c754a6bb04216b96fe1) Fix GraphQL FolderTypeCreator::resolveChildrenConnection on PostgreSQL (#901) (Serge Latyntcev)
* 2019-01-24 [d530bc2fb](https://github.com/silverstripe/silverstripe-framework/commit/d530bc2fb6c8c290f1f75f39bc4ec898cc326cf1) fix user feedback when jquery could not be loaded (Benedikt Seidl)
* 2019-01-13 [5c3b95ac](https://github.com/silverstripe/silverstripe-cms/commit/5c3b95ac8977f77e7d95d4da6333ca12b5ef465f) Multibyte URL routing (Ingo Schommer)
* 2019-01-11 [2cb49ea](https://github.com/silverstripe/silverstripe-versioned/commit/2cb49ea79d6babb80289f170dc6102c82f5b0d69) [Warning] on count() with PHP &gt;= 7.2 (Lukas)
* 2018-12-12 [0491ca03c](https://github.com/silverstripe/silverstripe-framework/commit/0491ca03cfcbd81690c54cc00f6234675978ef39) prevent death on urls with querystrings & anchors (mikeyc7m)

View File

@ -0,0 +1,20 @@
# 4.1.4
<!--- Changes below this line will be automatically regenerated -->
## Change Log
### Security
* 2018-12-10 [6f8dc77](https://github.com/silverstripe/silverstripe-graphql/commit/6f8dc779f39aebf79acbc0e2f3363705833b583b) Apply CSRF middlware API
* 2018-11-07 [fecedc2d9](https://github.com/silverstripe/silverstripe-framework/commit/fecedc2d98eeaaff6424fb59dc70ef6bdc6dc92d) Ensure that table names are escaped to prevent possible SQL injection (Robbie Averill) - See [ss-2018-020](https://www.silverstripe.org/download/security-releases/ss-2018-020)
### Bugfixes
* 2018-11-29 [59221e8](https://github.com/silverstripe/silverstripe-assets/commit/59221e8d74ac5e07b86a741e2709e0676130f7b4) Cache key cannot contain : chars, will happen when viewing from archive (Robbie Averill)
* 2018-11-21 [9ce6d91b7](https://github.com/silverstripe/silverstripe-framework/commit/9ce6d91b76e525a6fc81e02023e9e53cdf82e047) / TreeMultiselectField::objectForKey handles list of IDs correctly (Serge Latyntcev)
* 2018-11-16 [35c3a8c6](https://github.com/silverstripe/silverstripe-cms/commit/35c3a8c68db2660838dcd2ae5abd2bd1c3214af4) 'Search' text in default search form should be a placeholder (Robbie Averill)
* 2018-11-15 [b5bae137b](https://github.com/silverstripe/silverstripe-framework/commit/b5bae137bd341eeda3f4886f45fc8f8d657a9c4c) Redirect loop with multiple confirmation tokens present (fixes #8607) (Loz Calver)
* 2018-11-12 [15aaf9db9](https://github.com/silverstripe/silverstripe-framework/commit/15aaf9db9fe1679cf8b01b74fce3eee841278495) Fix a code style typo (Serge Latyntcev)
* 2018-11-08 [4b4fbabed](https://github.com/silverstripe/silverstripe-framework/commit/4b4fbabed5d70bf577e4b0d6fdbc9dab9da80451) TreeMultiselectField passes value 'unchanged' as null to ORM for 'ID' column key (Serge Latyntcev)
* 2018-10-15 [6de0fa0](https://github.com/silverstripe/silverstripe-versioned/commit/6de0fa087fe581b69a5978db82058490c44923b4) Fix codesniffer runs in Travis (Robbie Averill)

View File

@ -0,0 +1,18 @@
# 4.1.5
<!--- Changes below this line will be automatically regenerated -->
## Change Log
### Security
* 2018-12-18 [fd90cf6ce](https://github.com/silverstripe/silverstripe-framework/commit/fd90cf6ceb346142eee6ba620599ab36c2d18cbb) Fix potential SQL vulnerability in non-scalar value hyrdation (Maxime Rainville) - See [ss-2018-021](https://www.silverstripe.org/download/security-releases/ss-2018-021)
s
### Bugfixes
* 2019-01-31 [cda9eef](https://github.com/silverstripe/silverstripe-assets/commit/cda9eef992b70fd46377be6d4459260d31ea2215) Fix for issue #212 (Joe Madden)
* 2019-01-25 [0797ab7](https://github.com/silverstripe/silverstripe-asset-admin/commit/0797ab7762a4a64f2dc89c754a6bb04216b96fe1) Fix GraphQL FolderTypeCreator::resolveChildrenConnection on PostgreSQL (#901) (Serge Latyntcev)
* 2019-01-13 [5c3b95ac](https://github.com/silverstripe/silverstripe-cms/commit/5c3b95ac8977f77e7d95d4da6333ca12b5ef465f) Multibyte URL routing (Ingo Schommer)
* 2019-01-11 [2cb49ea](https://github.com/silverstripe/silverstripe-versioned/commit/2cb49ea79d6babb80289f170dc6102c82f5b0d69) [Warning] on count() with PHP &gt;= 7.2 (Lukas)
* 2018-12-18 [3d841a4](https://github.com/silverstripe/silverstripe-installer/commit/3d841a409c43752a8192afea5ebc48327e2ac3d3) / Behat tests (Serge Latyntcev)
* 2018-12-13 [c4a0d5f0](https://github.com/silverstripe/silverstripe-cms/commit/c4a0d5f0831f0f27022905700a0ffb86cc56aceb) Publish button text merge regression (Aaron Carlino)

View File

@ -204,8 +204,7 @@ SilverStripe\Core\Injector\Injector:
factory: SilverStripe\Core\Cache\CacheFactory
constructor:
namespace: "MyInsensitiveData"
args:
disable-container: true
disable-container: true
```
### HTTP Cache Header changes

View File

@ -0,0 +1,28 @@
# 4.2.3
<!--- Changes below this line will be automatically regenerated -->
## Change Log
### Security
* 2018-12-10 [b59ba39](https://github.com/silverstripe/silverstripe-graphql/commit/b59ba397ff42d8934bd2d9c932514f898c327f64) Add CSRF middlware
## Features and Enhancements
* 2018-07-16 [9270206c](https://github.com/silverstripe/silverstripe-reports/commit/9270206c3bd2fe35bb263ad43ad3a5d87360873a) Use Injector to create new class instances and pass $params (Robbie Averill)
### Bugfixes
* 2018-12-10 [9fce4b2](https://github.com/silverstripe/silverstripe-graphql/commit/9fce4b2408dd82d303925eee3b6cf393da371e85) Ensure httpMethod context is applied to all controller actions (#194) (Aaron Carlino)
* 2018-11-29 [59221e8](https://github.com/silverstripe/silverstripe-assets/commit/59221e8d74ac5e07b86a741e2709e0676130f7b4) Cache key cannot contain : chars, will happen when viewing from archive (Robbie Averill)
* 2018-11-21 [9ce6d91b7](https://github.com/silverstripe/silverstripe-framework/commit/9ce6d91b76e525a6fc81e02023e9e53cdf82e047) / TreeMultiselectField::objectForKey handles list of IDs correctly (Serge Latyntcev)
* 2018-11-16 [35c3a8c6](https://github.com/silverstripe/silverstripe-cms/commit/35c3a8c68db2660838dcd2ae5abd2bd1c3214af4) 'Search' text in default search form should be a placeholder (Robbie Averill)
* 2018-11-15 [b5bae137b](https://github.com/silverstripe/silverstripe-framework/commit/b5bae137bd341eeda3f4886f45fc8f8d657a9c4c) Redirect loop with multiple confirmation tokens present (fixes #8607) (Loz Calver)
* 2018-11-12 [15aaf9db9](https://github.com/silverstripe/silverstripe-framework/commit/15aaf9db9fe1679cf8b01b74fce3eee841278495) Fix a code style typo (Serge Latyntcev)
* 2018-11-08 [4b4fbabed](https://github.com/silverstripe/silverstripe-framework/commit/4b4fbabed5d70bf577e4b0d6fdbc9dab9da80451) TreeMultiselectField passes value 'unchanged' as null to ORM for 'ID' column key (Serge Latyntcev)
* 2018-10-15 [6de0fa0](https://github.com/silverstripe/silverstripe-versioned/commit/6de0fa087fe581b69a5978db82058490c44923b4) Fix codesniffer runs in Travis (Robbie Averill)
* 2018-10-06 [c498aa03](https://github.com/silverstripe/silverstripe-cms/commit/c498aa03379ca883803dda853e64c411ed7454dc) Fixing wrong Live-Preview-Link in SilverStripeNavigatorItem_LiveLink (fixes #865). (Stephan Bauer)
* 2018-09-13 [7189653b](https://github.com/silverstripe/silverstripe-cms/commit/7189653b1f9a744b9ee2393a8ef3fb8597c89b1b) SiteTree Title field should have rounded corners before Update URL button is shown (Robbie Averill)
* 2018-07-27 [bc70b877](https://github.com/silverstripe/silverstripe-reports/commit/bc70b87721c8278111e39e0af69db1052af7333f) Apply missing class to report header. (Maxime Rainville)
* 2018-07-01 [bc8bb13](https://github.com/silverstripe/silverstripe-campaign-admin/commit/bc8bb13c93c75e718872315a60f0eb8213bd8e69) Button outline secondary class is now correct in disabled "Publish campaign" button (Robbie Averill)

View File

@ -0,0 +1,24 @@
# 4.2.4
<!--- Changes below this line will be automatically regenerated -->
## Change Log
### Security
* 2018-12-18 [7cc40fe39](https://github.com/silverstripe/silverstripe-framework/commit/7cc40fe392ed907be8fbfc73579b4888840c39e6) Fix potential SQL vulnerability in non-scalar value hyrdation (Maxime Rainville) - See [ss-2018-021](https://www.silverstripe.org/download/security-releases/ss-2018-021)
### Bugfixes
* 2019-01-31 [cda9eef](https://github.com/silverstripe/silverstripe-assets/commit/cda9eef992b70fd46377be6d4459260d31ea2215) Fix for issue #212 (Joe Madden)
* 2019-01-25 [568be8e29](https://github.com/silverstripe/silverstripe-framework/commit/568be8e29b9a6f9205dd28a823ed5294cc95a590) Misconfiguration for versioned cache segmentation (fixes #8754) (Loz Calver)
* 2019-01-25 [0797ab7](https://github.com/silverstripe/silverstripe-asset-admin/commit/0797ab7762a4a64f2dc89c754a6bb04216b96fe1) Fix GraphQL FolderTypeCreator::resolveChildrenConnection on PostgreSQL (#901) (Serge Latyntcev)
* 2019-01-24 [d00c59c38](https://github.com/silverstripe/silverstripe-framework/commit/d00c59c383dee270c9c1753dd8b64b8cd1b15489) Fix tests not loading fixtures and loading themes in CWP recipe kitchen sink (Robbie Averill)
* 2019-01-17 [9ced2347](https://github.com/silverstripe/silverstripe-cms/commit/9ced23473f2f102de5b6d828d093be1102f8d570) Don't convert datalist to arraylist when filtering pages (Damian Mooyman)
* 2019-01-13 [5c3b95ac](https://github.com/silverstripe/silverstripe-cms/commit/5c3b95ac8977f77e7d95d4da6333ca12b5ef465f) Multibyte URL routing (Ingo Schommer)
* 2019-01-11 [2cb49ea](https://github.com/silverstripe/silverstripe-versioned/commit/2cb49ea79d6babb80289f170dc6102c82f5b0d69) [Warning] on count() with PHP &gt;= 7.2 (Lukas)
* 2019-01-10 [34ac22802](https://github.com/silverstripe/silverstripe-framework/commit/34ac228029a4609d993e8604aad16e72bd52ac9c) Fix issue with assertListEquals() ignoring field getters (Damian Mooyman)
* 2019-01-08 [c43f4e0](https://github.com/silverstripe/silverstripe-graphql/commit/c43f4e0708fd86e8078ef3326d963005d626baf4) Ensure queries are sorted before limiting (Damian Mooyman)
* 2018-12-18 [3d841a4](https://github.com/silverstripe/silverstripe-installer/commit/3d841a409c43752a8192afea5ebc48327e2ac3d3) / Behat tests (Serge Latyntcev)
* 2018-12-13 [c4a0d5f0](https://github.com/silverstripe/silverstripe-cms/commit/c4a0d5f0831f0f27022905700a0ffb86cc56aceb) Publish button text merge regression (Aaron Carlino)
* 2018-09-27 [ce3a1ce9](https://github.com/silverstripe/silverstripe-cms/commit/ce3a1ce91307424f643a15f0c292e16b35f35873) Use correct subsites namespace in SiteTree and test classes (Robbie Averill)

View File

@ -8,6 +8,7 @@
- New React-based search UI for the CMS, Asset-Admin, GridFields and ModelAdmins.
- A new `GridFieldLazyLoader` component can be added to `GridField`. This will delay the fetching of data until the user access the container Tab of the GridField.
- `SilverStripe\VersionedAdmin\Controllers\CMSPageHistoryViewerController` is now the default CMS history controller and `SilverStripe\CMS\Controllers\CMSPageHistoryController` has been deprecated.
- PHPUnit tests no longer auto-flush, requiring manual flush parameters when changing YAML config or certain PHP code
## Upgrading {#upgrading}
@ -26,7 +27,7 @@ To enable the legacy search API on a `GridFieldFilterHeader`, you can either:
* set the `useLegacyFilterHeader` property to `true`,
* or pass `true` to the first argument of its constructor.
To force the legacy search API on all instances of `GridFieldFilterHeader`, you can set it in your [configuration file](../developer_guides/configuration):
To force the legacy search API on all instances of `GridFieldFilterHeader`, you can set it in your [configuration file](../../configuration):
```yml
SilverStripe\Forms\GridField\GridFieldFilterHeader:
force_legacy: true
@ -67,18 +68,244 @@ SilverStripe\Core\Injector\Injector:
App\MySite\MyCustomControllerFactory
```
[Implementing a _Factory_ with the Injector](/developer_guides/extending/injector/#factories).
[Implementing a _Factory_ with the Injector](/developer_guides/extending/injector/#factories)
### Using the history viewer for custom DataObjects
### PHPUnit tests no longer auto-flush
For information on how to implement the history viewer UI in your own versioned DataObjects, please refer to
[the Versioning documentation](../developer_guides/model/versioning).
SilverStripe caches certain metadata in manifests, for example YAML configuration
and certain PHP class structures (e.g. `ClassInfo::implementorsOf()`).
This is also the case for CLI executions such as PHPUnit.
### Tests with dynamic extension customisations
Starting with SilverStripe 4.0, PHPUnit executions flushed the cache automatically.
While this meant less work for developers in manually flushing caches,
it significantly increased the time to run each test (from sub-second to multi-second).
In order to allow for efficient test execution and Test Driven Development (TDD),
we have decided to treat this as a performance regression.
In SilverStripe 4.2, some unit tests that modify an extension class with PHP configuration manifest customisations
may have passed and may now fail in SilverStripe 4.3. This behaviour is inconsistent, is not a recommended approach
to customising extensions and should be avoided in all SilverStripe 4.x releases.
In order to flush manifests on test execution, please use the following command
(note the empty quoted string):
For information on how to customise extensions, see
["Extending Extensions"](../developer_guides/extending/extensions#extendingextensions).
```
vendor/bin/phpunit vendor/silverstripe/framework/tests '' flush=1
```
See our [testing guide](https://docs.silverstripe.org/en/4/developer_guides/testing/)
for more details.
<!--- Changes below this line will be automatically regenerated -->
## Change Log
### Security
* 2018-12-11 [4f68e4b](https://github.com/silverstripe/silverstripe-admin/commit/4f68e4b759c104b2690050af034a41d370d2e6e4) Add CSRF middleware implementation (Aaron Carlino) - See [ss-2018-007](https://www.silverstripe.org/download/security-releases/ss-2018-007)
* 2018-11-07 [e18549beb](https://github.com/silverstripe/silverstripe-framework/commit/e18549beb08b47f63c87df3f1d727313e380bb95) Ensure that table names are escaped to prevent possible SQL injection (Robbie Averill) - See [ss-2018-020](https://www.silverstripe.org/download/security-releases/ss-2018-020)
* 2018-10-24 [88391f2](https://github.com/silverstripe/silverstripe-graphql/commit/88391f27463cac553360d7d94b0760e797247855) CSRF protection (Aaron Carlino) - See [ss-2018-007](https://www.silverstripe.org/download/security-releases/ss-2018-007)
* 2018-08-21 [3dbb10625](https://github.com/silverstripe/silverstripe-framework/commit/3dbb10625c6918094a18cd9a29f1f9daca8129c5) Add confirmation token to dev/build (Loz Calver) - See [ss-2018-019](https://www.silverstripe.org/download/security-releases/ss-2018-019)
* 2018-08-12 [3424431](https://github.com/silverstripe/silverstripe-admin/commit/3424431a5ed52fc9aae97423268aea5eba7334a2) Add CSRF to Apollo (Aaron Carlino) - See [ss-2018-007](https://www.silverstripe.org/download/security-releases/ss-2018-007)
* 2018-07-29 [637b4225c](https://github.com/silverstripe/silverstripe-framework/commit/637b4225c6a85bfa0d59e516a8c602203cc980d9) Ignore arguments in mysqli::real_connect backtrace calls (Robbie Averill) - See [ss-2018-018](https://www.silverstripe.org/download/security-releases/ss-2018-018)
### API Changes
* 2018-10-05 [5276b6cbb](https://github.com/silverstripe/silverstripe-framework/commit/5276b6cbb1ebd49d704734d940b0467c51b0d064) Add FieldList::getContainerField (Maxime Rainville)
* 2018-10-04 [dba237e](https://github.com/silverstripe/silverstripe-admin/commit/dba237e5d6ab13a7a5f8cbc9a9e594d98478c566) Allow Gridfield to be lazy loadable. (Maxime Rainville)
* 2018-10-04 [bdb53979a](https://github.com/silverstripe/silverstripe-framework/commit/bdb53979aaed743f278b299e3dab7b13e321fbe7) Create a new GridFieldLazyLoader GridField component (Maxime Rainville)
* 2018-09-28 [cb2b9498f](https://github.com/silverstripe/silverstripe-framework/commit/cb2b9498fb33bd57a19ba6a7ceed6d3fbc976d70) Deprecate updateSearchContextCallback and updateSearchFormCallback (Robbie Averill)
* 2018-09-03 [f48ab77](https://github.com/silverstripe/silverstripe-assets/commit/f48ab777c2845d7b414ebae0ff7903c6bb423340) Add `show_file_link_tracking` config to `FileLinkTracking` extension to control visibility of the File Tracking tab (bergice)
* 2018-08-31 [01db5c9e9](https://github.com/silverstripe/silverstripe-framework/commit/01db5c9e98f5406e8d6b0f1f4ea660edb24b199f) Add Link Tracking section to Relations developer guide and describe `show_sitetree_link_tracking`, `show_file_link_tracking`. (bergice)
* 2018-08-31 [115ed92e](https://github.com/silverstripe/silverstripe-cms/commit/115ed92e0aed63df1620700955afd6248fa97fae) Add `show_sitetree_link_tracking` config to `SiteTreeLinkTracking` extension to control visibility of the Link Tracking tab (bergice)
* 2018-08-05 [02be4cc](https://github.com/silverstripe/silverstripe-graphql/commit/02be4cc3e1cab7c1ed8e8f9d6dde6ee102364fa6) Multi-schema support (#169) (Aaron Carlino)
* 2018-07-12 [0343203](https://github.com/silverstripe/silverstripe-versioned/commit/03432030e99a496c6cd81f4bec780164851aa955) Add new RestoreAction and canRestoreToDraft method (#168) (Luke Edwards)
### Features and Enhancements
* 2018-11-01 [2f2fb2b](https://github.com/silverstripe/silverstripe-admin/commit/2f2fb2bfdcd88b54ac9426f172bec77a3c210172) Improve loading screen and indicator (#582) (Luke Edwards)
* 2018-10-29 [e51be58](https://github.com/silverstripe/silverstripe-asset-admin/commit/e51be58685527b512bf20e7542d506aef14f4ed8) Move Remove button into preview-image message (#859) (Sacha Judd)
* 2018-10-29 [2440432](https://github.com/silverstripe/silverstripe-asset-admin/commit/24404324790a26b0c19f68f862bb2679618199ee) Move the replace file into the more options action set (#848) (Sacha Judd)
* 2018-10-29 [952f37a](https://github.com/silverstripe/silverstripe-admin/commit/952f37abdc78d9f87da20461537c88cf570d7cba) Adding an HOC to provide DragDropContext for consumers of ReactDND (#711) (Guy Marriott)
* 2018-10-26 [2c3665d](https://github.com/silverstripe/silverstripe-admin/commit/2c3665d83375364249adf02c2eef6382d73e7c43) Adding a component for a generic popover filled with buttons (#684) (Guy Marriott)
* 2018-10-19 [e88b8e7](https://github.com/silverstripe/silverstripe-admin/commit/e88b8e7b2fa4c47e62d737ea3a3ccee8c7ff8794) Expose TabsActions (#703) (Raissa North)
* 2018-10-18 [761a6f7](https://github.com/silverstripe/silverstripe-admin/commit/761a6f7f6f809c36f21dd804ce1d6325909b5d1c) Reverse argument signature of methods using path (#698) (Aaron Carlino)
* 2018-10-17 [902fec0](https://github.com/silverstripe/silverstripe-asset-admin/commit/902fec0f89bb49783b2dc5f64b4d0c24c33531fb) Extensible readFiles query (#847) (Aaron Carlino)
* 2018-10-17 [437e53f2f](https://github.com/silverstripe/silverstripe-framework/commit/437e53f2fec17bc778cc7bba39de322d43214441) Some minor refactoring of the PDO and MySQLi connectors (Robbie Averill)
* 2018-10-15 [3e2ce31](https://github.com/silverstripe/silverstripe-admin/commit/3e2ce3138d88674fc27aa465dc9be9ea4533c830) Add nested fields, args, distribute args to fields (#683) (Aaron Carlino)
* 2018-10-08 [2775895d0](https://github.com/silverstripe/silverstripe-framework/commit/2775895d03af93d207079637a8eb6afdfad5ab01) Adding a helper to find a form field by label content (Guy Marriott)
* 2018-10-05 [6e66c48](https://github.com/silverstripe/silverstripe-admin/commit/6e66c48d65b52d568fbd34f8a7c630b4a829c2c9) Add CMS community help menu to cms-menu (#615) (Sacha Judd)
* 2018-10-04 [9ea7b58a8](https://github.com/silverstripe/silverstripe-framework/commit/9ea7b58a8f26ca8856211da30eed5751706d0c4b) Add memory cache to ThemeResourceLoader::findTemplate() (Robbie Averill)
* 2018-10-03 [90afb2037](https://github.com/silverstripe/silverstripe-framework/commit/90afb2037a6d2d9be3f605aad9ddfbda0bbab2e7) TabSet react component is no longer structural (Raissa North)
* 2018-10-03 [0be4a9a](https://github.com/silverstripe/silverstripe-admin/commit/0be4a9abfa4482deb2753c50c47dbf0192581a12) Adding an extension point to FormBuilderLoader after redux form is initialised (Raissa North)
* 2018-10-02 [cba5d30](https://github.com/silverstripe/silverstripe-admin/commit/cba5d307f499471b88f77fd201df2a4baacf0038) Connect Tabs component to redux-form to handle activeTab state (Raissa North)
* 2018-10-02 [e1f2f89d3](https://github.com/silverstripe/silverstripe-framework/commit/e1f2f89d37e44c706d350d02774733dd867ccdc7) Add test for PHP 7.3 support (SS4 version) (Sam Minnee)
* 2018-09-28 [bd37b90a](https://github.com/silverstripe/silverstripe-cms/commit/bd37b90a3a5811555248ef616698efbe24466b11) Add CMSMain.enable_archive_warning_message config (Sam Minnee)
* 2018-09-27 [4b155b9](https://github.com/silverstripe/silverstripe-graphql/commit/4b155b982ca6fd737aa70e281ce679bb43404060) Add getSortableFields to return sortable fields for query (#185) (Robbie Averill)
* 2018-09-27 [25759ffc5](https://github.com/silverstripe/silverstripe-framework/commit/25759ffc5fe532a239b1487ca6b025140d2e144f) Show file path on PHP parser exceptions (Ingo Schommer)
* 2018-09-25 [5b7a84141](https://github.com/silverstripe/silverstripe-framework/commit/5b7a84141b0fbef66a9f3f52a9ccee12e02ef1e0) Add Hierarchy::prepopulate_numchildren_cache() (#8380) (Sam Minnée)
* 2018-09-21 [e2701a4](https://github.com/silverstripe/silverstripe-admin/commit/e2701a4cd00ffc2136b935038d8422a25acfa2dd) TinyMCE inline toolbar for images and embeds (Luke Edwards)
* 2018-09-20 [c928e83](https://github.com/silverstripe/silverstripe-versioned/commit/c928e830825ec8ae5336d254835f754a11bac350) Backport of DataList for list of versions, for SS 4.3.x (Robbie Averill)
* 2018-09-19 [d9a6c10](https://github.com/silverstripe/silverstripe-admin/commit/d9a6c10a157d6c1fd17351b0097ce9d8c5336ecf) Form state & schema persists across form remounting (Guy Marriott)
* 2018-09-19 [40dde226f](https://github.com/silverstripe/silverstripe-framework/commit/40dde226fd1b8997308ef0f5718763a298295cdf) Add ?showqueries=backtrace (Sam Minnee)
* 2018-09-19 [6de12e1](https://github.com/silverstripe/silverstripe-asset-admin/commit/6de12e1375d146d61052f1a6dd001aa3a6c437bc) TinyMCE inline toolbar for images and embeds (Luke Edwards)
* 2018-09-17 [588bf83e1](https://github.com/silverstripe/silverstripe-framework/commit/588bf83e1238a79793da4bc4145b7597ae2626be) Add hideNav flag to schema defaults (Raissa North)
* 2018-09-17 [a800ce7](https://github.com/silverstripe/silverstripe-admin/commit/a800ce714a99a631b8a7401322df0653e2ee476c) Add hideNav flag to allow hiding of navigation in Tabs (Raissa North)
* 2018-09-12 [d8bf873](https://github.com/silverstripe/silverstripe-admin/commit/d8bf873e1236ff5cb634cce0a47c992366ae6139) Use bootstrap modal footer, use our icon font for close icon (Luke Edwards)
* 2018-09-12 [c03c685](https://github.com/silverstripe/silverstripe-asset-admin/commit/c03c685da247b8baa6d4180816bda35bc29fcf2d) Use bootstrap modal footer, use our icon font for close icon (Luke Edwards)
* 2018-09-03 [2394194](https://github.com/silverstripe/silverstripe-admin/commit/239419424ee001200830bc8b77dfef3ce7a05d7e) HtmlEditorField component for react rich text (Dylan Wagstaff)
* 2018-08-22 [a257c1c](https://github.com/silverstripe/silverstripe-admin/commit/a257c1cea84ebcdd27df5043c3690bdcb09f43da) Add toggleCallback function (Raissa North)
* 2018-08-22 [8153d12](https://github.com/silverstripe/silverstripe-admin/commit/8153d12537b53bd208ddb28675aa82bd84da2a16) Add dropdownToggleClassName prop to ActionMenu (Raissa North)
* 2018-08-20 [26262ea](https://github.com/silverstripe/silverstripe-admin/commit/26262ea3a9d64e4172ed277845a53e58c48772ef) Insert link shortcut for HTMLEditorField (#599) (Luke Edwards)
* 2018-08-12 [8d3b022](https://github.com/silverstripe/silverstripe-assets/commit/8d3b02259167ce08fd30742475a29219d64df0c8) Support setting quality on a per-image basis (#153) (Loz Calver)
* 2018-08-05 [9d0ae97](https://github.com/silverstripe/silverstripe-admin/commit/9d0ae970e5cdf2ec8710b471884d75b5485b9309) ViewModeToggle states are now stored in constants (Robbie Averill)
* 2018-08-01 [4213eeb](https://github.com/silverstripe/silverstripe-asset-admin/commit/4213eeb1513662ef2dd177065310c2062532a424) Use the new general purpose search component. (#812) (Maxime Rainville)
* 2018-08-01 [5a00d84](https://github.com/silverstripe/silverstripe-admin/commit/5a00d8462ec2806c640f1214971f4747f276d398) General purpose search form component (#572) (Maxime Rainville)
* 2018-07-30 [163ca65](https://github.com/silverstripe/silverstripe-graphql/commit/163ca65a1f2e858c7d47d4b15775153ea0451d5b) DataObjectScaffolder instantiation is now handled through Injector (Robbie Averill)
* 2018-07-30 [24d3023](https://github.com/silverstripe/silverstripe-graphql/commit/24d3023b0d361fee66b4e68aca1814b7dfdbcac2) Allow non-internal input types passed as args (#168) (Aaron Carlino)
* 2018-07-25 [79a5ea3](https://github.com/silverstripe/recipe-cms/commit/79a5ea3dace336558ef4d5be4a86cc1a0e84badc) Add versioned-admin (Luke Edwards)
* 2018-07-17 [337da78](https://github.com/silverstripe/silverstripe-campaign-admin/commit/337da782bd520beebdb734542b5403b69257d058) Update webpack-config constraint (Raissa North)
* 2018-07-16 [786446fb](https://github.com/silverstripe/silverstripe-reports/commit/786446fb670905832b6bfe49775b9c2eaff262cc) Use Injector to create new class instances and pass $params (Robbie Averill)
* 2018-07-12 [f2ebdb7f](https://github.com/silverstripe/silverstripe-cms/commit/f2ebdb7f5ec894e8c0c8f29b9aac984aef4ca8ed) add SiteTree::updateAnchorsOnPage() for user defining additional page anchors (Will Rossiter)
* 2018-07-12 [114b0a5ea](https://github.com/silverstripe/silverstripe-framework/commit/114b0a5ea7ea6b8f33b8c9b8d1611e5ee6619a1c) Option for secure "remember me" cookie (Ingo Schommer)
* 2018-07-12 [3292a8b77](https://github.com/silverstripe/silverstripe-framework/commit/3292a8b773c5b29a69b72718f996a36f3daead1d) Add `columnUnique` API SS_List classes. (Al Twohill)
* 2018-07-06 [6c1a34c](https://github.com/silverstripe/silverstripe-campaign-admin/commit/6c1a34cc2c9bfbc3bca471c7ca77976bac194918) Make use of ViewModeToggle component. (Raissa North)
* 2018-04-15 [5108734](https://github.com/silverstripe/silverstripe-admin/commit/5108734b19edbe9d6404b747e25059a586b009fe) Add ViewModeToggle component (Raissa North)
* 2018-01-30 [1857f00](https://github.com/silverstripe/silverstripe-admin/commit/1857f00bd2b172acb933064d3d454e91bc855200) Add tests for Form component (Robbie Averill)
* 2018-01-30 [cc945f0](https://github.com/silverstripe/silverstripe-admin/commit/cc945f09aac38dea25ec57538c51e6089ddac124) Add tests for CompositeField (Robbie Averill)
### Bugfixes
* 2018-12-10 [11970d5](https://github.com/silverstripe/silverstripe-graphql/commit/11970d56f3e57f370359cf9edab4c27e9f766c3c) Ensure httpMethod context is applied to all controller actions (#194) (Aaron Carlino)
* 2018-12-04 [a0bb79b](https://github.com/silverstripe/silverstripe-admin/commit/a0bb79b62bc5e907adf8ff93acd0d3fd0f9046f6) Ensure the container exists before unmounting React/removing it (Guy Marriott)
* 2018-12-02 [c28f5ad4](https://github.com/silverstripe/silverstripe-cms/commit/c28f5ad45fb7010b9ad71c3594161d0de15aa763) CMSPageHistoryControllerTest now uses a stub controller to avoid URL conflicts with versioned-admin (Robbie Averill)
* 2018-11-30 [7567488](https://github.com/silverstripe/silverstripe-admin/commit/7567488458daa16d20e6a95d1b33895f7293c69f) Fix new page form clearing when selecting a `Under another page` option (bergice)
* 2018-11-19 [1946aca](https://github.com/silverstripe/silverstripe-admin/commit/1946acab5ab70af8ac61cb419b5ca82336569e4b) Correct the entwine match for the loading animation (Maxime Rainville)
* 2018-11-13 [fa14ecd](https://github.com/silverstripe/recipe-core/commit/fa14ecdad5169cb3ab03ecdddb523f723ded1b27) Ensure that the PasswordValidator is registered with Injector (Robbie Averill)
* 2018-11-08 [2f896b1](https://github.com/silverstripe/recipe-core/commit/2f896b11530cb55acab9bc43d33ce867faafbf3c) Move password complexity requirements into framework (Robbie Averill)
* 2018-11-05 [7fd4a4e](https://github.com/silverstripe/silverstripe-admin/commit/7fd4a4ef76733968ed3babbc14564b2d0f7417b3) Fix duplicate plugins on HTML editor fields (#721) (Luke Edwards)
* 2018-11-05 [4a65d59](https://github.com/silverstripe/silverstripe-admin/commit/4a65d59dc340719f67df1d2c1998ea2645d58473) Fix form changes triggered, GridField add existing (#743) (Luke Edwards)
* 2018-11-02 [97180c261](https://github.com/silverstripe/silverstripe-framework/commit/97180c261258861b5b2b91609a71d044456625d7) Fix readonly grid state always being truthy (#8562) (Luke Edwards)
* 2018-11-02 [12e2cc3](https://github.com/silverstripe/silverstripe-asset-admin/commit/12e2cc37a7a2806310ac7ffd2ad704fb7ad37fe0) Fix duplicate plugins on HTML editor fields (#861) (Luke Edwards)
* 2018-11-02 [d9b1721a](https://github.com/silverstripe/silverstripe-cms/commit/d9b1721ac32829a33f54a9e628680cb3894191b3) Fix duplicate plugins on HTML editor fields (#2307) (Luke Edwards)
* 2018-11-01 [8866e7674](https://github.com/silverstripe/silverstripe-framework/commit/8866e7674a1a9c2be48c8e9532cfcaa667cdf7b5) Fix duplicate plugins on HTML editor fields (#8559) (Luke Edwards)
* 2018-11-01 [55f95b7bc](https://github.com/silverstripe/silverstripe-framework/commit/55f95b7bc8f91384df459bd70c87cacf92225f68) many many through not sorting by join table (#8534) (Michael Strong)
* 2018-10-31 [af7086a](https://github.com/silverstripe/silverstripe-asset-admin/commit/af7086a3adf86b06b6c6b6f938acabf2cfa3352b) Remove outdated CSS Safari hack interfering with the search panel and submit button (Serge Latyntcev)
* 2018-10-31 [2ef7bd29](https://github.com/silverstripe/silverstripe-cms/commit/2ef7bd29754f84fa6eafce08c00d6e1e794713af) IE11+Edge17 Pages tree List View button (Serge Latyntcev)
* 2018-10-30 [3f4d5ae0](https://github.com/silverstripe/silverstripe-cms/commit/3f4d5ae03e9fee10c54f3e628a194d03c07b5c3a) Bypass cached versions to prevent stale state (Aaron Carlino)
* 2018-10-30 [4b0e69a](https://github.com/silverstripe/silverstripe-admin/commit/4b0e69a084028eb60c9d3da90e648acd87946d73) Add aria-expanded to help menu toggle for screenreader accessibility (Sacha Judd)
* 2018-10-30 [2900ac6](https://github.com/silverstripe/silverstripe-admin/commit/2900ac6481b5cca1df1b6708522a60d8a946b790) Remove text-align start with IE supported left (Raissa North)
* 2018-10-29 [f2467d3](https://github.com/silverstripe/silverstripe-admin/commit/f2467d37241ca08b0cc4a112d1dc9054adf891a8) Fix search filtering and clearing (#687) (Luke Edwards)
* 2018-10-26 [3284bf48d](https://github.com/silverstripe/silverstripe-framework/commit/3284bf48d6e3da8b2b1a7831e2d7fe4b401e2fd6) Fix search filtering relations and clear filters (#8477) (Luke Edwards)
* 2018-10-22 [df86335](https://github.com/silverstripe/silverstripe-admin/commit/df863357b1413508f985f6f12a48f5d414a6d75f) Fix decimal search filter not showing up (bergice)
* 2018-10-20 [7f6f5c9ec](https://github.com/silverstripe/silverstripe-framework/commit/7f6f5c9ec9352172f37f8980d823e85c1c39062a) Flush extra methods cache on DataObjects after each unit test class has finished (Robbie Averill)
* 2018-10-19 [311fd62d9](https://github.com/silverstripe/silverstripe-framework/commit/311fd62d9527a47586d90a6f4e2c80922d15d44f) getExtensionInstance can return null, add a case to handle that (Robbie Averill)
* 2018-10-19 [a6855ec](https://github.com/silverstripe/silverstripe-admin/commit/a6855ecf02d41378b1ad03729a103d193aecd853) Remove deprecated help_link definition in testGetHelpLinks (Robbie Averill)
* 2018-10-19 [a28e2e183](https://github.com/silverstripe/silverstripe-framework/commit/a28e2e183e1d0684dd32bc7bcf72d4a9c573a8f4) Fix enum filter in Search component from adding `Any` as a filter (bergice)
* 2018-10-18 [e3d0bcb](https://github.com/silverstripe/silverstripe-admin/commit/e3d0bcb051e45d3e7b90dcdf6554e97d173ef6ce) Change one tab not all tabs (Raissa North)
* 2018-10-17 [87a5d07](https://github.com/silverstripe/silverstripe-admin/commit/87a5d07b9f22b894dd9f4397ff50868e662b79b2) Fix body overflow causing scroll bars (Loz Calver)
* 2018-10-16 [a3d611f](https://github.com/silverstripe/silverstripe-admin/commit/a3d611f0b7f6cd024783a7037245364237329375) Fix `ENTER` not triggering form save button (bergice)
* 2018-10-16 [c35e18110](https://github.com/silverstripe/silverstripe-framework/commit/c35e18110baa72756f2a5378b7e7d4d7803c7c33) Gridfield pagination detected as form change (Luke Edwards)
* 2018-10-16 [5d626fa](https://github.com/silverstripe/silverstripe-admin/commit/5d626fa53b6c67e586d6c6d4d19471709175e8f4) Dont track gridstate changes as form edits (Luke Edwards)
* 2018-10-16 [3d3c407](https://github.com/silverstripe/silverstripe-admin/commit/3d3c407caf099831af0e7b3a6320cad61e5801b0) Fix long gridfield actions overflowing (Luke Edwards)
* 2018-10-15 [ab259af](https://github.com/silverstripe/silverstripe-errorpage/commit/ab259af0707518f94561909c989617e155fd3b1b) Move phpcs to composer dependency, update Travis for it, add 7.2 to Travis (Robbie Averill)
* 2018-10-15 [ab0d7d9](https://github.com/silverstripe/silverstripe-versioned/commit/ab0d7d9e8c18b0e5ae6ee3e352317bbe0c70de53) Fix codesniffer runs in Travis (Robbie Averill)
* 2018-10-11 [0aa2d66](https://github.com/silverstripe/silverstripe-admin/commit/0aa2d6615b7f207791716b6e8654d16940597be4) Use correct lazy loadable class names for GridFieldLazyLoader (Robbie Averill)
* 2018-10-11 [ee21c4201](https://github.com/silverstripe/silverstripe-framework/commit/ee21c42011fd40b2065bb2acb868a427e2232d0a) Re-instate missing SS_DATABASE_SUFFIX functionality (fixes #7966) (Loz Calver)
* 2018-10-11 [4702a22](https://github.com/silverstripe/silverstripe-admin/commit/4702a223ed2c85cd8a55501351526648a70c41b7) Defensively programming some possible failure points (Guy Marriott)
* 2018-10-11 [0db2f84ad](https://github.com/silverstripe/silverstripe-framework/commit/0db2f84ade9b1e8e2811cd7c32bf5f3510544c74) Persist TinyMCE updates when writing with Behat (Guy Marriott)
* 2018-10-10 [e941a56](https://github.com/silverstripe/silverstripe-admin/commit/e941a56b9e918908f16712f2e7be1b43d5810062) Changing the value of a TinyMCE field will correctly trigger a change in the React component (Guy Marriott)
* 2018-10-09 [56d562193](https://github.com/silverstripe/silverstripe-framework/commit/56d56219345b4a8ba318261af98bcd62f3ce060d) Flush extra_methods statics between test runs (Robbie Averill)
* 2018-10-09 [d1281a571](https://github.com/silverstripe/silverstripe-framework/commit/d1281a571a56dca9d40b59a7baf31d32b09a37f5) Escape HTML in PHPDoc to fix API docs from rendering incorrectly (Robbie Averill)
* 2018-10-09 [522b288](https://github.com/silverstripe/silverstripe-admin/commit/522b28890e6b11b3a324a38c65199f96f86c4b2f) ModelAdmin pagination with a filter (Luke Edwards)
* 2018-10-08 [4766cae](https://github.com/silverstripe/silverstripe-admin/commit/4766cae7918e75c3b47d69487fecdb69b2993077) Retain polyfill for display block style in .collapse.show with Bootstrap 4.1.x (Robbie Averill)
* 2018-10-08 [6e649b57](https://github.com/silverstripe/silverstripe-cms/commit/6e649b570d70d83729527ea8fbbc069426f11338) CMSMain::duplicate() now checks canCreate() but not canEdit() (Robbie Averill)
* 2018-10-08 [c4788803e](https://github.com/silverstripe/silverstripe-framework/commit/c4788803ee7b903bc45541ccc0ef8446cf99922f) Remove unused cacheData prop from #8451 (Robbie Averill)
* 2018-10-08 [884a12c](https://github.com/silverstripe/silverstripe-admin/commit/884a12c864dd856a4beb8e636eb56c46be2dfa2e) Add fix for potential tabnabbing on community help links (Sacha Judd)
* 2018-10-08 [979dd38](https://github.com/silverstripe/silverstripe-assets/commit/979dd385947900b9df48928ad7ba4c2eb7a1361f) Fix migrating files with an incorrect class (Luke Edwards)
* 2018-10-08 [fdb53311b](https://github.com/silverstripe/silverstripe-framework/commit/fdb53311bac68bcee5f6b026c0f526c98ea1da65) Fix linting issue. (Maxime Rainville)
* 2018-10-08 [e06bb05](https://github.com/silverstripe/silverstripe-admin/commit/e06bb051d29c9aceb0d6863637bf038ae0715777) Ensure TinyMCE field changes are persisted before updating redux state (Guy Marriott)
* 2018-10-06 [8c7459a70](https://github.com/silverstripe/silverstripe-framework/commit/8c7459a7082ab3880202a3541bd11ed183465ef1) Fix CompositeField test that relied on a DropdownField bug (Sam Minnee)
* 2018-10-05 [e5d3b28a4](https://github.com/silverstripe/silverstripe-framework/commit/e5d3b28a4d10cb4d960897d37071246532ab8ebc) Dont break validation on selects without a source. (Sam Minnee)
* 2018-10-05 [98568262f](https://github.com/silverstripe/silverstripe-framework/commit/98568262f2c5d7cc9a9cd39af158d5df7dce12a7) Fixed phpcs violations (Robbie Averill)
* 2018-10-04 [fafd9dad6](https://github.com/silverstripe/silverstripe-framework/commit/fafd9dad6d60731c0ed6695a2df5535ea433632e) fixing name of constant ASSETS_PATH (Philipp Staender)
* 2018-10-04 [0fc06e51e](https://github.com/silverstripe/silverstripe-framework/commit/0fc06e51e5020b8959310682bade02f97653dc73) Drop seconds from DBDatetime::Nice() to restore SS3 behaviour. (Sam Minnee)
* 2018-10-03 [19af1ac](https://github.com/silverstripe/silverstripe-graphql/commit/19af1ac6d77089a5365ed9f1892306fdd943a2ca) Add codesniffer as a dev dependency and use it in Travis (Robbie Averill)
* 2018-10-03 [4668fab](https://github.com/silverstripe/silverstripe-assets/commit/4668fabc3a02d996a0a9be13245cf3ab3fba1079) Shortcode provider does not always request a protected asset grant, add tests for FlysystemAssetStore (Robbie Averill)
* 2018-10-03 [ce9496d](https://github.com/silverstripe/silverstripe-admin/commit/ce9496d2b9bcda50a4e74c386d31bac7c4dc0939) Quote injector alias references, deprecated and removed support for in Symfony 4 (Robbie Averill)
* 2018-10-03 [d535e71](https://github.com/silverstripe/silverstripe-graphql/commit/d535e71ef81165ac6e02c8b27b1e577b3a291b65) Quote injector alias references, deprecated and removed support for in Symfony 4 (Robbie Averill)
* 2018-10-03 [4740346ed](https://github.com/silverstripe/silverstripe-framework/commit/4740346ed8766549f0f948a4396954227f2494bb) Make ArrayList::limit() consistent with DataList::limit() (Sam Minnee)
* 2018-10-03 [0cc72c91a](https://github.com/silverstripe/silverstripe-framework/commit/0cc72c91ada58d6927dab6e93bfe785b623f3e7a) Use DELETE FROM instead of TRUNCATE for clearTable (Sam Minnee)
* 2018-10-02 [5970fc241](https://github.com/silverstripe/silverstripe-framework/commit/5970fc2417bcd29cfc85c209117b7ed6625141ad) Moving test to correct director (Guy Marriott)
* 2018-10-02 [79c2b5ad4](https://github.com/silverstripe/silverstripe-framework/commit/79c2b5ad427f4e95c8fb51b46c4ba31cdf2997c1) Use DELETE FROM instead of TRUNCATE for clearTable (Sam Minnee)
* 2018-10-01 [f2cbc1dfb](https://github.com/silverstripe/silverstripe-framework/commit/f2cbc1dfbb8b1972eeb72d230f7b5cc2ebad26ee) Dont use USE_FRM in MySQL repair. Fixes #6300. (Sam Minnee)
* 2018-10-01 [638e6ec28](https://github.com/silverstripe/silverstripe-framework/commit/638e6ec2814b4b4cbabd0adc0e166c4812b94740) Throw deprecation notice on limit=0 (Sam Minnee)
* 2018-10-01 [ad87890b2](https://github.com/silverstripe/silverstripe-framework/commit/ad87890b2e92f3f4092bbf9a70ab0d439d40ce31) Dont change state in ArrayList::getIterator() (Sam Minnee)
* 2018-10-01 [63cabc7](https://github.com/silverstripe/silverstripe-assets/commit/63cabc7fc84f295a95e88f2ce37f940b61b97223) Keep folder Name and Title in sync on update (Luke Edwards)
* 2018-10-01 [5c7b0da](https://github.com/silverstripe/silverstripe-admin/commit/5c7b0da18d894d32f3884fcd2f4e18e8ccd7b629) Searching now allows + symbols, use own method over jQuery serialisation (Robbie Averill)
* 2018-10-01 [71dad5f68](https://github.com/silverstripe/silverstripe-framework/commit/71dad5f68518b9052b657c8dc70d4581fb771e98) Append any fields that dont match name in insertBefore/insertAfter (Sam Minnee)
* 2018-10-01 [b0c4c5a1](https://github.com/silverstripe/silverstripe-cms/commit/b0c4c5a1775c95e1abd878f233e78b009f5d01ec) Updating SiteTree search fields to work with new search namespacing (Guy Marriott)
* 2018-10-01 [81292c5](https://github.com/silverstripe/silverstripe-asset-admin/commit/81292c52f04690349ea8d3634398faeda2190f8d) Fix outdated data in Apollo GraphQL cache when deleting/moving files (bergice)
* 2018-09-28 [ac1fe5e9d](https://github.com/silverstripe/silverstripe-framework/commit/ac1fe5e9d5de92dbdd7c03c187471fe6b5d8d7c0) joinClass's default_sort is used when nothing else has been set already (Robbie Averill)
* 2018-09-27 [fa4e031](https://github.com/silverstripe/silverstripe-admin/commit/fa4e031ef961215653e315ea441059c6945e5e3b) Update field names in Behat tests for new namespaces (Robbie Averill)
* 2018-09-27 [44b92c90](https://github.com/silverstripe/silverstripe-cms/commit/44b92c90bc18e629a73584a2a2eb0db8a02d740a) Update field names in Behat tests for new search form namespacing (Robbie Averill)
* 2018-09-27 [c54e7317d](https://github.com/silverstripe/silverstripe-framework/commit/c54e7317d2016727b1e2083996fc925fe862e9ab) Avoid having search fields with the same names as form elements (Guy Marriott)
* 2018-09-27 [2e41ea8](https://github.com/silverstripe/silverstripe-admin/commit/2e41ea83b95509ba1f68cf895c49fd846ec15841) Avoid having search fields with the same name as form elements (Guy Marriott)
* 2018-09-25 [dc59bd8](https://github.com/silverstripe/silverstripe-versioned/commit/dc59bd8e5613442b214f951ca16ec376e1ee1cda) Published GraphQL field now correctly indicates whether the record's version is published (Robbie Averill)
* 2018-09-25 [05b372c](https://github.com/silverstripe/silverstripe-versioned/commit/05b372c85f6720c931ac5dceba5fd05a84c74482) Use Hierarchy::prepopulateTreeDataCache() in CMS. (#183) (Sam Minnée)
* 2018-09-25 [5bfc37ff](https://github.com/silverstripe/silverstripe-cms/commit/5bfc37ff4bd22e8bbc02dc4f6dae59d25a4d5e67) Use Hierarchy::prepopulateTreeDataCache() in CMS (#2266) (Sam Minnée)
* 2018-09-24 [0276f6c08](https://github.com/silverstripe/silverstripe-framework/commit/0276f6c089ff5557a36eaf7367c8fc75fc6af20c) Revert semver break in adding GridField type hint to method signature (Robbie Averill)
* 2018-09-24 [f76fb26](https://github.com/silverstripe/silverstripe-asset-admin/commit/f76fb269b3450077623045ce84726dfd60f92894) fix psr (Thomas Portelange)
* 2018-09-24 [5e069ec](https://github.com/silverstripe/silverstripe-versioned/commit/5e069ec85c3b3cb74054b5cc18012531cfe22ce6) fix inferReciprocalComponent called on unsaved (Thomas Portelange)
* 2018-09-24 [9b5425d](https://github.com/silverstripe/silverstripe-graphql/commit/9b5425d5ba8a25eb799743e62733c57eb2837175) Incorrect parameter order of (Guy Marriott)
* 2018-09-24 [a2bb70c46](https://github.com/silverstripe/silverstripe-framework/commit/a2bb70c46dec00a6c9164bcc134e3fdc64a452e9) Don't flush manifests in tests by default (Ingo Schommer)
* 2018-09-20 [9a89aad](https://github.com/silverstripe/silverstripe-admin/commit/9a89aad5df0d5c67a5575a7e20d723cf9d6c4d95) Whitelist nonce parameters from JS resources to be loaded. (Luke Edwards)
* 2018-09-20 [16b3d18](https://github.com/silverstripe/silverstripe-assets/commit/16b3d18ebdf8896ed216faf2a33daa729c6b2c09) FlysystemAssetStore::getAsURL() only grant for protected filesystems (Christopher Darling)
* 2018-09-20 [a9b2443](https://github.com/silverstripe/silverstripe-admin/commit/a9b244349435028c7b55b30475a9fe4d50207fc1) Revert changes to default dropdownToggleClassNames on ActionMenu (Sacha Judd)
* 2018-09-19 [b98c87a6c](https://github.com/silverstripe/silverstripe-framework/commit/b98c87a6c51baa6696ef9f077775f633c4c5ecd4) Ensure existing session can be accessed if headers_sent() (Sam Minnee)
* 2018-09-17 [d597166](https://github.com/silverstripe/silverstripe-versioned/commit/d5971661cec5fdf12dbfe895fad08ce6bbb05e25) Performance optimisation for draft pages in treeview (Sam Minnee)
* 2018-09-12 [41c0b8fb](https://github.com/silverstripe/silverstripe-cms/commit/41c0b8fb85b7ac11f18eb1813f9e063e13cbafa2) Fix 'Insert links into a page' test (Luke Edwards)
* 2018-09-10 [fb0d81d](https://github.com/silverstripe/silverstripe-admin/commit/fb0d81d6c02bff292e69a0dcd42e3e01be728c01) Remove action menu toggle styles (Sacha Judd)
* 2018-09-04 [fbd8843](https://github.com/silverstripe/silverstripe-assets/commit/fbd88434cfc89eac7d75e34cdcc48f97821198ff) Remove unnecessary UploadTest\Validator (Sam Minnee)
* 2018-09-04 [40c7a0a](https://github.com/silverstripe/silverstripe-assets/commit/40c7a0aac6390237515cd30d9b23de8e7ad0f5ba) Better error message for invalid upload (Sam Minnee)
* 2018-09-03 [641208dc](https://github.com/silverstripe/silverstripe-siteconfig/commit/641208dcd29a7afaf70c3100a1b02a0bd149b667) Text collector translations now compile without errors (Robbie Averill)
* 2018-09-03 [225445931](https://github.com/silverstripe/silverstripe-framework/commit/22544593101ce670a809f3b354f5ff850840006b) Text collector translations now compile without errors (Robbie Averill)
* 2018-08-31 [f5869a5](https://github.com/silverstripe/silverstripe-campaign-admin/commit/f5869a56ba1c34568d424a2cf71000abfa0ef206) Do not render view mode toggle on campaign toolbar if the campaign is empty (bergice)
* 2018-08-30 [5488b31](https://github.com/silverstripe/silverstripe-admin/commit/5488b31f84f47a18d40cf34d7da53d498b169496) Add explicit `0` z-index to `cms-content` so the menu toggle can render above it (#620) (Andre Kiste)
* 2018-08-30 [463fdef](https://github.com/silverstripe/silverstripe-admin/commit/463fdefde0f827d783f9a519f261d1e4d35c01ab) Remove "more" action icon size, add btn-sm and fix icon alignment in gridfield (Sacha Judd)
* 2018-08-28 [dbfc25302](https://github.com/silverstripe/silverstripe-framework/commit/dbfc253021bce3997af0934b9015215047bbac7b) Fix incorrect version number in 4.3.0 changelog (Loz Calver)
* 2018-08-28 [d1951c94](https://github.com/silverstripe/silverstripe-cms/commit/d1951c946fe143e79ea6a7e1ee55ae90586c8a33) Sort history viewer versions in descending order (Robbie Averill)
* 2018-08-28 [10ef38f](https://github.com/silverstripe/silverstripe-campaign-admin/commit/10ef38f039fa4bf29be764ffd99196b0f9b62554) Hide 1px left border in preview component if we are in 'Preview Only' mode (bergice)
* 2018-08-27 [2ab622f](https://github.com/silverstripe/silverstripe-admin/commit/2ab622f88a5a179a7186b46699055cf761d3b749) Fix Add mock store to the loadComponent AppolloProvider (Maxime Rainville)
* 2018-08-24 [e196475](https://github.com/silverstripe/silverstripe-assets/commit/e196475220e1b97cc61f8e026b55984d2e240e0d) Graceful validation of image shortcode (Aaron Carlino)
* 2018-08-24 [2b16e2a](https://github.com/silverstripe/silverstripe-admin/commit/2b16e2afbaa322ef4ab2fce6c2add9b8f5596ba4) GridField delete button to offer archive action if possible (#602) (Luke Edwards)
* 2018-08-24 [6164d01d6](https://github.com/silverstripe/silverstripe-framework/commit/6164d01d65648ce6b25a7ef82fabaa10b81565d0) GridField delete button to offer archive action if possible (#8325) (Luke Edwards)
* 2018-08-22 [1b67bb08c](https://github.com/silverstripe/silverstripe-framework/commit/1b67bb08c8b61ad7e5324ef07eaea2834772b818) Fix failing HTML button test step (Luke Edwards)
* 2018-08-17 [160d595e2](https://github.com/silverstripe/silverstripe-framework/commit/160d595e226edcbaa64a47a0be74193a8b8058cc) fix trailing whitespace (maks)
* 2018-08-17 [16217f365](https://github.com/silverstripe/silverstripe-framework/commit/16217f3655c28ddcf6a721bca82d45d65b91e3ed) fix accidentaly deleted comma (maks)
* 2018-08-16 [61c046c](https://github.com/silverstripe/silverstripe-versioned/commit/61c046c9cd6ce97456b6123a05438c7cd05d07cc) If archive's possible switch GridField delete button with archive (Luke Edwards)
* 2018-08-15 [d9154bffb](https://github.com/silverstripe/silverstripe-framework/commit/d9154bffbf7b0031e5bd3ed1f68db3fae6ab5959) text/json is not a valid mimetype (Daniel Hensby)
* 2018-08-15 [d18b5ee](https://github.com/silverstripe/silverstripe-campaign-admin/commit/d18b5eed63e081a4cbcbb30edcf51839a2ae3461) text/json is not a valid mimetype (Daniel Hensby)
* 2018-08-15 [41a2a0c](https://github.com/silverstripe/silverstripe-admin/commit/41a2a0c38c073d82b96fd4fa2fa09bea3b556aa5) text/json is not a mimetype (Daniel Hensby)
* 2018-08-14 [fcaa9ba](https://github.com/silverstripe/silverstripe-versioned/commit/fcaa9ba7a68a839d84ff23d32275c510d0f9890e) Restore and archive action improvements (Luke Edwards)
* 2018-08-14 [fc7f712](https://github.com/silverstripe/silverstripe-admin/commit/fc7f7120a67ce95b03f13b4c8fb90b36f810f7b1) Modal response animation appearing outside the modal (#601) (Luke Edwards)
* 2018-08-07 [c2b54c7](https://github.com/silverstripe/silverstripe-admin/commit/c2b54c72a990df9d453bedc307f613f23107bfad) graphql route getting overwritten (Aaron Carlino)
* 2018-08-06 [e7cb0156](https://github.com/silverstripe/silverstripe-cms/commit/e7cb0156c69a3701b248dbbae4e72f8c0b372efd) Use LatestDraftVersion in GraphQL query to determine latest draft version (Robbie Averill)
* 2018-08-06 [13372f9a3](https://github.com/silverstripe/silverstripe-framework/commit/13372f9a37d1cb19f658404c79c2be6fbfa557b1) Installer redirect to home/ (without domain) (Michael Strong)
* 2018-08-02 [24927c5](https://github.com/silverstripe/silverstripe-campaign-admin/commit/24927c5aa18f9adc8ef79f0adf879f6bcd5c130c) Ensure only toolbar buttons that are immediate descendants of toolbars are given margins (Robbie Averill)
* 2018-08-01 [a981584](https://github.com/silverstripe/silverstripe-admin/commit/a9815845c0e923587fa81bdbac77be43f6d4dd1a) Remove rogue CSS margin on toolbar buttons. Implemented in campaign-admin preview instead. (Robbie Averill)
* 2018-08-01 [405d8a3](https://github.com/silverstripe/silverstripe-campaign-admin/commit/405d8a3213852f4a35e7cf8101df72c206d1b2f9) Toolbar button margins are constrained to campaign previews, and update ViewModeActions name (Robbie Averill)
* 2018-08-01 [6889a1a](https://github.com/silverstripe/silverstripe-admin/commit/6889a1adf0005e91288c4e2ddc6a2f3ea1b6b593) ViewModeToggle now uses BEM class naming convention (Robbie Averill)
* 2018-07-30 [420c3f8](https://github.com/silverstripe/silverstripe-asset-admin/commit/420c3f807d7426af2c76d778faa4ef26ab5dda11) Editor should ignore drag-and-drop files (#814) (Luke Edwards)
* 2018-07-30 [fde7b9ddc](https://github.com/silverstripe/silverstripe-framework/commit/fde7b9ddc5da697395897249819b3c52530692b6) Specify minimum composer version (Maxime Rainville)
* 2018-07-27 [67254da1](https://github.com/silverstripe/silverstripe-reports/commit/67254da18599f0fe86921098524ec3303d9de41e) Apply missing class to report header. (Maxime Rainville)
* 2018-07-26 [900ca9c8d](https://github.com/silverstripe/silverstripe-framework/commit/900ca9c8d75b70b13b425365022ec3f1f0ebe461) Recommend install of upgrader with PHAR exec. (Maxime Rainville)
* 2018-07-25 [0035f4a90](https://github.com/silverstripe/silverstripe-framework/commit/0035f4a90728d9e109b12585b32491a2afeaa916) Fix backtick in changelog breaking sentence formatting (Michal Kleiner)
* 2018-07-13 [d1024ee](https://github.com/silverstripe/silverstripe-assets/commit/d1024ee00b12c3a212fe12d168f4521e2188274b) Add HTMLFragment casting to $Tag (#148) (Jake Bentvelzen)
* 2018-07-12 [a8e5616](https://github.com/silverstripe/silverstripe-admin/commit/a8e56166f01ef0fb8ecd46edb4eddde30447cdb0) Update GridField.js so it works with new Archive View (#559) (Luke Edwards)
* 2018-07-12 [599a4420b](https://github.com/silverstripe/silverstripe-framework/commit/599a4420bf0d982343faa6145afaf6592566bb40) Improve GridFieldViewButton to work with new Archive Admin (#8240) (Luke Edwards)
* 2018-07-11 [c2347310](https://github.com/silverstripe/silverstripe-cms/commit/c23473103e4f93b82d60f1260923c9e413d02c41) URLSegment field styling fixes #2193 (Maxime Rainville)
* 2018-07-05 [91068c23b](https://github.com/silverstripe/silverstripe-framework/commit/91068c23b5cb448fe63ae9f40875a8f0818dbe1f) Make column query not distinct (Al Twohill)
* 2018-07-05 [730fc42](https://github.com/silverstripe/silverstripe-admin/commit/730fc42ef3e6d25345516f1583d05bf968bf762c) Fix test for password recovery (Ingo Schommer)
* 2018-07-01 [3262665b2](https://github.com/silverstripe/silverstripe-framework/commit/3262665b2d6bb6d56186be2e2e370b853f13c6b5) Fix link and turn of phrase. (Maxime Rainville)
* 2018-06-29 [cc9b36e01](https://github.com/silverstripe/silverstripe-framework/commit/cc9b36e01124349ea7ccbd0902d1b01c764f82f7) fix link (Lukas)
* 2018-06-22 [f9de357](https://github.com/silverstripe/silverstripe-admin/commit/f9de35724c4fb75c8a6d38e4f5b9185531fc961c) - Grid field headers misaligned (Petar Simic)
* 2018-06-18 [2f1c2992f](https://github.com/silverstripe/silverstripe-framework/commit/2f1c2992f8f61b4a87a0a363db289a19ac5a821b) Default cache state should be `no-cache` (Daniel Hensby)
* 2018-06-17 [25b0a18](https://github.com/silverstripe/silverstripe-admin/commit/25b0a18a743e81951a2d2df387b6e5442d0253c3) Fix display of GridField link existing button (Luke Edwards)
* 2018-06-15 [5e4ad34](https://github.com/silverstripe/silverstripe-installer/commit/5e4ad341622565cc998bd8537ad3ec7a6a6a7913) Fix incorrect base recipe dependency (Damian Mooyman)

View File

@ -0,0 +1,9 @@
# 4.3.1
<!--- Changes below this line will be automatically regenerated -->
## Change Log
### Security
* 2018-12-18 [c0338d191](https://github.com/silverstripe/silverstripe-framework/commit/c0338d191d8be0000ddb16b74832ed8e05ba7ff5) Fix potential SQL vulnerability in non-scalar value hyrdation (Maxime Rainville) - See [ss-2018-021](https://www.silverstripe.org/download/security-releases/ss-2018-021)

View File

@ -0,0 +1,101 @@
# 4.3.2
<!--- Changes below this line will be automatically regenerated -->
## Change Log
### Security
* 2018-12-18 [c0338d191](https://github.com/silverstripe/silverstripe-framework/commit/c0338d191d8be0000ddb16b74832ed8e05ba7ff5) Fix potential SQL vulnerability in non-scalar value hyrdation (Maxime Rainville) - See [ss-2018-021](https://www.silverstripe.org/download/security-releases/ss-2018-021)
### Features and Enhancements
* 2019-02-03 [9bd9fc5](https://github.com/silverstripe/silverstripe-admin/commit/9bd9fc5e441943fcdf77a8e7e93ee27a5382307a) Add getter for ModelAdmin::$modelClass (jcarter)
* 2018-11-21 [d368140](https://github.com/silverstripe/silverstripe-admin/commit/d36814030f893a85b3ff34777335f121a7650add) Allow state to be passed back on grid field actions (Guy Marriott)
* 2018-11-19 [05786a2](https://github.com/silverstripe/silverstripe-versioned/commit/05786a26e2d5e7a5b5305e11d8da8be2516a7c44) Adding tests for new Rollback mutation scaffolder (Guy Marriott)
* 2018-11-18 [07c7be8](https://github.com/silverstripe/silverstripe-versioned/commit/07c7be81d52a702cf3ba90a657fdb9328517218a) Adding test for argument on ChangeSet::publish (Guy Marriott)
* 2018-11-14 [d2aed1e](https://github.com/silverstripe/silverstripe-versioned/commit/d2aed1ed8a95ed61e754564da52a7b31a82a9a43) Adding GraphQL operation scaffolder for rollbackRecursive on versioned objects (Guy Marriott)
* 2018-07-16 [9270206c](https://github.com/silverstripe/silverstripe-reports/commit/9270206c3bd2fe35bb263ad43ad3a5d87360873a) Use Injector to create new class instances and pass $params (Robbie Averill)
### Bugfixes
* 2019-02-27 [b59aeaf](https://github.com/silverstripe/silverstripe-framework/commit/b59aeaf80217a3e25877dcb6da97e546080c9972) Renable the ability to do dynamic assignment with DBField #8815
* 2019-02-12 [0ac43ce02](https://github.com/silverstripe/silverstripe-framework/commit/0ac43ce025274345c80773b57a09a48a8fa70a08) Caching the result of counting a foreign list for performance (Guy Marriott)
* 2019-02-04 [bb78b79](https://github.com/silverstripe/silverstripe-versioned-admin/commit/bb78b7930292db5c8ed660744f49ec1b94046211) fix unit test failing. Due to missing implements TestOnly in ViewProviderVersionedObject (Ishan Jayamanne)
* 2019-02-01 [26e53ad](https://github.com/silverstripe/silverstripe-campaign-admin/commit/26e53adc2288996a1e1f45631339485fbd2e2e8d) Campaign intro screen is now included in the scrollable content area (Robbie Averill)
* 2019-02-01 [8a2b65d](https://github.com/silverstripe/silverstripe-admin/commit/8a2b65d2b93f3a999129004ca2f208c6ce3d891c) Use $link-color instead of $brand-primary or active ListGroupItems (Robbie Averill)
* 2019-01-31 [cda9eef](https://github.com/silverstripe/silverstripe-assets/commit/cda9eef992b70fd46377be6d4459260d31ea2215) Fix for issue #212 (Joe Madden)
* 2019-01-31 [f9763bb](https://github.com/silverstripe/silverstripe-versioned/commit/f9763bb68c1ef1e1555c57d0df27b9a0b161dba2) ReadOneExtensionTest now uses a test database since it performs writes (Robbie Averill)
* 2019-01-31 [e22aa5d](https://github.com/silverstripe/silverstripe-graphql/commit/e22aa5da881f18ba3439e1e58c607ce446c04892) Fall back to checking a singleton for canView() if no specific item exists (Robbie Averill)
* 2019-01-31 [9d5d4d2](https://github.com/silverstripe/silverstripe-graphql/commit/9d5d4d2918f059429550cc78d21fa44483781241) Correct string matching assertion for "No database selected" exception message (Robbie Averill)
* 2019-01-30 [ca0a898](https://github.com/silverstripe/silverstripe-graphql/commit/ca0a898b6b0ef993c14f4d53d2a467d97e27c103) Check readOne permissions against resolved DataObject (Guy Marriott)
* 2019-01-29 [5178f3f](https://github.com/silverstripe/silverstripe-graphql/commit/5178f3f1d3a16c6a5028022f32566c4b6cfdb41c) Update GraphQL controller flushing to allow failure when building DB for the first time (Guy Marriott)
* 2019-01-25 [0797ab7](https://github.com/silverstripe/silverstripe-asset-admin/commit/0797ab7762a4a64f2dc89c754a6bb04216b96fe1) Fix GraphQL FolderTypeCreator::resolveChildrenConnection on PostgreSQL (#901) (Serge Latyntcev)
* 2019-01-24 [e087e564e](https://github.com/silverstripe/silverstripe-framework/commit/e087e564e13d7c30b62850a271131684a619789e) Fix tests not loading fixtures and loading themes in CWP recipe kitchen sink (Robbie Averill)
* 2019-01-17 [9ced2347](https://github.com/silverstripe/silverstripe-cms/commit/9ced23473f2f102de5b6d828d093be1102f8d570) Don't convert datalist to arraylist when filtering pages (Damian Mooyman)
* 2019-01-15 [f6f1ba8](https://github.com/silverstripe/silverstripe-admin/commit/f6f1ba8fd5870cb7ef4410aba6cbcd9a0e32f638) Use lighter colour for placeholders (fixes #805) (Loz Calver)
* 2019-01-14 [cccfc62](https://github.com/silverstripe/silverstripe-admin/commit/cccfc621da171bfe378fc5547611d59484af246b) Fix reauthenticate behat tests (Serge Latyntcev)
* 2019-01-13 [5c3b95ac](https://github.com/silverstripe/silverstripe-cms/commit/5c3b95ac8977f77e7d95d4da6333ca12b5ef465f) Multibyte URL routing (Ingo Schommer)
* 2019-01-11 [2cb49ea](https://github.com/silverstripe/silverstripe-versioned/commit/2cb49ea79d6babb80289f170dc6102c82f5b0d69) [Warning] on count() with PHP &gt;= 7.2 (Lukas)
* 2019-01-11 [57d2949](https://github.com/silverstripe/silverstripe-admin/commit/57d294912e7b2d61e40c4656c63bd6569dcfcd27) Forwarding action state in another situation where it was not being forwarded (Guy Marriott)
* 2019-01-08 [c43f4e0](https://github.com/silverstripe/silverstripe-graphql/commit/c43f4e0708fd86e8078ef3326d963005d626baf4) Ensure queries are sorted before limiting (Damian Mooyman)
* 2018-12-19 [5f2efa1](https://github.com/silverstripe/silverstripe-admin/commit/5f2efa1f7bf38a9f990bf8f9600fd37f50bec4f0) Fix behat reauthenticate tests wording and remove (Serge Latyntcev)
* 2018-12-18 [3d841a4](https://github.com/silverstripe/silverstripe-installer/commit/3d841a409c43752a8192afea5ebc48327e2ac3d3) / Behat tests (Serge Latyntcev)
* 2018-12-13 [c4a0d5f0](https://github.com/silverstripe/silverstripe-cms/commit/c4a0d5f0831f0f27022905700a0ffb86cc56aceb) Publish button text merge regression (Aaron Carlino)
* 2018-12-10 [f7846fc34](https://github.com/silverstripe/silverstripe-framework/commit/f7846fc34c4c04bfa3ae57a552345f6a536856c5) behat CmsUiContext waits for cms-loading-container after step (Serge Latyntcev)
* 2018-12-10 [7b1a0a4](https://github.com/silverstripe/silverstripe-admin/commit/7b1a0a4d18dcd89fe699103355e9f853b6245bff) Ensure the container exists before unmounting React/removing it (#771) (Guy Marriott)
* 2018-12-10 [9fce4b2](https://github.com/silverstripe/silverstripe-graphql/commit/9fce4b2408dd82d303925eee3b6cf393da371e85) Ensure httpMethod context is applied to all controller actions (#194) (Aaron Carlino)
* 2018-12-07 [6a716b19](https://github.com/silverstripe/silverstripe-cms/commit/6a716b1906d290dca98f1ffc672edc342d8265d3) Show loading indicator when performing secondary CMS actions (Robbie Averill)
* 2018-12-05 [7b0e9ec](https://github.com/silverstripe/silverstripe-admin/commit/7b0e9ec97a987a6106fb7e72c10ebcb347724821) Only validate fields that are found in schema #716 (bergice)
* 2018-12-05 [3c9f12a](https://github.com/silverstripe/silverstripe-admin/commit/3c9f12afb1d988c97c6536bd007894de084959a8) Firefox sticky toggle misalignment and rotate help menu caret (Sacha Judd)
* 2018-12-05 [747a60a](https://github.com/silverstripe/silverstripe-admin/commit/747a60afe45d6aeafd352ed93c0c58c6290344b6) / behat reauthenticate features wait for rendering (Serge Latyntcev)
* 2018-12-04 [f2c3d1d](https://github.com/silverstripe/silverstripe-admin/commit/f2c3d1db71fab14fac54523961dab084599bd7f7) / Permission checkboxes toggle before changetracker gets to them (Serge Latyntcev)
* 2018-12-04 [e624376](https://github.com/silverstripe/silverstripe-admin/commit/e624376dc6ea354dd271bcbee285d28b6d123f95) Always continue loading scripts even if one script fails. (bergice)
* 2018-12-03 [17710cc](https://github.com/silverstripe/silverstripe-asset-admin/commit/17710cc242e2ca37cf4422f7606de61cbc6b04e9) Fix issue where `UploadField` thinks it has uploaded the max number of files when uploading multiple files simultaneously (bergice)
* 2018-12-03 [9a78bae](https://github.com/silverstripe/silverstripe-asset-admin/commit/9a78bae50938af5adc7e1b0d2467892bc55f32e3) Ensure max files uploadable stays the same while uploading multiple files so that the user can upload the correct max number of files (bergice)
* 2018-12-02 [602597b9](https://github.com/silverstripe/silverstripe-cms/commit/602597b9004ad56c02a2bf2f779770ff32afea8f) CMSPageHistoryControllerTest now uses a stub controller to avoid URL conflicts with versioned-admin (Robbie Averill)
* 2018-12-02 [0103f8d](https://github.com/silverstripe/silverstripe-admin/commit/0103f8d98d6b394a8d4117c04787d6a69d3c8af1) "Clear" button in PopoverOptionSet search now has no right margin (Robbie Averill)
* 2018-11-30 [e5273bb](https://github.com/silverstripe/silverstripe-asset-admin/commit/e5273bb557dfb8d0dde8870fde3709180724f85e) Fix sorting (Aaron Carlino)
* 2018-11-30 [4a9dbd0](https://github.com/silverstripe/silverstripe-asset-admin/commit/4a9dbd0a37111b03c1a333682ed3dee066e4fc3f) Fix pagination (Aaron Carlino)
* 2018-11-30 [c809674](https://github.com/silverstripe/silverstripe-admin/commit/c809674a780a22073268c12752641bdebfad5ba3) Missed a JSON.stringify (Guy Marriott)
* 2018-11-30 [cd54aa7](https://github.com/silverstripe/silverstripe-admin/commit/cd54aa72e0cf36316deec19d1be6b90358308f8f) Fix new page form clearing when selecting a `Under another page` option (bergice)
* 2018-11-29 [836fc6d](https://github.com/silverstripe/silverstripe-versioned-admin/commit/836fc6d6216f0725108b6f5d5582d7ecaee54272) Revert promise is now delayed until the GraphQL mutation resolves, added loading indicator (Robbie Averill)
* 2018-11-29 [59221e8](https://github.com/silverstripe/silverstripe-assets/commit/59221e8d74ac5e07b86a741e2709e0676130f7b4) Cache key cannot contain : chars, will happen when viewing from archive (Robbie Averill)
* 2018-11-29 [4c50dd9](https://github.com/silverstripe/silverstripe-asset-admin/commit/4c50dd95ae252be32666932ee7ee79f5ce793583) Ensure list is limited appropriately before evaluating (Aaron Carlino)
* 2018-11-28 [bba3fc9](https://github.com/silverstripe/silverstripe-admin/commit/bba3fc96b3e5d50eeaeee2edf10f67ee5a8c8204) Fix undefined index error in PopoverOptionSet tests for missing onClick handler (Robbie Averill)
* 2018-11-28 [a85b7cd](https://github.com/silverstripe/silverstripe-admin/commit/a85b7cd5bee87573a1588cd0ac8f33785614a292) Ensure TinyMCE is correctly instantiated and removed when dom matching changes (Robbie Averill)
* 2018-11-28 [0bb22732](https://github.com/silverstripe/silverstripe-cms/commit/0bb22732c920f1a4a2354582e36310558aa6bebb) Unsaved SiteTree records now return an empty list for DependentPages (Robbie Averill)
* 2018-11-28 [e54cdba](https://github.com/silverstripe/silverstripe-versioned-admin/commit/e54cdba9669da301475f1139dc7425c395948a11) HistoryViewer container is now padded and scrollable unless "isInGridField" is true (Robbie Averill)
* 2018-11-28 [c7b5b1efd](https://github.com/silverstripe/silverstripe-framework/commit/c7b5b1efd2e37cc0b5f732f461bd37fafe14bd83) Switching to use Controller::curr as it was using previously (Guy Marriott)
* 2018-11-28 [2fda791](https://github.com/silverstripe/silverstripe-admin/commit/2fda7917603206c3f911db118e080c1af336c322) Alternate GridFeild action state store supports dropdown actions (Guy Marriott)
* 2018-11-27 [9566934](https://github.com/silverstripe/silverstripe-asset-admin/commit/9566934d7030f7b7d296cfd0c1491237ad369839) Internal forms should specify custom overflow rules (#885) (Guy Marriott)
* 2018-11-27 [294b08e](https://github.com/silverstripe/silverstripe-admin/commit/294b08e9ceac5894fdb6ffabd4e5320d4927712f) Readding ARCHIVECONFIRMMESSAGE (Maxime Rainville)
* 2018-11-26 [0f34aa4](https://github.com/silverstripe/silverstripe-versioned-admin/commit/0f34aa40436333d2c82fd1ed0d71cd267c8ff172) HistoryViewerField::getPreviewEnabled() can now be adjusted with extensions (Robbie Averill)
* 2018-11-25 [41dc9229b](https://github.com/silverstripe/silverstripe-framework/commit/41dc9229bf6823262bbc4c25edf0da61cb08b260) Reverting ExtensionTestState and Extensible extra methods modifications to prevent PHP 5.6 segfault (#8581) (Robbie Averill)
* 2018-11-23 [ffe0f31](https://github.com/silverstripe/silverstripe-admin/commit/ffe0f312345bbe0f2f5e5e5fcb5f1e01817a42e7) Removing unnecessary overflow on fieldsets (Guy Marriott)
* 2018-11-23 [309dc97a](https://github.com/silverstripe/silverstripe-cms/commit/309dc97a325925b99e297343f5f76c71a33286e5) Pages should use rollback recursive to revert to previous versions (#2329) (Guy Marriott)
* 2018-11-22 [24d303a](https://github.com/silverstripe/silverstripe-admin/commit/24d303acfb072043a864fd0ae71cd3a024fb137a) Search filters and header inputs support the attribute store method for action state (Guy Marriott)
* 2018-11-21 [9ce6d91b7](https://github.com/silverstripe/silverstripe-framework/commit/9ce6d91b76e525a6fc81e02023e9e53cdf82e047) / TreeMultiselectField::objectForKey handles list of IDs correctly (Serge Latyntcev)
* 2018-11-21 [b4c8f699e](https://github.com/silverstripe/silverstripe-framework/commit/b4c8f699eb4dd75089100c1b180cd0df25146206) Provide alternatives to session for storing GridField_FormAction state (Guy Marriott)
* 2018-11-20 [d74af1c17](https://github.com/silverstripe/silverstripe-framework/commit/d74af1c17e0b72ae119abc00fd7ef0aca6dd4498) Explicity mark nodes when searching nodes in TreeDropdownField #8621 (Maxime Rainville)
* 2018-11-19 [d05e142](https://github.com/silverstripe/silverstripe-admin/commit/d05e1424ee1d7217474d7d16f8bdc4e289dd2700) Correct the entwine match for the loading animation (Maxime Rainville)
* 2018-11-16 [35c3a8c6](https://github.com/silverstripe/silverstripe-cms/commit/35c3a8c68db2660838dcd2ae5abd2bd1c3214af4) 'Search' text in default search form should be a placeholder (Robbie Averill)
* 2018-11-16 [f63e368](https://github.com/silverstripe/silverstripe-admin/commit/f63e368cb0aba1964327b853631a67ba2af5db09) Change Tag class to avoid conflict with taxanomie (Maxime Rainville)
* 2018-11-16 [4896004b](https://github.com/silverstripe/silverstripe-cms/commit/4896004b504e1ac2b0a32c0b94f96a9cd0019dc9) reverting to publish still shows draft changees (micmania1)
* 2018-11-15 [b5bae137b](https://github.com/silverstripe/silverstripe-framework/commit/b5bae137bd341eeda3f4886f45fc8f8d657a9c4c) Redirect loop with multiple confirmation tokens present (fixes #8607) (Loz Calver)
* 2018-11-14 [7d1d6d0f7](https://github.com/silverstripe/silverstripe-framework/commit/7d1d6d0f7b228b862807e7c2d67d55220e6d90b8) Ensure that tests setting passwords have stubbed configuration (Robbie Averill)
* 2018-11-13 [3ae43cd](https://github.com/silverstripe/silverstripe-versioned-admin/commit/3ae43cd25727711cced28d7607b8a947fdb5c986) Ensure HistoryViewerController has priority over CMSPageHistoryController (Robbie Averill)
* 2018-11-13 [0bb94b018](https://github.com/silverstripe/silverstripe-framework/commit/0bb94b018b55e28eef627af4a9854d4f0babac62) Remove default password validation rules before running unit tests (Robbie Averill)
* 2018-11-12 [15aaf9db9](https://github.com/silverstripe/silverstripe-framework/commit/15aaf9db9fe1679cf8b01b74fce3eee841278495) Fix a code style typo (Serge Latyntcev)
* 2018-11-08 [cf4b16ed3](https://github.com/silverstripe/silverstripe-framework/commit/cf4b16ed380c336a7eaec7e46bec03f7767e10ba) Move password complexity requirements into framework (Robbie Averill)
* 2018-11-08 [4b4fbabed](https://github.com/silverstripe/silverstripe-framework/commit/4b4fbabed5d70bf577e4b0d6fdbc9dab9da80451) TreeMultiselectField passes value 'unchanged' as null to ORM for 'ID' column key (Serge Latyntcev)
* 2018-11-05 [1ee027b](https://github.com/silverstripe/silverstripe-campaign-admin/commit/1ee027baf937d458789ea40821df1b8e16cb96a3) Fix edit and create campaign forms missing margins (Luke Edwards)
* 2018-11-02 [69341e8](https://github.com/silverstripe/silverstripe-asset-admin/commit/69341e86d1efb0ee1fdc2585748021beefd496ac) UploadField now considers the idea of having cached form state (Guy Marriott)
* 2018-10-23 [66a404ad1](https://github.com/silverstripe/silverstripe-framework/commit/66a404ad1a83ca2f26c5f968935503306eb54578) use Injector for FormField::castedCopy (Will Rossiter)
* 2018-10-15 [6de0fa0](https://github.com/silverstripe/silverstripe-versioned/commit/6de0fa087fe581b69a5978db82058490c44923b4) Fix codesniffer runs in Travis (Robbie Averill)
* 2018-10-06 [c498aa03](https://github.com/silverstripe/silverstripe-cms/commit/c498aa03379ca883803dda853e64c411ed7454dc) Fixing wrong Live-Preview-Link in SilverStripeNavigatorItem_LiveLink (fixes #865). (Stephan Bauer)
* 2018-09-27 [ce3a1ce9](https://github.com/silverstripe/silverstripe-cms/commit/ce3a1ce91307424f643a15f0c292e16b35f35873) Use correct subsites namespace in SiteTree and test classes (Robbie Averill)
* 2018-09-13 [7189653b](https://github.com/silverstripe/silverstripe-cms/commit/7189653b1f9a744b9ee2393a8ef3fb8597c89b1b) SiteTree Title field should have rounded corners before Update URL button is shown (Robbie Averill)
* 2018-09-13 [9f62c97](https://github.com/silverstripe/silverstripe-admin/commit/9f62c9755568bdd0d2d40226f7adc3d4ebf48c54) ActionsMenu does not pass toggleCallback prop to Dropdown (Robbie Averill)
* 2018-07-27 [bc70b877](https://github.com/silverstripe/silverstripe-reports/commit/bc70b87721c8278111e39e0af69db1052af7333f) Apply missing class to report header. (Maxime Rainville)
* 2018-07-01 [bc8bb13](https://github.com/silverstripe/silverstripe-campaign-admin/commit/bc8bb13c93c75e718872315a60f0eb8213bd8e69) Button outline secondary class is now correct in disabled "Publish campaign" button (Robbie Averill)

View File

@ -1,17 +1,36 @@
# 4.4.0
# 4.4.0 (Unreleased)
## Overview {#overview}
- [Correct PHP types are now returned from database queries](/developer_guides/model/sql_select#data-types)
- [Server Requirements](/getting_started/server_requirements/#web-server-software-requirements) have been refined
- MySQL 5.5 end of life reached in December 2018, thus SilverStripe 4.4 requires MySQL 5.6+.
- [Server Requirements](/getting_started/server_requirements/#web-server-software-requirements) have been refined:
MySQL 5.5 end of life reached in December 2018, thus SilverStripe 4.4 requires MySQL 5.6+.
SilverStripe 4.3 and prior still support MySQL 5.5 for their own lifetime.
- The name of the directory where vendor module resources are exposed can now be configured by defining a `extra.resources-dir` key in your `composer.json` file. If the key is not set, it will automatically default to `resources`. New projects will be preset to `_resources`. This will avoid potential conflict with SiteTree URL Segments.
- dev/build is now non-destructive for all Enums, not just ClassNames. This means your data won't be lost if you're switching between versions, but watch out for code that breaks when it sees an unrecognised value!
## Upgrading {#upgrading}
- dev/build is now non-destructive for all Enums, not just ClassNames. This means your data won't be lost if you're switching between versions, but watch out for code that breaks when it sees an unrecognised value!
### Adopting to new `_resources` directory
1. Update your `.gitignore` file to ignore the new `_resources` directory. This file is typically located in the root of your project or in the `public` folder.
2. Add a new `extra.resources-dir` key to your composer file.
```js
{
// ...
"extra": {
// ...
"resources-dir": "_resources"
}
}
```
3. Expose your vendor assets by running `composer vendor-expose`.
4. Remove the old `resources` folder. This folder will be located in the `public` folder if you have adopted the public web root, or in the root of your project if you haven't.
You may also need to update your server configuration if you have applied special conditions to the `resources` path.
## Changes to internal APIs
- `PDOQuery::__construct()` now has a 2nd argument. If you have subclassed PDOQuery and overridden __construct()
you may see an E_STRICT error
- The name of the directory where vendor module resources are exposed can now be configured by adding a `extra.resources-dir` key to your composer file. The new default in `silverstripe/installer` has been changed to `_resources` rather than `resources`. This allows you to use `resources` as a URL segment or a route.

View File

@ -57,6 +57,5 @@ read our guide on [how to write secure code](/developer_guides/security/secure_c
## Sharing your Opinion
* [silverstripe.org/forums](http://www.silverstripe.org/community/forums/): Forums on silverstripe.org
* [silverstripe-dev](http://groups.google.com/group/silverstripe-dev/): Core development mailinglist
* [forum.silverstripe.org](http://forum.silverstripe.org): Forums on silverstripe.org
* [All issues across modules](https://www.silverstripe.org/community/contributing-to-silverstripe/github-all-core-issues)

View File

@ -93,7 +93,7 @@ Once your pull request is issued, it's not the end of the road. A [core committe
If you've been naughty and not adhered to the [coding conventions](coding_conventions),
expect a few requests to make changes so your code is in-line.
If your change is particularly significant, it may be referred to the [mailing list](https://groups.google.com/forum/#!forum/silverstripe-dev) for further community discussion.
If your change is particularly significant, it may be referred to the [forum](https://forum.silverstripe.org) for further community discussion.
A core committer will also "label" your PR using the labels defined in GitHub, these are to correctly classify and help find your work at a later date.
@ -165,7 +165,7 @@ After you have edited the file, GitHub will offer to create a pull request for y
[API documentation](https://api.silverstripe.org/) for good examples.
* Check and update documentation on [docs.silverstripe.org](https://docs.silverstripe.org). Check for any references to functionality deprecated or extended through your patch. Documentation changes should be included in the patch.
* When introducing something "noteworthy" (new feature, API change), [update the release changelog](/changelogs) for the next release this commit will be included in.
* If you get stuck, please post to the [forum](https://www.silverstripe.org/community/forums) or for deeper core problems, to the [core mailinglist](https://groups.google.com/forum/#!forum/silverstripe-dev)
* If you get stuck, please post to the [forum](https://www.silverstripe.org/community/forums)
* When working with the CMS, please read the ["CMS Architecture Guide"](/developer_guides/customising_the_admin_interface/cms_architecture) first
## Commit Messages

View File

@ -119,7 +119,7 @@ SS_DEPRECATION_ENABLED="0"
### Reporting an issue
Report security issues in our [commercially supported modules](https://www.silverstripe.org/software/addons/silverstripe-commercially-supported-module-list/)
to [security@silverstripe.com](mailto:security@silverstripe.com).
to [security@silverstripe.com](mailto:security@silverstripe.org).
Please don't file security issues in our [bugtracker](issues_and_bugs).
### Acknowledgment and disclosure
@ -128,16 +128,11 @@ In the event of a confirmed vulnerability in our
[supported modules](https://www.silverstripe.org/software/addons/silverstripe-commercially-supported-module-list/),
we will take the following actions:
* Acknowledge to the reporter that weve received the report and that a fix is forthcoming. Well give a rough
timeline and ask the reporter to keep the issue confidential until we announce it.
* Assign a unique identifier to the issue in the format `SS-<year>-<count>`,
where `<count>` is a padded three digit number counting issues for the year.
Example: `SS-2013-001` would be the first of the year `2013`.
Additionally, [CVE](http://cve.mitre.org) numbers are accepted.
* Halt all other development as long as is needed to develop a fix, including patches against the current and one
previous major release (if applicable).
* Pre-announce the upcoming security release to a private mailing list of important stakeholders (see below).
* We will inform you about resolution and [announce](https://forum.silverstripe.org/c/releases) a
* Acknowledge to the reporter that weve received the report and that a fix is forthcoming.
Well give a rough timeline and ask the reporter to keep the issue confidential until we announce it.
* Assign a [CVE identifier](https://cve.mitre.org) to the issue.
* For "high" and "critical" issues (CVSS of >=7.0): Pre-announce the upcoming security release to aprivate pre-announcement mailing list of important stakeholders (see below).
* We will inform you about resolution and [announce](https://forum.silverstripe.org/c/releases) a
[new release](http://silverstripe.org/security-releases/) publically.
You can help us determine the problem and speed up responses by providing us with more information on how to reproduce
@ -147,64 +142,39 @@ webserver access logs (if a hack is suspected), any other services and web packa
### Severity rating
Each [security release](http://www.silverstripe.org/security-releases/) includes an overall severity rating and one for
each vulnerability. The rating indicates how important an update is:
each vulnerability. The rating indicates how important an update is.
It follows the [Common Vulnerability Scoring System (CVSS)](https://www.first.org/cvss).
| Severity | Description |
|---------------|-------------|
| **Critical** | Critical releases require immediate action. Such vulnerabilities allow attackers to take control of your site and you should upgrade on the day of release. *Example: Directory traversal, privilege escalation* |
| **Important** | Important releases should be evaluated immediately. These issues allow an attacker to compromise a site's data and should be fixed within days. *Example: SQL injection.* |
| **Moderate** | Releases of moderate severity should be applied as soon as possible. They allow the unauthorized editing or creation of content. *Examples: Cross Site Scripting (XSS) in template helpers.* |
| **Low** | Low risk releases fix information disclosure and read-only privilege escalation vulnerabilities. These updates should also be applied as soon as possible, but with an impact-dependent priority. *Example: Exposure of the core version number, Cross Site Scripting (XSS) limited to the admin interface.* |
| Severity | CVSS | Description |
|---------------|------|-------------|
| **Critical** | 9.0 to 10.0 | Critical releases require immediate action. Such vulnerabilities allow attackers to take control of your site and you should upgrade on the day of release. *Example: Directory traversal, privilege escalation* |
| **High** | 7.0 to 8.9 | Important releases should be evaluated immediately. These issues allow an attacker to compromise a site's data and should be fixed within days. *Example: SQL injection.* |
| **Medium** | 4.0 to 6.9 | Releases of moderate severity should be applied as soon as possible. They allow the unauthorized editing or creation of content. *Examples: Cross Site Scripting (XSS) in template helpers.* |
| **Low** | 0.1 to 3.9 | Low risk releases fix information disclosure and read-only privilege escalation vulnerabilities. These updates should also be applied as soon as possible, but with an impact-dependent priority. *Example: Exposure of the core version number, Cross Site Scripting (XSS) limited to the admin interface.* |
### Internal Security Process
Follow these instructions in sequence as much as possible:
See [SilverStripe Core Release Process](making-a-silverstripe-core-release).
* When receiving a report:
* Perform initial criticality assessment, and ensure that the reporter is given a justification for all issues we classify or demote as non-security vulnerabilities.
* Assign a unique identifier (see "Acknowledgement and disclosure").
Identifiers are based on reported year and order reported in JIRA (Example: `SS-2017-001`)
* Respond to issue reporter with this identifier on the same discussion thread (cc security@silverstripe.org). Clarify issue if required.
* If encrypted information is provided, add pass phrases into the SilverStripe Ltd. LastPass account. Keep encrypted documents in Google Drive and only share directly with relevant participants
* Add a new issue in the "Backlog" on the [project board](https://github.com/silverstripe-security/security-issues/projects/1).
Add a link to the [Google Groups](https://groups.google.com/a/silverstripe.com/forum/#!forum/security) discussion thread so it's easy to review follow up messages.
* Create a draft page under [Open Source > Download > Security Releases](https://www.silverstripe.org/admin/pages/edit/show/794) on silverstripe.org. Describe the issue in a readable way, make the impact clear. Credit the author if applicable.
* Clarify who picks up owns the issue resolution
* When developing a fix:
* Ensure you're working on the oldest supported minor release branch of every supported major release (see [Supported Versions](#supported-versions))
* Move the issue into "In Progress" on the [project board](https://github.com/silverstripe-security/security-issues/projects/1)
* Add fixes on the [http://github.com/silverstripe-security](http://github.com/silverstripe-security) repo
* Ensure that all security commit messages are prefixed with the CVE. E.g. "[ss-2015-001] Fixed invalid XSS"
* Get them peer reviewed by posting on security@silverstripe.org with a link to the Github issue
* Before release (or release candidate)
* Merge back from [http://github.com/silverstripe-security](http://github.com/silverstripe-security) repos shortly at the release (minimise early disclosure through source code)
* Merge up to newer minor release branches (see [Supported Versions](#supported-versions))
* Send out a note on the pre-announce list with a highlevel description of the issue and impact (usually a copy of the yet unpublished security release page on silverstripe.org)
* Link to silverstripe.org security release page in the changelog.
* Move the issue to "Awaiting Release" in the [project board](https://github.com/silverstripe-security/security-issues/projects/1)
* Perform release
* Follow the steps for [making a core release](making-a-silverstripe-core-release)
* After release
* Publish silverstripe.org security release page
* Respond to issue reporter with reference to the release on the same discussion thread (cc security@silverstripe.org)
* Move the issue to "Done" in the [project board](https://github.com/silverstripe-security/security-issues/projects/1)
### Pre-announcement mailing list
### Pre-announce Mailinglist
In addition to our public disclosure process, we maintain a private mailing list where upcoming
"high" or "critical" security releases are pre-announced.
Members of this list will receive a security pre-announcement, as soon as it has been
sufficiently researched, with a timeline for the upcoming release.
This will happen a few days before the announcement goes public alongside a new release,
and most likely before a patch has been developed.
In addition to our public disclosure process, we maintain a private mailinglist
where upcoming security releases will be pre-announced. Members in this list will receive a security
pre-announcement as soon as it has been sufficiently researched,
alongside a timeline for the upcoming release. This will happen a few days before
the announcement goes public alongside new release, and most likely before a patch has been developed.
Since well distribute sensitive info on unpatched vulnerabilities in this list,
the selection criteria for joining naturally has to be strict.
Applicants should provide references within the community,
as well as a demonstrated need for this level of information (e.g. a large website with sensitive customer data).
Since well distribute sensitive information on unpatched vulnerabilities in this list,
the selection criteria for joining naturally has to be strict.
Applicants should provide references within the community,
as well as a demonstrated need for this level of information
(e.g. involvement with a large website with sensitive customer data).
You dont need to be a client of SilverStripe Ltd to get on board,
but we will need to perform some low-touch background checks to ensure identity.
but we will need to perform some low-touch background checks to verify your identity.
Please contact security@silverstripe.org for details.
## Quality Assurance and Testing
The quality of our software is important to us, and we continously test it for regressions

View File

@ -1,5 +1,5 @@
title: Making a SilverStripe core release
summary: Development guide for core contributors to build and publish a new release
summary: Development guide for core contributors to build and publish a new release
# Making a SilverStripe core release
@ -28,8 +28,11 @@ As a core contributor it is necessary to have installed the following set of too
* [cow release tool](https://github.com/silverstripe/cow#install). This should typically
be installed in a global location via the below command. Please see the installation
docs on the cow repo for more setup details.
`composer global require silverstripe/cow dev-master`
* [transifex client](http://docs.transifex.com/client/).
`composer global require silverstripe/cow ^2`
* [satis repository tool](https://github.com/composer/satis). This should be installed
globally for minimum maintenance.
`composer global require composer/satis ^1`
* [transifex client](http://docs.transifex.com/client/).
`pip install transifex-client`
If you're on OSX 10.10+, the standard Python installer is locked down.
Use `brew install python; sudo easy_install pip` instead
@ -87,6 +90,8 @@ For doing security releases the following additional setup tasks are necessary:
## Security release process
### Overview
When doing a security release, typically one or more (or sometimes all) of the below
steps will need to be performed manually. As such, this guide should not be followed
exactly the same for these.
@ -99,49 +104,111 @@ Security issues are never disclosed until a public stable release containing thi
is available, or within a reasonable period of time of such a release.
</div>
Producing a security fix follows this general process:
### When receiving a report
* When a security issue is disclosed on security@silverstripe.com it should be given
a CVE (common vulnerability exposure) code. E.g. ss-2015-020. Make sure you thank
anyone who disclosed this issue, and confirm with them as soon as possible whether
this issue is a verified security issue.
* Log this CVE, along with description, release version, and name of reporter in
the [security issues GitHub repository](https://github.com/silverstripe-security/security-issues/issues).
* Create a similar record of this issue on the [security releases page](http://www.silverstripe.org/download/security-releases)
in draft mode.
* Post a pre-announcement to the [security pre-announcement list](https://groups.google.com/a/silverstripe.com/forum/#!forum/security-preannounce).
It's normally ideal to include a [CVSS](https://nvd.nist.gov/CVSS-v2-Calculator)
(common vulnerability scoring system) along with this pre-announcement. If the
release date of the final stable is not known, then it's ok to give an estimated
release schedule.
* Push the current upstream target branches (e.g. 3.2) to the corresponding security fork
to a new branch named for the target release (e.g. 3.2.4). Security fixes should be
applied to this branch only. Once a fix (or fixes) have been applied to this branch, then
a tag can be applied, and a private release can then be developed in order
to test this release.
* Once release testing is completed and the release is ready for stabilisation, then these fixes
* Perform initial criticality assessment, and ensure that the reporter is given a justification for all issues we classify or demote as non-security vulnerabilities.
* If encrypted information is provided, add pass phrases into the SilverStripe Ltd. LastPass account. Keep encrypted documents in Google Drive and only share directly with relevant participants
* Add a new issue in the "Backlog" on the [project board](https://github.com/silverstripe-security/security-issues/projects/1).
Add a link to the [Google Groups](https://groups.google.com/a/silverstripe.com/forum/#!forum/security) discussion thread so it's easy to review follow up messages.
* Use the [CVSS Calculator](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator) to determine the issue severity
* Once the issue is confirmed, [request a CVE identifier](https://cveform.mitre.org/) under the security@silverstripe.org contact email (see "Acknowledgement and disclosure").
* Once a CVE has been assigned, respond to issue reporter and add it to the Github issue
* Clarify who picks up owns the issue resolution (assign in Github)
### When developing a fix
* Ensure you're working on the oldest supported minor release branch of every supported major release (see [Supported Versions](#supported-versions))
* Move the issue into "In Progress" on the [project board](https://github.com/silverstripe-security/security-issues/projects/1)
* Add fixes on the [http://github.com/silverstripe-security](http://github.com/silverstripe-security) repo. Don't forget to update branches from the upstream repo.
* Ensure that all security commit messages are prefixed with the CVE. E.g. "[CVE-2019-001] Fixed invalid XSS"
* Get them peer reviewed by posting on security@silverstripe.org with a link to the Github issue
### Before release (or release candidate)
* For issues rated "high" or "critical" (CVSS of >=7.0), post a pre-announcement to the [security pre-announcement list](https://groups.google.com/a/silverstripe.com/forum/#!forum/security-preannounce).
It should include a basic "preannouncement description" which doesn't give away too much,
the CVSS score as well as the CVE identifier.
* Create a draft page under [Open Source > Download > Security Releases](https://www.silverstripe.org/admin/pages/edit/show/794).
Populate it with the information from the [Github project board](https://github.com/silverstripe-security/security-issues/projects/1).
* Link to silverstripe.org security release page in the changelog.
* Move the issue to "Awaiting Release" in the [project board](https://github.com/silverstripe-security/security-issues/projects/1)
### Perform release
* Public disclosure of security vulnerabilities need to happen in stable releases (not pre-releases)
* Merge back from [http://github.com/silverstripe-security](http://github.com/silverstripe-security) repos shortly at the release (minimise early disclosure through source code)
* Merge up to newer minor release branches (see [Supported Versions](#supported-versions))
* Setup a temporary [satis](https://github.com/composer/satis) repository which points to all relevant repositories
containing security fixes. See below for setting up a temporary satis repository.
* Once release testing is completed and the release is ready for stabilisation, then these fixes
can then be pushed to the upstream module fork, and the release completed as per normal.
Make sure to publish any draft security pages at the same time as the release is published (same day).
* After the final release has been published, close related GitHub issues
in the [security-issues repository](https://github.com/silverstripe-security/security-issues/issues).
* Follow the steps for [making a core release](making-a-silverstripe-core-release)
### After release
* Publish silverstripe.org security release page
* Respond to issue reporter with reference to the release on the same discussion thread (cc security@silverstripe.org)
* Move the issue to "Done" in the [project board](https://github.com/silverstripe-security/security-issues/projects/1)
### Setting up satis for hosting private security releases
When installing a project from protected repositories, it's necessary prior to creating your project
to override the public repository URLs with the private repositories containing undisclosed fixes. For
this we use [satis](https://github.com/composer/satis).
To setup a Satis project for a release:
* Ensure Satis is installed globally: `composer global require composer/satis ^1`
* `cd ~/Sites/` (or wherever your web-root is located)
* `mkdir satis-security && cd satis-security` (or some directory specific to your release)
* Create a config file (e.g. config.json) of the given format (add only those repositories necessary).
Note:
- The homepage path should match the eventual location of the package content
- You should add the root repository (silverstripe/installer) to ensure
`create-project` works (even if not a private security fork).
- You should add some package version constraints to prevent having to parse
all legacy tags and all branches.
```json
{
"name": "SilverStripe Security Repository",
"homepage": "http://localhost/satis-security/public",
"repositories": {
"installer": {
"type": "vcs",
"url": "https://github.com/silverstripe/silverstripe-installer.git"
},
"framework": {
"type": "vcs",
"url": "https://github.com/silverstripe-security/silverstripe-framework.git"
}
},
"require": {
"silverstripe/installer": "^3.5 || ^4",
"silverstripe/framework": "^3.5 || ^4"
},
"require-all": true
}
```
* Build the repository:
`satis build config.json ./public`
* Test you can view the satis home page at `http://localhost/satis-security/public/`
* When performing the release ensure you use `--repository=http://localhost/satis-security/public` (below)
<div class="warning" markdown="1">
Note: It's not considered acceptable to disclose any security vulnerability until a fix exists in
a public stable, not an RC or dev-branch. Security warnings that do not require a stable release
can be published as soon as a workaround or usable resolution exists.
It's important that you re-run `satis build` step after EVERY change that is pushed upstream; E.g. between
each release, if making multiple releases.
</div>
## Standard release process
The release process, at a high level, involves creating a release, publishing it, and
The release process, at a high level, involves creating a release, publishing it, and
reviewing the need for either another pre-release or a final stable tag within a short period
(normally within 3-5 business days).
During the pre-release cycle a temporary branch is created, and should only receive
absolutely critical fixes during the cycle. Any changes to this branch should
result in the requirement for a new release, thus a higher level of scrutiny is typically
placed on any pull request to these branches.
When creating a new pre-release or stable, the following process is broken down into two
main sets of commands:
@ -161,7 +228,7 @@ Check all tickets assigned to that milestone are either closed or reassigned to
Use the [list of all issues across modules](https://www.silverstripe.org/community/contributing-to-silverstripe/github-all-core-issues)
as a starting point, and add a `milestone:"your-milestone"` filter.
Merge up from other older [supported release branches](release-process#supported-versions) (e.g. merge `3.1`->`3.2`, `3.2`->`3.3`, `3.3`->`3`, `3`->`master`).
Merge up from other older [supported release branches](release-process#supported-versions) (e.g. merge `4.0`->`4.1`, `4.1`->`4.2`, `4.2`->`4`, `4`->`master`).
This is the part of the release that prepares and tests everything locally, but
doe not make any upstream changes (so it's safe to run without worrying about
@ -169,13 +236,30 @@ any mistakes migrating their way into the public sphere).
Invoked by running `cow release` in the format as below:
```
cow release <version> -vvv
```
`cow release <version> [recipe] -vvv`
This command has the following parameters:
E.g.
* `<version>` The version that is to be released. E.g. 3.2.4 or 4.0.0-alpha4
`cow release 4.0.1 -vvv`
* `<version>` The version that is to be released. E.g. `4.1.4` or `4.3.0-rc1`
* `<recipe>` `Optional: the recipe that is being released (default: "silverstripe/installer")
This command has these options (note that --repository option is critical for security releases):
* `-vvv` to ensure all underlying commands are echoed
* `--directory <directory>` to specify the folder to create or look for this project in. If you don't specify this,
it will install to the path specified by `./release-<version>` in the current directory.
* `--repository <repository>` will allow a custom composer package url to be specified. E.g. `http://packages.cwp.govt.nz`
See the above section "Setting up satis for hosting private security releases" on how to prepare a custom
repository for a security release.
* `--branching <type>` will specify a branching strategy. This allows these options:
* `auto` - Default option, will branch to the minor version (e.g. 1.1) unless doing a non-stable tag (e.g. rc1)
* `major` - Branch all repos to the major version (e.g. 1) unless already on a more-specific minor version.
* `minor` - Branch all repos to the minor semver branch (e.g. 1.1)
* `none` - Release from the current branch and do no branching.
* `--skip-tests` to skip tests
* `--skip-i18n` to skip updating localisations
This can take between 5-15 minutes, and will invoke the following steps,
each of which can also be run in isolation (in case the process stalls
@ -188,14 +272,17 @@ and needs to be manually advanced):
know to install dev-master, and installing 3.3.0 will install from 3.x-dev.
If installing pre-release versions for stabilisation, it will use the correct
temporary release branch.
* `release:plan` The release planning will take place, this reads the various dependencies of the recipe being released
and determines what new versions of those dependencies need to be tagged to create the final release. The conclusion
of the planning step is output to the screen and requires user confirmation.
* `release:branch` If release:create installed from a non-rc branch, it will
create the new temporary release branch (via `--branch-auto`). You can also customise this branch
with `--branch=<branchname>`, but it's best to use the standard.
* `release:translate` All upstream transifex strings will be pulled into the
local master strings, and then the [i18nTextCollector](api:SilverStripe\i18n\TextCollection\i18nTextCollector) task will be invoked
and will merge these strings together, before pushing all new master strings
back up to transifex to make them available for translation. Changes to these
files will also be automatically committed to git.
local master strings, and then the [i18nTextCollector](api:SilverStripe\i18n\TextCollection\i18nTextCollector)
task will be invoked and will merge these strings together, before pushing all
new master strings back up to transifex to make them available for translation.
Changes to these files will also be automatically committed to git.
* `release:test` Will run all unit tests on this release. Make sure that you
setup your `.env` correctly (as above) so that this will work.
* `release:changelog` Will compare the current branch head with `--from` parameter
@ -218,9 +305,7 @@ the build status of Behat end-to-end tests manually on travis-ci.org.
Check the badges on the various modules available on [github.com/silverstripe](http://github.com/silverstripe).
It's also ideal to eyeball the git changes generated by the release tool, making sure
that no translation strings were unintentionally lost, no malicious changes were
introduced in the (community contributed) translations, and that the changelog
was generated correctly.
that no translation strings were unintentionally lost, and that the changelog was generated correctly.
In particular, double check that all necessary information is included in the release notes,
including:
@ -240,14 +325,29 @@ building an archive, and uploading to
Invoked by running `cow release:publish` in the format as below:
```
cow release:publish <version> -vvv
```
`cow release:publish <version> [<recipe>] -vvv`
E.g.
`cow release:publish 4.0.1 silverstripe/installer`
This command has these options:
* `-vvv` to ensure all underlying commands are echoed
* `--directory <directory>` to specify the folder to look for the project created in the prior step. As with
above, it will be guessed if omitted. You can run this command in the `./release-<version>` directory and
omit this option.
* `--aws-profile <profile>` to specify the AWS profile name for uploading releases to s3. Check with
damian@silverstripe.com if you don't have an AWS key setup.
* `--skip-archive-upload` to disable both "archive" and "upload". This is useful if doing a private release and
you don't want to upload this file to AWS.
* `--skip-upload` to disable the "upload" command (but not archive)
As with the `cow release` command, this step is broken down into the following
subtasks which are invoked in sequence:
* `release:tag` Each module will have the appropriate tag applied (except the theme).
* `release:push` The temporary release branches and all tags are pushed up to origin on github.
* `release:tag` Each module will have the appropriate tag applied (except the theme). All tags are pushed up to origin
on github.
* `release:archive` This will generate a new tar.gz and zip archive, each for
cms and framework-only installations. These will be copied to the root folder
of the release directory, although the actual build will be created in temporary
@ -255,7 +355,7 @@ subtasks which are invoked in sequence:
If the tags generated in the prior step are not yet available on packagist (which can
take a few minutes at times) then this task will cycle through a retry-cycle,
which will re-attempt the archive creation periodically until these tags are available.
* `release:upload` This will invoke the AWS CLI command to upload these archives to the
* `release:upload` This will invoke the AWS CLI command to upload these archives to the
s3 bucket `silverstripe-ssorg-releases`. If you have setup your AWS profile
for silverstripe releases under a non-default name, you can specify this profile
on the command line with the `--aws-profile=<profile>` command.
@ -264,23 +364,23 @@ subtasks which are invoked in sequence:
Once all of these commands have completed there are a couple of final tasks left that
aren't strictly able to be automated:
* If this is a stable release, it will be necessary to perform a post-release merge
* It will be necessary to perform a post-release merge
on open source. This normally will require you to merge the temporary release branch into the
source branch (e.g. merge 3.2.4 into 3.2), or sometimes create new branches if
releasing a new minor version, and bumping up the branch-alias in composer.json.
E.g. branching 3.3 from 3, and aliasing 3 as 3.4.x-dev. You can then delete
the temporary release branches. This will need to be done before updating the
the temporary release branches. This will need to be done before updating the
release documentation in stage 3.
* Merging up the changes in this release to newer branches, following the
* Merging up the changes in this release to newer branches, following the
SemVer pattern (e.g. 3.2.4 > 3.2 > 3.3 > 3 > master). The more often this is
done the easier it is, but this can sometimes be left for when you have
more free time. Branches not receiving regular stable versions anymore (e.g.
3.0 or 3.1) should usually be omitted.
3.0 or 3.1) can be omitted.
* Set the github milestones to completed, and create placeholders for the next
minor versions. It may be necessary to re-assign any issues assigned to the prior
milestones to these new ones.
* Make sure that the [releases page](https://github.com/silverstripe/silverstripe-installer/releases)
on github shows the new tag.
on github shows the new tag.
*Updating non-patch versions*
@ -327,7 +427,7 @@ Running either of these tasks may time out when requested, but will continue to
only the search index rebuild takes a long period of time.
Note that markdown is automatically updated daily, and this should only be done if an immediate refresh is necessary.
### Stage 3: Let the world know
Once a release has been published there are a few places where user documentation
@ -335,7 +435,7 @@ will need to be regularly updated.
* Make sure that the [download page](http://www.silverstripe.org/download) on
silverstripe.org has the release available. If it's a stable, it will appear
at the top of the page. If it's a pre-release, it will be available under the
at the top of the page. If it's a pre-release, it will be available under the
[development builds](http://www.silverstripe.org/download#download-releases)
section. If it's not available, you might need to check that the release was
properly uploaded to aws s3, or that you aren't viewing a cached version of
@ -344,13 +444,11 @@ will need to be regularly updated.
you can run the [CoreReleaseUpdateTask](http://www.silverstripe.org/dev/tasks/CoreReleaseUpdateTask)
to synchronise with packagist.
* Ensure that [docs.silverstripe.org](http://docs.silverstripe.org) has the
updated documentation by running the build task in the root folder. If
you do not have ssh access to this server, then contact a SilverStripe staff member
to update this for you. Make sure that the download link below links to the
correct changelog page. E.g.
[https://docs.silverstripe.org/en/3.2/changelogs/3.2.1/](https://docs.silverstripe.org/en/3.2/changelogs/3.2.1/)
* Post a release announcement on the [silverstripe release announcement](https://groups.google.com/forum/#!forum/silverstripe-announce)
google group.
updated documentation and the changelog link in your announcement works.
* Announce the release on the ["Releases" forum](https://forum.silverstripe.org/c/releases).
Needs to happen on every minor release for previous releases, see [supported versions](https://docs.silverstripe.org/en/4/contributing/release_process/#supported-versions)
* Announce any new EOLs for minor versions on the ["Releases" forum](https://forum.silverstripe.org/c/releases).
* Update the [roadmap](https://www.silverstripe.org/roadmap) with new dates for EOL versions ([CMS edit link](https://www.silverstripe.org/admin/pages/edit/EditForm/3103/field/TableComponentItems/item/670/edit))
* Update the [Slack](https://www.silverstripe.org/community/slack-signup/) topic to include the new release version.
* For major or minor releases: Work with SilverStripe marketing to get a blog post out.
They might choose to announce the release on social media as well.

View File

@ -134,7 +134,7 @@ This also applies for any modules staying compatible with SilverStripe 2.x.
## Contact
Translators have their own [mailinglist](https://groups.google.com/forum/#!forum/silverstripe-translators), but you can
Translators have their own [mailing list](https://groups.google.com/forum/#!forum/silverstripe-translators), but you can
also reach a core member on [IRC](https://irc.silverstripe.org). The transifex.com interface has a built-in discussion
board if you have specific comments on a translation.

View File

@ -31,9 +31,9 @@ Use BEM conventions where possible.
## Linting
We use [sass-lint](hhttps://github.com/sasstools/sass-lint) to ensure all new SCSS
We use [sass-lint](https://github.com/sasstools/sass-lint) to ensure all new SCSS
written complies with the rules below. It will be provided as an npm dev dependency.
There are also quite a few [sass-lint IDE integrations]https://github.com/sasstools/sass-lint#ide-integration)
There are also quite a few [sass-lint IDE integrations](https://github.com/sasstools/sass-lint#ide-integration)
which highlight any linting errors right in your code.
We strongly recommend installing one of these into the editor of your choice, to
@ -72,7 +72,7 @@ consider porting them over into the new structure. Otherwise, follow these conve
- Class naming: Use the `cms-` class prefix for major components in the cms interface,
and the `ss-ui-` prefix for extensions to jQuery UI. Don't use the `ui-` class prefix, its reserved for jQuery UI built-in styles.
- Use jQuery UI's built-in styles where possible, e.g. `ui-widget` for a generic container, or `ui-state-highlight`
to highlight a specific component. See the [jQuery UI Theming API](http://jqueryui.com/docs/Theming/API) for a full list.
to highlight a specific component. See the [jQuery UI Theming API](https://api.jqueryui.com/category/theming/) for a full list.
## Related

View File

@ -3,7 +3,7 @@ introduction: Any open source product is only as good as the community behind it
## House rules for everybody contributing to SilverStripe
* Read over the SilverStripe Community [Code of Conduct](code_of_conduct)
* Ask questions on the [forum](http://silverstripe.org/community/forums), and stick to more high-level discussions on the [core mailinglist](https://groups.google.com/forum/#!forum/silverstripe-dev)
* Ask questions on the [forum](http://silverstripe.org/community/forums)
* Make sure you know how to [raise good bug reports](issues_and_bugs)
* Everybody can contribute to SilverStripe! If you do, ensure you can [submit solid pull requests](code)

View File

@ -15,13 +15,13 @@ and play with the interactive [demo website](http://demo.silverstripe.org/).
SilverStripe has an wide range of options for getting support:
* Join our [forum](https://forum.silverstripe.org)
* Ask technical questions on [Stack Overflow](https://stackoverflow.com/questions/tagged/silverstripe)
* Get help on our [Slack channel](https://www.silverstripe.org/community/slack-signup/)
* Read the technical reference in our [API Documentation](http://api.silverstripe.org/)
* Get a user-focused overview of the CMS features in our [User Help](http://userhelp.silverstripe.com)
* Discuss new features, API changes and the development [roadmap](http://www.silverstripe.org/software/roadmap/)
on [UserVoice](http://silverstripe.uservoice.com/forums/251266-new-features)
* Join our [core mailinglist](https://groups.google.com/forum/#!forum/silverstripe-dev)
## Building your first SilverStripe Web application

View File

@ -95,8 +95,6 @@ da:
DeletePermissionsFailure: 'Ingen slette rettigheder'
Deleted: 'Slet {type} {name}'
Save: Gem
SilverStripe\Forms\GridField\GridFieldEditButton:
EDIT: Rediger
SilverStripe\Forms\GridField\GridFieldGroupDeleteAction:
UnlinkSelfFailure: 'Kan ikke fjerne dig selv fra denne gruppe, du vil miste administrator rettigheder'
SilverStripe\Forms\GridField\GridFieldPaginator:
@ -149,8 +147,6 @@ da:
other: '{count} år'
SilverStripe\ORM\FieldType\DBEnum:
ANY: Enhver
SilverStripe\ORM\FieldType\DBForeignKey:
DROPDOWN_THRESHOLD_FALLBACK_MESSAGE: 'For mange relaterede objekter; fallback felt i brug'
SilverStripe\ORM\Hierarchy:
LIMITED_TITLE: 'For mange underelementer ({count})'
SilverStripe\ORM\Hierarchy\Hierarchy:
@ -324,5 +320,3 @@ da:
LOSTPASSWORDHEADER: 'Glemt kodeord'
NOTEPAGESECURED: 'Denne side er beskyttet. Indtast dine loginoplysninger herunder for at få adgang.'
NOTERESETPASSWORD: 'Indtast din email adresse, så sender vi dig et link som du kan nulstille dit kodeord med'
PASSWORDRESETSENTHEADER: 'link til at nulstille kodeord afsendt'
PASSWORDRESETSENTTEXT: 'Tak for det. Et link til at nulstille dit kodeord er afsendt, hvis der findes en bruger med denne email adresse.'

View File

@ -1,10 +1,35 @@
de:
SilverStripe\Admin\LeftAndMain:
VersionUnknown: unbekannt
SilverStripe\AssetAdmin\Forms\UploadField:
Dimensions: Dimensionen
EDIT: Bearbeiten
EDITINFO: 'Diese Datei bearbeiten'
REMOVE: Entfernen
SilverStripe\Control\ChangePasswordEmail_ss:
CHANGEPASSWORDTEXT1: 'Sie haben Ihr Passwort geändert für'
CHANGEPASSWORDTEXT3: 'Passwort ändern'
HELLO: Hallo
SilverStripe\Control\Email\ForgotPasswordEmail_ss:
HELLO: Hallo
TEXT1: 'Hier ist Ihr'
TEXT2: 'Link zum Zurücksetzen des Passworts'
TEXT3: für
SilverStripe\Control\RequestProcessor:
INVALID_REQUEST: 'Ungültige Anfrage'
REQUEST_ABORTED: 'Anfrage abgebrochen'
SilverStripe\Core\Manifest\VersionProvider:
VERSIONUNKNOWN: Unbekannt
SilverStripe\Forms\CheckboxField:
NOANSWER: Nein
YESANSWER: Ja
SilverStripe\Forms\CheckboxSetField_ss:
NOOPTIONSAVAILABLE: 'Keine Optionen verfügbar'
SilverStripe\Forms\ConfirmedPasswordField:
ATLEAST: 'Passwörter müssen mindestens {min} Zeichen lang sein.'
BETWEEN: 'Passwörter müssen zwischen {min} und {max} Zeichen lang sein.'
CURRENT_PASSWORD_ERROR: 'Das derzeitige Passwort ist nicht richtig.'
CURRENT_PASSWORD_MISSING: 'Bitte geben Sie Ihr derzeitiges Passwort ein.'
LOGGED_IN_ERROR: 'Sie müssen eingeloggt sein, um Ihr Passwort ändern zu können!'
MAXIMUM: 'Passwörter dürfen maximal {max} Zeichen lang sein.'
SHOWONCLICKTITLE: 'Passwort ändern'
@ -14,12 +39,18 @@ de:
VALIDDATEFORMAT2: 'Bitte geben sie das Datum im korrekten Format ein ({format})'
VALIDDATEMAXDATE: 'Ihr Datum muss vor dem erlaubtem Datum ({date}) liegen oder gleich sein'
VALIDDATEMINDATE: 'Ihr Datum muss nach dem erlaubtem Datum ({date}) liegen oder gleich sein'
SilverStripe\Forms\DatetimeField:
VALIDDATEMAXDATETIME: 'Ihr Datum muss vor dem erlaubtem Datum ({datetime}) liegen oder gleich sein'
VALIDDATETIMEFORMAT: 'Bitte geben Sie ein gültiges Datums- und Zeitformat ein ({format})'
VALIDDATETIMEMINDATE: 'Ihr Datum muss nach dem erlaubtem Datum ({datetime}) liegen oder gleich sein'
SilverStripe\Forms\DropdownField:
CHOOSE: (Auswahl)
CHOOSE_MODEL: '({name} auswählen)'
SOURCE_VALIDATION: 'Bitte wählen Sie aus der Liste. {value} ist kein gültiger Wert'
SilverStripe\Forms\EmailField:
VALIDATION: 'Bitte geben Sie eine E-Mail-Adresse ein'
SilverStripe\Forms\FileUploadReceiver:
FIELDNOTSET: 'Dateiinformation nicht gefunden'
SilverStripe\Forms\Form:
CSRF_EXPIRED_MESSAGE: 'Ihre Sitzung ist abgelaufen. Bitte schicken Sie das Formular erneut ab.'
CSRF_FAILED_MESSAGE: 'Es gab ein technisches Problem. Bitte versuchen Sie es erneut, nachdem sie die vorherige Seite neu geladen haben.'
@ -29,10 +60,14 @@ de:
VALIDATOR: Prüfer
VALIDCURRENCY: 'Bitte geben Sie einen korrekten Betrag ein'
SilverStripe\Forms\FormField:
EXAMPLE: 'z.B. {format}'
NONE: keine
SilverStripe\Forms\FormScaffolder:
TABMAIN: Hauptteil
SilverStripe\Forms\GridField\GridField:
Add: '{name} hinzufügen'
CSVEXPORT: 'Als CSV-Datei exportieren'
CSVIMPORT: 'CSV importieren'
Filter: Filter
FilterBy: 'Filtern nach'
Find: Suchen
@ -58,9 +93,18 @@ de:
DeletePermissionsFailure: 'Keine Berechtigungen zum löschen'
Deleted: 'Gelöscht {type} {name}'
Save: Speichern
SilverStripe\Forms\GridField\GridFieldEditButton:
EDIT: Bearbeiten
SilverStripe\Forms\GridField\GridFieldPaginator:
OF: von
Page: Seite
View: Anzeigen
SilverStripe\Forms\MoneyField:
FIELDLABELAMOUNT: Betrag
FIELDLABELCURRENCY: Währung
INVALID_CURRENCY: 'Währung {currency} ist nicht in der Liste der erlaubten Währungen'
SilverStripe\Forms\MultiSelectField:
SOURCE_VALIDATION: 'Bitte wählen Sie aus der Liste. {value} ist ungültig.'
SilverStripe\Forms\NullableField:
IsNullLabel: 'ist NULL'
SilverStripe\Forms\NumericField:
@ -69,19 +113,45 @@ de:
VALIDATEFORMAT: 'Bitte geben Sie die Uhrzeit im korrekten Format ein ({format})'
SilverStripe\ORM\DataObject:
PLURALNAME: DatenObjekte
PLURALS:
one: 'Ein DatenObjekt'
other: '{count} DatenObjekte'
SINGULARNAME: DatenObjekt
SilverStripe\ORM\FieldType\DBBoolean:
ANY: alle
NOANSWER: Nein
YESANSWER: Ja
SilverStripe\ORM\FieldType\DBDate:
DAYS_SHORT_PLURALS:
one: '{count} Tag'
other: '{count} Tage'
HOURS_SHORT_PLURALS:
one: '{count} Stunde'
other: '{count} Stunden'
LessThanMinuteAgo: 'weniger als eine Minute'
MINUTES_SHORT_PLURALS:
one: '{count} Minute'
other: '{count} Minuten'
MONTHS_SHORT_PLURALS:
one: '{count} Monat'
other: '{count} Monate'
SECONDS_SHORT_PLURALS:
one: '{count} Sekunde'
other: '{count} Sekunden'
TIMEDIFFAGO: 'vor {difference}'
TIMEDIFFIN: 'in {difference}'
YEARS_SHORT_PLURALS:
one: '{count} Jahr'
other: '{count} Jahre'
SilverStripe\ORM\FieldType\DBEnum:
ANY: alle
SilverStripe\ORM\Hierarchy:
LIMITED_TITLE: 'Zu viele Kindelemente ({count})'
SilverStripe\ORM\Hierarchy\Hierarchy:
InfiniteLoopNotAllowed: 'Es wurde eine Endlosschleife innerhalb der "{type}"-Hierarchie gefunden. Bitte ändern Sie die übergeordnete Seite, um den Fehler zu beheben'
LIMITED_TITLE: 'Zu viele Kindelemente ({count})'
SilverStripe\ORM\ValidationException:
DEFAULT_ERROR: Validierungsfehler
SilverStripe\Security\BasicAuth:
ENTERINFO: 'Bitte geben Sie einen Nutzernamen und ein Passwort ein'
ERRORNOTADMIN: 'Dieser Nutzer ist kein Administrator'
@ -90,28 +160,46 @@ de:
PASSWORDEXPIRED: '<p>Ihr Passwort ist abgelaufen. <a target="_top" href="{link}">Bitte wählen Sie ein neues Passwort.</a></p>'
SilverStripe\Security\CMSSecurity:
INVALIDUSER: '<p>Ungültiger Benutzer. <a target="_top" href="{link}">Bitte melden Sie sich hier an</a> um fortzufahren.</p>'
LOGIN_MESSAGE: '<p>Ihre Sitzung ist wegen Inaktivität abgelaufen.</p>'
SUCCESS: Erfolg
SUCCESSCONTENT: '<p>Login erfolgreich. Falls Sie nicht automatisch weitergeleitet werden, bitte <a target="_top" href="{link}">hier klicken</a></p>'
SUCCESS_TITLE: 'Login erfolgreich'
SilverStripe\Security\DefaultAdminService:
DefaultAdminFirstname: Standardadmin
SilverStripe\Security\Group:
AddRole: 'Rolle zur Gruppe hinzufügen'
Code: 'Gruppen Code'
DefaultGroupTitleAdministrators: Administratoren
DefaultGroupTitleContentAuthors: Inhaltsautoren
Description: Beschreibung
GROUPNAME: Gruppenname
GroupReminder: 'Diese Gruppe übernimmt automatisch die Rollen der Elterngruppe'
HierarchyPermsError: 'Kann Berechtigungen der Eltern-Gruppe "{group}" nicht hinzufügen (erfordert Administratorrechte)'
Locked: 'Gesperrt?'
MEMBERS: Mitglieder
NEWGROUP: 'Neue Gruppe'
NoRoles: 'Keine Rollen gefunden'
PERMISSIONS: Berechtigungen
PLURALNAME: Gruppen
PLURALS:
one: 'Eine Gruppe'
other: '{count} Gruppen'
Parent: Elterngruppe
ROLES: Rollen
ROLESDESCRIPTION: 'Hier können Sie der Gruppe Rollen zuweisen.<br />Rollen sind logische Gruppierungen von Berechtigungen.'
RolesAddEditLink: 'Rollen hinzufügen/editieren'
SINGULARNAME: Gruppe
Sort: Sortierreihenfolge
has_many_Permissions: Berechtigungen
many_many_Members: Mitglieder
SilverStripe\Security\LoginAttempt:
Email: E-Mail-Adresse
EmailHashed: 'E-Mail-Adresse (gehashed)'
IP: IP-Adresse
PLURALNAME: Loginversuche
PLURALS:
one: 'Ein Loginversuch'
other: '{count} Loginversuche'
SINGULARNAME: Loginversuch
Status: Status
SilverStripe\Security\Member:
@ -119,6 +207,7 @@ de:
BUTTONCHANGEPASSWORD: 'Passwort ändern'
BUTTONLOGIN: Einloggen
BUTTONLOGINOTHER: 'Als jemand anderes einloggen'
BUTTONLOGOUT: Abmelden
BUTTONLOSTPASSWORD: 'Ich habe mein Passwort vergessen'
CONFIRMNEWPASSWORD: 'Neues Passwort bestätigen'
CONFIRMPASSWORD: 'Passwort bestätigen'
@ -139,6 +228,10 @@ de:
PASSWORD: Passwort
PASSWORDEXPIRED: 'Ihr Passwort ist abgelaufen. Bitte wählen Sie ein neues Passwort.'
PLURALNAME: Benutzer
PLURALS:
one: 'Ein Mitglied'
other: '{count} Mitglieder'
REMEMBERME: 'Für das nächste Mal merken? (für {count} Tage auf diesem Gerät)'
SINGULARNAME: Benutzer
SUBJECTPASSWORDCHANGED: 'Ihr Passwort wurde geändert'
SUBJECTPASSWORDRESET: 'Ihr Link zur Passwortrücksetzung'
@ -151,8 +244,21 @@ de:
db_LockedOutUntil: 'Gesperrt bis'
db_Password: Passwort
db_PasswordExpiry: 'Ablaufdatum des Passworts'
SilverStripe\Security\MemberAuthenticator\CMSMemberLoginForm:
AUTHENTICATORNAME: 'CMS Benutzer Login Formular'
BUTTONFORGOTPASSWORD: 'Passwort vergessen'
BUTTONLOGIN: 'Wieder einloggen'
BUTTONLOGOUT: Abmelden
SilverStripe\Security\MemberAuthenticator\MemberAuthenticator:
ERRORWRONGCRED: 'Ihre Eingaben scheinen nicht richtig zu sein. Bitte versuchen Sie es erneut.'
NoPassword: 'Dieser Benutzer hat kein Passwort.'
SilverStripe\Security\MemberAuthenticator\MemberLoginForm:
AUTHENTICATORNAME: 'E-Mail & Passwort'
SilverStripe\Security\MemberPassword:
PLURALNAME: Benutzerpasswörter
PLURALS:
one: 'Ein Benutzerpasswort'
other: '{count} Benutzerpasswörter'
SINGULARNAME: Benutzerpasswort
SilverStripe\Security\PasswordValidator:
LOWCHARSTRENGTH: 'Bitte erhöhen Sie die Sicherheit des Passworts, indem Sie auch einige der folgenden Zeichen verwenden: {chars}'
@ -160,10 +266,15 @@ de:
TOOSHORT: 'Das Passwort ist zu kurz, es muss mindestens {minimum} Zeichen lang sein'
SilverStripe\Security\Permission:
AdminGroup: Administrator
CMS_ACCESS_CATEGORY: 'CMS Zugriff'
CONTENT_CATEGORY: Inhaltsberechtigungen
FULLADMINRIGHTS: 'Unbeschränkte Administratorenrechte'
FULLADMINRIGHTS_HELP: 'Schließt alle anderen verfügbaren Berechtigungen ein'
PERMISSIONS_CATEGORY: 'Rollen und Zugriffsberechtigungen'
PLURALNAME: Berechtigungen
PLURALS:
one: 'Eine Berechtigung'
other: '{count} Berechtigungen'
SINGULARNAME: Zugriffsberechtigung
UserPermissionsIntro: 'Durch das Hinzufügen von Benutzern zu Gruppen, werden dessen Rechte angepasst. Mehr Informationen über die Berechtigungen der einzelnen Gruppen erhältst Du im Abschnitt Gruppen.'
SilverStripe\Security\PermissionCheckboxSetField:
@ -174,12 +285,24 @@ de:
SilverStripe\Security\PermissionRole:
OnlyAdminCanApply: 'Nur Adminstratoren können beitreten'
PLURALNAME: Rollen
PLURALS:
one: 'Eine Rolle'
other: '{count} Rollen'
SINGULARNAME: Rolle
Title: Titel
SilverStripe\Security\PermissionRoleCode:
PLURALNAME: Berechtigungsrollencodes
PLURALS:
one: 'Ein Berechtigungsrollencode'
other: '{count} Berechtigungsrollencodes'
PermsError: 'Kann Berechtigungen dem Code "{code}" nicht hinzufügen (erfordert Administratorrechte)'
SINGULARNAME: Berechtigungsrollencode
SilverStripe\Security\RememberLoginHash:
PLURALNAME: 'Login Hashes'
PLURALS:
one: 'Ein Login Hash'
other: '{count} Login Hashes'
SINGULARNAME: 'Login Hash'
SilverStripe\Security\Security:
ALREADYLOGGEDIN: 'Sie haben keinen Zugriff auf diese Seite. Wenn Sie ein anderes Konto besitzen, mit dem Sie auf diese Seite zugreifen können, melden Sie sich bitte unten an.'
BUTTONSEND: 'Senden Sie mir den Link zur Passwortrücksetzung'
@ -188,6 +311,10 @@ de:
ENTERNEWPASSWORD: 'Bitte geben Sie ein neues Passwort ein'
ERRORPASSWORDPERMISSION: 'Sie müssen eingeloggt sein, um Ihr Passwort ändern zu können!'
LOGIN: Anmelden
LOGOUT: Abmelden
LOSTPASSWORDHEADER: 'Passwort vergessen'
NOTEPAGESECURED: 'Diese Seite ist geschützt. Bitte melden Sie sich an und Sie werden sofort weitergeleitet.'
NOTERESETLINKINVALID: '<p>Der Link zum Zurücksetzen des Passworts ist entweder nicht korrekt oder abgelaufen</p><p>Sie können <a href="{link1}">einen neuen Link anfordern</a> oder Ihr Passwort nach dem <a href="{link2}">einloggen</a> ändern.</p>'
NOTERESETPASSWORD: 'Geben Sie Ihre E-Mail-Adresse ein und wir werden Ihnen einen Link zuschicken, mit dem Sie Ihr Passwort zurücksetzen können.'
PASSWORDRESETSENTHEADER: 'Der Link zum Zurücksetzen des Passworts wurde gesendet'
PASSWORDRESETSENTTEXT: 'Vielen Dank! Wenn ein Account zu der E-Mail Adresse existiert, wurde eine E-Mail mit dem Link zum Zurücksetzen des Passworts verschickt.'

View File

@ -27,8 +27,6 @@ de_DE:
ATLEAST: 'Passwörter müssen mindestens {min} Zeichen lang sein.'
BETWEEN: 'Passwörter müssen {min} bis {max} Zeichen lang sein.'
SHOWONCLICKTITLE: 'Passwort ändern'
SilverStripe\Forms\DateField:
TODAY: heute
SilverStripe\Forms\DropdownField:
CHOOSE: (Auswählen)
CHOOSE_MODEL: '({name} auswählen)'
@ -45,10 +43,6 @@ de_DE:
SilverStripe\Forms\GridField\GridFieldDetailForm:
CancelBtn: Abbrechen
Save: Speichern
SilverStripe\Forms\GridField\GridFieldEditButton:
EDIT: Bearbeiten
SilverStripe\Forms\GridField\GridFieldFilterHeader:
Search: 'Suche "{name}"'
SilverStripe\Forms\GridField\GridFieldPaginator:
Page: Seite
SilverStripe\Forms\MoneyField:

View File

@ -95,8 +95,6 @@ eo:
DeletePermissionsFailure: 'Mankas permeso forigi'
Deleted: 'Forigita {type} {name}'
Save: Konservi
SilverStripe\Forms\GridField\GridFieldEditButton:
EDIT: Redakti
SilverStripe\Forms\GridField\GridFieldGroupDeleteAction:
UnlinkSelfFailure: 'Ne povas forigi vin el ĉi tiu grupo; vi perdus administrajn rajtojn'
SilverStripe\Forms\GridField\GridFieldPaginator:
@ -149,8 +147,6 @@ eo:
other: '{count} jaroj'
SilverStripe\ORM\FieldType\DBEnum:
ANY: Ajna
SilverStripe\ORM\FieldType\DBForeignKey:
DROPDOWN_THRESHOLD_FALLBACK_MESSAGE: 'Tro multaj objektoj; retropaŝa kampo uzata'
SilverStripe\ORM\Hierarchy:
LIMITED_TITLE: 'Tro da idoj ({count})'
SilverStripe\ORM\Hierarchy\Hierarchy:
@ -324,5 +320,3 @@ eo:
LOSTPASSWORDHEADER: 'Perdis pasvorton'
NOTEPAGESECURED: 'Tiu paĝo estas sekurigita. Enigu viajn akreditaĵojn sube kaj vi aliros pluen.'
NOTERESETPASSWORD: 'Enigu vian retpoŝtan adreson kaj ni sendos al vi ligilon per kiu vi povas reagordi vian pasvorton'
PASSWORDRESETSENTHEADER: 'Pasvorta reagorda ligilo sendiĝis'
PASSWORDRESETSENTTEXT: 'Dankon. Reagorda ligilo sendiĝis, kondiĉe ke konto ekzistas por ĉi tiu retadreso.'

View File

@ -76,7 +76,6 @@ fi:
LinkExisting: 'Linkitä olemassaoleva'
NewRecord: 'Uusi {type}'
NoItemsFound: 'Ei kohteita'
OpenFilter: 'Avaa haku ja suodatus'
PRINTEDAT: Tulostettu
PRINTEDBY: Tulostaja
PlaceHolder: 'Etsi {type}'
@ -96,19 +95,12 @@ fi:
DeletePermissionsFailure: 'Ei oikeuksia poistamiseen'
Deleted: 'Poistettiin {type} {name}'
Save: Tallenna
SilverStripe\Forms\GridField\GridFieldEditButton:
EDIT: Muokkaa
SilverStripe\Forms\GridField\GridFieldFilterHeader:
Search: 'Haku "{name}"'
SearchFormFaliure: 'Hakulomaketta ei pystytty luomaan.'
SilverStripe\Forms\GridField\GridFieldGroupDeleteAction:
UnlinkSelfFailure: 'Et voi siirtää itseäsi pois tästä ryhmästä: menettäisit pääkäyttäjän oikeudet'
SilverStripe\Forms\GridField\GridFieldPaginator:
OF: /
Page: Sivu
View: Näytä
SilverStripe\Forms\GridField\GridFieldViewButton:
VIEW: Avaa
SilverStripe\Forms\MoneyField:
FIELDLABELAMOUNT: Määrä
FIELDLABELCURRENCY: Valuutta
@ -155,8 +147,6 @@ fi:
other: '{count} vuotta'
SilverStripe\ORM\FieldType\DBEnum:
ANY: Yhtään
SilverStripe\ORM\FieldType\DBForeignKey:
DROPDOWN_THRESHOLD_FALLBACK_MESSAGE: 'Liian monta samaan liittyvää objektia: oletuskenttä käytössä'
SilverStripe\ORM\Hierarchy:
LIMITED_TITLE: 'Liian monta lapsiobjektia ({count}}'
SilverStripe\ORM\Hierarchy\Hierarchy:
@ -330,5 +320,3 @@ fi:
LOSTPASSWORDHEADER: 'Unohtunut salasana'
NOTEPAGESECURED: 'Tämä sivu on suojattu. Syötä tunnistetietosi alle niin pääset eteenpäin.'
NOTERESETPASSWORD: 'Syötä sähköpostiosoitteesi ja lähetämme sinulle linkin, jonka avulla saat palautettua salasanasi'
PASSWORDRESETSENTHEADER: 'Salasanan palautuslinkki lähetetty'
PASSWORDRESETSENTTEXT: 'Kiitos, palautuslinkki on lähetetty käyttäjätilille asetettuun sähköpostiosoitteeseen.'

View File

@ -95,8 +95,6 @@ it:
DeletePermissionsFailure: 'Non hai i permessi per eliminare'
Deleted: 'Eliminato {type} {name}'
Save: Salva
SilverStripe\Forms\GridField\GridFieldEditButton:
EDIT: Modifica
SilverStripe\Forms\GridField\GridFieldGroupDeleteAction:
UnlinkSelfFailure: 'Non è possibile rimuovere te stesso da questo gruppo, perderesti i diritti di admin'
SilverStripe\Forms\GridField\GridFieldPaginator:
@ -149,8 +147,6 @@ it:
other: '{count} anni'
SilverStripe\ORM\FieldType\DBEnum:
ANY: Qualsiasi
SilverStripe\ORM\FieldType\DBForeignKey:
DROPDOWN_THRESHOLD_FALLBACK_MESSAGE: 'Troppi oggetti correlati; campo di fallback in uso'
SilverStripe\ORM\Hierarchy:
LIMITED_TITLE: 'Troppi figli ({count})'
SilverStripe\ORM\Hierarchy\Hierarchy:
@ -324,5 +320,3 @@ it:
LOSTPASSWORDHEADER: 'Password smarrita'
NOTEPAGESECURED: 'La pagina è protetta. Inserisci le credenziali qui sotto per poter andare avanti.'
NOTERESETPASSWORD: 'Inserisci il tuo indirizzo e-mail e ti verrà inviato un link per poter azzerare la tua password.'
PASSWORDRESETSENTHEADER: 'Link di azzeramento password inviato'
PASSWORDRESETSENTTEXT: 'Grazie! Un link di azzeramento è stato inviato, supponendo un account esista a quell''indirizzo e-mail.'

View File

@ -76,7 +76,6 @@ nl:
LinkExisting: 'Koppel een bestaand item'
NewRecord: 'Nieuw {type}'
NoItemsFound: 'Geen items gevonden.'
OpenFilter: 'Zoeken en filteren openen'
PRINTEDAT: 'Geprint op'
PRINTEDBY: 'Geprint door'
PlaceHolder: 'Zoek {type}'
@ -96,19 +95,12 @@ nl:
DeletePermissionsFailure: 'Onvoldoende rechten om te verwijderen'
Deleted: '{type} {name} verwijderd'
Save: Opslaan
SilverStripe\Forms\GridField\GridFieldEditButton:
EDIT: Bewerken
SilverStripe\Forms\GridField\GridFieldFilterHeader:
Search: 'Zoek naar "{name}"'
SearchFormFaliure: 'Er kon geen zoekformulier worden aangemaakt'
SilverStripe\Forms\GridField\GridFieldGroupDeleteAction:
UnlinkSelfFailure: 'U kunt uzelf niet verwijderen van deze groep, omdat u dan geen admin-rechten meer heeft.'
SilverStripe\Forms\GridField\GridFieldPaginator:
OF: van
Page: Pagina
View: Bekijk
SilverStripe\Forms\GridField\GridFieldViewButton:
VIEW: Bekijk
SilverStripe\Forms\MoneyField:
FIELDLABELAMOUNT: Aantal
FIELDLABELCURRENCY: Munteenheid
@ -155,8 +147,6 @@ nl:
other: '{count} jaren'
SilverStripe\ORM\FieldType\DBEnum:
ANY: Elke
SilverStripe\ORM\FieldType\DBForeignKey:
DROPDOWN_THRESHOLD_FALLBACK_MESSAGE: 'Teveel keuzes in de lijst; een alternatief veld wordt getoond.'
SilverStripe\ORM\Hierarchy:
LIMITED_TITLE: 'Teveel onderliggende items ({count})'
SilverStripe\ORM\Hierarchy\Hierarchy:
@ -172,8 +162,8 @@ nl:
PASSWORDEXPIRED: '<p>Uw wachtwoord is verlopen. <a target="_top" href="{link}">Kies een nieuw wachtwoord.</a></p>'
SilverStripe\Security\CMSSecurity:
INVALIDUSER: '<p>Ongeldige gebruiker <a target="_top" href="{link}">Log hier opnieuw in</a> om verder te gaan.</p>'
LOGIN_MESSAGE: 'Sessie is verlopen'
LOGIN_TITLE: '<p>U kunt verder met wat u aan het doen was, door opnieuw in te loggen.</p>'
LOGIN_MESSAGE: '<p>De browsersessie is verlopen wegens inactiviteit</p>'
LOGIN_TITLE: 'Log opnieuw in om verder te gaan.'
SUCCESS: Succes
SUCCESSCONTENT: '<p>U bent ingelogd. <a target="_top" href="{link}">Klik hier</a> als u niet automatisch wordt doorgestuurd.</p>'
SUCCESS_TITLE: 'Inloggen is gelukt'
@ -329,7 +319,4 @@ nl:
LOGOUT: Uitloggen
LOSTPASSWORDHEADER: 'Wachtwoord vergeten'
NOTEPAGESECURED: 'Deze pagina is beveiligd. Voer uw gegevens in en u wordt automatisch doorgestuurd.'
NOTERESETLINKINVALID: '<p>De reset link is ongeldig of komen te vervallen.</p><p>Je kan <a href="{link1}">hier</a> een nieuwe link aanvragen of het wachtwoord veranderen nadat je bent <a href="{link2}">ingelogd</a>.</p>'
NOTERESETPASSWORD: 'Voer uw e-mailadres in en we sturen een link waarmee u een nieuw wachtwoord kunt instellen.'
PASSWORDRESETSENTHEADER: 'link om uw wachtwoord opnieuw aan te maken'
PASSWORDRESETSENTTEXT: 'Bedankt! Er is een link verstuurd om uw wachtwoord opnieuw in te stellen (mits het mailadres reeds bekend is bij ons).'

View File

@ -93,8 +93,6 @@ sv:
DeletePermissionsFailure: 'Rättighet för att radera saknas'
Deleted: 'Raderade {type} {name}'
Save: Spara
SilverStripe\Forms\GridField\GridFieldEditButton:
EDIT: Ändra
SilverStripe\Forms\GridField\GridFieldGroupDeleteAction:
UnlinkSelfFailure: 'Du kan inte radera dig själv från den här gruppen, då du då kommer att förlora dina admin-rättigheter'
SilverStripe\Forms\GridField\GridFieldPaginator:
@ -147,8 +145,6 @@ sv:
other: '{count} år'
SilverStripe\ORM\FieldType\DBEnum:
ANY: 'Vilken som helst'
SilverStripe\ORM\FieldType\DBForeignKey:
DROPDOWN_THRESHOLD_FALLBACK_MESSAGE: 'För många relaterade objekt; använder fallback-fält'
SilverStripe\ORM\Hierarchy:
LIMITED_TITLE: 'För många barn ({count})'
SilverStripe\ORM\Hierarchy\Hierarchy:
@ -303,5 +299,3 @@ sv:
LOSTPASSWORDHEADER: 'Bortglömt lösenord'
NOTEPAGESECURED: 'Den här sidan är låst. Fyll i dina uppgifter nedan så skickar vi dig vidare.'
NOTERESETPASSWORD: 'Ange din e-postadress så skickar vi en länk med vilken du kan återställa ditt lösenord'
PASSWORDRESETSENTHEADER: 'Återställningslänk för lösenord skickad'
PASSWORDRESETSENTTEXT: 'Tack. En återställningslänk har skickats, förutsatt att ett konto med denna adress existerar.'

View File

@ -0,0 +1,70 @@
<?php
namespace SilverStripe\Control\Middleware;
use SilverStripe\Assets\File;
use SilverStripe\Control\Director;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Dev\Debug;
/**
* Display execution metricts for the current request if in dev mode and `execmetric` is provided as a request variable.
*/
class ExecMetricMiddleware implements HTTPMiddleware
{
public function process(HTTPRequest $request, callable $delegate)
{
if (!$this->showMetric($request)) {
return $delegate($request);
}
$start = microtime(true);
try {
return $delegate($request);
} finally {
$end = microtime(true);
Debug::message(
sprintf(
"Execution time: %s, Peak memory usage: %s\n",
$this->formatExecutionTime($start, $end),
$this->formatPeakMemoryUsage()
),
false
);
}
}
/**
* Check if execution metric should be shown.
* @param HTTPRequest $request
* @return bool
*/
private function showMetric(HTTPRequest $request)
{
return Director::isDev() && array_key_exists('execmetric', $request->getVars());
}
/**
* Convert the provided start and end time to a interval in secs.
* @param float $start
* @param float $end
* @return string
*/
private function formatExecutionTime($start, $end)
{
$diff = round($end - $start, 4);
return $diff . ' seconds';
}
/**
* Get the peak memory usage formatted has a string and a meaningful unit.
* @return string
*/
private function formatPeakMemoryUsage()
{
$bytes = memory_get_peak_usage(true);
return File::format_size($bytes);
}
}

View File

@ -196,7 +196,7 @@ class RequestHandler extends ViewableData
if (!$this->hasAction($action)) {
return $this->httpError(404, "Action '$action' isn't available $classMessage.");
}
if (!$this->checkAccessAction($action) || in_array(strtolower($action), ['run', 'doInit'])) {
if (!$this->checkAccessAction($action) || in_array(strtolower($action), ['run', 'doinit'])) {
return $this->httpError(403, "Action '$action' isn't allowed $classMessage.");
}
$result = $this->handleAction($request, $action);
@ -414,7 +414,7 @@ class RequestHandler extends ViewableData
$actionsWithoutExtra = $this->config()->get('allowed_actions', true);
if (!is_array($actions) || !$actionsWithoutExtra) {
if ($action != 'doInit' && $action != 'run' && method_exists($this, $action)) {
if (!in_array(strtolower($action), ['run', 'doinit']) && method_exists($this, $action)) {
return true;
}
}

View File

@ -9,46 +9,43 @@ use SilverStripe\Dev\Deprecation;
/**
* Handles all manipulation of the session.
*
* The static methods are used to manipulate the currently active controller's session.
* The instance methods are used to manipulate a particular session. There can be more than one of these created.
* An instance of a `Session` object can be retrieved via an `HTTPRequest` by calling the `getSession()` method.
*
* In order to support things like testing, the session is associated with a particular Controller. In normal usage,
* this is loaded from and saved to the regular PHP session, but for things like static-page-generation and
* unit-testing, you can create multiple Controllers, each with their own session.
*
* The instance object is basically just a way of manipulating a set of nested maps, and isn't specific to session
* data.
*
* <b>Saving Data</b>
*
* You can write a value to a users session from your PHP code using the static function {@link Session::set()}. You
* can add this line in any function or file you wish to save the value.
* Once you've retrieved a session instance, you can write a value to a users session using the function {@link Session::set()}.
*
* <code>
* Session::set('MyValue', 6);
* $request->getSession()->set('MyValue', 6);
* </code>
*
* Saves the value of "6" to the MyValue session data. You can also save arrays or serialized objects in session (but
* note there may be size restrictions as to how much you can save)
*
* <code>
*
* $session = $request->getSession();
*
* // save a variable
* $var = 1;
* Session::set('MyVar', $var);
* $session->set('MyVar', $var);
*
* // saves an array
* Session::set('MyArrayOfValues', array('1', '2', '3'));
* $session->set('MyArrayOfValues', array('1', '2', '3'));
*
* // saves an object (you'll have to unserialize it back)
* $object = new Object();
*
* Session::set('MyObject', serialize($object));
* $session->set('MyObject', serialize($object));
* </code>
*
* <b>Accessing Data</b>
*
* Once you have saved a value to the Session you can access it by using the {@link Session::get()} function.
* Like the {@link Session::set()} function you can use this anywhere in your PHP files.
* Note that session data isn't persisted in PHP's own session store (via $_SESSION)
* until {@link Session::save()} is called, which happens automatically at the end of a standard request
* through {@link SilverStripe\Control\Middleware\SessionMiddleware}.
@ -57,17 +54,18 @@ use SilverStripe\Dev\Deprecation;
*
* <code>
* public function bar() {
* $value = Session::get('MyValue'); // $value = 6
* $var = Session::get('MyVar'); // $var = 1
* $array = Session::get('MyArrayOfValues'); // $array = array(1,2,3)
* $object = Session::get('MyObject', unserialize($object)); // $object = Object()
* $session = $this->getRequest()->getSession();
* $value = $session->get('MyValue'); // $value = 6
* $var = $session->get('MyVar'); // $var = 1
* $array = $session->get('MyArrayOfValues'); // $array = array(1,2,3)
* $object = $session->get('MyObject', unserialize($object)); // $object = Object()
* }
* </code>
*
* You can also get all the values in the session at once. This is useful for debugging.
*
* <code>
* Session::get_all(); // returns an array of all the session values.
* $session->getAll(); // returns an array of all the session values.
* </code>
*
* <b>Clearing Data</b>
@ -76,17 +74,18 @@ use SilverStripe\Dev\Deprecation;
* to specifically remove it. To clear a value you can either delete 1 session value by the name that you saved it
*
* <code>
* Session::clear('MyValue'); // MyValue is no longer 6.
* $session->clear('MyValue'); // MyValue is no longer 6.
* </code>
*
* Or you can clear every single value in the session at once. Note SilverStripe stores some of its own session data
* including form and page comment information. None of this is vital but clear_all will clear everything.
* including form and page comment information. None of this is vital but `clearAll()` will clear everything.
*
* <code>
* Session::clear_all();
* $session->clearAll();
* </code>
*
* @see Cookie
* @see HTTPRequest
*/
class Session
{
@ -300,55 +299,52 @@ class Session
// If the session cookie is already set, then the session can be read even if headers_sent() = true
// This helps with edge-case such as debugging.
if (!session_id() && (!headers_sent() || !empty($_COOKIE[ini_get('session.name')]))) {
$data = [];
if (!session_id() && (!headers_sent() || $this->requestContainsSessionId($request))) {
if (!headers_sent()) {
session_set_cookie_params($timeout, $path, $domain ?: null, $secure, true);
session_set_cookie_params($timeout ?: 0, $path, $domain ?: null, $secure, true);
$limiter = $this->config()->get('sessionCacheLimiter');
if (isset($limiter)) {
session_cache_limiter($limiter);
}
// If headers are sent then we can't have a session_cache_limiter otherwise we'll get a warning
// Allow storing the session in a non standard location
if ($session_path) {
session_save_path($session_path);
}
// If we want a secure cookie for HTTPS, use a separate session name. This lets us have a
// separate (less secure) session for non-HTTPS requests
// if headers_sent() is true then it's best to throw the resulting error rather than risk
// a security hole.
if ($secure) {
session_name($this->config()->get('cookie_name_secure'));
}
session_start();
// Session start emits a cookie, but only if there's no existing session. If there is a session timeout
// tied to this request, make sure the session is held for the entire timeout by refreshing the cookie age.
if ($timeout && $this->requestContainsSessionId($request)) {
Cookie::set(session_name(), session_id(), $timeout / 86400, $path, $domain ?: null, $secure, true);
}
} else {
// If headers are sent then we can't have a session_cache_limiter otherwise we'll get a warning
session_cache_limiter(null);
}
// Allow storing the session in a non standard location
if ($session_path) {
session_save_path($session_path);
}
// If we want a secure cookie for HTTPS, use a seperate session name. This lets us have a
// seperate (less secure) session for non-HTTPS requests. Note that if this causes problems
// if headers_sent() is true then it's best to throw the resulting error rather than risk
// a security hole.
if ($secure) {
session_name($this->config()->get('cookie_name_secure'));
}
session_start();
if (isset($_SESSION)) {
// Initialise data from session store if present
$data = $_SESSION;
// Merge in existing in-memory data, taking priority over session store data
$this->recursivelyApply((array)$this->data, $data);
} else {
// Use in-memory data if the session is lazy started
$data = $this->data;
}
$this->data = $data ?: [];
} else {
$this->data = [];
}
// Modify the timeout behaviour so it's the *inactive* time before the session expires.
// By default it's the total session lifetime
if ($timeout && !headers_sent()) {
Cookie::set(session_name(), session_id(), $timeout/86400, $path, $domain ? $domain
: null, $secure, true);
}
// Save any modified session data back to the session store if present, otherwise initialise it to an array.
$this->data = $data;
$this->started = true;
}
@ -438,7 +434,7 @@ class Session
}
$var[] = $val;
$diffVar[sizeof($var)-1] = $val;
$diffVar[sizeof($var) - 1] = $val;
}
/**

View File

@ -133,15 +133,15 @@ class SimpleResourceURLGenerator implements ResourceURLGenerator
$exists = $resource->exists();
$absolutePath = $resource->getPath();
// Rewrite to resources with public directory
// Rewrite to _resources with public directory
if (Director::publicDir()) {
// All resources mapped directly to resources/
$relativePath = Path::join(ManifestFileFinder::RESOURCES_DIR, $relativePath);
// All resources mapped directly to _resources/
$relativePath = Path::join(RESOURCES_DIR, $relativePath);
} elseif (stripos($relativePath, ManifestFileFinder::VENDOR_DIR . DIRECTORY_SEPARATOR) === 0) {
// @todo Non-public dir support will be removed in 5.0, so remove this block there
// If there is no public folder, map to resources/ but trim leading vendor/ too (4.0 compat)
// If there is no public folder, map to _resources/ but trim leading vendor/ too (4.0 compat)
$relativePath = Path::join(
ManifestFileFinder::RESOURCES_DIR,
RESOURCES_DIR,
substr($relativePath, strlen(ManifestFileFinder::VENDOR_DIR))
);
}
@ -167,10 +167,10 @@ class SimpleResourceURLGenerator implements ResourceURLGenerator
$absolutePath = Path::join(Director::baseFolder(), $relativePath);
$exists = file_exists($absolutePath);
// Rewrite vendor/ to resources/ folder
// Rewrite vendor/ to _resources/ folder
if (stripos($relativePath, ManifestFileFinder::VENDOR_DIR . DIRECTORY_SEPARATOR) === 0) {
$relativePath = Path::join(
ManifestFileFinder::RESOURCES_DIR,
RESOURCES_DIR,
substr($relativePath, strlen(ManifestFileFinder::VENDOR_DIR))
);
}
@ -200,7 +200,7 @@ class SimpleResourceURLGenerator implements ResourceURLGenerator
/**
* Resolve a resource that may either exist in a public/ folder, or be exposed from the base path to
* public/resources/
* public/_resources/
*
* @param string $relativePath
* @return array List of [$exists, $absolutePath, $relativePath]
@ -217,11 +217,11 @@ class SimpleResourceURLGenerator implements ResourceURLGenerator
return [true, $publicPath, $relativePath];
}
// Fall back to private path (and assume expose will make this available to resources/)
// Fall back to private path (and assume expose will make this available to _resources/)
$privatePath = Path::join(Director::baseFolder(), $relativePath);
if (!$publicOnly && file_exists($privatePath)) {
// String is private but exposed to resources/, so rewrite to the symlinked base
$relativePath = Path::join(ManifestFileFinder::RESOURCES_DIR, $relativePath);
// String is private but exposed to _resources/, so rewrite to the symlinked base
$relativePath = Path::join(RESOURCES_DIR, $relativePath);
return [true, $privatePath, $relativePath];
}

View File

@ -279,6 +279,10 @@ trait CustomMethods
$methods = $this->findMethodsFromExtension($extension);
if ($methods) {
foreach ($methods as $method) {
if (!isset(self::$extra_methods[$class][$method])) {
continue;
}
$methodInfo = self::$extra_methods[$class][$method];
if ($methodInfo['property'] === $property && $methodInfo['index'] === $index) {

View File

@ -22,7 +22,11 @@ class ManifestFileFinder extends FileFinder
const LANG_DIR = 'lang';
const TESTS_DIR = 'tests';
const VENDOR_DIR = 'vendor';
const RESOURCES_DIR = 'resources';
/**
* @deprecated 4.4.0:5.0.0 Use global `RESOURCES_DIR` instead.
*/
const RESOURCES_DIR = RESOURCES_DIR;
protected static $default_options = array(
'include_themes' => false,
@ -41,6 +45,11 @@ class ManifestFileFinder extends FileFinder
// Keep searching inside vendor
$inVendor = $this->isInsideVendor($basename, $pathname, $depth);
if ($inVendor) {
// Skip nested vendor folders (e.g. vendor/silverstripe/framework/vendor)
if ($depth == 4 && basename($pathname) === self::VENDOR_DIR) {
return false;
}
// Keep searching if we could have a subdir module
if ($depth < 3) {
return true;
@ -236,7 +245,7 @@ class ManifestFileFinder extends FileFinder
}
// Ignore these dirs in the root only
if ($depth === 1 && in_array($basename, [ASSETS_DIR, self::RESOURCES_DIR])) {
if ($depth === 1 && in_array($basename, [ASSETS_DIR, RESOURCES_DIR])) {
return true;
}

View File

@ -8,6 +8,10 @@ use Serializable;
use SilverStripe\Core\Path;
use SilverStripe\Dev\Deprecation;
/**
* Abstraction of a PHP Package. Can be used to retrieve information about SilverStripe modules, and other packages
* managed via composer, by reading their `composer.json` file.
*/
class Module implements Serializable
{
/**
@ -124,6 +128,20 @@ class Module implements Serializable
return basename($this->path);
}
/**
* Name of the resource directory where vendor resources should be exposed as defined by the `extra.resources-dir`
* key in the composer file. A blank string will will be returned if the key is undefined.
*
* Only applicable when reading the composer file for the main project.
* @return string
*/
public function getResourcesDir()
{
return isset($this->composerData['extra']['resources-dir'])
? $this->composerData['extra']['resources-dir']
: '';
}
/**
* Get base path for this module
*

View File

@ -49,7 +49,7 @@ class ModuleResource
/**
* Return the full filesystem path to this resource.
*
* Note: In the case that this resource is mapped to the `resources` folder, this will
* Note: In the case that this resource is mapped to the `_resources` folder, this will
* return the original rather than the copy / symlink.
*
* @return string Path with no trailing slash E.g. /var/www/module
@ -62,7 +62,7 @@ class ModuleResource
/**
* Get the path of this resource relative to the base path.
*
* Note: In the case that this resource is mapped to the `resources` folder, this will
* Note: In the case that this resource is mapped to the `_resources` folder, this will
* return the original rather than the copy / symlink.
*
* @return string Relative path (no leading /)
@ -81,7 +81,7 @@ class ModuleResource
* Public URL to this resource.
* Note: May be either absolute url, or root-relative url
*
* In the case that this resource is mapped to the `resources` folder this
* In the case that this resource is mapped to the `_resources` folder this
* will be the mapped url rather than the original path.
*
* @return string

View File

@ -64,7 +64,7 @@ class ViewableDataContains extends PHPUnit_Framework_Constraint implements TestO
$success = true;
foreach ($this->match as $fieldName => $value) {
if ($other->getField($fieldName) != $value) {
if ($other->$fieldName != $value) {
$success = false;
break;
}

View File

@ -54,9 +54,9 @@ class Installer
protected function installHeader()
{
$clientPath = PUBLIC_DIR
? 'resources/vendor/silverstripe/framework/src/Dev/Install/client'
: 'resources/silverstripe/framework/src/Dev/Install/client';
$clientPath = RESOURCES_DIR . (PUBLIC_DIR
? '/vendor/silverstripe/framework/src/Dev/Install/client'
: '/silverstripe/framework/src/Dev/Install/client');
?>
<html>
<head>
@ -611,7 +611,7 @@ TEXT;
<li id="ModRewriteResult">Testing...</li>
<script>
if (typeof $ == 'undefined') {
document.getElemenyById('ModeRewriteResult').innerHTML = "I can't run jQuery ajax to set rewriting; I will redirect you to the homepage to see if everything is working.";
document.getElementById('ModeRewriteResult').innerHTML = "I can't run jQuery ajax to set rewriting; I will redirect you to the homepage to see if everything is working.";
setTimeout(function() {
window.location = "$destinationURL";
}, 10000);

View File

@ -106,9 +106,9 @@ if ($installFromCli && ($req->hasErrors() || $dbReq->hasErrors())) {
// Path to client resources (copied through silverstripe/vendor-plugin)
$base = rtrim(BASE_URL, '/') . '/';
$clientPath = PUBLIC_DIR
? 'resources/vendor/silverstripe/framework/src/Dev/Install/client'
: 'resources/silverstripe/framework/src/Dev/Install/client';
$clientPath = RESOURCES_DIR . (PUBLIC_DIR
? '/vendor/silverstripe/framework/src/Dev/Install/client'
: '/silverstripe/framework/src/Dev/Install/client');
// If already installed, ensure the user clicked "reinstall"
$expectedArg = $alreadyInstalled ? 'reinstall' : 'go';

View File

@ -20,12 +20,12 @@ use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Core\Injector\InjectorLoader;
use SilverStripe\Core\Manifest\ClassLoader;
use SilverStripe\Core\Manifest\ModuleResourceLoader;
use SilverStripe\Dev\Constraint\SSListContains;
use SilverStripe\Dev\Constraint\SSListContainsOnly;
use SilverStripe\Dev\Constraint\SSListContainsOnlyMatchingItems;
use SilverStripe\Dev\State\FixtureTestState;
use SilverStripe\Dev\State\SapphireTestState;
use SilverStripe\Dev\State\TestState;
use SilverStripe\i18n\i18n;
use SilverStripe\ORM\Connect\TempDatabase;
use SilverStripe\ORM\DataObject;
@ -38,7 +38,6 @@ use SilverStripe\Security\Member;
use SilverStripe\Security\Permission;
use SilverStripe\Security\Security;
use SilverStripe\View\SSViewer;
use SilverStripe\Core\Manifest\ModuleResourceLoader;
if (!class_exists(PHPUnit_Framework_TestCase::class)) {
return;
@ -65,6 +64,7 @@ class SapphireTest extends PHPUnit_Framework_TestCase implements TestOnly
protected static $fixture_file = null;
/**
* @deprecated 4.0..5.0 Use FixtureTestState instead
* @var FixtureFactory
*/
protected $fixtureFactory;
@ -299,9 +299,11 @@ class SapphireTest extends PHPUnit_Framework_TestCase implements TestOnly
$fixtureFiles = $this->getFixturePaths();
if ($this->shouldSetupDatabaseForCurrentTest($fixtureFiles)) {
// Assign fixture factory to deprecated prop in case old tests use it over the getter
/** @var FixtureTestState $fixtureState */
$fixtureState = static::$state->getStateByName('fixtures');
$this->setFixtureFactory($fixtureState->getFixtureFactory(static::class));
$this->fixtureFactory = $fixtureState->getFixtureFactory(static::class);
$this->logInWithPermission('ADMIN');
}
@ -452,7 +454,9 @@ class SapphireTest extends PHPUnit_Framework_TestCase implements TestOnly
*/
protected function idFromFixture($className, $identifier)
{
$id = $this->getFixtureFactory()->getId($className, $identifier);
/** @var FixtureTestState $state */
$state = static::$state->getStateByName('fixtures');
$id = $state->getFixtureFactory(static::class)->getId($className, $identifier);
if (!$id) {
user_error(sprintf(
@ -474,7 +478,9 @@ class SapphireTest extends PHPUnit_Framework_TestCase implements TestOnly
*/
protected function allFixtureIDs($className)
{
return $this->getFixtureFactory()->getIds($className);
/** @var FixtureTestState $state */
$state = static::$state->getStateByName('fixtures');
return $state->getFixtureFactory(static::class)->getIds($className);
}
/**
@ -487,7 +493,9 @@ class SapphireTest extends PHPUnit_Framework_TestCase implements TestOnly
*/
protected function objFromFixture($className, $identifier)
{
$obj = $this->getFixtureFactory()->get($className, $identifier);
/** @var FixtureTestState $state */
$state = static::$state->getStateByName('fixtures');
$obj = $state->getFixtureFactory(static::class)->get($className, $identifier);
if (!$obj) {
user_error(sprintf(
@ -521,7 +529,9 @@ class SapphireTest extends PHPUnit_Framework_TestCase implements TestOnly
*/
public function clearFixtures()
{
$this->getFixtureFactory()->clear();
/** @var FixtureTestState $state */
$state = static::$state->getStateByName('fixtures');
$state->getFixtureFactory(static::class)->clear();
}
/**

View File

@ -17,8 +17,9 @@ class MigrateFileTask extends BuildTask
protected $title = 'Migrate File dataobjects from 3.x';
protected $description
= 'Imports all files referenced by File dataobjects into the new Asset Persistence Layer introduced in 4.0';
protected $description =
'Imports all files referenced by File dataobjects into the new Asset Persistence Layer introduced in 4.0. ' .
'If the task fails or times out, run it again and it will start where it left off.';
public function run($request)
{
@ -27,6 +28,11 @@ class MigrateFileTask extends BuildTask
return;
}
DB::alteration_message(
'If the task fails or times out, run it again and it will start where it left off.',
"info"
);
$migrated = FileMigrationHelper::singleton()->run();
if ($migrated) {
DB::alteration_message("{$migrated} File DataObjects upgraded", "changed");
@ -38,7 +44,6 @@ class MigrateFileTask extends BuildTask
DB::alteration_message("No image thumbnail helper detected", "notice");
return;
}
ImageThumbnailHelper::singleton()->run();
}
}

View File

@ -72,6 +72,7 @@ class CheckboxSetField extends MultiSelectField
{
$selectedValues = $this->getValueArray();
$defaultItems = $this->getDefaultItems();
$disabledItems = $this->getDisabledItems();
// Generate list of options to display
$odd = false;
@ -84,7 +85,7 @@ class CheckboxSetField extends MultiSelectField
$extraClass .= ' val' . preg_replace('/[^a-zA-Z0-9\-\_]/', '_', $itemValue);
$itemChecked = in_array($itemValue, $selectedValues) || in_array($itemValue, $defaultItems);
$itemDisabled = $this->isDisabled() || in_array($itemValue, $defaultItems);
$itemDisabled = $this->isDisabled() || in_array($itemValue, $disabledItems);
$options->push(new ArrayData(array(
'ID' => $itemID,

View File

@ -106,7 +106,7 @@ class DropdownField extends SingleSelectField
}
return new ArrayData(array(
'Title' => $title,
'Title' => (string)$title,
'Value' => $value,
'Selected' => $selected,
'Disabled' => $disabled,

View File

@ -286,6 +286,9 @@ class Form extends ViewableData implements HasRequestHandler
) {
parent::__construct();
$fields = $fields ? $fields : FieldList::create();
$actions = $actions ? $actions : FieldList::create();
$fields->setForm($this);
$actions->setForm($this);
@ -484,11 +487,13 @@ class Form extends ViewableData implements HasRequestHandler
// Set message on either a field or the parent form
foreach ($result->getMessages() as $message) {
$fieldName = $message['fieldName'];
if ($fieldName) {
$owner = $this->fields->dataFieldByName($fieldName) ?: $this;
} else {
$owner = $this;
}
$owner->setMessage($message['message'], $message['messageType'], $message['messageCast']);
}
return $this;
@ -772,7 +777,9 @@ class Form extends ViewableData implements HasRequestHandler
*/
public function setFields($fields)
{
$fields->setForm($this);
$this->fields = $fields;
return $this;
}
@ -794,7 +801,9 @@ class Form extends ViewableData implements HasRequestHandler
*/
public function setActions($actions)
{
$actions->setForm($this);
$this->actions = $actions;
return $this;
}
@ -1405,8 +1414,9 @@ class Form extends ViewableData implements HasRequestHandler
$submitted = true;
}
// dont include fields without data
// Don't include fields without data
$dataFields = $this->Fields()->dataFields();
if (!$dataFields) {
return $this;
}

View File

@ -295,8 +295,8 @@ class FormField extends RequestHandler
*
* Examples:
*
* - 'TotalAmount' will return 'Total Amount'
* - 'Organisation.ZipCode' will return 'Organisation Zip Code'
* - 'TotalAmount' will return 'Total amount'
* - 'Organisation.ZipCode' will return 'Organisation zip code'
*
* @param string $fieldName
*
@ -304,15 +304,27 @@ class FormField extends RequestHandler
*/
public static function name_to_label($fieldName)
{
// Handle dot delimiters
if (strpos($fieldName, '.') !== false) {
$parts = explode('.', $fieldName);
$label = $parts[count($parts) - 2] . ' ' . $parts[count($parts) - 1];
// Ensure that any letter following a dot is uppercased, so that the regex below can break it up
// into words
$label = implode(array_map('ucfirst', $parts));
} else {
$label = $fieldName;
}
return preg_replace('/([a-z]+)([A-Z])/', '$1 $2', $label);
// Replace any capital letter that is followed by a lowercase letter with a space, the lowercased
// version of itself then the remaining lowercase letters.
$labelWithSpaces = preg_replace_callback('/([A-Z])([a-z]+)/', function ($matches) {
return ' ' . strtolower($matches[1]) . $matches[2];
}, $label);
// Add a space before any capital letter block that is at the end of the string
$labelWithSpaces = preg_replace('/([a-z])([A-Z]+)$/', '$1 $2', $labelWithSpaces);
// The first letter should be uppercase
return ucfirst(trim($labelWithSpaces));
}
/**

View File

@ -0,0 +1,17 @@
<?php
namespace SilverStripe\Forms\GridField\FormAction;
use SilverStripe\Control\Controller;
use SilverStripe\Control\HTTPRequest;
abstract class AbstractRequestAwareStore implements StateStore
{
/**
* @return HTTPRequest
*/
public function getRequest()
{
// Replicating existing functionality from GridField_FormAction
return Controller::curr()->getRequest();
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace SilverStripe\Forms\GridField\FormAction;
/**
* Stores GridField action state on an attribute on the action and then analyses request parameters to load it back
*/
class AttributeStore extends AbstractRequestAwareStore
{
/**
* Save the given state against the given ID returning an associative array to be added as attributes on the form
* action
*
* @param string $id
* @param array $state
* @return array
*/
public function save($id, array $state)
{
// Just save the state in the attributes of the action
return [
'data-action-state' => json_encode($state),
];
}
/**
* Load state for a given ID
*
* @param string $id
* @return array
*/
public function load($id)
{
// Check the request
return (array) json_decode((string) $this->getRequest()->requestVar('ActionState'), true);
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace SilverStripe\Forms\GridField\FormAction;
use SilverStripe\Control\HTTPRequest;
/**
* Stores GridField action state in the session in exactly the same way it has in the past
*/
class SessionStore extends AbstractRequestAwareStore implements StateStore
{
/**
* Save the given state against the given ID returning an associative array to be added as attributes on the form
* action
*
* @param string $id
* @param array $state
* @return array
*/
public function save($id, array $state)
{
$this->getRequest()->getSession()->set($id, $state);
// This adapter does not require any additional attributes...
return [];
}
/**
* Load state for a given ID
*
* @param string $id
* @return array
*/
public function load($id)
{
return (array) $this->getRequest()->getSession()->get($id);
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace SilverStripe\Forms\GridField\FormAction;
interface StateStore
{
/**
* Save the given state against the given ID returning an associative array to be added as attributes on the form
* action
*
* @param string $id
* @param array $state
* @return array
*/
public function save($id, array $state);
/**
* Load state for a given ID
*
* @param string $id
* @return array
*/
public function load($id);
}

View File

@ -9,8 +9,11 @@ use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Control\HTTPResponse_Exception;
use SilverStripe\Control\RequestHandler;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Forms\Form;
use SilverStripe\Forms\FormField;
use SilverStripe\Forms\GridField\FormAction\SessionStore;
use SilverStripe\Forms\GridField\FormAction\StateStore;
use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\DataList;
use SilverStripe\ORM\DataObject;
@ -1009,9 +1012,14 @@ class GridField extends FormField
$state->setValue($fieldData['GridState']);
}
// Fetch the store for the "state" of actions (not the GridField)
/** @var StateStore $store */
$store = Injector::inst()->create(StateStore::class . '.' . $this->getName());
foreach ($data as $dataKey => $dataValue) {
if (preg_match('/^action_gridFieldAlterAction\?StateID=(.*)/', $dataKey, $matches)) {
$stateChange = $request->getSession()->get($matches[1]);
$stateChange = $store->load($matches[1]);
$actionName = $stateChange['actionName'];
$arguments = array();

View File

@ -289,14 +289,18 @@ class GridFieldFilterHeader implements GridField_URLHandler, GridField_HTMLProvi
}, array_keys($filters)), $filters);
}
$searchAction = GridField_FormAction::create($gridField, 'filter', false, 'filter', null);
$clearAction = GridField_FormAction::create($gridField, 'reset', false, 'reset', null);
$schema = [
'formSchemaUrl' => $schemaUrl,
'name' => $searchField,
'placeholder' => _t(__CLASS__ . '.Search', 'Search "{name}"', ['name' => $name]),
'filters' => $filters ?: new \stdClass, // stdClass maps to empty json object '{}'
'gridfield' => $gridField->getName(),
'searchAction' => GridField_FormAction::create($gridField, 'filter', false, 'filter', null)->getAttribute('name'),
'clearAction' => GridField_FormAction::create($gridField, 'reset', false, 'reset', null)->getAttribute('name')
'searchAction' => $searchAction->getAttribute('name'),
'searchActionState' => $searchAction->getAttribute('data-action-state'),
'clearAction' => $clearAction->getAttribute('name'),
'clearActionState' => $clearAction->getAttribute('data-action-state'),
];
return json_encode($schema);

View File

@ -133,7 +133,10 @@ class GridFieldPrintButton implements GridField_HTMLProvider, GridField_ActionPr
$this->extend('updatePrintData', $data);
if ($data) {
return $data->renderWith(get_class($gridField) . "_print");
return $data->renderWith([
get_class($gridField) . '_print',
GridField::class . '_print',
]);
}
return null;

View File

@ -3,8 +3,10 @@
namespace SilverStripe\Forms\GridField;
use SilverStripe\Control\Controller;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Forms\Form;
use SilverStripe\Forms\FormAction;
use SilverStripe\Forms\GridField\FormAction\StateStore;
/**
* This class is the base class when you want to have an action that alters the state of the
@ -12,6 +14,11 @@ use SilverStripe\Forms\FormAction;
*/
class GridField_FormAction extends FormAction
{
/**
* A common string prefix for keys generated to store form action "state" against
*/
const STATE_KEY_PREFIX = 'gf_';
/**
* @var GridField
*/
@ -80,29 +87,35 @@ class GridField_FormAction extends FormAction
*/
public function getAttributes()
{
// Store state in session, and pass ID to client side.
// Determine the state that goes with this action
$state = array(
'grid' => $this->getNameFromParent(),
'actionName' => $this->actionName,
'args' => $this->args,
);
// Ensure $id doesn't contain only numeric characters
$id = 'gf_' . substr(md5(serialize($state)), 0, 8);
// Generate a key and attach it to the action name
$key = static::STATE_KEY_PREFIX . substr(md5(serialize($state)), 0, 8);
// Note: This field needs to be less than 65 chars, otherwise Suhosin security patch will strip it
$name = 'action_gridFieldAlterAction?StateID=' . $key;
$session = Controller::curr()->getRequest()->getSession();
$session->set($id, $state);
$actionData['StateID'] = $id;
// Define attributes
$attributes = array(
'name' => $name,
'data-url' => $this->gridField->Link(),
'type' => "button",
);
// Create a "store" for the "state" of this action
/** @var StateStore $store */
$store = Injector::inst()->create(StateStore::class . '.' . $this->gridField->getName());
// Store the state and update attributes as required
$attributes += $store->save($key, $state);
// Return attributes
return array_merge(
parent::getAttributes(),
array(
// Note: This field needs to be less than 65 chars, otherwise Suhosin security patch
// will strip it from the requests
'name' => 'action_gridFieldAlterAction' . '?' . http_build_query($actionData),
'data-url' => $this->gridField->Link(),
'type' => "button",
)
$attributes
);
}

View File

@ -267,4 +267,34 @@ class TreeMultiselectField extends TreeDropdownField
$copy->setTitleField($this->getTitleField());
return $copy;
}
/**
* {@inheritdoc}
*
* @internal To be removed in 5.0
*/
protected function objectForKey($key)
{
/**
* Fixes https://github.com/silverstripe/silverstripe-framework/issues/8332
*
* Due to historic reasons, the default (empty) value for this field is 'unchanged', even though
* the field is usually integer on the database side.
* MySQL handles that gracefully and returns an empty result in that case,
* whereas some other databases (e.g. PostgreSQL) do not support comparison
* of numeric types with string values, issuing a database error.
*
* This fix is not ideal, but supposed to keep backward compatibility for SS4.
*
* In 5.0 this method to be removed and NULL should be used instead of 'unchanged' (or an empty array. to be decided).
* In 5.0 this class to be refactored so that $this->value is always an array of values (or null)
*/
if ($this->getKeyField() === 'ID' && $key === 'unchanged') {
$key = null;
} elseif (is_string($key)) {
$key = preg_split('/\s*,\s*/', trim($key));
}
return parent::objectForKey($key);
}
}

View File

@ -220,7 +220,7 @@ class MySQLSchemaManager extends DBSchemaManager
public function databaseExists($name)
{
// MySQLi doesn't like parameterised queries for some queries
$sqlName = $this->database->quoteString($name);
$sqlName = addcslashes($this->database->quoteString($name), '%_');
return !!($this->query("SHOW DATABASES LIKE $sqlName")->value());
}

View File

@ -947,9 +947,12 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* The field names can be simple names, or you can use a dot syntax to access $has_one relations.
* For example, array("Author.FirstName" => "Jim") will set $this->Author()->FirstName to "Jim".
*
* update() doesn't write the main object, but if you use the dot syntax, it will write()
* Doesn't write the main object, but if you use the dot syntax, it will write()
* the related objects that it alters.
*
* When using this method with user supplied data, it's very important to
* whitelist the allowed keys.
*
* @param array $data A map of field name to data values to update.
* @return DataObject $this
*/
@ -1418,8 +1421,10 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
// Inserts done one the base table are performed in another step, so the manipulation should instead
// attempt an update, as though it were a normal update.
$manipulation[$table]['command'] = $isNewRecord ? 'insert' : 'update';
$manipulation[$table]['id'] = $this->record['ID'];
$manipulation[$table]['class'] = $class;
if ($this->isInDB()) {
$manipulation[$table]['id'] = $this->record['ID'];
}
}
/**
@ -1438,10 +1443,10 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
}
// Perform an insert on the base table
$insert = new SQLInsert('"' . $baseTable . '"');
$insert
->assign('"Created"', $now)
->execute();
$manipulation = [];
$this->prepareManipulationTable($baseTable, $now, true, $manipulation, $this->baseClass());
DB::manipulate($manipulation);
$this->changed['ID'] = self::CHANGE_VALUE;
$this->record['ID'] = DB::get_generated_id($baseTable);
}
@ -1452,6 +1457,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* @param string $baseTable Base table
* @param string $now Timestamp to use for the current time
* @param bool $isNewRecord If this is a new record
* @throws InvalidArgumentException
*/
protected function writeManipulation($baseTable, $now, $isNewRecord)
{
@ -1470,6 +1476,24 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
$manipulation[$baseTable]['command'] = 'update';
}
// Make sure none of our field assignment are arrays
foreach ($manipulation as $tableManipulation) {
if (!isset($tableManipulation['fields'])) {
continue;
}
foreach ($tableManipulation['fields'] as $fieldName => $fieldValue) {
if (is_array($fieldValue)) {
$dbObject = $this->dbObject($fieldName);
// If the field allows non-scalar values we'll let it do dynamic assignments
if ($dbObject && $dbObject->scalarValueOnly()) {
throw new InvalidArgumentException(
'DataObject::writeManipulation: parameterised field assignments are disallowed'
);
}
}
}
}
// Perform the manipulation
DB::manipulate($manipulation);
}
@ -2238,7 +2262,15 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
// Otherwise, use the database field's scaffolder
} elseif ($object = $this->relObject($fieldName)) {
$field = $object->scaffoldSearchField();
if (is_object($object) && $object->hasMethod('scaffoldSearchField')) {
$field = $object->scaffoldSearchField();
} else {
throw new Exception(sprintf(
"SearchField '%s' on '%s' does not return a valid DBField instance.",
$fieldName,
get_class($this)
));
}
}
// Allow fields to opt out of search
@ -2646,15 +2678,27 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
throw new InvalidArgumentException('DataObject::setField: passed an object that is not a DBField');
}
if (!empty($val) && !is_scalar($val)) {
$dbField = $this->dbObject($fieldName);
if ($dbField && $dbField->scalarValueOnly()) {
throw new InvalidArgumentException(
sprintf(
'DataObject::setField: %s only accepts scalars',
$fieldName
)
);
}
}
// if a field is not existing or has strictly changed
if (!isset($this->original[$fieldName]) || $this->original[$fieldName] !== $val) {
if (!array_key_exists($fieldName, $this->original) || $this->original[$fieldName] !== $val) {
// TODO Add check for php-level defaults which are not set in the db
// TODO Add check for hidden input-fields (readonly) which are not set in the db
// At the very least, the type has changed
$this->changed[$fieldName] = self::CHANGE_STRICT;
if ((!isset($this->original[$fieldName]) && $val)
|| (isset($this->original[$fieldName]) && $this->original[$fieldName] != $val)
if ((!array_key_exists($fieldName, $this->original) && $val)
|| (array_key_exists($fieldName, $this->original) && $this->original[$fieldName] != $val)
) {
// Value has changed as well, not just the type
$this->changed[$fieldName] = self::CHANGE_VALUE;

View File

@ -8,6 +8,7 @@ use LogicException;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\Convert;
use SilverStripe\Core\Injector\Injectable;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\TestOnly;
@ -127,7 +128,7 @@ class DataObjectSchema
$tables = $this->getTableNames();
$class = ClassInfo::class_name($class);
if (isset($tables[$class])) {
return $tables[$class];
return Convert::raw2sql($tables[$class]);
}
return null;
}

View File

@ -339,4 +339,9 @@ abstract class DBComposite extends DBField
];
}
}
public function scalarValueOnly()
{
return false;
}
}

View File

@ -335,18 +335,16 @@ abstract class DBField extends ViewableData implements DBIndexable
* will be escaped automatically by the prepared query processor, so it
* should not be escaped or quoted at all.
*
* The field values could also be in paramaterised format, such as
* array('MAX(?,?)' => array(42, 69)), allowing the use of raw SQL values such as
* array('NOW()' => array()).
*
* @see SQLWriteExpression::addAssignments for syntax examples
*
* @param $value mixed The value to check
* @return mixed The raw value, or escaped parameterised details
*/
public function prepValueForDB($value)
{
if ($value === null || $value === "" || $value === false) {
if ($value === null ||
$value === "" ||
$value === false ||
($this->scalarValueOnly() && !is_scalar($value))
) {
return null;
} else {
return $value;
@ -653,4 +651,15 @@ DBG;
}
return null;
}
/**
* Whether or not this DBField only accepts scalar values.
*
* Composite DBFields can override this method and return `false` so they can accept arrays of values.
* @return boolean
*/
public function scalarValueOnly()
{
return true;
}
}

View File

@ -43,6 +43,13 @@ class DBForeignKey extends DBInt
private static $default_search_filter_class = 'ExactMatchFilter';
/**
* Cache for multiple subsequent calls to scaffold form fields with the same foreign key object
*
* @var array
*/
protected static $foreignListCache = [];
public function __construct($name, $object = null)
{
$this->object = $object;
@ -74,8 +81,21 @@ class DBForeignKey extends DBInt
// Don't scaffold a dropdown for large tables, as making the list concrete
// might exceed the available PHP memory in creating too many DataObject instances
$threshold = self::config()->get('dropdown_field_threshold');
if ($list->count() < $threshold) {
$field = new DropdownField($this->name, $title, $list->map('ID', $titleField));
// Add the count of the list to a cache for subsequent calls
if (!isset(static::$foreignListCache[$hasOneClass])) {
static::$foreignListCache[$hasOneClass] = [
'count' => $list->count(),
];
}
if (static::$foreignListCache[$hasOneClass]['count'] < $threshold) {
// Add the mapped list for the cache
if (!isset(static::$foreignListCache[$hasOneClass]['map'])) {
static::$foreignListCache[$hasOneClass]['map'] = $list->map('ID', $titleField);
}
$field = new DropdownField($this->name, $title, static::$foreignListCache[$hasOneClass]['map']);
$field->setEmptyString(' ');
} else {
$field = new NumericField($this->name, $title);

View File

@ -94,7 +94,7 @@ abstract class DBString extends DBField
public function prepValueForDB($value)
{
// Cast non-empty value
if (strlen($value)) {
if (is_scalar($value) && strlen($value)) {
return (string)$value;
}

View File

@ -284,6 +284,8 @@ class ManyManyList extends RelationList
];
}
/** @var DBField[] $fieldObjects */
$fieldObjects = [];
if ($extraFields && $this->extraFields) {
// Write extra field to manipluation in the same way
// that DataObject::prepareManipulationTable writes fields
@ -293,6 +295,7 @@ class ManyManyList extends RelationList
$fieldObject = Injector::inst()->create($fieldSpec, $fieldName);
$fieldObject->setValue($extraFields[$fieldName]);
$fieldObject->writeToManipulation($manipulation[$this->joinTable]);
$fieldObjects[$fieldName] = $fieldObject;
}
}
}
@ -300,6 +303,23 @@ class ManyManyList extends RelationList
$manipulation[$this->joinTable]['fields'][$this->localKey] = $itemID;
$manipulation[$this->joinTable]['fields'][$this->foreignKey] = $foreignID;
// Make sure none of our field assignments are arrays
foreach ($manipulation as $tableManipulation) {
if (!isset($tableManipulation['fields'])) {
continue;
}
foreach ($tableManipulation['fields'] as $fieldName => $fieldValue) {
if (is_array($fieldValue)) {
// If the field allows non-scalar values we'll let it do dynamic assignments
if (isset($fieldObjects[$fieldName]) && $fieldObjects[$fieldName]->scalarValueOnly()) {
throw new InvalidArgumentException(
'ManyManyList::add: parameterised field assignments are disallowed'
);
}
}
}
}
DB::manipulate($manipulation);
}
}

View File

@ -15,26 +15,56 @@ use SilverStripe\Forms\Form;
*/
abstract class LoginForm extends Form
{
/**
* Authenticator class to use with this login form
*
* Set this variable to the authenticator class to use with this login
* form.
* @deprecated 4.4.0:5.0.0 Use getAuthenticatorClass() or setAuthenticatorClass() instead
* @var string
*/
protected $authenticator_class;
/**
* Authenticator class to use with this login form
*
* Set this variable to the authenticator class to use with this login form.
*
* @var string
*/
protected $authenticatorClass;
/**
* Set the authenticator class name to use
*
* @param string $class
* @return $this
*/
public function setAuthenticatorClass($class)
{
$this->authenticator_class = $class;
$authenticatorField = $this->Fields()->dataFieldByName('AuthenticationMethod');
$this->authenticatorClass = $class;
/** @var FieldList|null $fields */
$fields = $this->Fields();
if (!$fields) {
return $this;
}
$authenticatorField = $fields->dataFieldByName('AuthenticationMethod');
if ($authenticatorField) {
$authenticatorField->setValue($class);
}
return $this;
}
/**
* Returns the authenticator class name to use
*
* @return string
*/
public function getAuthenticatorClass()
{
// B/C for deprecated authenticator_class property
return $this->authenticator_class ?: $this->authenticatorClass;
}
/**
* Return the title of the form for use in the frontend
* For tabs with multiple login methods, for example.

View File

@ -30,7 +30,7 @@ class CMSMemberLoginForm extends MemberLoginForm
{
$this->controller = $controller;
$this->authenticator_class = $authenticatorClass;
$this->setAuthenticatorClass($authenticatorClass);
$fields = $this->getFormFields();
@ -48,7 +48,7 @@ class CMSMemberLoginForm extends MemberLoginForm
{
// Set default fields
$fields = FieldList::create([
HiddenField::create("AuthenticationMethod", null, $this->authenticator_class, $this),
HiddenField::create("AuthenticationMethod", null, $this->getAuthenticatorClass(), $this),
HiddenField::create('tempid', null, $this->controller->getRequest()->requestVar('tempid')),
PasswordField::create("Password", _t('SilverStripe\\Security\\Member.PASSWORD', 'Password'))
]);

View File

@ -77,7 +77,7 @@ class MemberLoginForm extends BaseLoginForm
$checkCurrentUser = true
) {
$this->setController($controller);
$this->authenticator_class = $authenticatorClass;
$this->setAuthenticatorClass($authenticatorClass);
$customCSS = project() . '/css/member_login.css';
if (Director::fileExists($customCSS)) {
@ -88,7 +88,7 @@ class MemberLoginForm extends BaseLoginForm
// @todo find a more elegant way to handle this
$logoutAction = Security::logout_url();
$fields = FieldList::create(
HiddenField::create('AuthenticationMethod', null, $this->authenticator_class, $this)
HiddenField::create('AuthenticationMethod', null, $this->getAuthenticatorClass(), $this)
);
$actions = FieldList::create(
FormAction::create('logout', _t(
@ -133,7 +133,7 @@ class MemberLoginForm extends BaseLoginForm
$label = Member::singleton()->fieldLabel(Member::config()->get('unique_identifier_field'));
$fields = FieldList::create(
HiddenField::create("AuthenticationMethod", null, $this->authenticator_class, $this),
HiddenField::create("AuthenticationMethod", null, $this->getAuthenticatorClass(), $this),
// Regardless of what the unique identifer field is (usually 'Email'), it will be held in the
// 'Email' value, below:
// @todo Rename the field to a more generic covering name

View File

@ -69,6 +69,7 @@ class Permission extends DataObject implements TemplateGlobalProvider, Resettabl
* Method to globally disable "strict" checking, which means a permission
* will be granted if the key does not exist at all.
*
* @deprecated 4.4.0
* @var array
*/
private static $declared_permissions = null;
@ -76,6 +77,7 @@ class Permission extends DataObject implements TemplateGlobalProvider, Resettabl
/**
* Linear list of declared permissions in the system.
*
* @deprecated 4.4.0
* @var array
*/
private static $declared_permissions_list = null;
@ -650,6 +652,7 @@ class Permission extends DataObject implements TemplateGlobalProvider, Resettabl
* Get a linear list of the permissions in the system.
*
* @return array Linear list of declared permissions in the system.
* @deprecated 4.4.0
*/
public static function get_declared_permissions_list()
{
@ -673,6 +676,7 @@ class Permission extends DataObject implements TemplateGlobalProvider, Resettabl
*
* @param string $perm Permission code
* @return string Label for the given permission, or the permission itself if the label doesn't exist
* @deprecated 4.4.0
*/
public static function get_label_for_permission($perm)
{
@ -690,6 +694,7 @@ class Permission extends DataObject implements TemplateGlobalProvider, Resettabl
* @param array $declared Nested structure of permissions.
* @param array $list List of permissions in the structure. The result will be
* written to this array.
* @deprecated 4.4.0
*/
protected static function traverse_declared_permissions($declared, &$list)
{

View File

@ -35,7 +35,11 @@ abstract class HTMLValue extends ViewableData
*/
public function getContent()
{
$doc = clone $this->getDocument();
$document = $this->getDocument();
if (!$document) {
return '';
}
$doc = clone $document;
$xp = new DOMXPath($doc);
// If there's no body, the content is empty string

View File

@ -476,7 +476,7 @@ class ShortcodeParser
if ($tags) {
$node->nodeValue = $this->replaceTagsWithText(
$node->nodeValue,
htmlspecialchars($node->nodeValue),
$tags,
function ($idx, $tag) use ($parser, $extra) {
return $parser->getShortcodeReplacementText($tag, $extra, false);

View File

@ -18,6 +18,7 @@ use SilverStripe\Core\Path;
use SilverStripe\Dev\Debug;
use SilverStripe\Dev\Deprecation;
use SilverStripe\i18n\i18n;
use SilverStripe\ORM\FieldType\DBField;
class Requirements_Backend
{
@ -1004,13 +1005,23 @@ class Requirements_Backend
$files = array();
$candidates = array(
'en.js',
'en_US.js',
i18n::getData()->langFromLocale(i18n::config()->get('default_locale')) . '.js',
i18n::config()->get('default_locale') . '.js',
i18n::getData()->langFromLocale(i18n::get_locale()) . '.js',
i18n::get_locale() . '.js',
'en',
'en_US',
i18n::getData()->langFromLocale(i18n::config()->get('default_locale')),
i18n::config()->get('default_locale'),
i18n::getData()->langFromLocale(i18n::get_locale()),
i18n::get_locale(),
strtolower(DBField::create_field('Locale', i18n::get_locale())->RFC1766()),
strtolower(DBField::create_field('Locale', i18n::config()->get('default_locale'))->RFC1766())
);
$candidates = array_map(
function ($candidate) {
return $candidate . '.js';
},
$candidates
);
foreach ($candidates as $candidate) {
$relativePath = Path::join($langDir, $candidate);
$absolutePath = Director::getAbsFile($relativePath);

View File

@ -114,11 +114,11 @@ class EmbedShortcodeProvider implements ShortcodeHandler
if (empty($arguments['width']) && $embed->getWidth()) {
$arguments['width'] = $embed->getWidth();
}
return self::videoEmbed($arguments, $embed->getCode());
return static::videoEmbed($arguments, $embed->getCode());
case 'link':
return self::linkEmbed($arguments, $embed->getUrl(), $embed->getTitle());
return static::linkEmbed($arguments, $embed->getUrl(), $embed->getTitle());
case 'photo':
return self::photoEmbed($arguments, $embed->getUrl());
return static::photoEmbed($arguments, $embed->getUrl());
default:
return null;
}

View File

@ -27,6 +27,7 @@ use SilverStripe\Core\TempFolder;
* - PUBLIC_PATH: Absolute path to webroot, e.g. "/var/www/project/public"
* - THIRDPARTY_DIR: Path relative to webroot, e.g. "framework/thirdparty"
* - THIRDPARTY_PATH: Absolute filepath, e.g. "/var/www/my-webroot/framework/thirdparty"
* - RESOURCES_DIR: Name of the directory where vendor assets will be exposed, e.g. "_ressources"
*/
require_once __DIR__ . '/functions.php';
@ -198,3 +199,18 @@ if (!defined('TEMP_PATH')) {
if (!defined('TEMP_FOLDER')) {
define('TEMP_FOLDER', TEMP_PATH);
}
// Define the Ressource Dir constant that will be use to exposed vendor assets
if (!defined('RESOURCES_DIR')) {
$project = new SilverStripe\Core\Manifest\Module(BASE_PATH, BASE_PATH);
$resourcesDir = $project->getResourcesDir() ?: 'resources';
if (preg_match('/^[_\-a-z0-9]+$/i', $resourcesDir)) {
define('RESOURCES_DIR', $resourcesDir);
} else {
throw new LogicException(sprintf(
'Resources dir error: "%s" is not a valid resources directory name. Update the ' .
'`extra.resources-dir` key in your composer.json file',
$resourcesDir
));
}
}

View File

@ -58,7 +58,10 @@ class CmsUiContext implements Context
$timeoutMs = $this->getMainContext()->getAjaxTimeout();
$this->getSession()->wait(
$timeoutMs,
"document.getElementsByClassName('cms-content-loading-overlay').length == 0"
"(".
"document.getElementsByClassName('cms-content-loading-overlay').length +".
"document.getElementsByClassName('cms-loading-container').length".
") == 0"
);
}

View File

@ -36,7 +36,7 @@ class SimpleResourceURLGeneratorTest extends SapphireTest
__DIR__ . '/SimpleResourceURLGeneratorTest/_fakewebroot/basemodule/client/file.js'
);
$this->assertEquals(
'/resources/basemodule/client/file.js?m=' . $mtime,
'/'. RESOURCES_DIR . '/basemodule/client/file.js?m=' . $mtime,
$generator->urlForResource('basemodule/client/file.js')
);
}
@ -49,7 +49,7 @@ class SimpleResourceURLGeneratorTest extends SapphireTest
__DIR__ . '/SimpleResourceURLGeneratorTest/_fakewebroot/vendor/silverstripe/mymodule/client/style.css'
);
$this->assertEquals(
'/resources/vendor/silverstripe/mymodule/client/style.css?m=' . $mtime,
'/'. RESOURCES_DIR . '/vendor/silverstripe/mymodule/client/style.css?m=' . $mtime,
$generator->urlForResource('vendor/silverstripe/mymodule/client/style.css')
);
}
@ -72,7 +72,7 @@ class SimpleResourceURLGeneratorTest extends SapphireTest
);
$this->assertEquals(
'/resources/basemodule/client/file.js?m=' . $mtime,
'/'. RESOURCES_DIR . '/basemodule/client/file.js?m=' . $mtime,
$generator->urlForResource('basemodule/client/file.js')
);
}
@ -89,7 +89,7 @@ class SimpleResourceURLGeneratorTest extends SapphireTest
__DIR__ . '/SimpleResourceURLGeneratorTest/_fakewebroot/vendor/silverstripe/mymodule/client/style.css'
);
$this->assertEquals(
'/resources/vendor/silverstripe/mymodule/client/style.css?m=' . $mtime,
'/'. RESOURCES_DIR . '/vendor/silverstripe/mymodule/client/style.css?m=' . $mtime,
$generator->urlForResource($module->getResource('client/style.css'))
);
}

View File

@ -42,7 +42,7 @@ class ModuleResourceTest extends SapphireTest
$resource->getPath()
);
$this->assertStringStartsWith(
'/basefolder/resources/module/client/script.js?m=',
'/basefolder/'. RESOURCES_DIR . '/module/client/script.js?m=',
$resource->getURL()
);
}
@ -60,7 +60,7 @@ class ModuleResourceTest extends SapphireTest
$resource->getPath()
);
$this->assertStringStartsWith(
'/basefolder/resources/vendor/silverstripe/modulec/client/script.js?m=',
'/basefolder/'. RESOURCES_DIR . '/vendor/silverstripe/modulec/client/script.js?m=',
$resource->getURL()
);
}
@ -80,7 +80,7 @@ class ModuleResourceTest extends SapphireTest
$resource->getPath()
);
$this->assertStringStartsWith(
'/basefolder/resources/vendor/silverstripe/modulec/client/script.js?m=',
'/basefolder/'. RESOURCES_DIR . '/vendor/silverstripe/modulec/client/script.js?m=',
$resource->getURL()
);
}

View File

@ -0,0 +1,24 @@
<?php
namespace SilverStripe\Core\Tests\Manifest;
use SilverStripe\Control\Director;
use SilverStripe\Core\Manifest\Module;
use SilverStripe\Dev\SapphireTest;
class ModuleTest extends SapphireTest
{
public function testUnsetResourcesDir()
{
$path = __DIR__ . '/fixtures/ss-projects/withoutCustomResourcesDir';
$module = new Module($path, $path);
$this->assertEquals('', $module->getResourcesDir());
}
public function testResourcesDir()
{
$path = __DIR__ . '/fixtures/ss-projects/withCustomResourcesDir';
$module = new Module($path, $path);
$this->assertEquals('customised-resources-dir', $module->getResourcesDir());
}
}

View File

@ -0,0 +1,29 @@
{
"name": "silverstripe/ss44",
"type": "silverstripe-project",
"description": "Fake project using SS 4.4",
"homepage": "https://www.silverstripe.org",
"license": "BSD-3-Clause",
"require": {
"silverstripe/recipe-cms": "4.4.x-dev as 4.4.0"
},
"extra": {
"project-files-installed": [
"app/.htaccess",
"app/_config.php",
"app/_config/mysite.yml",
"app/src/Page.php",
"app/src/PageController.php"
],
"public-files-installed": [
".htaccess",
"index.php",
"install-frameworkmissing.html",
"install.php",
"web.config"
],
"resources-dir": "customised-resources-dir"
},
"prefer-stable": true,
"minimum-stability": "dev"
}

View File

@ -0,0 +1,28 @@
{
"name": "silverstripe/ss44",
"type": "silverstripe-project",
"description": "Fake project using SS 4.4",
"homepage": "https://www.silverstripe.org",
"license": "BSD-3-Clause",
"require": {
"silverstripe/recipe-cms": "4.4.x-dev as 4.4.0"
},
"extra": {
"project-files-installed": [
"app/.htaccess",
"app/_config.php",
"app/_config/mysite.yml",
"app/src/Page.php",
"app/src/PageController.php"
],
"public-files-installed": [
".htaccess",
"index.php",
"install-frameworkmissing.html",
"install.php",
"web.config"
]
},
"prefer-stable": true,
"minimum-stability": "dev"
}

View File

@ -4,6 +4,7 @@ namespace SilverStripe\Dev\Tests;
use SilverStripe\Dev\Constraint\ViewableDataContains;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Dev\Tests\ViewableDataContainsTest\TestObject;
use SilverStripe\Security\Member;
use SilverStripe\View\ArrayData;
@ -100,4 +101,14 @@ class ViewableDataContainsTest extends SapphireTest
$this->assertFalse($constraint->evaluate($item, '', true));
}
public function testFieldAccess()
{
$data = new TestObject(['name' => 'Damian']);
$constraint = new ViewableDataContains(['name' => 'Damian', 'Something' => 'something']);
$this->assertTrue($constraint->evaluate($data, '', true));
$constraint = new ViewableDataContains(['name' => 'Damian', 'Something' => 'notthing']);
$this->assertFalse($constraint->evaluate($data, '', true));
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace SilverStripe\Dev\Tests\ViewableDataContainsTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\View\ViewableData;
class TestObject extends ViewableData implements TestOnly
{
protected $data = null;
public function __construct($data)
{
$this->data = $data;
}
public function hasField($name)
{
return isset($this->data[$name]);
}
public function getField($name)
{
return isset($this->data[$name]) ?: null;
}
public function getSomething()
{
return 'something';
}
}

View File

@ -20,7 +20,9 @@ class ConfirmedPasswordFieldTest extends SapphireTest
{
parent::setUp();
PasswordValidator::singleton()->setMinLength(0);
PasswordValidator::singleton()
->setMinLength(0)
->setTestNames([]);
}
public function testSetValue()

View File

@ -482,29 +482,29 @@ class FieldListTest extends SapphireTest
);
$this->assertEquals(
$tabSetWithTitle->Title(),
'My TabSet Title',
'Automatic conversion of tab identifiers through findOrMakeTab() with FormField::name_to_label()'
$tabSetWithTitle->Title(),
'Existing field title should be used'
);
$tabWithoutTitle = $set->findOrMakeTab('Root.TabWithoutTitle');
$this->assertEquals(
'Tab without title',
$tabWithoutTitle->Title(),
'Tab Without Title',
'Automatic conversion of tab identifiers through findOrMakeTab() with FormField::name_to_label()'
);
$tabWithTitle = $set->findOrMakeTab('Root.TabWithTitle', 'My Tab with Title');
$this->assertEquals(
$tabWithTitle->Title(),
'My Tab with Title',
$tabWithTitle->Title(),
'Setting of simple tab titles through findOrMakeTab()'
);
$childTabWithTitle = $set->findOrMakeTab('Root.TabSetWithoutTitle.NewChildTab', 'My Child Tab Title');
$this->assertEquals(
$childTabWithTitle->Title(),
'My Child Tab Title',
$childTabWithTitle->Title(),
'Setting of nested tab titles through findOrMakeTab() works on last child tab'
);
}

View File

@ -402,4 +402,31 @@ class FormFieldTest extends SapphireTest
$field = new FormField('Test');
$field->Link('bar');
}
/**
* @param string $name
* @param string $expected
* @dataProvider nameToLabelProvider
*/
public function testNameToLabel($name, $expected)
{
$this->assertSame($expected, FormField::name_to_label($name));
}
/**
* @return array[]
*/
public function nameToLabelProvider()
{
return [
['TotalAmount', 'Total amount'],
['Organisation.ZipCode', 'Organisation zip code'],
['Organisation.zipCode', 'Organisation zip code'],
['FooBarBaz', 'Foo bar baz'],
['URLSegment', 'URL segment'],
['ONLYCAPS', 'ONLYCAPS'],
['onlylower', 'Onlylower'],
['SpecialURL', 'Special URL'],
];
}
}

View File

@ -415,7 +415,7 @@ class FormTest extends FunctionalTest
// Firstly, assert that required fields still work when not using an exempt action
$this->assertPartialMatchBySelector(
'#Form_Form_SomeRequiredField_Holder .required',
array('"Some Required Field" is required'),
array('"Some required field" is required'),
'Required fields show a notification on field when left blank'
);
@ -487,7 +487,7 @@ class FormTest extends FunctionalTest
$this->assertPartialMatchBySelector(
'#Form_Form_SomeRequiredField_Holder span.required',
array(
'"Some Required Field" is required'
'"Some required field" is required'
),
'Required fields show a notification on field when left blank'
);

View File

@ -4,28 +4,301 @@ namespace SilverStripe\Forms\Tests;
use SilverStripe\Assets\File;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Forms\Form;
use SilverStripe\Forms\FormTemplateHelper;
use SilverStripe\Forms\TreeMultiselectField;
use SilverStripe\ORM\Tests\HierarchyTest\TestObject;
use SilverStripe\View\SSViewer;
class TreeMultiselectFieldTest extends SapphireTest
{
protected static $fixture_file = 'TreeDropdownFieldTest.yml';
public function testReadonly()
protected static $extra_dataobjects = [
TestObject::class,
];
protected $formId = 'TheFormID';
protected $fieldName = 'TestTree';
/**
* Mock object of a generic form
*
* @var Form
*/
protected $form;
/**
* Instance of the TreeMultiselectField
*
* @var TreeMultiselectField
*/
protected $field;
/**
* The File objects of folders loaded from the fixture
*
* @var File[]
*/
protected $folders;
/**
* The array of folder ids
*
* @var int[]
*/
protected $folderIds;
/**
* Concatenated folder ids for use as a value for the field
*
* @var string
*/
protected $fieldValue;
protected function setUp()
{
parent::setUp();
// Don't let other themes interfere with these tests
SSViewer::set_themes([]);
$this->form = $this->buildFormMock();
$this->field = $this->buildField($this->form);
$this->folders = $this->loadFolders();
$this->folderIds = array_map(
static function ($f) {
return $f->ID;
},
$this->folders
);
$this->fieldValue = implode(',', $this->folderIds);
}
/**
* Build a new mock object of a Form
*
* @return Form
*/
protected function buildFormMock()
{
$form = $this->createMock(Form::class);
$form->method('getTemplateHelper')
->willReturn(FormTemplateHelper::singleton());
$form->method('getHTMLID')
->willReturn($this->formId);
return $form;
}
/**
* Build a new instance of TreeMultiselectField
*
* @param Form $form The field form
*
* @return TreeMultiselectField
*/
protected function buildField(Form $form)
{
$field = new TreeMultiselectField($this->fieldName, 'Test tree', File::class);
$field->setForm($form);
return $field;
}
/**
* Load several files from the fixtures and return them in an array
*
* @return File[]
*/
protected function loadFolders()
{
$field = new TreeMultiselectField('TestTree', 'Test tree', File::class);
$asdf = $this->objFromFixture(File::class, 'asdf');
$subfolderfile1 = $this->objFromFixture(File::class, 'subfolderfile1');
$field->setValue(implode(',', [$asdf->ID, $subfolderfile1->ID]));
$readonlyField = $field->performReadonlyTransformation();
$this->assertEquals(
<<<"HTML"
<span id="TestTree_ReadonlyValue" class="readonly">
&lt;Special &amp; characters&gt;, TestFile1InSubfolder
</span><input type="hidden" name="TestTree" value="{$asdf->ID},{$subfolderfile1->ID}" class="hidden" id="TestTree" />
HTML
,
(string)$readonlyField->Field()
return [$asdf, $subfolderfile1];
}
/**
* Test the TreeMultiselectField behaviour with no selected values
*/
public function testEmpty()
{
$field = $this->field;
$fieldId = $field->ID();
$this->assertEquals($fieldId, sprintf('%s_%s', $this->formId, $this->fieldName));
$schemaStateDefaults = $field->getSchemaStateDefaults();
$this->assertArraySubset(
[
'id' => $fieldId,
'name' => $this->fieldName,
'value' => 'unchanged'
],
$schemaStateDefaults,
true
);
$schemaDataDefaults = $field->getSchemaDataDefaults();
$this->assertArraySubset(
[
'id' => $fieldId,
'name' => $this->fieldName,
'type' => 'text',
'schemaType' => 'SingleSelect',
'component' => 'TreeDropdownField',
'holderId' => sprintf('%s_Holder', $fieldId),
'title' => 'Test tree',
'extraClass' => 'treemultiselect multiple searchable',
'data' => [
'urlTree' => 'field/TestTree/tree',
'showSearch' => true,
'emptyString' => '(Choose File)',
'hasEmptyDefault' => false,
'multiple' => true
]
],
$schemaDataDefaults,
true
);
$items = $field->getItems();
$this->assertCount(0, $items, 'there must be no items selected');
$html = $field->Field();
$this->assertContains($field->ID(), $html);
$this->assertContains('unchanged', $html);
}
/**
* Test the field with some values set
*/
public function testChanged()
{
$field = $this->field;
$field->setValue($this->fieldValue);
$schemaStateDefaults = $field->getSchemaStateDefaults();
$this->assertArraySubset(
[
'id' => $field->ID(),
'name' => 'TestTree',
'value' => $this->folderIds
],
$schemaStateDefaults,
true
);
$items = $field->getItems();
$this->assertCount(2, $items, 'there must be exactly 2 items selected');
$html = $field->Field();
$this->assertContains($field->ID(), $html);
$this->assertContains($this->fieldValue, $html);
}
/**
* Test empty field in readonly mode
*/
public function testEmptyReadonly()
{
$field = $this->field->performReadonlyTransformation();
$schemaStateDefaults = $field->getSchemaStateDefaults();
$this->assertArraySubset(
[
'id' => $field->ID(),
'name' => 'TestTree',
'value' => 'unchanged'
],
$schemaStateDefaults,
true
);
$schemaDataDefaults = $field->getSchemaDataDefaults();
$this->assertArraySubset(
[
'id' => $field->ID(),
'name' => $this->fieldName,
'type' => 'text',
'schemaType' => 'SingleSelect',
'component' => 'TreeDropdownField',
'holderId' => sprintf('%s_Holder', $field->ID()),
'title' => 'Test tree',
'extraClass' => 'treemultiselectfield_readonly multiple searchable',
'data' => [
'urlTree' => 'field/TestTree/tree',
'showSearch' => true,
'emptyString' => '(Choose File)',
'hasEmptyDefault' => false,
'multiple' => true
]
],
$schemaDataDefaults,
true
);
$items = $field->getItems();
$this->assertCount(0, $items, 'there must be 0 selected items');
$html = $field->Field();
$this->assertContains($field->ID(), $html);
}
/**
* Test changed field in readonly mode
*/
public function testChangedReadonly()
{
$field = $this->field;
$field->setValue($this->fieldValue);
$field = $field->performReadonlyTransformation();
$schemaStateDefaults = $field->getSchemaStateDefaults();
$this->assertArraySubset(
[
'id' => $field->ID(),
'name' => 'TestTree',
'value' => $this->folderIds
],
$schemaStateDefaults,
true
);
$schemaDataDefaults = $field->getSchemaDataDefaults();
$this->assertArraySubset(
[
'id' => $field->ID(),
'name' => $this->fieldName,
'type' => 'text',
'schemaType' => 'SingleSelect',
'component' => 'TreeDropdownField',
'holderId' => sprintf('%s_Holder', $field->ID()),
'title' => 'Test tree',
'extraClass' => 'treemultiselectfield_readonly multiple searchable',
'data' => [
'urlTree' => 'field/TestTree/tree',
'showSearch' => true,
'emptyString' => '(Choose File)',
'hasEmptyDefault' => false,
'multiple' => true
]
],
$schemaDataDefaults,
true
);
$items = $field->getItems();
$this->assertCount(2, $items, 'there must be exactly 2 selected items');
$html = $field->Field();
$this->assertContains($field->ID(), $html);
$this->assertContains($this->fieldValue, $html);
}
}

View File

@ -2,17 +2,32 @@
namespace SilverStripe\ORM\Tests;
use SilverStripe\Assets\Image;
use SilverStripe\ORM\FieldType\DBBigInt;
use SilverStripe\ORM\FieldType\DBBoolean;
use SilverStripe\ORM\FieldType\DBCurrency;
use SilverStripe\ORM\FieldType\DBDate;
use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\ORM\FieldType\DBDecimal;
use SilverStripe\ORM\FieldType\DBDouble;
use SilverStripe\ORM\FieldType\DBEnum;
use SilverStripe\ORM\FieldType\DBFloat;
use SilverStripe\ORM\FieldType\DBForeignKey;
use SilverStripe\ORM\FieldType\DBHTMLText;
use SilverStripe\ORM\FieldType\DBHTMLVarchar;
use SilverStripe\ORM\FieldType\DBInt;
use SilverStripe\ORM\FieldType\DBLocale;
use SilverStripe\ORM\FieldType\DBMoney;
use SilverStripe\ORM\FieldType\DBMultiEnum;
use SilverStripe\ORM\FieldType\DBPercentage;
use SilverStripe\ORM\FieldType\DBPolymorphicForeignKey;
use SilverStripe\ORM\FieldType\DBPrimaryKey;
use SilverStripe\ORM\FieldType\DBString;
use SilverStripe\ORM\FieldType\DBTime;
use SilverStripe\ORM\FieldType\DBVarchar;
use SilverStripe\ORM\FieldType\DBText;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\ORM\FieldType\DBYear;
/**
* Tests for DBField objects.
@ -189,6 +204,54 @@ class DBFieldTest extends SapphireTest
$this->assertEquals(PHP_INT_MAX, $bigInt->getValue());
}
/**
* @dataProvider dataProviderPrepValueForDBArrayValue
*/
public function testPrepValueForDBArrayValue($dbFieldName, $scalarValueOnly, $extraArgs = [])
{
$reflection = new \ReflectionClass($dbFieldName);
/**
* @var DBField
*/
$dbField = $reflection->newInstanceArgs($extraArgs);
$dbField->setName('SomeField');
$payload = ['GREATEST(0,?)' => '2'];
$preparedValue = $dbField->prepValueForDB($payload);
$this->assertTrue(
!$scalarValueOnly || !is_array($preparedValue),
'`prepValueForDB` can not return an array if scalarValueOnly is true'
);
$this->assertEquals($scalarValueOnly, $dbField->scalarValueOnly());
}
public function dataProviderPrepValueForDBArrayValue()
{
return [
[DBBigInt::class, true],
[DBBoolean::class, true],
[DBCurrency::class, true],
[DBDate::class, true],
[DBDatetime::class, true],
[DBDecimal::class, true],
[DBDouble::class, true],
[DBEnum::class, true],
[DBFloat::class, true],
[DBForeignKey::class, true, ['SomeField']],
[DBHTMLText::class, true],
[DBHTMLVarchar::class, true],
[DBInt::class, true],
[DBLocale::class, true],
[DBMoney::class, false],
[DBMultiEnum::class, true, ['SomeField', ['One', 'Two', 'Three']]],
[DBPercentage::class, true],
[DBPolymorphicForeignKey::class, false, ['SomeField']],
[DBText::class, true],
[DBTime::class, true],
[DBVarchar::class, true],
[DBYear::class, true],
];
}
public function testExists()
{
$varcharField = new DBVarchar("testfield");

View File

@ -343,4 +343,35 @@ class DataObjectSchemaTest extends SapphireTest
'columns' => ['IndexedMoneyCurrency', 'IndexedMoneyAmount']
], $indexes['IndexedMoney']);
}
/**
* Ensure that records with unique indexes can be written
*/
public function testWriteUniqueIndexes()
{
// Create default object
$zeroObject = new AllIndexes();
$zeroObject->Number = 0;
$zeroObject->write();
$this->assertListEquals(
[
['Number' => 0],
],
AllIndexes::get()
);
// Test a new record can be created without clashing with default value
$validObject = new AllIndexes();
$validObject->Number = 1;
$validObject->write();
$this->assertListEquals(
[
['Number' => 0],
['Number' => 1],
],
AllIndexes::get()
);
}
}

View File

@ -5,6 +5,11 @@ use SilverStripe\Dev\TestOnly;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\FieldType\DBIndexable;
/**
* @property int $Number
* @property string $Content
* @property string $Title
*/
class AllIndexes extends DataObject implements TestOnly
{
private static $table_name = 'DataObjectSchemaTest_AllIndexes';

View File

@ -17,7 +17,9 @@ use SilverStripe\ORM\FieldType\DBField;
use SilverStripe\ORM\FieldType\DBPolymorphicForeignKey;
use SilverStripe\ORM\FieldType\DBVarchar;
use SilverStripe\ORM\ManyManyList;
use SilverStripe\ORM\Tests\DataObjectTest\Company;
use SilverStripe\ORM\Tests\DataObjectTest\Player;
use SilverStripe\ORM\Tests\DataObjectTest\Team;
use SilverStripe\View\ViewableData;
use stdClass;
@ -56,6 +58,7 @@ class DataObjectTest extends SapphireTest
DataObjectTest\RelationParent::class,
DataObjectTest\RelationChildFirst::class,
DataObjectTest\RelationChildSecond::class,
DataObjectTest\MockDynamicAssignmentDataObject::class
);
public static function getExtraDataObjects()
@ -918,6 +921,13 @@ class DataObjectTest extends SapphireTest
$this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_STRICT));
$this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_VALUE));
$obj->write();
$obj->FirstName = null;
$this->assertFalse($obj->isChanged('FirstName', DataObject::CHANGE_STRICT), 'Unchanged property was marked as changed');
$obj->FirstName = 0;
$this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_STRICT), 'Strict (type) change was not detected');
$this->assertFalse($obj->isChanged('FirstName', DataObject::CHANGE_VALUE), 'Type-only change was marked as a value change');
/* Test when there's not field provided */
$obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain2');
$this->assertFalse($obj->isChanged());
@ -2311,4 +2321,50 @@ class DataObjectTest extends SapphireTest
['"DataObjectTest_TeamComment"."Name"' => 'does not exists']
));
}
public function testSetFieldWithArrayOnScalarOnlyField()
{
$this->expectException(InvalidArgumentException::class);
$do = Company::singleton();
$do->FoundationYear = '1984';
$do->FoundationYear = array('Amount' => 123, 'Currency' => 'CAD');
$this->assertEmpty($do->FoundationYear);
}
public function testSetFieldWithArrayOnCompositeField()
{
$do = Company::singleton();
$do->SalaryCap = array('Amount' => 123456, 'Currency' => 'CAD');
$this->assertNotEmpty($do->SalaryCap);
}
public function testWriteManipulationWithNonScalarValuesAllowed()
{
$do = DataObjectTest\MockDynamicAssignmentDataObject::create();
$do->write();
$do->StaticScalarOnlyField = true;
$do->DynamicScalarOnlyField = false;
$do->DynamicField = true;
$do->write();
$this->assertTrue($do->StaticScalarOnlyField);
$this->assertFalse($do->DynamicScalarOnlyField);
$this->assertTrue($do->DynamicField);
}
public function testWriteManipulationWithNonScalarValuesDisallowed()
{
$this->expectException(InvalidArgumentException::class);
$do = DataObjectTest\MockDynamicAssignmentDataObject::create();
$do->write();
$do->StaticScalarOnlyField = false;
$do->DynamicScalarOnlyField = true;
$do->DynamicField = false;
$do->write();
}
}

View File

@ -10,7 +10,9 @@ class Company extends DataObject implements TestOnly
private static $table_name = 'DataObjectTest_Company';
private static $db = [
'Name' => 'Varchar'
'Name' => 'Varchar',
'MarketValue' => 'Money',
'FoundationYear' => 'Year'
];
private static $has_one = [

View File

@ -0,0 +1,55 @@
<?php
namespace SilverStripe\ORM\Tests\DataObjectTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\ORM\FieldType\DBBoolean;
use SilverStripe\ORM\FieldType\DBField;
/**
* This is a fake DB field specifically design to test dynamic value assignment. You can set `scalarValueOnly` in
* the constructor. You can control whetever the field will try to do a dynamic assignment by specifing
* `$dynamicAssignment` in nthe consturctor.
*
* If the field is set to false, it will try to do a plain assignment. This is so you can save the initial value no
* matter what. If the field is set to true, it will try to do a dynamic assignment.
*/
class MockDynamicAssignmentDBField extends DBBoolean implements TestOnly
{
private $scalarOnly;
private $dynamicAssignment;
/**
* @param string $name
* @param boolean $scalarOnly Whether our fake field should be scalar only.
* @param boolean $dynamicAssignment Whether our fake field will try to do a dynamic assignment.
*/
public function __construct($name = '', $scalarOnly = false, $dynamicAssignment = false)
{
$this->scalarOnly = $scalarOnly;
$this->dynamicAssignment = $dynamicAssignment;
parent::__construct($name);
}
/**
* If the field value and $dynamicAssignment are true, we'll try to do a dynamic assignment.
* @param $value
* @return array|int
*/
public function prepValueForDB($value)
{
if ($value) {
return $this->dynamicAssignment
? ['ABS(?)' => [1]]
: 1;
}
return 0;
}
public function scalarValueOnly()
{
return $this->scalarOnly;
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace SilverStripe\ORM\Tests\DataObjectTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\ManyManyList;
/**
* This is a fake DB field specifically design to test dynamic value assignment
* @property boolean $StaticScalarOnlyField
* @property boolean $DynamicScalarOnlyField
* @property boolean $DynamicField
* @method ManyManyList MockManyMany
*/
class MockDynamicAssignmentDataObject extends DataObject implements TestOnly
{
private static $table_name = 'MockDynamicAssignmentDataObject';
private static $db = [
// This field only emits scalar value and will save
'StaticScalarOnlyField' => MockDynamicAssignmentDBField::class . '(1,0)',
// This field tries to emit dynamic assignment but will fail because of scalar only
'DynamicScalarOnlyField' => MockDynamicAssignmentDBField::class . '(1,1)',
// This field does dynamic assignement and will pass
'DynamicField' => MockDynamicAssignmentDBField::class . '(0,1)',
];
private static $many_many = [
'MockManyMany' => self::class,
];
private static $belongs_many_many = [
'MockBelongsManyMany' => self::class,
];
private static $many_many_extraFields = [
'MockManyMany' => [
// This field only emits scalar value and will save
'ManyManyStaticScalarOnlyField' => MockDynamicAssignmentDBField::class . '(1,0)',
// This field tries to emit dynamic assignment but will fail because of scalar only
'ManyManyDynamicScalarOnlyField' => MockDynamicAssignmentDBField::class . '(1,1)',
// This field does dynamic assignement and will pass
'ManyManyDynamicField' => MockDynamicAssignmentDBField::class . '(0,1)',
]
];
}

View File

@ -10,6 +10,8 @@ use SilverStripe\ORM\ManyManyList;
/**
* @property string Title
* @property string DatabaseField
* @property array SalaryCap
* @property string FoundationYear
* @method Player Captain()
* @method Player Founder()
* @method Player HasOneRelationship()
@ -27,7 +29,7 @@ class Team extends DataObject implements TestOnly
private static $db = array(
'Title' => 'Varchar',
'DatabaseField' => 'HTMLVarchar'
'DatabaseField' => 'HTMLVarchar',
);
private static $has_one = array(

View File

@ -11,6 +11,7 @@ use SilverStripe\ORM\Tests\DataObjectTest\Player;
use SilverStripe\ORM\Tests\DataObjectTest\Team;
use SilverStripe\ORM\Tests\ManyManyListTest\ExtraFieldsObject;
use SilverStripe\ORM\Tests\ManyManyListTest\Product;
use InvalidArgumentException;
class ManyManyListTest extends SapphireTest
{
@ -21,6 +22,7 @@ class ManyManyListTest extends SapphireTest
ManyManyListTest\Category::class,
ManyManyListTest\ExtraFieldsObject::class,
ManyManyListTest\Product::class,
DataObjectTest\MockDynamicAssignmentDataObject::class
];
public static function getExtraDataObjects()
@ -419,4 +421,40 @@ class ManyManyListTest extends SapphireTest
$productsRelatedToProductB = $category->Products()->filter('RelatedProducts.Title', 'Product A');
$this->assertEquals(1, $productsRelatedToProductB->count());
}
public function testWriteManipulationWithNonScalarValuesAllowed()
{
$left = DataObjectTest\MockDynamicAssignmentDataObject::create();
$left->write();
$right = DataObjectTest\MockDynamicAssignmentDataObject::create();
$right->write();
$left->MockManyMany()->add($right, [
'ManyManyStaticScalarOnlyField' => true,
'ManyManyDynamicScalarOnlyField' => false,
'ManyManyDynamicField' => true,
]);
$pivot = $left->MockManyMany()->first();
$this->assertNotFalse($pivot->ManyManyStaticScalarOnlyField);
$this->assertNotTrue($pivot->ManyManyDynamicScalarOnlyField);
$this->assertNotFalse($pivot->ManyManyDynamicField);
}
public function testWriteManipulationWithNonScalarValuesDisallowed()
{
$this->expectException(InvalidArgumentException::class);
$left = DataObjectTest\MockDynamicAssignmentDataObject::create();
$left->write();
$right = DataObjectTest\MockDynamicAssignmentDataObject::create();
$right->write();
$left->MockManyMany()->add($right, [
'ManyManyStaticScalarOnlyField' => false,
'ManyManyDynamicScalarOnlyField' => true,
'ManyManyDynamicField' => false,
]);
}
}

View File

@ -18,6 +18,7 @@ use SilverStripe\Security\MemberAuthenticator\CMSMemberAuthenticator;
use SilverStripe\Security\MemberAuthenticator\CMSMemberLoginForm;
use SilverStripe\Security\MemberAuthenticator\MemberAuthenticator;
use SilverStripe\Security\MemberAuthenticator\MemberLoginForm;
use SilverStripe\Security\PasswordValidator;
use SilverStripe\Security\Security;
/**
@ -44,6 +45,10 @@ class MemberAuthenticatorTest extends SapphireTest
$this->defaultPassword = null;
}
DefaultAdminService::setDefaultAdmin('admin', 'password');
PasswordValidator::singleton()
->setMinLength(0)
->setTestNames([]);
}
protected function tearDown()

View File

@ -6,6 +6,7 @@ use SilverStripe\ORM\DataObject;
use SilverStripe\Security\Group;
use SilverStripe\Security\MemberCsvBulkLoader;
use SilverStripe\Security\Member;
use SilverStripe\Security\PasswordValidator;
use SilverStripe\Security\Security;
use SilverStripe\Dev\SapphireTest;
@ -13,6 +14,15 @@ class MemberCsvBulkLoaderTest extends SapphireTest
{
protected static $fixture_file = 'MemberCsvBulkLoaderTest.yml';
protected function setUp()
{
parent::setUp();
PasswordValidator::singleton()
->setMinLength(0)
->setTestNames([]);
}
public function testNewImport()
{
$loader = new MemberCsvBulkLoader();

View File

@ -57,7 +57,9 @@ class MemberTest extends FunctionalTest
Member::config()->set('unique_identifier_field', 'Email');
PasswordValidator::singleton()->setMinLength(0);
PasswordValidator::singleton()
->setMinLength(0)
->setTestNames([]);
i18n::set_locale('en_US');
}

View File

@ -18,10 +18,10 @@ class PasswordValidatorTest extends SapphireTest
{
parent::setUp();
// Unset framework default values
PasswordValidator::config()
->remove('min_length')
->remove('historic_count');
->remove('historic_count')
->set('min_test_score', 0);
}
public function testValidate()

View File

@ -21,6 +21,7 @@ use SilverStripe\ORM\ValidationResult;
use SilverStripe\Security\LoginAttempt;
use SilverStripe\Security\Member;
use SilverStripe\Security\MemberAuthenticator\MemberAuthenticator;
use SilverStripe\Security\PasswordValidator;
use SilverStripe\Security\Security;
use SilverStripe\Security\SecurityToken;
@ -51,6 +52,13 @@ class SecurityTest extends FunctionalTest
*/
Member::config()->set('unique_identifier_field', 'Email');
PasswordValidator::config()
->remove('min_length')
->remove('historic_count')
->remove('min_test_score');
Member::set_password_validator(null);
parent::setUp();
Director::config()->set('alternate_base_url', '/');
@ -388,7 +396,7 @@ class SecurityTest extends FunctionalTest
// Test external redirection on ChangePasswordForm
$this->get('Security/changepassword?BackURL=http://myspoofedhost.com');
$changedResponse = $this->doTestChangepasswordForm('1nitialPassword', 'changedPassword');
$changedResponse = $this->doTestChangepasswordForm('1nitialPassword', 'changedPassword#123');
$this->assertNotRegExp(
'/^' . preg_quote('http://myspoofedhost.com', '/') . '/',
(string)$changedResponse->getHeader('Location'),
@ -435,7 +443,7 @@ class SecurityTest extends FunctionalTest
// Make sure it redirects correctly after the password has been changed
$this->mainSession->followRedirection();
$changedResponse = $this->doTestChangepasswordForm('1nitialPassword', 'changedPassword');
$changedResponse = $this->doTestChangepasswordForm('1nitialPassword', 'changedPassword#123');
$this->assertEquals(302, $changedResponse->getStatusCode());
$this->assertEquals(
Controller::join_links(Director::absoluteBaseURL(), 'test/link'),
@ -449,7 +457,7 @@ class SecurityTest extends FunctionalTest
// Change the password
$this->get('Security/changepassword?BackURL=test/back');
$changedResponse = $this->doTestChangepasswordForm('1nitialPassword', 'changedPassword');
$changedResponse = $this->doTestChangepasswordForm('1nitialPassword', 'changedPassword#123');
$this->assertEquals(302, $changedResponse->getStatusCode());
$this->assertEquals(
Controller::join_links(Director::absoluteBaseURL(), 'test/back'),
@ -459,7 +467,7 @@ class SecurityTest extends FunctionalTest
// Check if we can login with the new password
$this->logOut();
$goodResponse = $this->doTestLoginForm('testuser@example.com', 'changedPassword');
$goodResponse = $this->doTestLoginForm('testuser@example.com', 'changedPassword#123');
$this->assertEquals(302, $goodResponse->getStatusCode());
$this->assertEquals(
Controller::join_links(Director::absoluteBaseURL(), 'test/link'),
@ -501,12 +509,12 @@ class SecurityTest extends FunctionalTest
// Follow redirection to form without hash in GET parameter
$this->get('Security/changepassword');
$this->doTestChangepasswordForm('1nitialPassword', 'changedPassword');
$this->doTestChangepasswordForm('1nitialPassword', 'changedPassword#123');
$this->assertEquals($this->idFromFixture(Member::class, 'test'), $this->session()->get('loggedInAs'));
// Check if we can login with the new password
$this->logOut();
$goodResponse = $this->doTestLoginForm('testuser@example.com', 'changedPassword');
$goodResponse = $this->doTestLoginForm('testuser@example.com', 'changedPassword#123');
$this->assertEquals(302, $goodResponse->getStatusCode());
$this->assertEquals($this->idFromFixture(Member::class, 'test'), $this->session()->get('loggedInAs'));

View File

@ -80,4 +80,19 @@ class HTML4ValueTest extends SapphireTest
$value->setContent('<a href="&quot;"></a>');
$this->assertEquals('<a href="&quot;"></a>', $value->getContent(), "'\"' character is escaped");
}
public function testGetContent()
{
$value = new HTML4Value();
$value->setContent('<p>This is valid</p>');
$this->assertEquals('<p>This is valid</p>', $value->getContent(), "Valid content is returned");
$value->setContent('<p?< This is not really valid but it will get parsed into something valid');
// can sometimes get a this state where HTMLValue->valid is false
// for instance if a content editor saves something really weird in a LiteralField
// we can manually get to this state via ->setInvalid()
$value->setInvalid();
$this->assertEquals('', $value->getContent(), "Blank string is returned when invalid");
}
}

View File

@ -311,6 +311,15 @@ class ShortcodeParserTest extends SapphireTest
$this->assertEquals($this->extra['element']->tagName, 'a');
}
public function testShortcodeWithAnchorAndQuerystring()
{
$result = $this->parser->parse('<a href="[test_shortcode]?my-string=this&thing=2#my-anchor">Link</a>');
$this->assertContains('my-string=this', $result);
$this->assertContains('thing=2', $result);
$this->assertContains('my-anchor', $result);
}
public function testNoParseAttemptIfNoCode()
{
$stub = $this->getMockBuilder(ShortcodeParser::class)->setMethods(array('replaceElementTagsWithMarkers'))

View File

@ -6,6 +6,7 @@ use InvalidArgumentException;
use SilverStripe\Control\Director;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\i18n\i18n;
use SilverStripe\View\Requirements;
use SilverStripe\View\ArrayData;
use Silverstripe\Assets\Dev\TestAssetStore;
@ -1109,4 +1110,77 @@ EOS
}
return array();
}
public function testAddI18nJavascript()
{
/** @var Requirements_Backend $backend */
$backend = Injector::inst()->create(Requirements_Backend::class);
$this->setupRequirements($backend);
$backend->add_i18n_javascript('i18n');
$actual = $backend->getJavascript();
// English and English US should always be loaded no matter what
$this->assertArrayHasKey('i18n/en.js', $actual);
$this->assertArrayHasKey('i18n/en_US.js', $actual);
$this->assertArrayHasKey('i18n/en-us.js', $actual);
}
public function testAddI18nJavascriptWithDefaultLocale()
{
i18n::config()->set('default_locale', 'fr_CA');
/** @var Requirements_Backend $backend */
$backend = Injector::inst()->create(Requirements_Backend::class);
$this->setupRequirements($backend);
$backend->add_i18n_javascript('i18n');
$actual = $backend->getJavascript();
$this->assertArrayHasKey('i18n/en.js', $actual);
$this->assertArrayHasKey('i18n/en_US.js', $actual);
$this->assertArrayHasKey('i18n/en-us.js', $actual);
// Default locale should be loaded
$this->assertArrayHasKey('i18n/fr.js', $actual);
$this->assertArrayHasKey('i18n/fr_CA.js', $actual);
$this->assertArrayHasKey('i18n/fr-ca.js', $actual);
}
public function testAddI18nJavascriptWithMemberLocale()
{
i18n::set_locale('en_GB');
/** @var Requirements_Backend $backend */
$backend = Injector::inst()->create(Requirements_Backend::class);
$this->setupRequirements($backend);
$backend->add_i18n_javascript('i18n');
$actual = $backend->getJavascript();
// The current member's Locale as defined by i18n::get_locale should be loaded
$this->assertArrayHasKey('i18n/en.js', $actual);
$this->assertArrayHasKey('i18n/en_US.js', $actual);
$this->assertArrayHasKey('i18n/en-us.js', $actual);
$this->assertArrayHasKey('i18n/en-gb.js', $actual);
$this->assertArrayHasKey('i18n/en_GB.js', $actual);
}
public function testAddI18nJavascriptWithMissingLocale()
{
i18n::set_locale('fr_BE');
/** @var Requirements_Backend $backend */
$backend = Injector::inst()->create(Requirements_Backend::class);
$this->setupRequirements($backend);
$backend->add_i18n_javascript('i18n');
$actual = $backend->getJavascript();
// We don't have a file for French Belgium. Regular french should be loaded anyway.
$this->assertArrayHasKey('i18n/en.js', $actual);
$this->assertArrayHasKey('i18n/en_US.js', $actual);
$this->assertArrayHasKey('i18n/en-us.js', $actual);
$this->assertArrayHasKey('i18n/fr.js', $actual);
}
}

View File

View File

View File

View File

@ -102,7 +102,7 @@ class i18nTest extends SapphireTest
$obj->fieldLabel('MyProperty')
);
$this->assertEquals(
'My Untranslated Property',
'My untranslated property',
$obj->fieldLabel('MyUntranslatedProperty')
);
}