Merge branch '3.7' into 3

This commit is contained in:
Dan Hensby 2020-04-05 21:16:43 +01:00
commit bf797a1a88
No known key found for this signature in database
GPG Key ID: F76D6B5FE0626A99
11 changed files with 186 additions and 83 deletions

View File

@ -31,8 +31,11 @@ matrix:
- php: 7.3 - php: 7.3
env: DB=SQLITE env: DB=SQLITE
- php: 7.4snapshot - php: 7.4
env: DB=SQLITE INSTALL_PHPUNIT_FORK=1 env: DB=SQLITE INSTALL_PHPUNIT_FORK=1
dist: xenial
services:
- mysql
# CMS test # CMS test
- php: 5.5 - php: 5.5

View File

@ -1995,7 +1995,7 @@ class LeftAndMain_TreeNode extends ViewableData {
// Get additional filter classes // Get additional filter classes
if($this->filter && ($filterClasses = $this->filter->getPageClasses($this->obj))) { if($this->filter && ($filterClasses = $this->filter->getPageClasses($this->obj))) {
if(is_array($filterClasses)) { if(is_array($filterClasses)) {
$filterClasses = implode(' ' . $filterClasses); $filterClasses = implode(' ', $filterClasses);
} }
$classes .= ' ' . $filterClasses; $classes .= ' ' . $filterClasses;
} }

View File

@ -14,7 +14,7 @@ Our web-based [PHP installer](installation/) can check if you meet the requireme
## Web server software requirements ## Web server software requirements
* PHP 5.3.3+, <7.2 * PHP 5.3.3+, <=7.3
* We recommend using a PHP accelerator or opcode cache, such as [xcache](http://xcache.lighttpd.net/) or [WinCache](http://www.iis.net/download/wincacheforphp). * We recommend using a PHP accelerator or opcode cache, such as [xcache](http://xcache.lighttpd.net/) or [WinCache](http://www.iis.net/download/wincacheforphp).
``` ```
* Note: Some PHP 5.5+ packages already have [Zend OpCache](http://php.net/manual/en/book.opcache.php) installed by default. If this is the case on your system, do not try and run additional opcaches alongside Zend OpCache without first disabling it, as it will likely have unexpected consequences. * Note: Some PHP 5.5+ packages already have [Zend OpCache](http://php.net/manual/en/book.opcache.php) installed by default. If this is the case on your system, do not try and run additional opcaches alongside Zend OpCache without first disabling it, as it will likely have unexpected consequences.
@ -43,9 +43,8 @@ Our web-based [PHP installer](installation/) can check if you meet the requireme
* Microsoft Windows XP SP3, Vista, Windows 7, Server 2008, Server 2008 R2 * Microsoft Windows XP SP3, Vista, Windows 7, Server 2008, Server 2008 R2
* Mac OS X 10.4+ * Mac OS X 10.4+
### Does SilverStripe 3 work with PHP 7? ### Does SilverStripe 3 work with PHP 7.3+?
SilverStripe 3.6 or greater will work with PHP 7.0 and 7.1. SilverStripe 3.5 or lower will only work with PHP SilverStripe 3.7.3 or greater will work with PHP 7.2 and 7.3. Silverstripe 3.6.x is compatible with PHP 7.0 and 7.1, and SilverStripe 3.5 or lower will only work with PHP 5.3 - 5.6.
5.3 - 5.6.
## Web server hardware requirements ## Web server hardware requirements

View File

@ -1,5 +1,5 @@
--- ---
title: Configure Lighttpd title: Configure Nginx
summary: Write a custom config for nginx summary: Write a custom config for nginx
--- ---
@ -23,6 +23,74 @@ Especially be aware of [accidental php-execution](https://nealpoole.com/blog/201
But enough of the disclaimer, on to the actual configuration — typically in `nginx.conf`: But enough of the disclaimer, on to the actual configuration — typically in `nginx.conf`:
```nginx
server {
include mime.types;
default_type application/octet-stream;
client_max_body_size 0; # Manage this in php.ini
listen 80;
root /path/to/ss/folder;
server_name example.com www.example.com;
# Defend against SS-2015-013 -- http://www.silverstripe.org/software/download/security-releases/ss-2015-013
if ($http_x_forwarded_host) {
return 400;
}
location / {
try_files $uri /framework/main.php?url=$uri&$query_string;
}
error_page 404 /assets/error-404.html;
error_page 500 /assets/error-500.html;
location ^~ /assets/ {
sendfile on;
try_files $uri =404;
}
location ~ /framework/.*(main|rpc|tiny_mce_gzip)\.php$ {
fastcgi_buffer_size 32k;
fastcgi_busy_buffers_size 64k;
fastcgi_buffers 4 32k;
fastcgi_keep_conn on;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# Denials
location ~ /\.. {
deny all;
}
location ~ \.ss$ {
satisfy any;
allow 127.0.0.1;
deny all;
}
location ~ web\.config$ {
deny all;
}
location ~ \.ya?ml$ {
deny all;
}
location ~* README.*$ {
deny all;
}
location ^~ /vendor/ {
deny all;
}
location ~* /silverstripe-cache/ {
deny all;
}
location ~* composer\.(json|lock)$ {
deny all;
}
location ~* /(cms|framework)/silverstripe_version$ {
deny all;
}
}
``` ```
The above configuration sets up a virtual host `example.com` with The above configuration sets up a virtual host `example.com` with

View File

@ -33,8 +33,8 @@ Let's look at a simple example:
'Birthday' => 'Date' 'Birthday' => 'Date'
); );
} }
``` ```
This `Player` class definition will create a database table `Player` with columns for `PlayerNumber`, `FirstName` and This `Player` class definition will create a database table `Player` with columns for `PlayerNumber`, `FirstName` and
so on. After writing this class, we need to regenerate the database schema. so on. After writing this class, we need to regenerate the database schema.
@ -89,39 +89,40 @@ automatically set on the `DataObject`.
'Birthday' => 'Date' 'Birthday' => 'Date'
); );
} }
``` ```
Generates the following `SQL`.
``` ```
CREATE TABLE `Player` ( CREATE TABLE `Player` (
`ID` int(11) NOT NULL AUTO_INCREMENT, `ID` int(11) NOT NULL AUTO_INCREMENT,
`ClassName` enum('Player') DEFAULT 'Player', `ClassName` enum('Player') DEFAULT 'Player',
`LastEdited` datetime DEFAULT NULL, `LastEdited` datetime DEFAULT NULL,
`Created` datetime DEFAULT NULL, `Created` datetime DEFAULT NULL,
``` `PlayerNumber` int(11) NOT NULL DEFAULT '0',
```
`FirstName` varchar(255) DEFAULT NULL, `FirstName` varchar(255) DEFAULT NULL,
``` `LastName` mediumtext,
`Birthday` datetime DEFAULT NULL, `Birthday` datetime DEFAULT NULL,
PRIMARY KEY (`ID`), PRIMARY KEY (`ID`),
```
KEY `ClassName` (`ClassName`) KEY `ClassName` (`ClassName`)
); );
``` ```
## Creating Data Records
A new instance of a [api:DataObject] can be created using the `new` syntax. A new instance of a [api:DataObject] can be created using the `new` syntax.
```php ```php
$player = new Player(); $player = new Player();
``` ```
Or, a better way is to use the `create` method.
```php ```php
$player = Player::create(); $player = Player::create();
``` ```
[notice]
Using the `create()` method provides chainability, which can add elegance and brevity to your code, e.g. `Player::create()->write()`. More importantly, however, it will look up the class in the [Injector](../extending/injector) so that the class can be overriden by [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection). Using the `create()` method provides chainability, which can add elegance and brevity to your code, e.g. `Player::create()->write()`. More importantly, however, it will look up the class in the [Injector](../extending/injector) so that the class can be overriden by [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection).
[/notice] [/notice]
@ -132,21 +133,24 @@ of the values through a custom `__set()` method.
```php ```php
$player->FirstName = "Sam"; $player->FirstName = "Sam";
$player->PlayerNumber = 07; $player->PlayerNumber = 07;
``` ```
To save the `DataObject` to the database, use the `write()` method. The first time `write()` is called, an `ID` will be
set. set.
```php ```php
$player->write(); $player->write();
``` ```
For convenience, the `write()` method returns the record's ID. This is particularly useful when creating new records.
```php ```php
$player = Player::create(); $player = Player::create();
$id = $player->write(); $id = $player->write();
``` ```
## Querying Data
With the `Player` class defined we can query our data using the `ORM` or Object-Relational Model. The `ORM` provides With the `Player` class defined we can query our data using the `ORM` or Object-Relational Model. The `ORM` provides
shortcuts and methods for fetching, sorting and filtering data from our database. shortcuts and methods for fetching, sorting and filtering data from our database.
@ -162,8 +166,9 @@ shortcuts and methods for fetching, sorting and filtering data from our database
echo $player->dbObject('LastEdited')->Ago(); echo $player->dbObject('LastEdited')->Ago();
// calls the `Ago` method on the `LastEdited` property. // calls the `Ago` method on the `LastEdited` property.
``` ```
The `ORM` uses a "fluent" syntax, where you specify a query by chaining together different methods. Two common methods
are `filter()` and `sort()`: are `filter()` and `sort()`:
```php ```php
@ -172,8 +177,9 @@ are `filter()` and `sort()`:
))->sort('Surname'); ))->sort('Surname');
// returns a `DataList` containing all the `Player` records that have the `FirstName` of 'Sam' // returns a `DataList` containing all the `Player` records that have the `FirstName` of 'Sam'
``` ```
[info]
Provided `filter` values are automatically escaped and do not require any escaping. Provided `filter` values are automatically escaped and do not require any escaping.
[/info] [/info]
@ -195,6 +201,7 @@ result set in PHP. In `MySQL` the query generated by the ORM may look something
// SELECT * FROM Player WHERE FirstName = 'Sam' ORDER BY Surname // SELECT * FROM Player WHERE FirstName = 'Sam' ORDER BY Surname
``` ```
This also means that getting the count of a list of objects will be done with a single, efficient query. This also means that getting the count of a list of objects will be done with a single, efficient query.
```php ```php
@ -207,6 +214,7 @@ This also means that getting the count of a list of objects will be done with a
echo $players->Count(); echo $players->Count();
``` ```
## Looping over a list of objects ## Looping over a list of objects
`get()` returns a `DataList` instance. You can loop over `DataList` instances in both PHP and templates. `get()` returns a `DataList` instance. You can loop over `DataList` instances in both PHP and templates.
@ -217,18 +225,20 @@ This also means that getting the count of a list of objects will be done with a
foreach($players as $player) { foreach($players as $player) {
echo $player->FirstName; echo $player->FirstName;
} }
``` ```
Notice that we can step into the loop safely without having to check if `$players` exists. The `get()` call is robust, and will at worst return an empty `DataList` object. If you do want to check if the query returned any records, you can use the `exists()` method, e.g.
```php ```php
$players = Player::get(); $players = Player::get();
if($players->exists()) { if($players->exists()) {
// do something here // do something here
} }
``` ```
See the [Lists](lists) documentation for more information on dealing with [api:SS_List] instances.
## Returning a single DataObject ## Returning a single DataObject
There are a couple of ways of getting a single DataObject from the ORM. If you know the ID number of the object, you There are a couple of ways of getting a single DataObject from the ORM. If you know the ID number of the object, you
@ -236,17 +246,19 @@ can use `byID($id)`:
```php ```php
$player = Player::get()->byID(5); $player = Player::get()->byID(5);
``` ```
`get()` returns a [api:DataList] instance. You can use operations on that to get back a single record.
```php ```php
$players = Player::get(); $players = Player::get();
$first = $players->first(); $first = $players->first();
$last = $players->last(); $last = $players->last();
``` ```
## Sorting
If would like to sort the list by `FirstName` in a ascending way (from A to Z). If would like to sort the list by `FirstName` in a ascending way (from A to Z).
```php ```php
@ -255,16 +267,18 @@ If would like to sort the list by `FirstName` in a ascending way (from A to Z).
// Ascending is implied // Ascending is implied
$players = Player::get()->sort('FirstName'); $players = Player::get()->sort('FirstName');
``` ```
To reverse the sort
```php ```php
$players = Player::get()->sort('FirstName', 'DESC'); $players = Player::get()->sort('FirstName', 'DESC');
// or.. // or..
$players = Player::get()->sort('FirstName', 'ASC')->reverse(); $players = Player::get()->sort('FirstName', 'ASC')->reverse();
``` ```
However you might have several entries with the same `FirstName` and would like to sort them by `FirstName` and
`LastName` `LastName`
```php ```php
@ -272,14 +286,15 @@ If would like to sort the list by `FirstName` in a ascending way (from A to Z).
'FirstName' => 'ASC', 'FirstName' => 'ASC',
'LastName'=>'ASC' 'LastName'=>'ASC'
)); ));
``` ```
You can also sort randomly. Using the `DB` class, you can get the random sort method per database type.
```php ```php
$random = DB::get_conn()->random(); $random = DB::get_conn()->random();
$players = Player::get()->sort($random) $players = Player::get()->sort($random)
``` ```
## Filtering Results ## Filtering Results
The `filter()` method filters the list of objects that gets returned. The `filter()` method filters the list of objects that gets returned.
@ -288,8 +303,9 @@ The `filter()` method filters the list of objects that gets returned.
$players = Player::get()->filter(array( $players = Player::get()->filter(array(
'FirstName' => 'Sam' 'FirstName' => 'Sam'
)); ));
``` ```
Each element of the array specifies a filter. You can specify as many filters as you like, and they **all** must be
true for the record to be included in the result. true for the record to be included in the result.
The key in the filter corresponds to the field that you want to filter and the value in the filter corresponds to the The key in the filter corresponds to the field that you want to filter and the value in the filter corresponds to the
@ -304,22 +320,25 @@ So, this would return only those players called "Sam Minnée".
)); ));
// SELECT * FROM Player WHERE FirstName = 'Sam' AND LastName = 'Minnée' // SELECT * FROM Player WHERE FirstName = 'Sam' AND LastName = 'Minnée'
``` ```
There is also a shorthand way of getting Players with the FirstName of Sam.
```php ```php
$players = Player::get()->filter('FirstName', 'Sam'); $players = Player::get()->filter('FirstName', 'Sam');
``` ```
Or if you want to find both Sam and Sig.
```php ```php
$players = Player::get()->filter( $players = Player::get()->filter(
'FirstName', array('Sam', 'Sig') 'FirstName', array('Sam', 'Sig')
); );
// SELECT * FROM Player WHERE FirstName IN ('Sam', 'Sig') // SELECT * FROM Player WHERE FirstName IN ('Sam', 'Sig')
``` ```
You can use [SearchFilters](searchfilters) to add additional behavior to your `filter` command rather than an
exact match. exact match.
```php ```php
@ -327,8 +346,8 @@ exact match.
'FirstName:StartsWith' => 'S' 'FirstName:StartsWith' => 'S'
'PlayerNumber:GreaterThan' => '10' 'PlayerNumber:GreaterThan' => '10'
)); ));
``` ```
[notice] [notice]
Please note that in SilverStripe 3.x it's not possible to filter a list based on a field containing a `null` value (see [this issue](https://github.com/silverstripe/silverstripe-framework/issues/3621) for context). You can workaround this with a `where` statement, for example: Please note that in SilverStripe 3.x it's not possible to filter a list based on a field containing a `null` value (see [this issue](https://github.com/silverstripe/silverstripe-framework/issues/3621) for context). You can workaround this with a `where` statement, for example:
@ -349,9 +368,10 @@ Use the `filterAny()` method to match multiple criteria non-exclusively (with an
)); ));
// SELECT * FROM Player WHERE ("FirstName" = 'Sam' OR "Age" = '17') // SELECT * FROM Player WHERE ("FirstName" = 'Sam' OR "Age" = '17')
``` ```
You can combine both conjunctive ("AND") and disjunctive ("OR") statements.
```php ```php
$players = Player::get() $players = Player::get()
->filter(array( ->filter(array(
@ -362,16 +382,17 @@ Use the `filterAny()` method to match multiple criteria non-exclusively (with an
'Age' => 17, 'Age' => 17,
)); ));
// SELECT * FROM Player WHERE ("LastName" = 'Minnée' AND ("FirstName" = 'Sam' OR "Age" = '17')) // SELECT * FROM Player WHERE ("LastName" = 'Minnée' AND ("FirstName" = 'Sam' OR "Age" = '17'))
``` ```
You can use [SearchFilters](searchfilters) to add additional behavior to your `filterAny` command.
```php ```php
$players = Player::get()->filterAny(array( $players = Player::get()->filterAny(array(
'FirstName:StartsWith' => 'S' 'FirstName:StartsWith' => 'S'
'PlayerNumber:GreaterThan' => '10' 'PlayerNumber:GreaterThan' => '10'
)); ));
``` ```
### filterByCallback ### filterByCallback
It is also possible to filter by a PHP callback, this will force the data model to fetch all records and loop them in It is also possible to filter by a PHP callback, this will force the data model to fetch all records and loop them in
@ -392,25 +413,28 @@ The below example will get all `Players` aged over 10.
$players = Player::get()->filterByCallback(function($item, $list) { $players = Player::get()->filterByCallback(function($item, $list) {
return ($item->Age() > 10); return ($item->Age() > 10);
}); });
``` ```
### Exclude
The `exclude()` method is the opposite to the filter in that it removes entries from a list. The `exclude()` method is the opposite to the filter in that it removes entries from a list.
```php ```php
$players = Player::get()->exclude('FirstName', 'Sam'); $players = Player::get()->exclude('FirstName', 'Sam');
// SELECT * FROM Player WHERE FirstName != 'Sam' // SELECT * FROM Player WHERE FirstName != 'Sam'
``` ```
Remove both Sam and Sig..
```php ```php
$players = Player::get()->exclude( $players = Player::get()->exclude(
'FirstName', array('Sam','Sig') 'FirstName', array('Sam','Sig')
); );
``` ```
`Exclude` follows the same pattern as filter, so for removing only Sam Minnée from the list:
```php ```php
$players = Player::get()->exclude(array( $players = Player::get()->exclude(array(
'FirstName' => 'Sam', 'FirstName' => 'Sam',
@ -418,16 +442,18 @@ The `exclude()` method is the opposite to the filter in that it removes entries
)); ));
// SELECT * FROM Player WHERE (FirstName != 'Sam' OR LastName != 'Minnée') // SELECT * FROM Player WHERE (FirstName != 'Sam' OR LastName != 'Minnée')
``` ```
Removing players with *either* the first name of Sam or the last name of Minnée requires multiple `->exclude` calls:
```php ```php
$players = Player::get()->exclude('FirstName', 'Sam')->exclude('Surname', 'Minnée'); $players = Player::get()->exclude('FirstName', 'Sam')->exclude('Surname', 'Minnée');
// SELECT * FROM Player WHERE FirstName != 'Sam' AND LastName != 'Minnée' // SELECT * FROM Player WHERE FirstName != 'Sam' AND LastName != 'Minnée'
``` ```
And removing Sig and Sam with that are either age 17 or 43.
```php ```php
$players = Player::get()->exclude(array( $players = Player::get()->exclude(array(
'FirstName' => array('Sam', 'Sig'), 'FirstName' => array('Sam', 'Sig'),
@ -435,17 +461,19 @@ The `exclude()` method is the opposite to the filter in that it removes entries
)); ));
// SELECT * FROM Player WHERE ("FirstName" NOT IN ('Sam','Sig) OR "Age" NOT IN ('17', '43')); // SELECT * FROM Player WHERE ("FirstName" NOT IN ('Sam','Sig) OR "Age" NOT IN ('17', '43'));
``` ```
You can use [SearchFilters](searchfilters) to add additional behavior to your `exclude` command.
```php ```php
$players = Player::get()->exclude(array( $players = Player::get()->exclude(array(
'FirstName:EndsWith' => 'S' 'FirstName:EndsWith' => 'S'
'PlayerNumber:LessThanOrEqual' => '10' 'PlayerNumber:LessThanOrEqual' => '10'
)); ));
``` ```
### Subtract
You can subtract entries from a [api:DataList] by passing in another DataList to `subtract()` You can subtract entries from a [api:DataList] by passing in another DataList to `subtract()`
```php ```php
@ -453,30 +481,34 @@ You can subtract entries from a [api:DataList] by passing in another DataList to
$players = Player::get(); $players = Player::get();
$noSams = $players->subtract($sam); $noSams = $players->subtract($sam);
``` ```
Though for the above example it would probably be easier to use `filter()` and `exclude()`. A better use case could be
when you want to find all the members that does not exist in a Group. when you want to find all the members that does not exist in a Group.
```php ```php
// ... Finding all members that does not belong to $group. // ... Finding all members that does not belong to $group.
$otherMembers = Member::get()->subtract($group->Members()); $otherMembers = Member::get()->subtract($group->Members());
``` ```
### Limit
You can limit the amount of records returned in a DataList by using the `limit()` method. You can limit the amount of records returned in a DataList by using the `limit()` method.
```php ```php
$members = Member::get()->limit(5); $members = Member::get()->limit(5);
``` ```
`limit()` accepts two arguments, the first being the amount of results you want returned, with an optional second
parameter to specify the offset, which allows you to tell the system where to start getting the results from. The parameter to specify the offset, which allows you to tell the system where to start getting the results from. The
offset, if not provided as an argument, will default to 0. offset, if not provided as an argument, will default to 0.
```php ```php
// Return 10 members with an offset of 4 (starting from the 5th result). // Return 10 members with an offset of 4 (starting from the 5th result).
$members = Member::get()->sort('Surname')->limit(10, 4); $members = Member::get()->sort('Surname')->limit(10, 4);
``` ```
[alert]
Note that the `limit` argument order is different from a MySQL LIMIT clause. Note that the `limit` argument order is different from a MySQL LIMIT clause.
[/alert] [/alert]
@ -498,9 +530,10 @@ You can specify a WHERE clause fragment (that will be combined with other filter
```php ```php
$members = Member::get()->where("\"FirstName\" = 'Sam'") $members = Member::get()->where("\"FirstName\" = 'Sam'")
``` ```
#### Joining Tables
You can specify a join with the `innerJoin` and `leftJoin` methods. Both of these methods have the same arguments: You can specify a join with the `innerJoin` and `leftJoin` methods. Both of these methods have the same arguments:
* The name of the table to join to. * The name of the table to join to.
@ -514,8 +547,9 @@ You can specify a join with the `innerJoin` and `leftJoin` methods. Both of the
$members = Member::get() $members = Member::get()
->innerJoin("Group_Members", "\"Rel\".\"MemberID\" = \"Member\".\"ID\"", "Rel"); ->innerJoin("Group_Members", "\"Rel\".\"MemberID\" = \"Member\".\"ID\"", "Rel");
``` ```
[alert]
Passing a *$join* statement to will filter results further by the JOINs performed against the foreign table. It will Passing a *$join* statement to will filter results further by the JOINs performed against the foreign table. It will
**not** return the additionally joined data. **not** return the additionally joined data.
[/alert] [/alert]
@ -534,8 +568,9 @@ whenever a new object is created.
"Status" => 'Active', "Status" => 'Active',
); );
} }
``` ```
[notice]
Note: Alternatively you can set defaults directly in the database-schema (rather than the object-model). See Note: Alternatively you can set defaults directly in the database-schema (rather than the object-model). See
[Data Types and Casting](/developer_guides/model/data_types_and_casting) for details. [Data Types and Casting](/developer_guides/model/data_types_and_casting) for details.
[/notice] [/notice]
@ -562,9 +597,10 @@ For example, suppose we have the following set of classes:
'Summary' => 'Text' 'Summary' => 'Text'
); );
} }
``` ```
The data for the following classes would be stored across the following tables:
```yml ```yml
SiteTree: SiteTree:
- ID: Int - ID: Int
@ -576,18 +612,20 @@ For example, suppose we have the following set of classes:
NewsPage: NewsPage:
- ID: Int - ID: Int
- Summary: Text - Summary: Text
``` ```
Accessing the data is transparent to the developer.
```php ```php
$news = NewsPage::get(); $news = NewsPage::get();
foreach($news as $article) { foreach($news as $article) {
echo $article->Title; echo $article->Title;
} }
``` ```
The way the ORM stores the data is this:
* "Base classes" are direct sub-classes of [api:DataObject]. They are always given a table, whether or not they have * "Base classes" are direct sub-classes of [api:DataObject]. They are always given a table, whether or not they have
special fields. This is called the "base table". In our case, `SiteTree` is the base table. special fields. This is called the "base table". In our case, `SiteTree` is the base table.

View File

@ -52,15 +52,17 @@ This can be done by calling the static method [api:Config::inst()], like so:
```php ```php
$config = Config::inst()->get('MyClass'); $config = Config::inst()->get('MyClass');
```
Or through the config() method on the class.
``` ```php
```
$config = $this->config(); $config = $this->config();
``` ```
There are three public methods available on the instance. `get($class, $variable)`, `remove($class, $variable)` and
`update($class, $variable, $value)`. `update($class, $variable, $value)`.
[notice] [notice]
There is no "set" method. It is not possible to completely set the value of a classes' property. `update` adds new There is no "set" method. It is not possible to completely set the value of a classes' property. `update` adds new
values that are treated as the highest priority in the merge, and remove adds a merge mask that filters out values. values that are treated as the highest priority in the merge, and remove adds a merge mask that filters out values.
@ -77,9 +79,10 @@ To set those configuration options on our previously defined class we can define
- Foo - Foo
- Bar - Bar
- Baz - Baz
``` ```
To use those variables in your application code:
```php ```php
$me = new MyClass(); $me = new MyClass();
@ -109,6 +112,8 @@ To set those configuration options on our previously defined class we can define
// returns 'Qux' // returns 'Qux'
``` ```
[notice]
There is no way currently to restrict read or write access to any configuration property, or influence/check the values There is no way currently to restrict read or write access to any configuration property, or influence/check the values
being read or written. being read or written.
[/notice] [/notice]
@ -158,9 +163,10 @@ rather than add.
$actionsWithoutExtra = $this->config()->get( $actionsWithoutExtra = $this->config()->get(
'allowed_actions', Config::UNINHERITED 'allowed_actions', Config::UNINHERITED
); );
``` ```
They are much simpler. They consist of a list of key / value pairs. When applied against the current composite value
- If the composite value is a sequential array, any member of that array that matches any value in the mask is removed - If the composite value is a sequential array, any member of that array that matches any value in the mask is removed
- If the composite value is an associative array, any member of that array that matches both the key and value of any - If the composite value is an associative array, any member of that array that matches both the key and value of any
pair in the mask is removed pair in the mask is removed
@ -181,14 +187,11 @@ The name of the files within the applications `_config` directly are arbitrary.
The structure of each YAML file is a series of headers and values separated by YAML document separators. The structure of each YAML file is a series of headers and values separated by YAML document separators.
```yml ```yml
``` ---
```
Name: adminroutes Name: adminroutes
After: After:
```
- '#coreroutes' - '#coreroutes'
--- ---
```
Director: Director:
rules: rules:
'admin': 'AdminRootController' 'admin': 'AdminRootController'
@ -230,14 +233,11 @@ To specify these rules you add an "After" and/or "Before" key to the relevant he
keys is a list of reference paths to other value sections. A basic example: keys is a list of reference paths to other value sections. A basic example:
```yml ```yml
``` ---
```
Name: adminroutes Name: adminroutes
After: After:
```
- '#coreroutes' - '#coreroutes'
--- ---
```
Director: Director:
rules: rules:
'admin': 'AdminRootController' 'admin': 'AdminRootController'
@ -286,28 +286,23 @@ You then list any of the following rules as sub-keys, with informational values
- 'classexists', in which case the value(s) should be classes that must exist - 'classexists', in which case the value(s) should be classes that must exist
- 'moduleexists', in which case the value(s) should be modules that must exist - 'moduleexists', in which case the value(s) should be modules that must exist
- 'environment', in which case the value(s) should be one of "live", "test" or "dev" to indicate the SilverStripe - 'environment', in which case the value(s) should be one of "live", "test" or "dev" to indicate the SilverStripe
```
mode the site must be in mode the site must be in
``` - 'envvarset', in which case the value(s) should be environment variables that must be set
- 'constantdefined', in which case the value(s) should be constants that must be defined - 'constantdefined', in which case the value(s) should be constants that must be defined
For instance, to add a property to "foo" when a module exists, and "bar" otherwise, you could do this: For instance, to add a property to "foo" when a module exists, and "bar" otherwise, you could do this:
```yml ```yml
``` ---
```
Only: Only:
moduleexists: 'MyFineModule' moduleexists: 'MyFineModule'
``` ---
```
MyClass: MyClass:
property: 'foo' property: 'foo'
``` ---
```
Except: Except:
moduleexists: 'MyFineModule' moduleexists: 'MyFineModule'
``` ---
```
MyClass: MyClass:
property: 'bar' property: 'bar'
``` ```

View File

@ -768,7 +768,7 @@ class Email extends ViewableData {
return strtr($email, $obfuscated); return strtr($email, $obfuscated);
case 'hex' : case 'hex' :
$encoded = ''; $encoded = '';
for ($x=0; $x < strlen($email); $x++) $encoded .= '&#x' . bin2hex($email{$x}).';'; for ($x=0; $x < strlen($email); $x++) $encoded .= '&#x' . bin2hex($email[$x]).';';
return $encoded; return $encoded;
default: default:
user_error('Email::obfuscate(): Unknown obfuscation method', E_USER_NOTICE); user_error('Email::obfuscate(): Unknown obfuscation method', E_USER_NOTICE);

View File

@ -1810,7 +1810,7 @@ class Form extends RequestHandler {
* @return string * @return string
*/ */
public function extraClass() { public function extraClass() {
return implode(array_unique($this->extraClasses), ' '); return implode(' ', array_unique($this->extraClasses));
} }
/** /**

View File

@ -2615,7 +2615,7 @@ class i18n extends SS_Object implements TemplateGlobalProvider, Flushable {
if(is_dir($themesBase)) { if(is_dir($themesBase)) {
foreach(scandir($themesBase) as $theme) { foreach(scandir($themesBase) as $theme) {
if( if(
strpos($theme, Config::inst()->get('SSViewer', 'theme')) === 0 strpos($theme, (string)Config::inst()->get('SSViewer', 'theme')) === 0
&& file_exists("{$themesBase}/{$theme}/lang/") && file_exists("{$themesBase}/{$theme}/lang/")
) { ) {
$filename = $adapter->getFilenameForLocale($locale); $filename = $adapter->getFilenameForLocale($locale);

View File

@ -767,7 +767,7 @@ class Image extends File implements Flushable {
foreach($matches as $formatdir) { foreach($matches as $formatdir) {
$prepend[] = $formatdir[0]; $prepend[] = $formatdir[0];
} }
$filename = implode($prepend) . $filename; $filename = implode('', $prepend) . $filename;
if (!preg_match($pattern['FullPattern'], $filename)) { if (!preg_match($pattern['FullPattern'], $filename)) {
throw new InvalidArgumentException('Filename ' . $filename throw new InvalidArgumentException('Filename ' . $filename

View File

@ -88,7 +88,7 @@ class Text extends StringField {
} }
} }
return count($output)==0 ? '' : implode($output, '. ') . '.'; return count($output)==0 ? '' : implode('. ', $output) . '.';
} }