Merge 4.0 -> 4

This commit is contained in:
Damian Mooyman 2017-11-02 16:52:05 +13:00
commit 0b3ed7ff15
No known key found for this signature in database
GPG Key ID: 78B823A10DE27D1A
183 changed files with 7717 additions and 7030 deletions

View File

@ -1150,6 +1150,8 @@ warnings:
message: 'Removed'
'dontEscape()':
message: 'FormField::dontEscape() has been removed. Escaping is now managed on a class by class basis.'
'SilverStripe\Forms\CompositeField::setID()':
message: 'ID is generated from name indirectly; Use SilverStripe\Form\FormField::setName() instead'
'SilverStripe\Forms\FormField::createTag()':
message: 'moved to SilverStripe\View\HTML->createTag()'
'SilverStripe\Forms\Form->transformTo()':

View File

@ -6,7 +6,7 @@ SilverStripe\Control\HTTP:
max-age: 0
must-revalidate: "true"
no-transform: "true"
vary: "Cookie, X-Forwarded-Protocol, User-Agent, Accept"
vary: "Cookie, X-Forwarded-Protocol, X-Forwarded-Proto, User-Agent, Accept"
SilverStripe\Core\Manifest\VersionProvider:
modules:
silverstripe/framework: Framework

View File

@ -11,6 +11,7 @@ SilverStripe\Core\Injector\Injector:
SessionMiddleware: '%$SilverStripe\Control\Middleware\SessionMiddleware'
RequestProcessorMiddleware: '%$SilverStripe\Control\RequestProcessor'
FlushMiddleware: '%$SilverStripe\Control\Middleware\FlushMiddleware'
CanonicalURLMiddleware: '%$SilverStripe\Control\Middleware\CanonicalURLMiddleware'
SilverStripe\Control\Middleware\AllowedHostsMiddleware:
properties:
AllowedHosts: '`SS_ALLOWED_HOSTS`'
@ -37,3 +38,12 @@ After:
SilverStripe\Core\Injector\Injector:
# Note: If Director config changes, take note it will affect this config too
SilverStripe\Core\Startup\ErrorDirector: '%$SilverStripe\Control\Director'
---
Name: canonicalurls
---
SilverStripe\Core\Injector\Injector:
SilverStripe\Control\Middleware\CanonicalURLMiddleware:
properties:
ForceSSL: false
ForceWWW: false

View File

@ -1,3 +1,10 @@
# Run framework behat tests with this command (installed with silverstripe/installer)
# Note that framework behat tests require CMS module
# ========================================================================= #
# vendor/bin/selenium-server-standalone -Dwebdriver.firefox.bin="/Applications/Firefox31.app/Contents/MacOS/firefox-bin"
# vendor/bin/serve --bootstrap-file vendor/silverstripe/framework/tests/behat/serve-bootstrap.php
# vendor/bin/behat @framework
# ========================================================================= #
default:
suites:
framework:

View File

@ -79,7 +79,7 @@ since it makes permissions easier to handle when running commands both
from the command line and through the web server. Find and adjust the following options,
replacing the `<user>` placeholder:
User <user>
user <user>
Group staff
Now start the web server:

View File

@ -195,8 +195,6 @@ This creates the navigation at the top of the page:
![](../_images/tutorial1_menu.jpg)
### Highlighting the current page
A useful feature is highlighting the current page the user is looking at. We can do this by using the `is` methods `$isSection` and `$isCurrent`.
@ -330,7 +328,6 @@ The following example runs an if statement and a loop on *Children*, checking to
</ul>
```
## Using a different template for the home page
So far, a single template layout *Layouts/Page.ss* is being used for the entire site. This is useful for the purpose of this

View File

@ -9,7 +9,6 @@ This tutorial is deprecated, and has been replaced by Lessons 4, 5, and 6 in the
## Overview
In the [first tutorial (Building a basic site)](/tutorials/building_a_basic_site) we learnt how to create a basic site using SilverStripe. This tutorial will build on that, and explore extending SilverStripe by creating our own page types. After doing this we should have a better understanding of how SilverStripe works.
## What are we working towards?
@ -68,25 +67,21 @@ We'll start with the *ArticlePage* page type. First we create the model, a class
**mysite/code/ArticlePage.php**
```php
use Page;
class ArticlePage extends Page
{
}
```
**mysite/code/ArticlePageController.php**
```php
use PageController;
**mysite/code/ArticlePageController.php**
```php
class ArticlePageController extends PageController
{
}
```
Here we've created our data object/controller pair, but we haven't extended them at all. SilverStripe will use the template for the *Page* page type as explained in the first tutorial, so we don't need
to specifically create the view for this page type.
@ -95,17 +90,15 @@ Let's create the *ArticleHolder* page type.
**mysite/code/ArticleHolder.php**
```php
use Page;
class ArticleHolder extends Page
{
private static $allowed_children = ['ArticlePage'];
}
```
**mysite/code/ArticleHolderController.php**
```php
use PageController;
**mysite/code/ArticleHolderController.php**
```php
class ArticleHolderController extends PageController
{
@ -132,16 +125,12 @@ the $db array to add extra fields to the database. It would be nice to know when
it. Add a *$db* property definition in the *ArticlePage* class:
```php
use Page;
class ArticlePage extends Page
{
private static $db = [
'Date' => 'Date',
'Author' => 'Text'
];
// .....
}
```
@ -157,12 +146,8 @@ When we rebuild the database, we will see that the *ArticlePage* table has been
To add our new fields to the CMS we have to override the *getCMSFields()* method, which is called by the CMS when it creates the form to edit a page. Add the method to the *ArticlePage* class.
```php
use Page;
class ArticlePage extends Page
{
// ...
public function getCMSFields()
{
$fields = parent::getCMSFields();
@ -175,9 +160,6 @@ To add our new fields to the CMS we have to override the *getCMSFields()* method
return $fields;
}
}
// ...
```
Let's walk through this method.
@ -227,13 +209,8 @@ To make the date field a bit more user friendly, you can add a dropdown calendar
the date field will have the date format defined by your locale.
```php
use Page;
class ArticlePage extends Page
{
// .....
public function getCMSFields()
{
$fields = parent::getCMSFields();
@ -247,6 +224,7 @@ the date field will have the date format defined by your locale.
return $fields;
}
}
```
Let's walk through these changes.
@ -357,12 +335,11 @@ Cut the code between "loop Children" in *ArticleHolder.ss** and replace it with
**themes/simple/templates/Layout/ArticleHolder.ss**
```ss
...
<% loop $Children %>
<% include ArticleTeaser %>
<% end_loop %>
...
```
Paste the code that was in ArticleHolder into a new include file called ArticleTeaser.ss:
**themes/simple/templates/Includes/ArticleTeaser.ss**
@ -405,7 +382,6 @@ It would be nice to greet page visitors with a summary of the latest news when t
**mysite/code/HomePage.php**
```php
// ...
public function LatestNews($num=5)
{
$holder = ArticleHolder::get()->First();
@ -432,8 +408,6 @@ The controller for a page is only created when page is actually visited, while t
![](../_images/tutorial2_homepage-news.jpg)
## Creating a RSS feed
An RSS feed is something that no news section should be without. SilverStripe makes it easy to create RSS feeds by providing an [RSSFeed](api:SilverStripe\Control\RSS\RSSFeed) class to do all the hard work for us. Add the following in the *ArticleHolderController* class:
@ -482,8 +456,6 @@ Now that we have a complete news section, let's take a look at the staff section
**mysite/code/StaffHolder.php**
```php
use Page;
class StaffHolder extends Page
{
private static $db = [];
@ -495,8 +467,6 @@ Now that we have a complete news section, let's take a look at the staff section
**mysite/code/StaffHolderController.php**
```php
use PageController;
class StaffHolderController extends PageController
{
@ -533,8 +503,6 @@ Nothing here should be new. The *StaffPage* page type is more interesting though
**mysite/code/StaffPageController.php**
```php
use PageController;
class StaffPageController extends PageController
{
@ -579,7 +547,6 @@ The staff section templates aren't too difficult to create, thanks to the utilit
<% end_loop %>
$Form
</div>
```
This template is very similar to the *ArticleHolder* template. The *ScaleWidth* method of the [Image](api:SilverStripe\Assets\Image) class

View File

@ -36,7 +36,6 @@ To add the search form, we can add `$SearchForm` anywhere in our templates. In t
**themes/simple/templates/Includes/Header.ss**
```ss
...
<% if $SearchForm %>
<span class="search-dropdown-icon">L</span>
<div class="search-bar">
@ -62,8 +61,6 @@ is applied via `FulltextSearchable::enable()`
class ContentControllerSearchExtension extends Extension
{
...
public function results($data, $form, $request)
{
$data = [
@ -74,7 +71,6 @@ is applied via `FulltextSearchable::enable()`
return $this->owner->customise($data)->renderWith(['Page_results', 'Page']);
}
}
```
The code populates an array with the data we wish to pass to the template - the search results, query and title of the page. The final line is a little more complicated.

View File

@ -24,7 +24,6 @@ Let's look at a simple example:
class Player extends DataObject
{
private static $db = [
'PlayerNumber' => 'Int',
'FirstName' => 'Varchar(255)',
@ -32,7 +31,6 @@ Let's look at a simple example:
'Birthday' => 'Date'
];
}
```
This `Player` class definition will create a database table `Player` with columns for `PlayerNumber`, `FirstName` and
@ -82,7 +80,6 @@ automatically set on the `DataObject`.
class Player extends DataObject
{
private static $db = [
'PlayerNumber' => 'Int',
'FirstName' => 'Varchar(255)',
@ -90,11 +87,11 @@ automatically set on the `DataObject`.
'Birthday' => 'Date'
];
}
```
Generates the following `SQL`.
```sql
CREATE TABLE `Player` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`ClassName` enum('Player') DEFAULT 'Player',
@ -108,6 +105,7 @@ Generates the following `SQL`.
PRIMARY KEY (`ID`),
KEY `ClassName` (`ClassName`)
);
```
## Creating Data Records
@ -201,7 +199,6 @@ result set in PHP. In `MySQL` the query generated by the ORM may look something
// executes the following single query
// 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.
@ -214,7 +211,6 @@ This also means that getting the count of a list of objects will be done with a
// This will create an single SELECT COUNT query
// SELECT COUNT(*) FROM Player WHERE FirstName = 'Sam'
echo $players->Count();
```
## Looping over a list of objects
@ -288,7 +284,6 @@ However you might have several entries with the same `FirstName` and would like
'FirstName' => 'ASC',
'LastName'=>'ASC'
]);
```
You can also sort randomly. Using the `DB` class, you can get the random sort method per database type.
@ -306,7 +301,6 @@ The `filter()` method filters the list of objects that gets returned.
$players = Player::get()->filter([
'FirstName' => 'Sam'
]);
```
Each element of the array specifies a filter. You can specify as many filters as you like, and they **all** must be
@ -324,7 +318,6 @@ So, this would return only those players called "Sam 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.
@ -341,7 +334,6 @@ Or if you want to find both Sam and 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
@ -349,10 +341,9 @@ exact match.
```php
$players = Player::get()->filter([
'FirstName:StartsWith' => 'S'
'PlayerNumber:GreaterThan' => '10'
'FirstName:StartsWith' => 'S',
'PlayerNumber:GreaterThan' => '10',
]);
```
### filterAny
@ -366,7 +357,6 @@ Use the `filterAny()` method to match multiple criteria non-exclusively (with an
]);
// SELECT * FROM Player WHERE ("FirstName" = 'Sam' OR "Age" = '17')
```
You can combine both conjunctive ("AND") and disjunctive ("OR") statements.
@ -374,24 +364,22 @@ You can combine both conjunctive ("AND") and disjunctive ("OR") statements.
```php
$players = Player::get()
->filter([
'LastName' => 'Minnée'
'LastName' => 'Minnée',
])
->filterAny([
'FirstName' => 'Sam',
'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
$players = Player::get()->filterAny([
'FirstName:StartsWith' => 'S'
'PlayerNumber:GreaterThan' => '10'
'FirstName:StartsWith' => 'S',
'PlayerNumber:GreaterThan' => '10',
]);
```
### Filtering by null values
@ -418,7 +406,6 @@ If null values should be excluded, include the null in your check.
// ... WHERE "FirstName" != 'Sam' AND "FirstName" IS NOT NULL
// Only returns non-null values for "FirstName" that aren't Sam.
// Strictly the IS NOT NULL isn't necessary, but is included for explicitness
```
It is also often useful to filter by all rows with either empty or null for a given field.
@ -428,7 +415,6 @@ It is also often useful to filter by all rows with either empty or null for a gi
$players = Player::get()->filter('FirstName', [null, '']);
// ... WHERE "FirstName" == '' OR "FirstName" IS NULL
// Returns rows with FirstName which is either empty or null
```
### Filtering by aggregates
@ -487,7 +473,6 @@ Remove both Sam and Sig..
$players = Player::get()->exclude(
'FirstName', ['Sam','Sig']
);
```
`Exclude` follows the same pattern as filter, so for removing only Sam Minnée from the list:
@ -518,17 +503,15 @@ And removing Sig and Sam with that are either age 17 or 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
$players = Player::get()->exclude([
'FirstName:EndsWith' => 'S'
'FirstName:EndsWith' => 'S',
'PlayerNumber:LessThanOrEqual' => '10'
]);
```
### Subtract
@ -547,6 +530,7 @@ when you want to find all the members that does not exist in a Group.
```php
// ... Finding all members that does not belong to $group.
use SilverStripe\Security\Member;
$otherMembers = Member::get()->subtract($group->Members());
```
@ -555,6 +539,7 @@ when you want to find all the members that does not exist in a Group.
You can limit the amount of records returned in a DataList by using the `limit()` method.
```php
use SilverStripe\Security\Member;
$members = Member::get()->limit(5);
```
@ -582,7 +567,9 @@ For instance, the below model will be stored in the table name `BannerImage`
```php
namespace SilverStripe\BannerManager;
class BannerImage extends \DataObject
use SilverStripe\ORM\DataObject;
class BannerImage extends DataObject
{
private static $table_name = 'BannerImage';
}
@ -616,6 +603,9 @@ table and column.
```php
use SilverStripe\ORM\Queries\SQLSelect;
use SilverStripe\ORM\DataObject;
public function countDuplicates($model, $fieldToCheck)
{
$table = DataObject::getSchema()->tableForField($model, $field);
@ -683,7 +673,6 @@ whenever a new object is created.
"Status" => 'Active',
];
}
```
<div class="notice" markdown='1'>
@ -693,7 +682,6 @@ Note: Alternatively you can set defaults directly in the database-schema (rather
## Subclasses
Inheritance is supported in the data model: separate tables will be linked together, the data spread across these
tables. The mapping and saving logic is handled by SilverStripe, you don't need to worry about writing SQL most of the
time.
@ -702,7 +690,6 @@ For example, suppose we have the following set of classes:
```php
use SilverStripe\CMS\Model\SiteTree;
use Page;
class Page extends SiteTree
{
@ -710,28 +697,25 @@ For example, suppose we have the following set of classes:
}
class NewsPage extends Page
{
private static $db = [
'Summary' => 'Text'
];
}
```
The data for the following classes would be stored across the following tables:
```yml
SiteTree:
- ID: Int
- ClassName: Enum('SiteTree', 'Page', 'NewsPage')
- Created: Datetime
- LastEdited: Datetime
- Title: Varchar
- Content: Text
SilverStripe\CMS\Model\SiteTree:
ID: Int
ClassName: Enum('SiteTree', 'Page', 'NewsPage')
Created: Datetime
LastEdited: Datetime
Title: Varchar
Content: Text
NewsPage:
- ID: Int
- Summary: Text
ID: Int
Summary: Text
```
Accessing the data is transparent to the developer.

View File

@ -20,7 +20,6 @@ A 1-to-1 relation creates a database-column called "`<relationship-name>`ID", in
class Team extends DataObject
{
private static $db = [
'Title' => 'Varchar'
];
@ -31,12 +30,10 @@ A 1-to-1 relation creates a database-column called "`<relationship-name>`ID", in
}
class Player extends DataObject
{
private static $has_one = [
"Team" => "Team",
];
}
```
This defines a relationship called `Team` which links to a `Team` class. The `ORM` handles navigating the relationship
@ -57,7 +54,6 @@ At the database level, the `has_one` creates a `TeamID` field on `Player`. A `ha
The relationship can also be navigated in [templates](../templates).
```ss
<% with $Player %>
<% if $Team %>
Plays for $Team.Title
@ -101,7 +97,6 @@ Ideally, the associated has_many (or belongs_to) should be specified with dot no
"FanOf" => "DataObject"
];
}
```
<div class="warning" markdown='1'>
@ -126,7 +121,6 @@ available on both ends.
class Team extends DataObject
{
private static $db = [
'Title' => 'Varchar'
];
@ -142,7 +136,6 @@ available on both ends.
"Team" => "Team",
];
}
```
Much like the `has_one` relationship, `has_many` can be navigated through the `ORM` as well. The only difference being
@ -169,7 +162,6 @@ To specify multiple `$has_many` to the same object you can use dot notation to d
class Person extends DataObject
{
private static $has_many = [
"Managing" => "Company.Manager",
"Cleaning" => "Company.Cleaner",
@ -177,13 +169,11 @@ To specify multiple `$has_many` to the same object you can use dot notation to d
}
class Company extends DataObject
{
private static $has_one = [
"Manager" => "Person",
"Cleaner" => "Person"
];
}
```
Multiple `$has_one` relationships are okay if they aren't linking to the same object type. Otherwise, they have to be
@ -227,7 +217,6 @@ This is not mandatory unless the relationship would be otherwise ambiguous.
'Team' => 'Team.Coach'
];
}
```
## many_many
@ -261,7 +250,6 @@ be created with a pair of ID fields.
Extra fields on the mapping table can be created by declaring a `many_many_extraFields`
config to add extra columns.
```php
use SilverStripe\ORM\DataObject;
@ -270,15 +258,16 @@ config to add extra columns.
private static $many_many = [
"Supporters" => "Supporter",
];
private static $many_many_extraFields = [
'Supporters' => [
'Ranking' => 'Int'
]
];
}
class Supporter extends DataObject
{
private static $belongs_many_many = [
"Supports" => "Team",
];
@ -334,7 +323,7 @@ The syntax for `belongs_many_many` is unchanged.
private static $has_one = [
'Team' => 'Team',
'Supporter' => 'Supporter'
'Supporter' => 'Supporter',
];
}
```
@ -342,7 +331,6 @@ The syntax for `belongs_many_many` is unchanged.
In order to filter on the join table during queries, you can use the class name of the joining table
for any sql conditions.
```php
$team = Team::get()->byId(1);
$supporters = $team->Supporters()->where(['"TeamSupporter"."Ranking"' => 1]);
@ -358,7 +346,6 @@ The relationship can also be navigated in [templates](../templates).
The joined record can be accessed via `Join` or `TeamSupporter` property (many_many through only)
```ss
<% with $Supporter %>
<% loop $Supports %>
Supports $Title <% if $TeamSupporter %>(rank $TeamSupporter.Ranking)<% end_if %>
@ -389,15 +376,14 @@ distinguish them like below:
'FeaturedProducts' => 'Product'
];
}
class Product extends DataObject
{
private static $belongs_many_many = [
'Categories' => 'Category.Products',
'FeaturedInCategories' => 'Category.FeaturedProducts'
];
}
```
If you're unsure about whether an object should take on `many_many` or `belongs_many_many`,
@ -412,6 +398,8 @@ Relationships between objects can cause cascading deletions, if necessary, throu
`cascade_deletes` config on the parent class.
```php
use SilverStripe\ORM\DataObject;
class ParentObject extends DataObject {
private static $has_one = [
'Child' => ChildObject::class,
@ -462,7 +450,6 @@ See [DataObject::$has_many](api:SilverStripe\ORM\DataObject::$has_many) for more
class Team extends DataObject
{
private static $has_many = [
"Players" => "Player"
];

View File

@ -12,6 +12,8 @@ modify.
[SS_List](api:SilverStripe\ORM\SS_List) implements `IteratorAggregate`, allowing you to loop over the instance.
```php
use SilverStripe\Security\Member;
$members = Member::get();
foreach($members as $member) {
@ -22,7 +24,6 @@ modify.
Or in the template engine:
```ss
<% loop $Members %>
<!-- -->
<% end_loop %>
@ -52,7 +53,6 @@ A map is an array where the array indexes contain data as well as the values. Yo
// 2 => 'Sig'
// 3 => 'Will'
// );
```
This functionality is provided by the [Map](api:SilverStripe\ORM\Map) class, which can be used to build a map around any `SS_List`.
@ -74,7 +74,6 @@ This functionality is provided by the [Map](api:SilverStripe\ORM\Map) class, whi
// 'sig@silverstripe.com',
// 'will@silverstripe.com'
// );
```
## ArrayList
@ -91,7 +90,6 @@ This functionality is provided by the [Map](api:SilverStripe\ORM\Map) class, whi
echo $list->Count();
// returns '2'
```
## API Documentation

View File

@ -18,7 +18,6 @@ In the `Player` example, we have four database columns each with a different dat
class Player extends DataObject
{
private static $db = [
'PlayerNumber' => 'Int',
'FirstName' => 'Varchar(255)',
@ -26,7 +25,6 @@ In the `Player` example, we have four database columns each with a different dat
'Birthday' => 'Date'
];
}
```
## Available Types
@ -59,7 +57,6 @@ For simple values you can make use of the `$defaults` array. For example:
class Car extends DataObject
{
private static $db = [
'Wheels' => 'Int',
'Condition' => 'Enum(array("New","Fair","Junk"))'
@ -70,7 +67,6 @@ For simple values you can make use of the `$defaults` array. For example:
'Condition' => 'New'
];
}
```
### Default values for new database columns
@ -92,14 +88,12 @@ For example:
class Car extends DataObject
{
private static $db = [
'Wheels' => 'Int(4)',
'Condition' => 'Enum(array("New","Fair","Junk"), "New")',
'Make' => 'Varchar(["default" => "Honda"]),
);
}
```
## Formatting Output
@ -118,9 +112,6 @@ object we can control the formatting and it allows us to call methods defined fr
class Player extends DataObject
{
..
public function getName()
{
return DBField::create_field('Varchar', $this->FirstName . ' '. $this->LastName);
@ -152,7 +143,6 @@ Rather than manually returning objects from your custom functions. You can use t
class Player extends DataObject
{
private static $casting = [
"Name" => 'Varchar',
];
@ -162,7 +152,6 @@ Rather than manually returning objects from your custom functions. You can use t
return $this->FirstName . ' '. $this->LastName;
}
}
```
The properties on any SilverStripe object can be type casted automatically, by transforming its scalar value into an
@ -173,6 +162,7 @@ On the most basic level, the class can be used as simple conversion class from o
number.
```php
use SilverStripe\ORM\FieldType\DBField;
DBField::create_field('Double', 1.23456)->Round(2); // results in 1.23
```
@ -180,6 +170,7 @@ Of course that's much more verbose than the equivalent PHP call. The power of [D
sophisticated helpers, like showing the time difference to the current date:
```php
use SilverStripe\ORM\FieldType\DBField;
DBField::create_field('Date', '1982-01-01')->TimeDiff(); // shows "30 years ago"
```
@ -209,7 +200,6 @@ context. Through a `$casting` array, arbitrary properties and getters can be cas
$obj->MyDate; // returns string
$obj->obj('MyDate'); // returns object
$obj->obj('MyDate')->InPast(); // returns boolean
```
## Casting HTML Text
@ -235,7 +225,6 @@ database column using `dbObject`.
class Player extends DataObject
{
private static $db = [
"Status" => "Enum(array('Active', 'Injured', 'Retired'))"
];
@ -244,7 +233,7 @@ database column using `dbObject`.
{
return (!$this->obj("Birthday")->InPast()) ? "Unborn" : $this->dbObject('Status')->Value();
}
}
```
## API Documentation

View File

@ -24,9 +24,8 @@ Example: Disallow creation of new players if the currently logged-in player is n
class Player extends DataObject
{
private static $has_many = [
"Teams"=>"Team"
"Teams" => "Team",
];
public function onBeforeWrite()
@ -84,7 +83,6 @@ member is logged in who belongs to a group containing the permission "PLAYER_DEL
parent::onBeforeDelete();
}
}
```
<div class="notice" markdown='1'>

View File

@ -28,7 +28,6 @@ An example of a `SearchFilter` in use:
'FirstName:PartialMatch' => 'z',
'LastName:PartialMatch' => 'z'
]);
```
Developers can define their own [SearchFilter](api:SilverStripe\ORM\Filters\SearchFilter) if needing to extend the ORM filter and exclude behaviors.
@ -44,10 +43,9 @@ config:
```yaml
SilverStripe\Core\Injector\Injector:
DataListFilter.CustomMatch:
class: MyVendor/Search/CustomMatchFilter
class: MyVendor\Search\CustomMatchFilter
```
The following is a query which will return everyone whose first name starts with "S", either lowercase or uppercase:
@ -61,7 +59,6 @@ The following is a query which will return everyone whose first name starts with
$players = Player::get()->filter([
'FirstName:StartsWith:not' => 'W'
]);
```
## API Documentation

View File

@ -22,7 +22,6 @@ code.
class MyDataObject extends DataObject
{
public function canView($member = null)
{
return Permission::check('CMS_ACCESS_CMSMain', 'any', $member);

View File

@ -19,6 +19,10 @@ For example, if you want to run a simple `COUNT` SQL statement,
the following three statements are functionally equivalent:
```php
use SilverStripe\ORM\DB;
use SilverStripe\ORM\Queries\SQLSelect;
use SilverStripe\Security\Member;
// Through raw SQL.
$count = DB::query('SELECT COUNT(*) FROM "Member"')->value();
@ -58,7 +62,6 @@ conditional filters, grouping, limiting, and sorting.
E.g.
```php
$sqlQuery = new SQLSelect();
$sqlQuery->setFrom('Player');
$sqlQuery->selectField('FieldName', 'Name');
@ -81,7 +84,6 @@ E.g.
foreach($result as $row) {
echo $row['BirthYear'];
}
```
The result of `SQLSelect::execute()` is an array lightly wrapped in a database-specific subclass of [Query](api:SilverStripe\ORM\Connect\Query).
@ -96,32 +98,31 @@ object instead.
For example, creating a `SQLDelete` object
```php
use SilverStripe\ORM\Queries\SQLDelete;
$query = SQLDelete::create()
->setFrom('"SiteTree"')
->setWhere(['"SiteTree"."ShowInMenus"' => 0]);
$query->execute();
```
Alternatively, turning an existing `SQLSelect` into a delete
```php
use SilverStripe\ORM\Queries\SQLSelect;
$query = SQLSelect::create()
->setFrom('"SiteTree"')
->setWhere(['"SiteTree"."ShowInMenus"' => 0])
->toDelete();
$query->execute();
```
Directly querying the database
```php
use SilverStripe\ORM\DB;
DB::prepared_query('DELETE FROM "SiteTree" WHERE "SiteTree"."ShowInMenus" = ?', [0]);
```
### INSERT/UPDATE
@ -169,6 +170,8 @@ SQLInsert also includes the following api methods:
E.g.
```php
use SilverStripe\ORM\Queries\SQLUpdate;
$update = SQLUpdate::create('"SiteTree"')->addWhere(['ID' => 3]);
// assigning a list of items
@ -192,7 +195,6 @@ E.g.
// Perform the update
$update->execute();
```
In addition to assigning values, the SQLInsert object also supports multi-row
@ -202,6 +204,8 @@ these are translated internally as multiple single row inserts.
For example,
```php
use SilverStripe\ORM\Queries\SQLInsert;
$insert = SQLInsert::create('"SiteTree"');
// Add multiple rows in a single call. Note that column names do not need
@ -221,7 +225,6 @@ For example,
// $columns will be array('"Title"', '"Content"', '"ClassName"');
$insert->execute();
```
### Value Checks
@ -232,13 +235,14 @@ e.g. when you want a single column rather than a full-blown object representatio
Example: Get the count from a relationship.
```php
use SilverStripe\ORM\Queries\SQLSelect;
$sqlQuery = new SQLSelect();
$sqlQuery->setFrom('Player');
$sqlQuery->addSelect('COUNT("Player"."ID")');
$sqlQuery->addWhere(['"Team"."ID"' => 99]);
$sqlQuery->addLeftJoin('Team', '"Team"."ID" = "Player"."TeamID"');
$count = $sqlQuery->execute()->value();
```
Note that in the ORM, this call would be executed in an efficient manner as well:
@ -255,6 +259,9 @@ This can be useful for creating dropdowns.
Example: Show player names with their birth year, but set their birth dates as values.
```php
use SilverStripe\ORM\Queries\SQLSelect;
use SilverStripe\Forms\DropdownField;
$sqlQuery = new SQLSelect();
$sqlQuery->setFrom('Player');
$sqlQuery->setSelect('Birthdate');
@ -282,7 +289,6 @@ An alternative approach would be a custom getter in the object definition.
}
$players = Player::get();
$map = $players->map('Name', 'NameWithBirthyear');
```
## Related

View File

@ -43,7 +43,6 @@ The return value of `validate()` is a [ValidationResult](api:SilverStripe\ORM\Va
return $result;
}
}
```
## API Documentation

View File

@ -174,6 +174,8 @@ By default, all records are retrieved from the "Draft" stage (so the `MyRecord`
explicitly request a certain stage through various getters on the `Versioned` class.
```php
use SilverStripe\Versioned\Versioned;
// Fetching multiple records
$stageRecords = Versioned::get_by_stage('MyRecord', Versioned::DRAFT);
$liveRecords = Versioned::get_by_stage('MyRecord', Versioned::LIVE);

View File

@ -33,13 +33,11 @@ An example is `DataObject`, SilverStripe will automatically create your CMS inte
return $fields;
}
}
```
To fully customise your form fields, start with an empty FieldList.
```php
public function getCMSFields()
{
$fields = FieldList::create(
@ -74,7 +72,6 @@ system. The default is a set of array values listing the fields.
'ProductCode'
];
}
```
Searchable fields will be appear in the search interface with a default form field (usually a [TextField](api:SilverStripe\Forms\TextField)) and a
@ -92,7 +89,6 @@ additional information on `$searchable_fields`:
'ProductCode' => 'NumericField'
];
}
```
If you assign a single string value, you can set it to be either a [FormField](api:SilverStripe\Forms\FormField) or [SearchFilter](api:SilverStripe\ORM\Filters\SearchFilter). To specify
@ -116,7 +112,6 @@ both, you can assign an array:
],
];
}
```
To include relations (`$has_one`, `$has_many` and `$many_many`) in your search, you can use a dot-notation.
@ -126,7 +121,6 @@ To include relations (`$has_one`, `$has_many` and `$many_many`) in your search,
class Team extends DataObject
{
private static $db = [
'Title' => 'Varchar'
];
@ -140,12 +134,12 @@ To include relations (`$has_one`, `$has_many` and `$many_many`) in your search,
'Players.Name',
];
}
class Player extends DataObject
{
private static $db = [
'Name' => 'Varchar',
'Birthday' => 'Date'
'Birthday' => 'Date',
];
private static $belongs_many_many = [
@ -165,7 +159,6 @@ is their display as table columns, e.g. in the search results of a [ModelAdmin](
class MyDataObject extends DataObject
{
private static $db = [
'Name' => 'Text',
'OtherProperty' => 'Text',
@ -174,10 +167,9 @@ is their display as table columns, e.g. in the search results of a [ModelAdmin](
private static $summary_fields = [
'Name',
'ProductCode'
'ProductCode',
];
}
```
To include relations or field manipulations in your summaries, you can use a dot-notation.
@ -187,27 +179,26 @@ To include relations or field manipulations in your summaries, you can use a dot
class OtherObject extends DataObject
{
private static $db = [
'Title' => 'Varchar'
'Title' => 'Varchar',
];
}
class MyDataObject extends DataObject
{
private static $db = [
'Name' => 'Text',
'Description' => 'HTMLText'
'Description' => 'HTMLText',
];
private static $has_one = [
'OtherObject' => 'OtherObject'
'OtherObject' => 'OtherObject',
];
private static $summary_fields = [
'Name' => 'Name',
'Description.Summary' => 'Description (summary)',
'OtherObject.Title' => 'Other Object Title'
'OtherObject.Title' => 'Other Object Title',
];
}
@ -220,18 +211,17 @@ Non-textual elements (such as images and their manipulations) can also be used i
class MyDataObject extends DataObject
{
private static $db = [
'Name' => 'Text'
'Name' => 'Text',
];
private static $has_one = [
'HeroImage' => 'Image'
'HeroImage' => 'Image',
];
private static $summary_fields = [
'Name' => 'Name',
'HeroImage.CMSThumbnail' => 'Hero Image'
'HeroImage.CMSThumbnail' => 'Hero Image',
];
}

View File

@ -29,7 +29,6 @@ descriptor. There are several supported notations:
class MyObject extends DataObject
{
private static $indexes = [
'<column-name>' => true,
'<index-name>' => [
@ -59,7 +58,6 @@ support the following:
class MyTestObject extends DataObject
{
private static $db = [
'MyField' => 'Varchar',
'MyOtherField' => 'Varchar',

View File

@ -19,6 +19,7 @@ A simple example is to set a field to the current date and time:
parent::populateDefaults();
}
```
It's also possible to get the data from any other source, or another object, just by using the usual data retrieval
methods. For example:

View File

@ -52,7 +52,6 @@ will be used both for grouping and for the title in the template.
return $this->Title[0];
}
}
```
The next step is to create a method or variable that will contain/return all the objects,
@ -64,9 +63,6 @@ sorted by title. For this example this will be a method on the `Page` class.
class Page extends SiteTree
{
// ...
/**
* Returns all modules, sorted by their title.
* @return GroupedList
@ -75,7 +71,6 @@ sorted by title. For this example this will be a method on the `Page` class.
{
return GroupedList::create(Module::get()->sort('Title'));
}
}
```
@ -113,9 +108,6 @@ This will have a method which returns the month it was posted in:
class Module extends DataObject
{
// ...
/**
* Returns the month name this news item was posted in.
* @return string
@ -124,7 +116,6 @@ This will have a method which returns the month it was posted in:
{
return date('F', strtotime($this->Created));
}
}
```
@ -137,9 +128,6 @@ sorted by month name from January to December. This can be accomplshed by sortin
class Page extends SiteTree
{
// ...
/**
* Returns all news items, sorted by the month they were posted
* @return GroupedList
@ -148,7 +136,6 @@ sorted by month name from January to December. This can be accomplshed by sortin
{
return GroupedList::create(Module::get()->sort('Created'));
}
}
```
The final step is the render this into the template using the [GroupedList::GroupedBy()](api:SilverStripe\ORM\GroupedList::GroupedBy()) method.
@ -165,7 +152,7 @@ The final step is the render this into the template using the [GroupedList::Grou
</ul>
<% end_loop %>
```
## Related
* [Howto: "Pagination"](/developer_guides/templates/how_tos/pagination)

View File

@ -12,7 +12,6 @@ An example of a SilverStripe template is below:
**mysite/templates/Page.ss**
```ss
<html>
<head>
<% base_tag %>
@ -69,7 +68,6 @@ Variables are placeholders that will be replaced with data from the [DataModel](
alphabetic character or underscore, with subsequent characters being alphanumeric or underscore:
```ss
$Title
```
@ -78,7 +76,6 @@ This inserts the value of the Title database field of the page being displayed i
Variables can be chained together, and include arguments.
```ss
$Foo
$Foo(param)
$Foo.Bar
@ -113,7 +110,6 @@ Variables can come from your database fields, or custom methods you define on yo
**mysite/code/Page.ss**
```html
<p>You are coming from $UsersIpAddress.</p>
```
@ -129,7 +125,6 @@ record and any subclasses of those two.
**mysite/code/Layout/Page.ss**
```ss
$Title
// returns the page `Title` property
@ -142,7 +137,6 @@ record and any subclasses of those two.
The simplest conditional block is to check for the presence of a value (does not equal 0, null, false).
```ss
<% if $CurrentMember %>
<p>You are logged in as $CurrentMember.FirstName $CurrentMember.Surname.</p>
<% end_if %>
@ -151,7 +145,6 @@ The simplest conditional block is to check for the presence of a value (does not
A conditional can also check for a value other than falsy.
```ss
<% if $MyDinner == "kipper" %>
Yummy, kipper for tea.
<% end_if %>
@ -164,7 +157,6 @@ When inside template tags variables should have a '$' prefix, and literals shoul
Conditionals can also provide the `else` case.
```ss
<% if $MyDinner == "kipper" %>
Yummy, kipper for tea
<% else %>
@ -175,7 +167,6 @@ Conditionals can also provide the `else` case.
`else_if` commands can be used to handle multiple `if` statements.
```ss
<% if $MyDinner == "quiche" %>
Real men don't eat quiche
<% else_if $MyDinner == $YourDinner %>
@ -190,7 +181,6 @@ Conditionals can also provide the `else` case.
The inverse of `<% if %>` is `<% if not %>`.
```ss
<% if not $DinnerInOven %>
I'm going out for dinner tonight.
<% end_if %>
@ -203,7 +193,6 @@ Multiple checks can be done using `||`, `or`, `&&` or `and`.
If *either* of the conditions is true.
```ss
<% if $MyDinner == "kipper" || $MyDinner == "salmon" %>
yummy, fish for tea
<% end_if %>
@ -212,7 +201,6 @@ If *either* of the conditions is true.
If *both* of the conditions are true.
```ss
<% if $MyDinner == "quiche" && $YourDinner == "kipper" %>
Lets swap dinners
<% end_if %>
@ -223,7 +211,6 @@ If *both* of the conditions are true.
You can use inequalities like `<`, `<=`, `>`, `>=` to compare numbers.
```ss
<% if $Number >= "5" && $Number <= "10" %>
Number between 5 and 10
<% end_if %>
@ -236,7 +223,6 @@ will be searched for using the same filename look-up rules as a regular template
an additional `Includes` directory will be inserted into the resolved path just prior to the filename.
```ss
<% include SideBar %> <!-- chooses templates/Includes/Sidebar.ss -->
<% include MyNamespace/SideBar %> <!-- chooses templates/MyNamespace/Includes/Sidebar.ss -->
```
@ -245,7 +231,6 @@ When using subfolders in your template structure
(e.g. to fit with namespaces in your PHP class structure), the `Includes/` folder needs to be innermost.
```ss
<% include MyNamespace/SideBar %> <!-- chooses templates/MyNamespace/Includes/Sidebar.ss -->
```
@ -253,7 +238,6 @@ The `include` tag can be particularly helpful for nested functionality and break
the include only happens if the user is logged in.
```ss
<% if $CurrentMember %>
<% include MembersOnlyInclude %>
<% end_if %>
@ -263,7 +247,6 @@ Includes can't directly access the parent scope when the include is included. Ho
include.
```ss
<% with $CurrentMember %>
<% include MemberDetails Top=$Top, Name=$Name %>
<% end_with %>
@ -275,9 +258,7 @@ The `<% loop %>` tag is used to iterate or loop over a collection of items such
collection.
```ss
<h1>Children of $Title</h1>
<ul>
<% loop $Children %>
<li>$Title</li>
@ -304,7 +285,6 @@ templates can call [DataList](api:SilverStripe\ORM\DataList) methods.
Sorting the list by a given field.
```ss
<ul>
<% loop $Children.Sort(Title, ASC) %>
<li>$Title</li>
@ -315,7 +295,6 @@ Sorting the list by a given field.
Limiting the number of items displayed.
```ss
<ul>
<% loop $Children.Limit(10) %>
<li>$Title</li>
@ -326,7 +305,6 @@ Limiting the number of items displayed.
Reversing the loop.
```ss
<ul>
<% loop $Children.Reverse %>
<li>$Title</li>
@ -337,7 +315,6 @@ Reversing the loop.
Filtering the loop.
```ss
<ul>
<% loop $Children.Filter('School', 'College') %>
<li>$Title</li>
@ -348,7 +325,6 @@ Filtering the loop.
Methods can also be chained.
```ss
<ul>
<% loop $Children.Filter('School', 'College').Sort(Score, DESC) %>
<li>$Title</li>
@ -372,7 +348,6 @@ iteration.
* `$TotalItems`: Number of items in the list (integer).
```ss
<ul>
<% loop $Children.Reverse %>
<% if First %>
@ -394,7 +369,6 @@ pagination.
$Modulus and $MultipleOf can help to build column and grid layouts.
```ss
// returns an int
$Modulus(value, offset)
@ -419,7 +393,6 @@ $MultipleOf(value, offset) can also be utilized to build column and grid layouts
after every 3rd item.
```ss
<% loop $Children %>
<% if $MultipleOf(3) %>
<br>
@ -432,7 +405,6 @@ after every 3rd item.
Sometimes you will have template tags which need to roll into one another. Use `{}` to contain variables.
```ss
$Foopx // will returns "" (as it looks for a `Foopx` value)
{$Foo}px // returns "3px" (CORRECT)
```
@ -440,7 +412,6 @@ Sometimes you will have template tags which need to roll into one another. Use `
Or when having a `$` sign in front of the variable such as displaying money.
```ss
$$Foo // returns ""
${$Foo} // returns "$3"
```
@ -448,7 +419,6 @@ Or when having a `$` sign in front of the variable such as displaying money.
You can also use a backslash to escape the name of the variable, such as:
```ss
$Foo // returns "3"
\$Foo // returns "$Foo"
```
@ -480,7 +450,6 @@ classes of the current scope object, and any [Extension](api:SilverStripe\Core\E
When in a particular scope, `$Up` takes the scope back to the previous level.
```ss
<h1>Children of '$Title'</h1>
<% loop $Children %>
@ -513,7 +482,6 @@ Additional selectors implicitely change the scope so you need to put additional
</div>
```ss
<h1>Children of '$Title'</h1>
<% loop $Children.Sort('Title').First %>
<%-- We have two additional selectors in the loop expression so... --%>
@ -527,7 +495,6 @@ While `$Up` provides us a way to go up one level of scope, `$Top` is a shortcut
page. The previous example could be rewritten to use the following syntax.
```ss
<h1>Children of '$Title'</h1>
<% loop $Children %>
@ -544,7 +511,6 @@ page. The previous example could be rewritten to use the following syntax.
The `<% with %>` tag lets you change into a new scope. Consider the following example:
```ss
<% with $CurrentMember %>
Hello, $FirstName, welcome back. Your current balance is $Balance.
<% end_with %>
@ -553,7 +519,6 @@ The `<% with %>` tag lets you change into a new scope. Consider the following ex
This is functionalty the same as the following:
```ss
Hello, $CurrentMember.FirstName, welcome back. Your current balance is $CurrentMember.Balance
```
@ -568,7 +533,6 @@ refer directly to properties and methods of the [Member](api:SilverStripe\Securi
`$Me` outputs the current object in scope. This will call the `forTemplate` of the object.
```ss
$Me
```
@ -577,7 +541,6 @@ refer directly to properties and methods of the [Member](api:SilverStripe\Securi
Using standard HTML comments is supported. These comments will be included in the published site.
```ss
$EditForm <!-- Some public comment about the form -->
```
@ -585,7 +548,6 @@ However you can also use special SilverStripe comments which will be stripped ou
for adding notes for other developers but for things you don't want published in the public html.
```ss
$EditForm <%-- Some hidden comment about the form --%>
```

View File

@ -31,7 +31,6 @@ functionality may not be included.
## Base Tag
```ss
<head>
<% base_tag %>
@ -51,8 +50,9 @@ A `<% base_tag %>` is nearly always required or assumed by SilverStripe to exist
## CurrentMember
Returns the currently logged in [Member](api:SilverStripe\Security\Member) instance, if there is one logged in.```ss
Returns the currently logged in [Member](api:SilverStripe\Security\Member) instance, if there is one logged in.
```ss
<% if $CurrentMember %>
Welcome Back, $CurrentMember.FirstName
<% end_if %>
@ -61,7 +61,6 @@ Returns the currently logged in [Member](api:SilverStripe\Security\Member) insta
## Title and Menu Title
```ss
$Title
$MenuTitle
```
@ -79,7 +78,6 @@ If `MenuTitle` is left blank by the CMS author, it'll just default to the value
## Page Content
```ss
$Content
```
@ -103,7 +101,6 @@ web pages. You'll need to install it via `composer`.
</div>
```ss
$SiteConfig.Title
```
@ -127,14 +124,14 @@ If you dont want to include the title tag use `$MetaTags(false)`.
By default `$MetaTags` renders:
```ss
<title>Title of the Page</title>
<meta name="generator" http-equiv="generator" content="SilverStripe 3.0" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
```
`$MetaTags(false)` will render```ss
`$MetaTags(false)` will render
```ss
<meta name="generator" http-equiv="generator" content="SilverStripe 3.0" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
```
@ -142,7 +139,6 @@ By default `$MetaTags` renders:
If using `$MetaTags(false)` we can provide a more custom `title`.
```ss
$MetaTags(false)
<title>$Title - Bob's Fantasy Football</title>
```
@ -150,7 +146,6 @@ If using `$MetaTags(false)` we can provide a more custom `title`.
## Links
```ss
<a href="$Link">..</a>
```
@ -159,7 +154,6 @@ returns the relative URL for the object and `AbsoluteLink` outputs your full web
link.
```ss
$Link
<!-- returns /about-us/offices/ -->
@ -170,7 +164,6 @@ link.
### Linking Modes
```ss
$isSection
$isCurrent
```
@ -181,7 +174,6 @@ will return true or false based on page being looped over relative to the curren
For instance, to only show the menu item linked if it's the current one:
```ss
<% if $isCurrent %>
$Title
<% else %>
@ -192,7 +184,6 @@ For instance, to only show the menu item linked if it's the current one:
An example for checking for `current` or `section` is as follows:
```ss
<a class="<% if $isCurrent %>current<% else_if $isSection %>section<% end_if %>" href="$Link">$MenuTitle</a>
```
@ -201,7 +192,6 @@ An example for checking for `current` or `section` is as follows:
* `$InSection(page-url)`: This if block will pass if we're currently on the page-url page or one of its children.
```ss
<% if $InSection(about-us) %>
<p>You are viewing the about us section</p>
<% end_if %>
@ -214,7 +204,6 @@ This returns the part of the URL of the page you're currently on. For example on
It can be used within templates to generate anchors or other CSS classes.
```ss
<div id="section-$URLSegment">
</div>
@ -229,7 +218,6 @@ handy for a number of uses. A common use case is to add to your `<body>` tag to
behavior based on the page type used:
```ss
<body class="$ClassName">
<!-- returns <body class="HomePage">, <body class="BlogPage"> -->
@ -238,7 +226,6 @@ behavior based on the page type used:
## Children Loops
```ss
<% loop $Children %>
<% end_loop %>
@ -255,7 +242,6 @@ context.
### ChildrenOf
```ss
<% loop $ChildrenOf(<my-page-url>) %>
<% end_loop %>
@ -273,7 +259,6 @@ This option will be honored by `<% loop $Children %>` and `<% loop $Menu %>` how
preference, `AllChildren` does not filter by `ShowInMenus`.
```ss
<% loop $AllChildren %>
...
<% end_loop %>
@ -282,7 +267,6 @@ preference, `AllChildren` does not filter by `ShowInMenus`.
### Menu Loops
```ss
<% loop $Menu(1) %>
...
<% end_loop %>
@ -297,7 +281,6 @@ Pages with the `ShowInMenus` property set to `false` will be filtered out.
## Access to a specific Page
```ss
<% with $Page(my-page) %>
$Title
<% end_with %>
@ -310,7 +293,6 @@ Page will return a single page from site, looking it up by URL.
### Level
```ss
<% with $Level(1) %>
$Title
<% end_with %>
@ -328,7 +310,6 @@ For example, imagine you're on the "bob marley" page, which is three levels in:
### Parent
```ss
<!-- given we're on 'Bob Marley' in "about us > staff > bob marley" -->
$Parent.Title
@ -351,7 +332,6 @@ While you can achieve breadcrumbs through the `$Level(<level>)` control manually
`$Breadcrumbs` variable.
```ss
$Breadcrumbs
```
@ -359,7 +339,6 @@ By default, it uses the template defined in `templates/BreadcrumbsTemplate.ss`
of the `silverstripe/cms` module.
```ss
<% if $Pages %>
<% loop $Pages %>
<% if $Last %>$Title.XML<% else %><a href="$Link">$MenuTitle.XML</a> &raquo;<% end_if %>
@ -376,7 +355,6 @@ To customise the markup that the `$Breadcrumbs` generates, copy `templates/Bread
## Forms
```ss
$Form
```

View File

@ -16,7 +16,6 @@ The `Requirements` class can work with arbitrary file paths.
**<my-module-dir>/templates/SomeTemplate.ss**
```ss
<% require css("<my-module-dir>/css/some_file.css") %>
<% require themedCSS("some_themed_file") %>
<% require javascript("<my-module-dir>/javascript/some_file.js") %>
@ -50,6 +49,8 @@ class MyCustomController extends Controller
### CSS Files
```php
use SilverStripe\View\Requirements;
Requirements::css($path, $media);
```

View File

@ -3,97 +3,40 @@ summary: Override and extend module and core markup templates from your applicat
# Template Inheritance
Bundled within SilverStripe are default templates for any markup the framework outputs for things like Form templates,
Emails or RSS Feeds. These templates are provided to make getting your site up and running quick with sensible defaults
but it's easy to replace and customise SilverStripe (and add-on's) by providing custom templates in your own
`mysite/templates` folder or in your `themes/your_theme/templates` folder.
## Theme types
Take for instance the `GenericEmail` template in SilverStripe. This is the HTML default template that any email created
in SilverStripe is rendered with. It's bundled in the core framework at `framework/templates/email/GenericEmail.ss`.
Templates in SilverStripe are bundled into one of two groups:
- Default Templates, such as those provided in `mymodule/templates` folder.
- Theme templates, such as those provided in `themes/mytheme/templates` folders.
Instead of editing that file to provide a custom template for your application, simply define a template of the same
name in the `mysite/templates/email` folder or in the `themes/your_theme/templates/email` folder if you're using themes.
The default templates provide basic HTML formatting for elements such as Forms, Email, or RSS Feeds, and provide a
generic base for web content to be built on.
**mysite/templates/email/GenericEmail.ss**
## Template types and locations
```ss
$Body
Typically all templates within one of the above locations will be nested in a folder deterministically through
the fully qualified namespace of the underlying class, and an optional `type` specifier to segment template types.
Basic template types include `Layout` and `Includes`, and a less commonly used `Content` type.
<p>Thanks from Bob's Fantasy Football League.</p>
```
All emails going out of our application will have the footer `Thanks from Bob's Fantasy Football Leaguee` added.
For instance, a class `SilverStripe\Blog\BlogPage` will have a default template of type `Layout`
in the folder `vendor/silverstripe/blog/templates/SilverStripe/Blog/Layout/BlogPage.ss`.
<div class="alert" markdown="1">
As we've added a new file, make sure you flush your SilverStripe cache by visiting `http://yoursite.com/?flush=1`
</div>
Note: The optional `type`, if specified, will require a nested folder at the end of the parent namespace
(`SilverStripe\Blog`) to the class, but before the filename (`BlogPage`).
Template inheritance works on more than email templates. All files within the `templates` directory including `includes`,
`layout` or anything else from core (or add-on's) template directory can be overridden by being located inside your
`mysite/templates` directory. SilverStripe keeps an eye on what templates have been overridden and the location of the
correct template through a [ThemeResourceLoader](api:SilverStripe\View\ThemeResourceLoader).
Templates not backed by any class can exist in any location, but must always be referred to in code
by the full path (from the `templates` folder onwards).
## ThemeResourceLoader
The location of each template and the hierarchy of what template to use is stored within a [ThemeResourceLoader](api:SilverStripe\View\ThemeResourceLoader)
instance. This is a serialized object containing a map of [ThemeManifest](api:SilverStripe\View\ThemeManifest) instances. For SilverStripe to find the `GenericEmail` template
it does not check all your `template` folders on the fly, it simply asks the manifests. The manifests are created and added to the loader when the
[kernel](api:SilverStripe\Core\CoreKernel) is instantiated.
## Template Priority
The order in which templates are selected from themes can be explicitly declared
through configuration. To specify the order you want, make a list of the module
names under `SilverStripe\Core\Manifest\ModuleManifest.module_priority` in a
configuration YAML file.
*some-module/_config.yml*
```yml
SilverStripe\Core\Manifest\ModuleManifest:
module_priority:
- 'example/module-one'
- 'example/module-two'
- '$other_modules'
- 'example/module-three'
```
The placeholder `$other_modules` is used to mark where all of the modules not specified
in the list should appear. (In alphabetical order of their containing directory names).
In this example, the module named `example/module-one` has the highest level of precedence,
followed by `example/module-two`. The module `example/module-three` is guaranteed the lowest
level of precedence.
### Defining a "project"
It is a good idea to define one of your modules as the `project`. Commonly, this is the
`mysite/` module, but there is nothing compulsory about that module name. The "project"
module can be specified as a variable in the `module_priorities` list, as well.
*some-module/_config.yml*
```yml
SilverStripe\Core\Manifest\ModuleManifest:
project: 'myapp'
module_priority:
- '$project'
- '$other_modules'
```
### About module "names"
Module names are derived their local `composer.json` files using the following precedence:
* The value of the `name` attribute in `composer.json`
* The value of `extras.installer_name` in `composer.json`
* The basename of the directory that contains the module
## Nested Layouts through `$Layout`
### Nested Layouts through `$Layout` type
SilverStripe has basic support for nested layouts through a fixed template variable named `$Layout`. It's used for
storing top level template information separate to individual page layouts.
When `$Layout` is found within a root template file (one in `templates`), SilverStripe will attempt to fetch a child
template from the `templates/Layout` directory. It will do a full sweep of your modules, core and custom code as it
would if it was looking for a new root template.
template from the `templates/<namespace>/Layout/<class>.ss` path, where `<namespace>` and `<class>` represent
the class being rendered. It will do a full sweep of your modules, core and custom code as it
would if it was looking for a new root template, as well as looking down the class hierarchy until
it finds a template.
This is better illustrated with an example. Take for instance our website that has two page types `Page` and `HomePage`.
@ -102,6 +45,7 @@ footer and navigation will remain the same and we don't want to replicate this w
`$Layout` function allows us to define the child template area which can be overridden.
**mysite/templates/Page.ss**
```ss
<html>
<head>
@ -116,14 +60,18 @@ footer and navigation will remain the same and we don't want to replicate this w
<% include Footer %>
</body>
``
```
**mysite/templates/Layout/Page.ss**
```ss
<p>You are on a $Title page</p>
$Content
```
**mysite/templates/Layout/HomePage.ss**
```ss
<h1>This is the homepage!</h1>
@ -134,3 +82,101 @@ If your classes have in a namespace, the Layout folder will be a found inside of
For example, the layout template for `SilverStripe\Control\Controller` will be
found at `templates/SilverStripe/Control/Layout/Controller.ss`.
## Cascading themes
Within each theme or templates folder, a specific path representing a template can potentially be found. As
there may be multiple instances of any matching path for a template across the set of all themes, a cascading
search is done in order to determine the resolved template for any specified string.
In order to declare the priority for this search, themes can be declared in a cascading fashion in order
to determine resolution priority. This search is based on the following three configuration values:
- `SilverStripe\View\SSViewer.themes` - The list of all themes in order of priority (highest first).
This includes the default set via `$default` as a theme set. This config is normally set by the web
developer.
- `SilverStripe\Core\Manifest\ModuleManifest.module_priority` - The list of modules within which $default
theme templates should be sorted, in order of priority (highest first). This config is normally set by
the module author, and does not normally need to be customised. This includes the `$project` and
`$other_modules` placeholder values.
- `SilverStripe\Core\Manifest\ModuleManifest.project` - The name of the `$project` module, which
defaults to `mysite`.
### ThemeResourceLoader
The resolution of themes is performed by a [ThemeResourceLoader](api:SilverStripe\View\ThemeResourceLoader)
instance, which resolves a template (or list of templates) and a set of themes to a system template path.
For each path the loader will search in this order:
- Loop through each theme which is configured.
- If a theme is a set (declared with the `$` prefix, e.g. `$default`) it will perform a nested search within
that set.
- When searching the `$default` set, all modules will be searched in the order declared via the `module_priority`
config, interpolating keys `$project` and `$other_modules` as necessary.
- When the first template is found, it will be immediately returned, and will not continue to search.
### Declaring themes
All themes can be enabled and sorted via the `SilverStripe\View\SSViewer.themes` config value. For reference
on what syntax styles you can use for this value please see the [themes configuration](./themes) documentation.
Basic example:
```yaml
---
Name: mytheme
---
SilverStripe\View\SSViewer:
themes:
- theme_name
- '$default'
```
### Declaring module priority
The order in which templates are selected from themes can be explicitly declared
through configuration. To specify the order you want, make a list of the module
names under `SilverStripe\Core\Manifest\ModuleManifest.module_priority` in a
configuration YAML file.
Note: In order for modules to sort relative to other modules, it's normally necessary
to provide `before:` / `after:` declarations.
*mymodule/_config.yml*
```yml
Name: modules-mymodule
After:
- '#modules-framework'
- `#modules-other`
---
SilverStripe\Core\Manifest\ModuleManifest:
module_priority:
- myvendor/mymodule
```
In this example, our module has applied its priority lower than framework modules, meaning template lookup
will only defer to our modules templates folder if not found elsewhere.
### Declaring project
It is a good idea to define one of your modules as the `project`. Commonly, this is the
`mysite/` module, but there is nothing compulsory about that module name.
*myapp/_config/config.yml*
```yml
---
Name: myproject
---
SilverStripe\Core\Manifest\ModuleManifest:
project: 'myapp'
```
### About module "names"
Module names are derived their local `composer.json` files using the following precedence:
* The value of the `name` attribute in `composer.json`
* The value of `extras.installer_name` in `composer.json`
* The basename of the directory that contains the module

View File

@ -21,7 +21,6 @@ example, this controller method will not behave as you might imagine.
```ss
$Counter, $Counter, $Counter
// returns 1, 1, 1
@ -37,7 +36,6 @@ Partial caching is a feature that allows the caching of just a portion of a page
from the database to display, the contents of the area are fetched from a [cache backend](../performance/caching).
```ss
<% cached 'MyCachedContent', LastEdited %>
$Title
<% end_cached %>

View File

@ -5,11 +5,13 @@ summary: Definition of the syntax for writing i18n compatible templates.
Translations are easy to use with a template, and give access to SilverStripe's translation facilities. Here is an
example:
```ss
<%t Foo.BAR 'Bar' %>
<%t Member.WELCOME 'Welcome {name} to {site}' name=$Member.Name site="Foobar.com" %>
```
`Member.WELCOME` is an identifier in the translation system, for which different translations may be available. This
string may include named placeholders, in braces.

View File

@ -13,7 +13,6 @@ output the result of the [DBHtmlText::FirstParagraph()](api:SilverStripe\ORM\Fie
**mysite/code/Page.ss**
```ss
$Content.FirstParagraph
<!-- returns the result of HtmlText::FirstParagragh() -->
@ -25,7 +24,6 @@ Any public method from the object in scope can be called within the template. If
`ViewableData` instance, you can chain the method calls.
```ss
$Content.FirstParagraph.NoHTML
<!-- "First Paragraph" -->
@ -47,6 +45,7 @@ When rendering an object to the template such as `$Me` the `forTemplate` method
provide default template for an object.
**mysite/code/Page.php**
```php
use SilverStripe\CMS\Model\SiteTree;
@ -61,8 +60,8 @@ provide default template for an object.
```
**mysite/templates/Page.ss**
```ss
```ss
$Me
<!-- returns Page: Home -->
```
@ -88,7 +87,6 @@ to a template, SilverStripe will ensure that the object is wrapped in the correc
return "<h1>This is my header</h1>";
}
}
```
When calling `$MyCustomMethod` SilverStripe now has the context that this method will contain HTML and escape the data

View File

@ -27,7 +27,7 @@ top level menu with a nested second level using the `Menu` loop and a `Children`
</li>
<% end_loop %>
</ul>
```
```w
## Related
* [Template Syntax](../syntax)

View File

@ -11,6 +11,8 @@ The `PaginatedList` will automatically set up query limits and read the request
**mysite/code/Page.php**
```php
use SilverStripe\ORM\PaginatedList;
/**
* Returns a paginated list of all pages in the site.
*/
@ -79,6 +81,8 @@ will break the pagination. You can disable automatic limiting using the [Paginat
when using custom lists.
```php
use SilverStripe\ORM\PaginatedList;
$myPreLimitedList = Page::get()->limit(10);
$pages = new PaginatedList($myPreLimitedList, $this->getRequest());

View File

@ -33,13 +33,15 @@ situations, you can disable anchor link rewriting by setting the `SSViewer.rewri
**mysite/_config/app.yml**
```yml
SSViewer:
SilverStripe\View\SSViewer:
rewrite_hash_links: false
```
Or, a better way is to call this just for the rendering phase of this particular file:
```php
use SilverStripe\View\SSViewer;
public function RenderCustomTemplate()
{
SSViewer::setRewriteHashLinks(false);

View File

@ -29,7 +29,6 @@ subclass the base `Controller` class.
print_r($request->allParams());
}
}
```
## Routing
@ -50,7 +49,6 @@ Make sure that after you have modified the `routes.yml` file, that you clear you
**mysite/_config/routes.yml**
```yml
---
Name: mysiteroutes
After: framework/routes#coreroutes
@ -137,7 +135,6 @@ Action methods can return one of four main things:
return $this->getResponse().
}
```
For more information on how a URL gets mapped to an action see the [Routing](routing) documentation.

View File

@ -37,7 +37,6 @@ directly calling methods that they shouldn't.
'complexactioncheck' => '->canComplexAction("MyRestrictedAction", false, 42)',
];
}
```
<div class="info">
@ -50,7 +49,6 @@ is specifically restricted.
```php
use SilverStripe\Control\Controller;
<?php
class MyController extends Controller
{
@ -73,7 +71,6 @@ is specifically restricted.
'mycustomaction'
];
}
```
Only public methods can be made accessible.
@ -103,12 +100,12 @@ Only public methods can be made accessible.
```
If a method on a parent class is overwritten, access control for it has to be redefined as well.
```php
use SilverStripe\Control\Controller;
class MyController extends Controller
{
private static $allowed_actions = [
'action',
];
@ -141,6 +138,7 @@ Access checks on parent classes need to be overwritten via the [Configuration AP
Form action methods should **not** be included in `$allowed_actions`. However, the form method **should** be included
as an `allowed_action`.
```php
use SilverStripe\Forms\Form;
use SilverStripe\Control\Controller;
@ -206,6 +204,7 @@ execution. This behavior can be used to implement permission checks.
<div class="info" markdown="1">
`init` is called for any possible action on the controller and before any specific method such as `index`.
</div>
```php
use SilverStripe\Security\Permission;
use SilverStripe\Control\Controller;
@ -224,7 +223,6 @@ execution. This behavior can be used to implement permission checks.
}
}
}
```
## Related Documentation

View File

@ -27,6 +27,7 @@ HTTP header.
The `redirect()` method takes an optional HTTP status code, either `301` for permanent redirects, or `302` for
temporary redirects (default).
```php
$this->redirect('/', 302);
// go back to the homepage, don't cache that this page has moved
@ -37,12 +38,10 @@ temporary redirects (default).
Controllers can specify redirections in the `$url_handlers` property rather than defining a method by using the '~'
operator.
```php
private static $url_handlers = [
'players/john' => '~>coach'
];
```
For more information on `$url_handlers` see the [Routing](routing) documenation.

View File

@ -16,6 +16,9 @@ Creating a [Form](api:SilverStripe\Forms\Form) has the following signature.
```php
use SilverStripe\Forms\Form;
use SilverStripe\Forms\FieldList;
$form = new Form(
$controller, // the Controller to render this form on
$name, // name of the method that returns this form on the controller
@ -341,7 +344,6 @@ $validator = new SilverStripe\Forms\RequiredFields([
]);
$form = new Form($this, 'MyForm', $fields, $actions, $validator);
```
## API Documentation

View File

@ -174,7 +174,6 @@ class Page_Controller extends ContentController
return $this->redirectBack();
}
}
```
## Exempt validation actions

View File

@ -22,6 +22,8 @@ The `SecurityToken` automatically added looks something like:
```php
use SilverStripe\Forms\Form;
$form = new Form(..);
echo $form->getSecurityToken()->getValue();
@ -29,8 +31,8 @@ The `SecurityToken` automatically added looks something like:
```
This token value is passed through the rendered Form HTML as a [HiddenField](api:SilverStripe\Forms\HiddenField).
```html
```html
<input type="hidden" name="SecurityID" value="c443076989a7f24cf6b35fe1360be8683a753e2c" class="hidden" />
```

View File

@ -11,6 +11,8 @@ To make an entire [Form](api:SilverStripe\Forms\Form) read-only.
```php
use SilverStripe\Forms\Form;
$form = new Form(..);
$form->makeReadonly();
```
@ -19,6 +21,8 @@ To make all the fields within a [FieldList](api:SilverStripe\Forms\FieldList) re
```php
use SilverStripe\Forms\FieldList;
$fields = new FieldList(..);
$fields = $fields->makeReadonly();
```
@ -27,6 +31,9 @@ To make a [FormField](api:SilverStripe\Forms\FormField) read-only you need to kn
```php
use SilverStripe\Forms\TextField;
use SilverStripe\Forms\FieldList;
$field = new TextField(..);
$field = $field->performReadonlyTransformation();
@ -55,5 +62,4 @@ a normal form, but set the `disabled` attribute on the `input` tag.
echo $field->forTemplate();
// returns '<input type="text" class="text" .. disabled="disabled" />'
```

View File

@ -58,7 +58,6 @@ display up to two levels of tabs in the interface.
TextField::create('Name'),
TextField::create('Email')
]);
```
## API Documentation

View File

@ -21,7 +21,6 @@ The following example will add a simple DateField to your Page, allowing you to
class Page extends SiteTree
{
private static $db = [
'MyDate' => 'Date',
];
@ -38,7 +37,6 @@ The following example will add a simple DateField to your Page, allowing you to
return $fields;
}
}
```
## Custom Date Format

View File

@ -35,7 +35,6 @@ functionality. It is usually added through the [DataObject::getCMSFields()](api:
);
}
}
```
### Specify which configuration to use
@ -103,6 +102,8 @@ transparently generate the relevant underlying TinyMCE code.
**mysite/_config.php**
```php
use SilverStripe\Forms\HTMLEditor\HtmlEditorConfig;
HtmlEditorConfig::get('cms')->enablePlugins('media');
```
@ -175,7 +176,6 @@ You can enable them through [HtmlEditorConfig::enablePlugins()](api:SilverStripe
```php
HtmlEditorConfig::get('cms')->enablePlugins(['myplugin' => '../../../mysite/javascript/myplugin/editor_plugin.js']);
```
You can learn how to [create a plugin](http://www.tinymce.com/wiki.php/Creating_a_plugin) from the TinyMCE documentation.
@ -266,10 +266,10 @@ Example: Remove field for "image captions"
}
```
```php
// File: mysite/_config.php
use SilverStripe\Admin\ModalController;
ModalController::add_extension('MyToolbarExtension');
```
@ -314,7 +314,6 @@ of the CMS you have to take care of instantiate yourself:
Note: The dialogs rely on CMS-access, e.g. for uploading and browsing files,
so this is considered advanced usage of the field.
```php
// File: mysite/_config.php
HtmlEditorConfig::get('cms')->disablePlugins('ssbuttons');

View File

@ -8,6 +8,8 @@ tabular data in a format that is easy to view and modify. It can be thought of a
```php
use SilverStripe\Forms\GridField\GridField;
$field = new GridField($name, $title, $list);
```
@ -103,6 +105,9 @@ the `getConfig()` method on `GridField`.
With the `GridFieldConfig` instance, we can modify the behavior of the `GridField`.
```php
use SilverStripe\Forms\GridField\GridFieldConfig;
use SilverStripe\Forms\GridField\GridFieldDataColumns;
// `GridFieldConfig::create()` will create an empty configuration (no components).
$config = GridFieldConfig::create();
@ -115,8 +120,12 @@ With the `GridFieldConfig` instance, we can modify the behavior of the `GridFiel
`GridFieldConfig` provides a number of methods to make setting the configuration easier. We can insert a component
before another component by passing the second parameter.
```php
$config->addComponent(new GridFieldFilterHeader(), 'GridFieldDataColumns');
use SilverStripe\Forms\GridField\GridFieldFilterHeader;
use SilverStripe\Forms\GridField\GridFieldDataColumns;
$config->addComponent(new GridFieldFilterHeader(), GridFieldDataColumns::class);
```
We can add multiple components in one call.
@ -133,14 +142,16 @@ Or, remove a component.
```php
$config->removeComponentsByType('GridFieldDeleteAction');
use SilverStripe\Forms\GridField\GridFieldDeleteAction;
$config->removeComponentsByType(GridFieldDeleteAction::class);
```
Fetch a component to modify it later on.
```php
$component = $config->getComponentByType('GridFieldFilterHeader')
use SilverStripe\Forms\GridField\GridFieldFilterHeader;
$component = $config->getComponentByType(GridFieldFilterHeader::class)
```
Here is a list of components for use bundled with the core framework. Many more components are provided by third-party
@ -169,6 +180,8 @@ A simple read-only and paginated view of records with sortable and searchable he
```php
use SilverStripe\Forms\GridField\GridFieldConfig_Base;
$config = GridFieldConfig_Base::create();
$gridField->setConfig($config);
@ -199,6 +212,8 @@ this record.
```php
use SilverStripe\Forms\GridField\GridFieldConfig_RecordViewer;
$config = GridFieldConfig_RecordViewer::create();
$gridField->setConfig($config);
@ -238,6 +253,7 @@ Permission control for editing and deleting the record uses the `canEdit()` and
Similar to `GridFieldConfig_RecordEditor`, but adds features to work on a record's has-many or many-many relationships.
As such, it expects the list used with the `GridField` to be a instance of `RelationList`.
```php
$config = GridFieldConfig_RelationEditor::create();
@ -258,7 +274,9 @@ The `GridFieldDetailForm` component drives the record viewing and editing form.
```php
$form = $gridField->getConfig()->getComponentByType('GridFieldDetailForm');
use SilverStripe\Forms\GridField\GridFieldDetailForm;
$form = $gridField->getConfig()->getComponentByType(GridFieldDetailForm::class);
$form->setFields(new FieldList(
new TextField('Title')
));
@ -332,7 +350,6 @@ The namespace notation is `ManyMany[<extradata-field-name>]`, so for example `Ma
return $fields;
}
}
```
## Flexible Area Assignment through Fragments
@ -352,8 +369,10 @@ These built-ins can be used by passing the fragment names into the constructor o
[GridFieldConfig](api:SilverStripe\Forms\GridField\GridFieldConfig) classes will already have rows added to them. The following example will add a print button at the
bottom right of the table.
```php
use SilverStripe\Forms\GridField\GridFieldButtonRow;
use SilverStripe\Forms\GridField\GridFieldPrintButton;
$config->addComponent(new GridFieldButtonRow('after'));
$config->addComponent(new GridFieldPrintButton('buttons-after-right'));
```
@ -366,6 +385,8 @@ create an area rendered before the table wrapped in a simple `<div>`.
```php
use SilverStripe\Forms\GridField\GridField_HTMLProvider;
class MyAreaComponent implements GridField_HTMLProvider
{
@ -389,6 +410,8 @@ Now you can add other components into this area by returning them as an array fr
```php
use SilverStripe\Forms\GridField\GridField_HTMLProvider;
class MyShareLinkComponent implements GridField_HTMLProvider
{

View File

@ -68,8 +68,6 @@ code for a `Form` is to create it as a subclass to `Form`. Let's look at a examp
return $form;
}
..
}
```
@ -79,7 +77,6 @@ should be. Good practice would be to move this to a subclass and create a new in
**mysite/code/forms/SearchForm.php**
```php
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\RequiredFields;
@ -169,7 +166,6 @@ Our controller will now just have to create a new instance of this form object.
return new SearchForm($this, 'SearchForm');
}
}
```
Form actions can also be defined within your `Form` subclass to keep the entire form logic encapsulated.

View File

@ -13,7 +13,6 @@ totally custom template to meet our needs. To do this, we'll provide the class w
```php
public function SearchForm()
{
$fields = new FieldList(
@ -35,7 +34,6 @@ totally custom template to meet our needs. To do this, we'll provide the class w
```ss
<form $FormAttributes>
<fieldset>
$Fields.dataFieldByName(q)

View File

@ -19,6 +19,11 @@ below:
```php
use SilverStripe\Forms\GridField\GridField_ColumnProvider;
use SilverStripe\Forms\GridField\GridField_ActionProvider;
use SilverStripe\Forms\GridField\GridField_FormAction;
use SilverStripe\Control\Controller;
class GridFieldCustomAction implements GridField_ColumnProvider, GridField_ActionProvider
{
@ -79,7 +84,6 @@ below:
}
}
}
```
## Add the GridFieldCustomAction to the current `GridFieldConfig`

View File

@ -43,6 +43,11 @@ There's quite a bit in this function, so we'll step through one piece at a time.
```php
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\TextField;
use SilverStripe\Forms\EmailField;
use SilverStripe\Forms\TextareaField;
$fields = new FieldList(
new TextField('Name'),
new EmailField('Email'),
@ -83,7 +88,6 @@ Now that we have a contact form, we need some way of collecting the data submitt
```php
use SilverStripe\Control\Email\Email;
use PageController;
class ContactPageController extends PageController
{
@ -137,6 +141,9 @@ The framework comes with a predefined validator called [RequiredFields](api:Silv
```php
use SilverStripe\Forms\Form;
use SilverStripe\Forms\RequiredFields;
public function Form()
{
// ...
@ -146,4 +153,3 @@ The framework comes with a predefined validator called [RequiredFields](api:Silv
```
We've created a RequiredFields object, passing the name of the fields we want to be required. The validator we have created is then passed as the fifth argument of the form constructor. If we now try to submit the form without filling out the required fields, JavaScript validation will kick in, and the user will be presented with a message about the missing fields. If the user has JavaScript disabled, PHP validation will kick in when the form is submitted, and the user will be redirected back to the Form with messages about their missing fields.

View File

@ -29,8 +29,6 @@ be marked `private static` and follow the `lower_case_with_underscores` structur
```php
use Page;
class MyClass extends Page
{
@ -43,8 +41,6 @@ be marked `private static` and follow the `lower_case_with_underscores` structur
* @config
*/
private static $option_two = [];
// ..
}
```
@ -59,6 +55,7 @@ This can be done by calling the static method [Config::inst()](api:SilverStripe\
```
Or through the `config()` object on the class.
```php
$config = $this->config()->get('property')';
```
@ -86,7 +83,6 @@ To set those configuration options on our previously defined class we can define
```yml
MyClass:
option_one: false
option_two:
@ -174,9 +170,11 @@ be raised due to optimizations in the lookup code.
At some of these levels you can also set masks. These remove values from the composite value at their priority point
rather than add.
```php
$actionsWithoutExtra = $this->config()->get(
'allowed_actions', Config::UNINHERITED
);
```
Available masks include:
@ -202,17 +200,17 @@ The name of the files within the applications `_config` directly are arbitrary.
</div>
The structure of each YAML file is a series of headers and values separated by YAML document separators.
```yml
```yml
---
Name: adminroutes
After:
- '#rootroutes'
- '#coreroutes'
---
Director:
SilverStripe\Control\Director:
rules:
'admin': 'AdminRootController'
'admin': 'SilverStripe\Admin\AdminRootController'
---
```
@ -253,16 +251,15 @@ keys is a list of reference paths to other value sections. A basic example:
```yml
---
Name: adminroutes
After:
- '#rootroutes'
- '#coreroutes'
---
Director:
SilverStripe\Control\Director:
rules:
'admin': 'AdminRootController'
'admin': 'SilverStripe\Admin\AdminRootController'
---
```
@ -319,7 +316,6 @@ For instance, to add a property to "foo" when a module exists, and "bar" otherwi
```yml
---
Only:
moduleexists: 'MyFineModule'
@ -337,15 +333,13 @@ For instance, to add a property to "foo" when a module exists, and "bar" otherwi
Multiple conditions of the same type can be declared via array format
```yaml
---
Only:
moduleexists:
- 'silverstripe/blog'
- 'silverstripe/lumberjack'
---
```
<div class="alert" markdown="1">

View File

@ -12,7 +12,6 @@ throughout the site. Out of the box this includes selecting the current site the
```ss
$SiteConfig.Title
$SiteConfig.Tagline
@ -58,7 +57,6 @@ To extend the options available in the panel, define your own fields via a [Data
);
}
}
```
Then activate the extension.
@ -67,7 +65,6 @@ Then activate the extension.
```yml
Silverstripe\SiteConfig\SiteConfig:
extensions:
- CustomSiteConfig

View File

@ -34,7 +34,6 @@ and `RequestHandler`. You can still apply extensions to descendants of these cla
return "Hi " . $this->owner->Name;
}
}
```
<div class="info" markdown="1">
@ -48,8 +47,7 @@ we want to add the `MyMemberExtension` too. To activate this extension, add the
```yml
Member:
SilverStripe\Security\Member:
extensions:
- MyMemberExtension
```
@ -58,7 +56,7 @@ Alternatively, we can add extensions through PHP code (in the `_config.php` file
```php
Member::add_extension('MyMemberExtension');
SilverStripe\Security\Member::add_extension('MyMemberExtension');
```
This class now defines a `MyMemberExtension` that applies to all `Member` instances on the website. It will have
@ -101,14 +99,12 @@ $has_one etc.
return "Hi " . $this->owner->Name;
}
}
```
**mysite/templates/Page.ss**
```ss
$CurrentMember.Position
$CurrentMember.Image
```
@ -121,15 +117,15 @@ we added a `SayHi` method which is unique to our extension.
**mysite/templates/Page.ss**
```ss
<p>$CurrentMember.SayHi</p>
// "Hi Sam"
```
**mysite/code/Page.php**
```php
use SilverStripe\Security\Security;
$member = Security::getCurrentUser();
echo $member->SayHi;
@ -169,9 +165,6 @@ validator by defining the `updateValidator` method.
class MyMemberExtension extends DataExtension
{
// ..
public function updateValidator($validator)
{
// we want to make date of birth required for each member
@ -211,7 +204,6 @@ extension. The `CMS` provides a `updateCMSFields` Extension Hook to tie into.
$upload->setAllowedFileCategories('image/supported');
}
}
```
<div class="notice" markdown="1">
@ -245,7 +237,6 @@ In your [Extension](api:SilverStripe\Core\Extension) class you can only refer to
class MyMemberExtension extends DataExtension
{
public function updateFoo($foo)
{
// outputs the original class
@ -258,6 +249,7 @@ In your [Extension](api:SilverStripe\Core\Extension) class you can only refer to
To see what extensions are currently enabled on an object, use the [getExtensionInstances()](api:SilverStripe\Core\Extensible::getExtensionInstances()) and
[hasExtension()](api:SilverStripe\Core\Extensible::hasExtension()) methods of the [Extensible](api:SilverStripe\Core\Extensible) trait.
```php
$member = Security::getCurrentUser();
@ -286,12 +278,11 @@ if not specified in `self::$defaults`, but before extensions have been called:
```php
function __construct() {
$self = $this;
$this->beforeExtending('populateDefaults', function() use ($self) {
if(empty($self->MyField)) {
$self->MyField = 'Value we want as a default if not specified in $defaults, but set before extensions';
public function __construct()
{
$this->beforeExtending('populateDefaults', function() {
if(empty($this->MyField)) {
$this->MyField = 'Value we want as a default if not specified in $defaults, but set before extensions';
}
});
@ -309,7 +300,6 @@ This method is preferred to disabling, enabling, and calling field extensions ma
```php
public function getCMSFields()
{
$this->beforeUpdateCMSFields(function($fields) {
// Include field which must be present when updateCMSFields is called on extensions
$fields->addFieldToTab("Root.Main", new TextField('Detail', 'Details', null, 255));

View File

@ -38,6 +38,8 @@ Other fields can be manually parsed with shortcodes through the `parse` method.
```php
use SilverStripe\View\Parsers\ShortcodeParser;
$text = "My awesome [my_shortcode] is here.";
ShortcodeParser::get_active()->parse($text);
```
@ -54,7 +56,6 @@ First we need to define a callback for the shortcode.
class Page extends SiteTree
{
private static $casting = [
'MyShortCodeMethod' => 'HTMLText'
];
@ -64,7 +65,6 @@ First we need to define a callback for the shortcode.
return "<em>" . $tagName . "</em> " . $content . "; " . count($arguments) . " arguments.";
}
}
```
These parameters are passed to the `MyShortCodeMethod` callback:
@ -88,7 +88,6 @@ To register a shortcode you call the following.
// ShortcodeParser::get('default')->register($shortcode, $callback);
ShortcodeParser::get('default')->register('my_shortcode', ['Page', 'MyShortCodeMethod']);
```
## Built-in Shortcodes
@ -119,7 +118,9 @@ Images inserted through the "Insert Media" form (WYSIWYG editor) need to retain
the underlying `[Image](api:SilverStripe\Assets\Image)` database record. The `[image]` shortcode saves this database reference
instead of hard-linking to the filesystem path of a given image.
```html
[image id="99" alt="My text"]
```
### Media (Photo, Video and Rich Content)
@ -130,11 +131,11 @@ Youtube link pasted into the "Insert Media" form of the CMS.
Since TinyMCE can't represent all these variations, we're showing a placeholder instead, and storing the URL with a
custom `[embed]` shortcode.
```html
[embed width=480 height=270 class=left thumbnail=http://i1.ytimg.com/vi/lmWeD-vZAMY/hqdefault.jpg?r=8767]
http://www.youtube.com/watch?v=lmWeD-vZAMY
[/embed]
```
### Attribute and element scope
@ -147,16 +148,17 @@ The first is called "element scope" use, the second "attribute scope"
You may not use shortcodes in any other location. Specifically, you can not use shortcodes to generate attributes or
change the name of a tag. These usages are forbidden:
```ss
```html
<[paragraph]>Some test</[paragraph]>
<a [titleattribute]>link</a>
```
You may need to escape text inside attributes `>` becomes `&gt;`, You can include HTML tags inside a shortcode tag, but
you need to be careful of nesting to ensure you don't break the output.
```ss
```html
<!-- Good -->
<div>
[shortcode]
@ -178,53 +180,65 @@ you need to be careful of nesting to ensure you don't break the output.
Element scoped shortcodes have a special ability to move the location they are inserted at to comply with HTML lexical
rules. Take for example this basic paragraph tag:
```ss
```html
<p><a href="#">Head [figure,src="assets/a.jpg",caption="caption"] Tail</a></p>
```
When converted naively would become:
```ss
```html
<p><a href="#">Head <figure><img src="assets/a.jpg" /><figcaption>caption</figcaption></figure> Tail</a></p>
```
However this is not valid HTML - P elements can not contain other block level elements.
To fix this you can specify a "location" attribute on a shortcode. When the location attribute is "left" or "right"
the inserted content will be moved to immediately before the block tag. The result is this:
```ss
```html
<figure><img src="assets/a.jpg" /><figcaption>caption</figcaption></figure><p><a href="#">Head Tail</a></p>
```
When the location attribute is "leftAlone" or "center" then the DOM is split around the element. The result is this:
```ss
```html
<p><a href="#">Head </a></p><figure><img src="assets/a.jpg" /><figcaption>caption</figcaption></figure><p><a href="#"> Tail</a></p>
```
### Parameter values
Here is a summary of the callback parameter values based on some example shortcodes.
```php
public function MyCustomShortCode($arguments, $content = null, $parser = null, $tagName)
{
// ..
}
```
```
[my_shortcode]
$attributes => [];
$content => null;
$parser => ShortcodeParser instance,
$tagName => 'my_shortcode')
```
```
[my_shortcode,attribute="foo",other="bar"]
$attributes => ['attribute' => 'foo', 'other' => 'bar']
$enclosedContent => null
$parser => ShortcodeParser instance
$tagName => 'my_shortcode'
```
```
[my_shortcode,attribute="foo"]content[/my_shortcode]
$attributes => ['attribute' => 'foo']
$enclosedContent => 'content'
$parser => ShortcodeParser instance
$tagName => 'my_shortcode'
```
## Limitations
@ -232,9 +246,11 @@ Here is a summary of the callback parameter values based on some example shortco
Since the shortcode parser is based on a simple regular expression it cannot properly handle nested shortcodes. For
example the below code will not work as expected:
```html
[shortcode]
[shortcode][/shortcode]
[/shortcode]
```
The parser will raise an error if it can not find a matching opening tag for any particular closing tag

View File

@ -20,6 +20,8 @@ The following sums up the simplest usage of the `Injector` it creates a new obje
```php
use SilverStripe\Core\Injector\Injector;
$object = Injector::inst()->create('MyClassName');
```
@ -29,8 +31,7 @@ The benefit of constructing objects through this syntax is `ClassName` can be sw
**mysite/_config/app.yml**
```yml
Injector:
SilverStripe\Core\Injector\Injector:
MyClassName:
class: MyBetterClassName
```
@ -86,7 +87,6 @@ The `Injector` API can be used to define the types of `$dependencies` that an ob
'permissions' => '%$PermissionService',
];
}
```
When creating a new instance of `MyController` the dependencies on that class will be met.
@ -107,7 +107,6 @@ The [Configuration YAML](../configuration) does the hard work of configuring tho
**mysite/_config/app.yml**
```yml
Injector:
PermissionService:
class: MyCustomPermissionService
@ -118,7 +117,6 @@ The [Configuration YAML](../configuration) does the hard work of configuring tho
Now the dependencies will be replaced with our configuration.
```php
$object = Injector::inst()->get('MyController');
@ -133,8 +131,7 @@ As well as properties, method calls can also be specified:
```yml
Injector:
SilverStripe\Core\Injector\Injector:
Logger:
class: Monolog\Logger
calls:
@ -145,7 +142,6 @@ As well as properties, method calls can also be specified:
Any of the core constants can be used as a service argument by quoting with back ticks "`". Please ensure you also quote the entire value (see below).
```yaml
CachingService:
class: SilverStripe\Cache\CacheProvider
@ -166,10 +162,8 @@ An example using the `MyFactory` service to create instances of the `MyService`
**mysite/_config/app.yml**
```yml
Injector:
SilverStripe\Core\Injector\Injector:
MyService:
factory: MyFactory
```
@ -189,7 +183,6 @@ An example using the `MyFactory` service to create instances of the `MyService`
// Will use MyFactoryImplementation::create() to create the service instance.
$instance = Injector::inst()->get('MyService');
```
## Dependency overrides
@ -197,12 +190,14 @@ An example using the `MyFactory` service to create instances of the `MyService`
To override the `$dependency` declaration for a class, define the following configuration file.
**mysite/_config/app.yml**
```yml
MyController:
dependencies:
textProperty: a string value
permissions: %$PermissionService
```
## Managed objects
Simple dependencies can be specified by the `$dependencies`, but more complex configurations are possible by specifying
@ -213,7 +208,6 @@ runtime.
Assuming a class structure such as
```php
class RestrictivePermissionService
{
@ -241,13 +235,13 @@ And the following configuration..
```yml
---
name: MyController
---
MyController:
dependencies:
permissions: %$PermissionService
Injector:
SilverStripe\Core\Injector\Injector:
PermissionService:
class: RestrictivePermissionService
properties:
@ -260,7 +254,6 @@ And the following configuration..
Calling..
```php
// sets up ClassName as a singleton
$controller = Injector::inst()->get('MyController');
@ -283,7 +276,7 @@ Thus if you want an object to have the injected dependencies of a service of ano
assign a reference to that service.
```yaml
Injector:
SilverStripe\Core\Injector\Injector:
JSONServiceDefinition:
class: JSONServiceImplementor
properties:
@ -300,7 +293,7 @@ If class is not specified, then the class will be inherited from the outer servi
For example with this config:
```yml
Injector:
SilverStripe\Core\Injector\Injector:
Connector:
properties:
AsString: true
@ -319,6 +312,8 @@ This is useful when writing test cases, as certain services may be necessary to
```php
use SilverStripe\Core\Injector\Injector;
// Setup default service
Injector::inst()->registerService(new LiveService(), 'ServiceName');

View File

@ -72,7 +72,6 @@ used.
}
}
}
```
To actually make use of this class, a few different objects need to be configured. First up, define the `writeDb`
@ -82,7 +81,7 @@ object that's made use of above.
```yml
SilverStripe\Core\Injector\Injector:
WriteMySQLDatabase:
class: MySQLDatabase
constructor:
@ -100,9 +99,8 @@ Next, this should be bound into an instance of the `Aspect` class
**mysite/_config/app.yml**
```yml
SilverStripe\Core\Injector\Injector:
MySQLWriteDbAspect:
properties:
writeDb: %$WriteMySQLDatabase
@ -111,8 +109,9 @@ Next, this should be bound into an instance of the `Aspect` class
Next, we need to define the database connection that will be used for all non-write queries
**mysite/_config/app.yml**
```yml
```yml
SilverStripe\Core\Injector\Injector:
ReadMySQLDatabase:
class: MySQLDatabase
constructor:
@ -127,8 +126,9 @@ The final piece that ties everything together is the [AopProxyService](api:Silve
object when the framework creates the database connection.
**mysite/_config/app.yml**
```yml
```yml
SilverStripe\Core\Injector\Injector:
MySQLDatabase:
class: AopProxyService
properties:
@ -147,9 +147,9 @@ defined method\_name
Overall configuration for this would look as follows
**mysite/_config/app.yml**
```yml
Injector:
```yml
SilverStripe\Core\Injector\Injector:
ReadMySQLDatabase:
class: MySQLDatabase
constructor:
@ -183,9 +183,9 @@ Overall configuration for this would look as follows
One major feature of an `Aspect` is the ability to modify what is returned from the client's call to the proxied method.
As seen in the above example, the `beforeCall` method modifies the `&$alternateReturn` variable, and returns `false`
after doing so.
```php
$alternateReturn = $this->writeDb->query($sql, $code);
return false;
```

View File

@ -95,6 +95,8 @@ Linking to resources in vendor modules uses exactly the same syntax as non-vendo
this is how you would require a script in this module:
```php
use SilverStripe\View\Requirements;
Requirements::javascript('tractorcow/test-vendor-module:client/js/script.js');
```

View File

@ -6,7 +6,7 @@ To demonstrate how easy it is to build custom shortcodes, we'll build one to dis
address. We want our CMS authors to be able to embed the map using the following code:
```php
```html
[googlemap,width=500,height=300]97-99 Courtenay Place, Wellington, New Zealand[/googlemap]
```
@ -16,6 +16,8 @@ We'll add defaults to those in our shortcode parser so they're optional.
**mysite/_config.php**
```php
use SilverStripe\View\Parsers\ShortcodeParser;
ShortcodeParser::get('default')->register('googlemap', function($arguments, $address, $parser, $shortcode) {
$iframeUrl = sprintf(
'http://maps.google.com/maps?q=%s&amp;hnear=%s&amp;ie=UTF8&hq=&amp;t=m&amp;z=14&amp;output=embed',

View File

@ -58,8 +58,7 @@ explicitly logging in or by invoking the "remember me" functionality.
Now you just need to apply this extension through your config:
```yml
Member:
SilverStripe\Security\Member:
extensions:
- MyMemberExtension
```

View File

@ -10,7 +10,6 @@ to ensure that it works as it should. A simple example would be to test the resu
```php
use SilverStripe\CMS\Model\SiteTree;
class Page extends SiteTree
@ -26,8 +25,6 @@ to ensure that it works as it should. A simple example would be to test the resu
```php
use Page;
use SilverStripe\Dev\SapphireTest;
class PageTest extends SapphireTest
@ -89,17 +86,10 @@ needs.
```xml
<phpunit bootstrap="framework/tests/bootstrap.php" colors="true">
<phpunit bootstrap="vendor/silverstripe/framework/tests/bootstrap.php" colors="true">
<testsuite name="Default">
<directory>mysite/tests</directory>
<directory>cms/tests</directory>
<directory>framework/tests</directory>
</testsuite>
<listeners>
<listener class="SS_TestListener" file="framework/dev/TestListener.php" />
</listeners>
<groups>
<exclude>
<group>sanitychecks</group>
@ -114,15 +104,13 @@ In addition to loading data through a [Fixture File](fixtures), a test case may
run before each test method. For this, use the PHPUnit `setUp` and `tearDown` methods. These are run at the start and
end of each test.
```php
use SilverStripe\Core\Config\Config;
use SilverStripe\Dev\SapphireTest;
class PageTest extends SapphireTest
{
function setUp()
public function setUp()
{
parent::setUp();
@ -134,7 +122,7 @@ end of each test.
}
// set custom configuration for the test.
Config::inst()->update('Foo', 'bar', 'Hello!');
Config::modify()->update('Foo', 'bar', 'Hello!');
}
public function testMyMethod()
@ -147,7 +135,6 @@ end of each test.
// ..
}
}
```
`tearDownAfterClass` and `setUpBeforeClass` can be used to run code just once for the file rather than before and after
@ -156,7 +143,6 @@ takes place.
```php
use SilverStripe\Dev\SapphireTest;
class PageTest extends SapphireTest

View File

@ -8,6 +8,7 @@ core idea of these tests is the same as `SapphireTest` unit tests but `Functiona
creating [HTTPRequest](api:SilverStripe\Control\HTTPRequest), receiving [HTTPResponse](api:SilverStripe\Control\HTTPResponse) objects and modifying the current user session.
## Get
```php
$page = $this->get($url);
```
@ -28,7 +29,6 @@ of the response.
```php
$submit = $this->submitForm($formID, $button = null, $data = []);
```
Submits the given form (`#ContactForm`) on the current page and returns the [HTTPResponse](api:SilverStripe\Control\HTTPResponse).
@ -58,7 +58,6 @@ The `FunctionalTest` class also provides additional asserts to validate your tes
$this->assertPartialMatchBySelector('p.good',[
'Test save was successful'
]);
```
Asserts that the most recently queried page contains a number of content tags specified by a CSS selector. The given CSS
@ -73,7 +72,6 @@ assertion fails if one of the expectedMatches fails to appear.
$this->assertExactMatchBySelector("#MyForm_ID p.error", [
"That email address is invalid."
]);
```
Asserts that the most recently queried page contains a number of content tags specified by a CSS selector. The given CSS
@ -81,11 +79,11 @@ selector will be applied to the HTML of the most recent page. The full HTML of e
assertion fails if one of the expectedMatches fails to appear.
### assertPartialHTMLMatchBySelector
```php
$this->assertPartialHTMLMatchBySelector("#MyForm_ID p.error", [
"That email address is invalid."
]);
```
Assert that the most recently queried page contains a number of content tags specified by a CSS selector. The given CSS
@ -101,7 +99,6 @@ assertion fails if one of the expectedMatches fails to appear.
$this->assertExactHTMLMatchBySelector("#MyForm_ID p.error", [
"That email address is invalid."
]);
```
Assert that the most recently queried page contains a number of content tags specified by a CSS selector. The given CSS

View File

@ -16,31 +16,36 @@ To include your fixture file in your tests, you should define it as your `$fixtu
```php
use SilverStripe\Dev\SapphireTest;
class MyNewTest extends SapphireTest
{
protected static $fixture_file = 'fixtures.yml';
}
```
You can also use an array of fixture files, if you want to use parts of multiple other tests:
You can also use an array of fixture files, if you want to use parts of multiple other tests.
If you are using [api:SilverStripe\Dev\TestOnly] dataobjects in your fixtures, you must
declare these classes within the $extra_dataobjects variable.
**mysite/tests/MyNewTest.php**
```php
use SilverStripe\Dev\SapphireTest;
class MyNewTest extends SapphireTest
{
protected static $fixture_file = [
'fixtures.yml',
'otherfixtures.yml'
];
protected static $extra_dataobjects = [
Player::class,
Team::class,
];
}
```
Typically, you'd have a separate fixture file for each class you are testing - although overlap between tests is common.
@ -50,10 +55,10 @@ ideal for fixture generation. Say we have the following two DataObjects:
```php
use SilverStripe\ORM\DataObject;
use SilverStripe\Dev\TestOnly;
class Player extends DataObject
class Player extends DataObject implements TestOnly
{
private static $db = [
'Name' => 'Varchar(255)'
@ -64,7 +69,7 @@ ideal for fixture generation. Say we have the following two DataObjects:
];
}
class Team extends DataObject
class Team extends DataObject implements TestOnly
{
private static $db = [
'Name' => 'Varchar(255)',
@ -75,14 +80,12 @@ ideal for fixture generation. Say we have the following two DataObjects:
'Players' => 'Player'
];
}
```
We can represent multiple instances of them in `YAML` as follows:
**mysite/tests/fixtures.yml**
```yml
Team:
@ -143,7 +146,6 @@ We can also declare the relationships conversely. Another way we could write the
```yml
Player:
john:
Name: John
@ -176,7 +178,6 @@ writing:
$team->write();
$team->Players()->add($john);
```
<div class="notice" markdown="1">
@ -190,11 +191,9 @@ As of SilverStripe 4 you will need to use fully qualfied class names in your YAM
```yml
MyProject\Model\Player:
john:
Name: join
MyProject\Model\Team:
crusaders:
Name: Crusaders
@ -242,14 +241,12 @@ declare the role each player has in the team.
]
];
}
```
To provide the value for the `many_many_extraField` use the YAML list syntax.
```yml
Player:
john:
Name: John
@ -293,6 +290,8 @@ using them.
```php
use SilverStripe\Core\Injector\Injector;
$factory = Injector::inst()->create('FixtureFactory');
$obj = $factory->createObject('Team', 'hurricanes');
@ -305,7 +304,6 @@ In order to create an object with certain properties, just add a third argument:
$obj = $factory->createObject('Team', 'hurricanes', [
'Name' => 'My Value'
]);
```
<div class="warning" markdown="1">
@ -330,7 +328,6 @@ name, we can set the default to be `Unknown Team`.
$factory->define('Team', [
'Name' => 'Unknown Team'
]);
```
### Dependent Properties
@ -350,7 +347,6 @@ values based on other fixture data.
$obj->Score = rand(0,10);
}
)];
```
### Relations
@ -363,7 +359,6 @@ Model relations can be expressed through the same notation as in the YAML fixtur
$obj = $factory->createObject('Team', 'hurricanes', [
'MyHasManyRelation' => '=>Player.john,=>Player.joe'
]);
```
#### Callbacks
@ -419,4 +414,3 @@ equal the class names they manage.
* [FixtureFactory](api:SilverStripe\Dev\FixtureFactory)
* [FixtureBlueprint](api:SilverStripe\Dev\FixtureBlueprint)

View File

@ -7,9 +7,7 @@ how you can load default records into the test database.
**mysite/tests/PageTest.php**
```php
use SilverStripe\Dev\SapphireTest;
class PageTest extends SapphireTest
@ -44,7 +42,6 @@ how you can load default records into the test database.
}
}
}
```
Firstly we define a static `$fixture_file`, this should point to a file that represents the data we want to test,

View File

@ -48,7 +48,6 @@ response and modify the session within a test.
]);
}
}
```
## Related Documentation

View File

@ -11,6 +11,8 @@ with information that we need.
```php
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Core\Injector\Injector;
class MyObjectTest extends SapphireTest
{
@ -43,7 +45,6 @@ with information that we need.
// returns "My Custom Value"
}
}
```
## Related Documentation

View File

@ -8,6 +8,8 @@ email was sent using this method.
```php
use SilverStripe\Control\Email\Email;
public function MyMethod()
{
$e = new Email();

View File

@ -29,7 +29,6 @@ want to password protect the site. You can enable that by adding this to your `m
```yml
---
Only:
environment: 'test'
@ -52,6 +51,7 @@ Live sites should always run in live mode. You should not run production website
You can check for the current environment type in [config files](../configuration) through the `environment` variant.
**mysite/_config/app.yml**
```yml
---
Only:
@ -68,7 +68,10 @@ You can check for the current environment type in [config files](../configuratio
```
Checking for what environment you're running in can also be done in PHP. Your application code may disable or enable
certain functionality depending on the environment type.
```php
use SilverStripe\Control\Director;
if (Director::isLive()) {
// is in live
} elseif (Director::isTest()) {

View File

@ -14,6 +14,8 @@ For informational and debug logs, you can use the Logger directly. The Logger is
can be accessed via the `Injector`:
```php
use SilverStripe\Core\Injector\Injector;
Injector::inst()->get(LoggerInterface::class)->info('User has logged in: ID #' . Security::getCurrentUser()->ID);
Injector::inst()->get(LoggerInterface::class)->debug('Query executed: ' . $sql);
```

View File

@ -10,11 +10,11 @@ to track down a template or two. The template engine can help you along by displ
source code comments indicating which template is responsible for rendering each
block of html on your page.
```yaml
```yml
---
Only:
environment: 'dev'
---
SSViewer:
SilverStripe\View\SSViewer:
source_file_comments: true
```

View File

@ -18,6 +18,9 @@ bottle-necks and identify slow moving parts of your application chain.
The [Debug](api:SilverStripe\Dev\Debug) class contains a number of static utility methods for more advanced debugging.
```php
use SilverStripe\Dev\Debug;
use SilverStripe\Dev\Backtrace;
Debug::show($myVariable);
// similar to print_r($myVariable) but shows it in a more useful format.

View File

@ -4,8 +4,8 @@ summary: Cache SilverStripe templates to reduce database queries.
# Partial Caching
Partial caching is a feature that allows the caching of just a portion of a page.
```ss
```ss
<% cached 'CacheKey' %>
$DataTable
...
@ -23,7 +23,6 @@ Here are some more complex examples:
```ss
<% cached 'database', $LastEdited %>
<!-- that updates every time the record changes. -->
<% end_cached %>
@ -48,8 +47,7 @@ user does not influence your template content, you can update this key as below;
**mysite/_config/app.yml**
```yaml
```yml
SilverStripe\View\SSViewer:
global_key: '$CurrentReadingMode, $Locale'
```
@ -65,7 +63,6 @@ otherwise. By using aggregates, we do that like this:
```ss
<% cached 'navigation', $List('SiteTree').max('LastEdited'), $List('SiteTree').count() %>
```
@ -76,7 +73,6 @@ or edited
```ss
<% cached 'categorylist', $List('Category').max('LastEdited'), $List('Category').count() %>
```
@ -109,14 +105,12 @@ For example, a block that shows a collection of rotating slides needs to update
];
return implode('-_-', $fragments);
}
```
Then reference that function in the cache key:
```ss
<% cached $SliderCacheKey %>
```
@ -138,7 +132,6 @@ For instance, if we show some blog statistics, but are happy having them be slig
```ss
<% cached 'blogstatistics', $Blog.ID %>
```
@ -158,7 +151,6 @@ and then use it in the cache key
```ss
<% cached 'blogstatistics', $Blog.ID, $BlogStatisticsCounter %>
```
@ -173,7 +165,6 @@ heavy load:
```ss
<% cached 'blogstatistics', $Blog.ID if $HighLoad %>
```
@ -184,7 +175,6 @@ To cache the contents of a page for all anonymous users, but dynamically calcula
```ss
<% cached unless $CurrentUser %>
```
@ -196,7 +186,6 @@ particular cache block by changing just the tag, leaving the key and conditional
```ss
<% uncached %>
```
@ -212,7 +201,6 @@ An example:
```ss
<% cached $LastEdited %>
Our wonderful site
@ -232,7 +220,6 @@ could also write the last example as:
```ss
<% cached $LastEdited %>
Our wonderful site
@ -253,7 +240,6 @@ Failing example:
```ss
<% cached $LastEdited %>
<% loop $Children %>
@ -269,23 +255,18 @@ Can be re-written as:
```ss
<% cached $LastEdited %>
<% cached $AllChildren.max('LastEdited') %>
<% loop $Children %>
$Name
<% end_loop %>
<% end_cached %>
<% end_cached %>
```
Or:
```ss
<% cached $LastEdited %>
(other code)
<% end_cached %>

View File

@ -32,7 +32,6 @@ and SilverStripe's [dependency injection](/developer-guides/extending/injector)
```yml
SilverStripe\Core\Injector\Injector:
Psr\SimpleCache\CacheInterface.myCache:
factory: SilverStripe\Core\Cache\CacheFactory
@ -47,6 +46,8 @@ This factory allows us you to globally define an adapter for all cache instances
```php
use Psr\SimpleCache\CacheInterface
use SilverStripe\Core\Injector\Injector;
$cache = Injector::inst()->get(CacheInterface::class . '.myCache');
```
@ -65,7 +66,9 @@ Cache objects follow the [PSR-16](http://www.php-fig.org/psr/psr-16/) class inte
```php
use Psr\SimpleCache\CacheInterface
use Psr\SimpleCache\CacheInterface;
use SilverStripe\Core\Injector\Injector;
$cache = Injector::inst()->get(CacheInterface::class . '.myCache');
@ -89,19 +92,22 @@ this will only affect a subset of cache keys ("myCache" in this example):
```php
use Psr\SimpleCache\CacheInterface
use Psr\SimpleCache\CacheInterface;
use SilverStripe\Core\Injector\Injector;
$cache = Injector::inst()->get(CacheInterface::class . '.myCache');
// remove all items in this (namespaced) cache
$cache->clear();
```
You can also delete a single item based on it's cache key:
```php
use Psr\SimpleCache\CacheInterface
use Psr\SimpleCache\CacheInterface;
use SilverStripe\Core\Injector\Injector;
$cache = Injector::inst()->get(CacheInterface::class . '.myCache');
// remove the cache item
@ -112,7 +118,9 @@ Individual cache items can define a lifetime, after which the cached value is ma
```php
use Psr\SimpleCache\CacheInterface
use Psr\SimpleCache\CacheInterface;
use SilverStripe\Core\Injector\Injector;
$cache = Injector::inst()->get(CacheInterface::class . '.myCache');
// remove the cache item
@ -128,7 +136,6 @@ you need to be careful with resources here (e.g. filesystem space).
```yml
SilverStripe\Core\Injector\Injector:
Psr\SimpleCache\CacheInterface.cacheblock:
constructor:
@ -144,7 +151,9 @@ old cache keys will be garbage collected as the cache fills up.
```php
use Psr\SimpleCache\CacheInterface
use Psr\SimpleCache\CacheInterface;
use SilverStripe\Core\Injector\Injector;
$cache = Injector::inst()->get(CacheInterface::class . '.myCache');
// Automatically changes when any group is edited
@ -184,7 +193,6 @@ and takes a `MemcachedClient` instance as a constructor argument.
```yml
---
After:
- '#corecache'

View File

@ -17,7 +17,10 @@ headers:
## Customizing Cache Headers
### HTTP::set_cache_age
```php
use SilverStripe\Control\HTTP;
HTTP::set_cache_age(0);
```
@ -46,10 +49,6 @@ Cookie, X-Forwarded-Protocol, User-Agent, Accept
To change the value of the `Vary` header, you can change this value by specifying the header in configuration
```yml
HTTP:
SilverStripe\Control\HTTP:
vary: ""
```

View File

@ -24,6 +24,8 @@ SilverStripe can request more resources through `Environment::increaseMemoryLimi
</div>
```php
use SilverStripe\Core\Environment;
public function myBigFunction()
{
Environment::increaseTimeLimitTo(400);

View File

@ -15,6 +15,8 @@ The [api:Security] class comes with a static method for getting information abou
Retrieves the current logged in member. Returns *null* if user is not logged in, otherwise, the Member object is returned.
```php
use SilverStripe\Security\Security;
if( $member = Security::getCurrentUser() ) {
// Work with $member
} else {
@ -32,6 +34,8 @@ This is the least desirable way of extending the [Member](api:SilverStripe\Secur
You can define subclasses of [Member](api:SilverStripe\Security\Member) to add extra fields or functionality to the built-in membership system.
```php
use SilverStripe\Security\Member;
class MyMember extends Member {
private static $db = array(
"Age" => "Int",
@ -58,6 +62,8 @@ details in the newsletter system. This function returns a [FieldList](api:Silve
parent::getCMSFields() and manipulate the [FieldList](api:SilverStripe\Forms\FieldList) from there.
```php
use SilverStripe\Forms\TextField;
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->insertBefore("HTMLEmail", new TextField("Age"));
@ -99,7 +105,6 @@ use SilverStripe\ORM\DataExtension;
class MyMemberExtension extends DataExtension
{
/**
* Modify the field set to be displayed in the CMS detail pop-up
*/
public function updateCMSFields(FieldList $currentFields)
@ -150,8 +155,6 @@ reasonably be expected to be allowed to do.
E.g.
```php
use SilverStripe\Control\Director;
use SilverStripe\Security\Security;

View File

@ -38,11 +38,13 @@ CMS access for the first time. SilverStripe provides a default admin configurati
and password to be configured for a single special user outside of the normal membership system.
It is advisable to configure this user in your `.env` file inside of the web root, as below:
```
# Configure a default username and password to access the CMS on all sites in this environment.
SS_DEFAULT_ADMIN_USERNAME="admin"
SS_DEFAULT_ADMIN_PASSWORD="password"
```
When a user logs in with these credentials, then a [Member](api:SilverStripe\Security\Member) with the Email 'admin' will be generated in
the database, but without any password information. This means that the password can be reset or changed by simply
updating the `.env` file.
@ -68,8 +70,8 @@ SilverStripe\Core\Injector\Injector:
By default, the `SilverStripe\Security\MemberAuthenticator\MemberAuthenticator` is seen as the default authenticator until it's explicitly set in the config.
Every Authenticator is expected to handle services. The `Authenticator` Interface provides the available services:
```php
```php
const LOGIN = 1;
const LOGOUT = 2;
const CHANGE_PASSWORD = 4;

View File

@ -25,7 +25,12 @@ must still be taken when working with literal values or table/column identifiers
come from user input.
Example:
```php
use SilverStripe\ORM\DB;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\Queries\SQLSelect;
$records = DB::prepared_query('SELECT * FROM "MyClass" WHERE "ID" = ?', [3]);
$records = MyClass::get()->where(['"ID" = ?' => 3]);
$records = MyClass::get()->where(['"ID"' => 3]);
@ -33,13 +38,15 @@ Example:
$records = DataObject::get_one('MyClass', ['"ID" = ?' => 3]);
$records = MyClass::get()->byID(3);
$records = SQLSelect::create()->addWhere(['"ID"' => 3])->execute();
```
Parameterised updates and inserts are also supported, but the syntax is a little different
```php
use SilverStripe\ORM\Queries\SQLInsert;
use SilverStripe\ORM\DB;
SQLInsert::create('"MyClass"')
->assign('"Name"', 'Daniel')
->addAssignments([
@ -54,7 +61,6 @@ Parameterised updates and inserts are also supported, but the syntax is a little
'INSERT INTO "MyClass" ("Name", "Position", "Age", "Created") VALUES(?, ?, GREATEST(0,?,?), NOW())'
['Daniel', 'Accountant', 24, 28]
);
```
### Automatic escaping
@ -79,7 +85,10 @@ Data is not escaped when writing to object-properties, as inserts and updates ar
handled via prepared statements.
Example:
```php
use SilverStripe\Security\Member;
// automatically escaped/quoted
$members = Member::get()->filter('Name', $_GET['name']);
// automatically escaped/quoted
@ -88,7 +97,6 @@ Example:
$members = Member::get()->where(['"Name" = ?' => $_GET['name']]);
// needs to be escaped and quoted manually (note raw2sql called with the $quote parameter set to true)
$members = Member::get()->where(sprintf('"Name" = %s', Convert::raw2sql($_GET['name'], true)));
```
<div class="warning" markdown='1'>
@ -135,7 +143,6 @@ Example:
Example:
```php
use SilverStripe\Core\Convert;
use SilverStripe\Control\Controller;
@ -151,7 +158,6 @@ Example:
// ...
}
}
```
As a rule of thumb, you should escape your data **as close to querying as possible**
@ -181,7 +187,6 @@ passing data through, escaping should happen at the end of the chain.
DB::query("UPDATE Player SET Name = {$SQL_name}");
}
}
```
This might not be applicable in all cases - especially if you are building an API thats likely to be customised. If
@ -216,9 +221,11 @@ stripped out
To enable filtering, set the HtmlEditorField::$sanitise_server_side [configuration](/developer_guides/configuration/configuration) property to
true, e.g.
```
HtmlEditorField::config()->sanitise_server_side = true
```
The built in sanitiser enforces the TinyMCE whitelist rules on the server side, and is sufficient to eliminate the
most common XSS vectors.
@ -298,7 +305,6 @@ static *$casting* array. Caution: Casting only applies when using values in a te
PHP:
```php
use SilverStripe\ORM\DataObject;
@ -317,7 +323,6 @@ PHP:
return $this->Title . '<small>(' . $suffix. ')</small>';
}
}
```
Template:
@ -344,7 +349,6 @@ also used by *XML* and *ATT* in template code).
PHP:
```php
use SilverStripe\Core\Convert;
use SilverStripe\Control\Controller;
@ -363,12 +367,10 @@ PHP:
]);
}
}
```
Template:
```php
<h2 title="Searching for $Query.ATT">$HTMLTitle</h2>
```
@ -401,7 +403,6 @@ PHP:
]);
}
}
```
Template:
@ -503,13 +504,14 @@ with a `.yml` or `.yaml` extension through the default web server rewriting rule
If you need users to access files with this extension,
you can bypass the rules for a specific directory.
Here's an example for a `.htaccess` file used by the Apache web server:
```
<Files *.yml>
Order allow,deny
Allow from all
</Files>
```
### User uploaded files
Certain file types are by default excluded from user upload. html, xhtml, htm, and xml files may have embedded,
@ -532,7 +534,6 @@ take the following precautions:
See [the Adobe Flash security page](http://www.adobe.com/devnet/flashplayer/security.html) for more information.
ADMIN privileged users may be allowed to override the above upload restrictions if the
`File.apply_restrictions_to_admin` config is set to false. By default this is true, which enforces these
restrictions globally.
@ -555,12 +556,14 @@ a [PasswordValidator](api:SilverStripe\Security\PasswordValidator):
```php
use SilverStripe\Security\Member;
use SilverStripe\Security\PasswordValidator;
$validator = new PasswordValidator();
$validator->minLength(7);
$validator->checkHistoricalPasswords(6);
$validator->characterStrength(3, ["lowercase", "uppercase", "digits", "punctuation"]);
Member::set_password_validator($validator);
```
In addition, you can tighten password security with the following configuration settings:
@ -607,9 +610,11 @@ To prevent a forged hostname appearing being used by the application, SilverStri
allows the configure of a whitelist of hosts that are allowed to access the system. By defining
this whitelist in your `.env` file, any request presenting a `Host` header that is
_not_ in this list will be blocked with a HTTP 400 error:
```
SS_ALLOWED_HOSTS="www.mysite.com,mysite.com,subdomain.mysite.com"
```
Please note that if this configuration is defined, you _must_ include _all_ subdomains (eg www.)
that will be accessing the site.
@ -624,21 +629,24 @@ into visiting external sites.
In order to prevent this kind of attack, it's necessary to whitelist trusted proxy
server IPs using the SS_TRUSTED_PROXY_IPS define in your `.env`.
```
SS_TRUSTED_PROXY_IPS="127.0.0.1,192.168.0.1"
```
If you wish to change the headers that are used to find the proxy information, you should reconfigure the
TrustedProxyMiddleware service:
```yml
SilverStripe\Control\TrustedProxyMiddleware:
properties:
ProxyHostHeaders: X-Forwarded-Host
ProxySchemeHeaders: X-Forwarded-Protocol
ProxyIPHeaders: X-Forwarded-Ip
```
```
SS_TRUSTED_PROXY_HOST_HEADER="HTTP_X_FORWARDED_HOST"
SS_TRUSTED_PROXY_IP_HEADER="HTTP_X_FORWARDED_FOR"
SS_TRUSTED_PROXY_PROTOCOL_HEADER="HTTP_X_FORWARDED_PROTOCOL"
@ -671,7 +679,10 @@ variable will be no longer necessary, thus it will be necessary to always set
SilverStripe recommends the use of TLS(HTTPS) for your application, and you can easily force the use through the
director function `forceSSL()`
```php
use SilverStripe\Control\Director;
if (!Director::isDev()) {
Director::forceSSL();
}
@ -683,9 +694,9 @@ Forcing HTTPS so requires a certificate to be purchased or obtained through a ve
We also want to ensure cookies are not shared between secure and non-secure sessions, so we must tell SilverStripe to
use a [secure session](https://docs.silverstripe.org/en/3/developer_guides/cookies_and_sessions/sessions/#secure-session-cookie).
To do this, you may set the `cookie_secure` parameter to `true` in your `config.yml` for `Session`
```yml
Session:
```yml
SilverStripe\Control\Session:
cookie_secure: true
```
@ -699,7 +710,9 @@ clear text and can be intercepted and stolen by an attacker who is listening on
- The `HTTPOnly` flag lets the browser know whether or not a cookie should be accessible by client-side JavaScript
code. It is best practice to set this flag unless the application is known to use JavaScript to access these cookies
as this prevents an attacker who achieves cross-site scripting from accessing these cookies.
```php
use SilverStripe\Control\Cookie;
Cookie::set('cookie-name', 'chocolate-chip', $expiry = 30, $path = null, $domain = null, $secure = true,
$httpOnly = false

View File

@ -24,6 +24,8 @@ SilverStripe\Core\Injector\Injector:
```php
use SilverStripe\Control\Email\Email;
$email = new Email($from, $to, $subject, $body);
$email->sendPlain();
```
@ -56,7 +58,6 @@ email object additional information using the `setData` and `addData` methods.
```ss
<h1>Hi $Member.FirstName</h1>
<p>You can go to $Link.</p>
```
@ -107,10 +108,8 @@ You can set the default sender address of emails through the `Email.admin_email`
```yaml
SilverStripe\Control\Email\Email:
admin_email: support@silverstripe.org
```
<div class="alert" markdown="1">
@ -133,8 +132,9 @@ Configuration of those properties looks like the following:
**mysite/_config.php**
```php
use SilverStripe\Control\Director;
use SilverStripe\Core\Config\Config;
if(Director::isLive()) {
Config::inst()->update('Email', 'bcc_all_emails_to', "client@example.com");
} else {
@ -148,6 +148,7 @@ For email messages that should have an email address which is replied to that ac
email, do the following. This is encouraged especially when the domain responsible for sending the message isn't
necessarily the same which should be used for return correspondence and should help prevent your message from being
marked as spam.
```php
$email = new Email(..);
$email->setReplyTo('me@address.com');
@ -162,7 +163,6 @@ For email headers which do not have getters or setters (like setTo(), setFrom())
```php
$email = new Email(...);
$email->getSwiftMessage()->getHeaders()->addTextHeader('HeaderName', 'HeaderValue');
..
```
<div class="info" markdown="1">

View File

@ -32,10 +32,13 @@ in your CSV file match `$db` properties in your dataobject. E.g. a simple import
Donald,Duck,donald@disney.com
Daisy,Duck,daisy@disney.com
```
The loader would be triggered through the `load()` method:
```php
use SilverStripe\Dev\CsvBulkLoader;
$loader = new CsvBulkLoader('Member');
$result = $loader->load('<my-file-path>');
```
@ -61,8 +64,6 @@ The simplest way to use [CsvBulkLoader](api:SilverStripe\Dev\CsvBulkLoader) is t
];
private static $url_segment = 'players';
}
?>
```
The new admin interface will be available under `http://localhost/admin/players`, the import form is located
@ -127,7 +128,6 @@ You'll need to add a route to your controller to make it accessible via URL
return $this->redirectBack();
}
}
```
Note: This interface is not secured, consider using [Permission::check()](api:SilverStripe\Security\Permission::check()) to limit the controller to users
@ -163,13 +163,10 @@ Datamodel for Player
'Team' => 'FootballTeam'
];
}
?>
```
Datamodel for FootballTeam:
```php
use SilverStripe\ORM\DataObject;
@ -182,8 +179,6 @@ Datamodel for FootballTeam:
'Players' => 'Player'
];
}
?>
```
Sample implementation of a custom loader. Assumes a CSV-file in a certain format (see below).
@ -192,6 +187,7 @@ Sample implementation of a custom loader. Assumes a CSV-file in a certain format
* Splits a combined "Name" fields from the CSV-data into `FirstName` and `Lastname` by a custom importer method
* Avoids duplicate imports by a custom `$duplicateChecks` definition
* Creates `Team` relations automatically based on the `Gruppe` column in the CSV data
```php
use SilverStripe\Dev\CsvBulkLoader;
@ -224,8 +220,6 @@ Sample implementation of a custom loader. Assumes a CSV-file in a certain format
return FootballTeam::get()->filter('Title', $val)->First();
}
}
?>
```
Building off of the ModelAdmin example up top, use a custom loader instead of the default loader by adding it to `$model_importers`. In this example, `CsvBulkLoader` is replaced with `PlayerCsvBulkLoader`.
@ -244,8 +238,6 @@ Building off of the ModelAdmin example up top, use a custom loader instead of th
];
private static $url_segment = 'players';
}
?>
```
## Related

View File

@ -24,6 +24,8 @@ An outline of step one looks like:
```php
use SilverStripe\Control\RSS\RSSFeed;
$feed = new RSSFeed(
$list,
$link,
@ -41,6 +43,7 @@ An outline of step one looks like:
To achieve step two include the following code where ever you want to include the `<link>` tag to the RSS Feed. This
will normally go in your `Controllers` `init` method.
```php
RSSFeed::linkToFeed($link, $title);
```
@ -54,17 +57,12 @@ You can use [RSSFeed](api:SilverStripe\Control\RSS\RSSFeed) to easily create a f
**mysite/code/Page.php**
```php
use SilverStripe\Control\RSS\RSSFeed;
use Page;
use SilverStripe\CMS\Controllers\ContentController;
..
class PageController extends ContentController
{
private static $allowed_actions = [
'rss'
];
@ -162,7 +160,6 @@ Then in our controller, we add a new action which returns a the XML list of `Pla
return $rss->outputToBrowser();
}
}
```
### Customizing the RSS Feed template
@ -174,9 +171,7 @@ Say from that last example we want to include the Players Team in the XML feed w
**mysite/templates/PlayersRss.ss**
```xml
<?xml version="1.0"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
@ -199,8 +194,9 @@ Say from that last example we want to include the Players Team in the XML feed w
**mysite/code/Page.php**
```php
use SilverStripe\Control\RSS\RSSFeed;
public function players()
{
$rss = new RSSFeed(

View File

@ -73,7 +73,6 @@ form (which is used for `MyDataObject` instances). You can access it through
return $this->redirectBack();
}
}
```
<div class="alert" markdown="1">

View File

@ -4,12 +4,14 @@ title: A custom CSVBulkLoader instance
A an implementation of a custom `CSVBulkLoader` loader. In this example. we're provided with a unique CSV file
containing a list of football players and the team they play for. The file we have is in the format like below.
```
"SpielerNummer", "Name", "Geburtsdatum", "Gruppe"
11, "John Doe", 1982-05-12,"FC Bayern"
12, "Jane Johnson", 1982-05-12,"FC Bayern"
13, "Jimmy Dole",,"Schalke 04"
```
This data needs to be imported into our application. For this, we have two `DataObjects` setup. `Player` contains
information about the individual player and a relation set up for managing the `Team`.
@ -33,7 +35,6 @@ information about the individual player and a relation set up for managing the `
'Team' => 'FootballTeam'
];
}
```
**mysite/code/FootballTeam.php**
@ -44,7 +45,6 @@ information about the individual player and a relation set up for managing the `
class FootballTeam extends DataObject
{
private static $db = [
'Title' => 'Text'
];
@ -53,7 +53,6 @@ information about the individual player and a relation set up for managing the `
'Players' => 'Player'
];
}
```
Now going back to look at the CSV, we can see that what we're provided with does not match what our data model looks
@ -108,7 +107,6 @@ Our final import looks like this.
return FootballTeam::get()->filter('Title', $val)->First();
}
}
```
## Related

View File

@ -1,64 +0,0 @@
title: Embed an RSS Feed
# Embed an RSS Feed
[RestfulService](api:RestfulService) can be used to easily embed an RSS feed from a site. In this How to we'll embed the latest
weather information from the Yahoo Weather API.
First, we write the code to query the API feed.
**mysite/code/Page.php**
```php
public function getWellingtonWeather()
{
$fetch = new RestfulService(
'https://query.yahooapis.com/v1/public/yql'
);
$fetch->setQueryString([
'q' => 'select * from weather.forecast where woeid in (select woeid from geo.places(1) where text="Wellington, NZ")'
]);
// perform the query
$conn = $fetch->request();
// parse the XML body
$msgs = $fetch->getValues($conn->getBody(), "results");
// generate an object our templates can read
$output = new ArrayList();
if($msgs) {
foreach($msgs as $msg) {
$output->push(new ArrayData([
'Description' => Convert::xml2raw($msg->channel_item_description)
]));
}
}
return $output;
}
```
This will provide our `Page` template with a new `WellingtonWeather` variable (an [ArrayList](api:SilverStripe\ORM\ArrayList)). Each item has a
single field `Description`.
**mysite/templates/Page.ss**
```ss
<% if WellingtonWeather %>
<% loop WellingtonWeather %>
$Description
<% end_loop %>
<% end_if %>
```
## Related
* [RestfulService Documentation](../restfulservice)
* [RestfulService](api:RestfulService)

View File

@ -24,7 +24,6 @@ Defining search-able fields on your DataObject.
class MyDataObject extends DataObject
{
private static $searchable_fields = [
'Name',
'ProductCode'
@ -73,7 +72,6 @@ and `MyDate`. The attribute `HiddenProperty` should not be searchable, and `MyDa
);
}
}
```
<div class="notice" markdown="1">
@ -125,7 +123,6 @@ the `$fields` constructor parameter.
])->renderWith('Page_results');
}
}
```
### Pagination
@ -137,6 +134,8 @@ in order to read page limit information. It is also passed the current
```php
use SilverStripe\ORM\PaginatedList;
public function getResults($searchCriteria = [])
{
$start = ($this->getRequest()->getVar('start')) ? (int)$this->getRequest()->getVar('start') : 0;
@ -155,7 +154,6 @@ in order to read page limit information. It is also passed the current
return $records;
}
```
notice that if you want to use this getResults function, you need to change the function doSearch for this one:
@ -170,7 +168,6 @@ notice that if you want to use this getResults function, you need to change the
'Results' => $results
])->renderWith(['Catalogo_results', 'Page']);
}
```
The change is in **$results = $this->getResults($data);**, because you are using a custom getResults function.
@ -187,8 +184,8 @@ to show the results of your custom search you need at least this content in your
Results.PaginationSummary(4) defines how many pages the search will show in the search results. something like:
**Next 1 2 *3* 4 5 &hellip; 558**
```ss
```ss
<% if $Results %>
<ul>
<% loop $Results %>

View File

@ -24,15 +24,14 @@ You can do so by adding this static variable to your class definition:
```php
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\Connect\MySQLSchemaManager;
class MyDataObject extends DataObject
{
private static $create_table_options = [
'MySQLDatabase' => 'ENGINE=MyISAM'
MySQLSchemaManager::ID => 'ENGINE=MyISAM'
];
}
```
The [FulltextSearchable](api:SilverStripe\ORM\Search\FulltextSearchable) extension will add the correct `Fulltext` indexes to the data model.
@ -53,6 +52,7 @@ Example DataObject:
```php
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\Connect\MySQLSchemaManager;
class SearchableDataObject extends DataObject
{
@ -70,7 +70,7 @@ Example DataObject:
];
private static $create_table_options = [
'MySQLDatabase' => 'ENGINE=MyISAM'
MySQLSchemaManager::ID => 'ENGINE=MyISAM'
];
}

View File

@ -29,6 +29,8 @@ you want to set.
```php
use SilverStripe\i18n\i18n;
// mysite/_config.php
i18n::set_locale('de_DE'); // Setting the locale to German (Germany)
i18n::set_locale('ca_AD'); // Setting to Catalan (Andorra)
@ -54,7 +56,6 @@ To let browsers know which language they're displaying a document in, you can de
```html
//'Page.ss' (HTML)
<html lang="$ContentLocale">
@ -71,7 +72,6 @@ and default alignment of paragraphs and tables to browsers.
```html
<html lang="$ContentLocale" dir="$i18nScriptDirection">
```
@ -82,8 +82,12 @@ You can use these settings for your own view logic.
```php
Config::inst()->update('i18n', 'date_format', 'dd.MM.yyyy');
Config::inst()->update('i18n', 'time_format', 'HH:mm');
use SilverStripe\Core\Config\Config;
use SilverStripe\i18n\i18n;
i18n::config()
->set('date_format', 'dd.MM.yyyy')
->set('time_format', 'HH:mm');
```
Localization in SilverStripe uses PHP's [intl extension](http://php.net/intl).
@ -106,8 +110,7 @@ In order to add a value, add the following to your `config.yml`:
```yml
i18n:
SilverStripe\i18n\i18n:
common_locales:
de_CGN:
name: German (Cologne)
@ -118,8 +121,7 @@ Similarly, to change an existing language label, you can overwrite one of these
```yml
i18n:
SilverStripe\i18n\i18n:
common_locales:
en_NZ:
native: Niu Zillund
@ -155,6 +157,8 @@ followed by `setLocale()` or `setDateFormat()`/`setTimeFormat()`.
```php
use SilverStripe\Forms\DateField;
$field = new DateField();
$field->setLocale('de_AT'); // set Austrian/German locale, defaulting format to dd.MM.y
$field->setDateFormat('d.M.y'); // set a more specific date format (single digit day/month)
@ -213,7 +217,7 @@ For instance, this is an example of how to correctly declare pluralisations for
```php
use SilverStripe\ORM\DataObject;
class MyObject extends DataObject, implements i18nEntityProvider
class MyObject extends DataObject implements i18nEntityProvider
{
public function provideI18nEntities()
{
@ -232,10 +236,7 @@ For instance, this is an example of how to correctly declare pluralisations for
In YML format this will be expressed as the below. This follows the
[ruby i18n convention](guides.rubyonrails.org/i18n.html#pluralization) for plural forms.
```yaml
en:
MyObject:
SINGULAR_NAME: 'object'
@ -263,7 +264,6 @@ Please ensure that any required plurals are exposed via provideI18nEntities.
// Plurals are invoked via a `|` pipe-delimeter with a {count} argument
_t('MyObject.PLURALS', 'An object|{count} objects', [ 'count' => '$count ]);
```
#### Usage in Template Files
@ -281,7 +281,6 @@ the PHP version of the function.
```ss
// Simple string translation
<%t Namespace.Entity "String to translate" %>
@ -297,9 +296,7 @@ the PHP version of the function.
When caching a `<% loop %>` or `<% with %>` with `<%t params %>`. It is important to add the Locale to the cache key
otherwise it won't pick up locale changes.
```ss
<% cached 'MyIdentifier', $CurrentLocale %>
<% loop $Students %>
$Name
@ -336,12 +333,13 @@ By default, the language files are loaded from modules in this order:
This default order is configured in `framework/_config/i18n.yml`. This file specifies two blocks of module ordering: `basei18n`, listing admin, and framework, and `defaulti18n` listing all other modules.
To create a custom module order, you need to specify a config fragment that inserts itself either after or before those items. For example, you may have a number of modules that have to come after the framework/admin, but before anyhting else. To do that, you would use this
```yml
---
Name: customi18n
Before: 'defaulti18n'
---
i18n:
SilverStripe\i18n\i18n:
module_priority:
- module1
- module2
@ -365,6 +363,7 @@ By default, SilverStripe uses a YAML format which is loaded via the
[symfony/translate](http://symfony.com/doc/current/translation.html) library.
Example: framework/lang/en.yml (extract)
```yml
en:
ImageUploader:
@ -372,7 +371,9 @@ Example: framework/lang/en.yml (extract)
UploadField:
NOTEADDFILES: 'You can add files once you have saved for the first time.'
```
Translation table: framework/lang/de.yml (extract)
```yml
de:
ImageUploader:
@ -380,6 +381,7 @@ Translation table: framework/lang/de.yml (extract)
UploadField:
NOTEADDFILES: 'Sie können Dateien hinzufügen sobald Sie das erste mal gespeichert haben'
```
Note that translations are cached across requests.
The cache can be cleared through the `?flush=1` query parameter,
or explicitly through `Zend_Translate::getCache()->clean(Zend_Cache::CLEANING_MODE_ALL)`.
@ -402,6 +404,8 @@ If using this on the frontend, it's also necessary to include the stand-alone i1
js file.
```php
use SilverStripe\View\Requirements;
Requirements::javascript('silverstripe/admin:client/dist/js/i18n.js');
Requirements::add_i18n_javascript('<my-module-dir>/javascript/lang');
```
@ -415,7 +419,6 @@ Master Table (`<my-module-dir>/javascript/lang/en.js`)
```js
if(typeof(ss) == 'undefined' || typeof(ss.i18n) == 'undefined') {
console.error('Class ss.i18n not defined');
} else {
@ -429,7 +432,6 @@ Example Translation Table (`<my-module-dir>/javascript/lang/de.js`)
```js
ss.i18n.addDictionary('de', {
'MYMODULE.MYENTITY' : "Artikel wirklich löschen?"
});
@ -444,7 +446,6 @@ format which can be processed more easily by external translation providers (see
```js
alert(ss.i18n._t('MYMODULE.MYENTITY'));
```
@ -454,11 +455,11 @@ The `ss.i18n` object contain a couple functions to help and replace dynamic vari
#### Legacy sequential replacement with sprintf()
`sprintf()` will substitute occurencies of `%s` in the main string with each of the following arguments passed to the function. The substitution is done sequentially.
`sprintf()` will substitute occurencies of `%s` in the main string with
each of the following arguments passed to the function. The substitution
is done sequentially.
```js
// MYMODULE.MYENTITY contains "Really delete %s articles by %s?"
alert(ss.i18n.sprintf(
ss.i18n._t('MYMODULE.MYENTITY'),
@ -470,11 +471,12 @@ The `ss.i18n` object contain a couple functions to help and replace dynamic vari
#### Variable injection with inject()
`inject()` will substitute variables in the main string like `{myVar}` by the keys in the object passed as second argument. Each variable can be in any order and appear multiple times.
`inject()` will substitute variables in the main string like `{myVar}` by the
keys in the object passed as second argument. Each variable can be in any order
and appear multiple times.
```js
// MYMODULE.MYENTITY contains "Really delete {count} articles by {author}?"
alert(ss.i18n.inject(
ss.i18n._t('MYMODULE.MYENTITY'),

View File

@ -130,6 +130,8 @@ to live until a publish is made (either on this object, or cascading from a pare
When files are renamed using the ORM, all file variants are automatically renamed at the same time.
```php
use SilverStripe\Assets\File;
$file = File::get()->filter('Name', 'oldname.jpg')->first();
if ($file) {
// The below will move 'oldname.jpg' and 'oldname__variant.jpg'

View File

@ -37,7 +37,6 @@ Here are some examples, assuming the `$Image` object has dimensions of 200x100px
```ss
// Scaling functions
$Image.ScaleWidth(150) // Returns a 150x75px image
$Image.ScaleMaxWidth(100) // Returns a 100x50px image (like ScaleWidth but prevents up-sampling)
@ -72,9 +71,7 @@ Here are some examples, assuming the `$Image` object has dimensions of 200x100px
Image methods are chainable. Example:
```ss
<body style="background-image:url($Image.ScaleWidth(800).CropHeight(800).Link)">
```
@ -107,9 +104,9 @@ You can also create your own functions by decorating the `Image` class.
```php
class ImageExtension extends \SilverStripe\Core\Extension
use SilverStripe\Core\Extension;
class ImageExtension extends Extension
{
public function Square($width)
{
$variant = $this->owner->variantName(__FUNCTION__, $width);
@ -135,8 +132,9 @@ You can also create your own functions by decorating the `Image` class.
}
}
```
:::yml
```yml
SilverStripe\Assets\Image:
extensions:
- ImageExtension
@ -176,12 +174,11 @@ necessary, you can add this to your mysite/config/config.yml file:
```yml
# Configure resampling for File dataobject
File:
SilverStripe\Assets\File:
force_resample: false
# DBFile can be configured independently
SilverStripe\Filesystem\Storage\DBFile:
SilverStripe\Assets\Storage\DBFile:
force_resample: false
```
@ -192,7 +189,6 @@ following to your mysite/config/config.yml file:
```yml
SilverStripe\Core\Injector\Injector:
SilverStripe\Assets\Image_Backend:
properties:

View File

@ -190,6 +190,8 @@ will be moved to `assets/a870de278b/NewCompanyLogo.gif`, and will be served dire
the web server, bypassing the need for additional PHP requests.
```php
use SilverStripe\Assets\Storage\AssetStore;
$store = singleton(AssetStore::class);
$store->publish('NewCompanyLogo.gif', 'a870de278b475cb75f5d9f451439b2d378e13af1');
```

View File

@ -13,7 +13,7 @@ has been added to assist in migration of legacy files.
You can run this task on the command line:
```
$ ./vendor/silverstripe/framework/sake dev/tasks/MigrateFileTask
$ ./vendor/bin/sake dev/tasks/MigrateFileTask
```
This task will also support migration of existing File DataObjects to file versioning. Any

View File

@ -36,7 +36,6 @@ a category.
'Category' => 'Category'
];
}
```
**mysite/code/Category.php**
@ -56,7 +55,6 @@ a category.
'Products' => 'Product'
];
}
```
To create your own `ModelAdmin`, simply extend the base class, and edit the `$managed_models` property with the list of
@ -82,7 +80,6 @@ We'll name it `MyAdmin`, but the class name can be anything you want.
private static $menu_title = 'My Product Admin';
}
```
This will automatically add a new menu entry to the SilverStripe Admin UI entitled `My Product Admin` and logged in
@ -115,7 +112,6 @@ permissions by default. For most cases, less restrictive checks make sense, e.g.
class Category extends DataObject
{
// ...
public function canView($member = null)
{
return Permission::check('CMS_ACCESS_MyAdmin', 'any', $member);
@ -135,6 +131,7 @@ permissions by default. For most cases, less restrictive checks make sense, e.g.
{
return Permission::check('CMS_ACCESS_MyAdmin', 'any', $member);
}
}
```
## Searching Records
@ -161,7 +158,6 @@ class (see [SearchContext](../search/searchcontext) docs for details).
'ProductCode'
];
}
```
<div class="hint" markdown="1">
@ -182,7 +178,6 @@ model class, where you can add or remove columns. To change the title, use [Data
class Product extends DataObject
{
private static $field_labels = [
'Price' => 'Cost' // renames the column to "Cost"
];
@ -192,7 +187,6 @@ model class, where you can add or remove columns. To change the title, use [Data
'Price'
];
}
```
The results list are retrieved from [SearchContext::getResults()](api:SilverStripe\ORM\Search\SearchContext::getResults()), based on the parameters passed through the search
@ -209,7 +203,6 @@ For example, we might want to exclude all products without prices in our sample
class MyAdmin extends ModelAdmin
{
public function getList()
{
$list = parent::getList();
@ -229,14 +222,12 @@ checkbox which limits search results to expensive products (over $100).
**mysite/code/MyAdmin.php**
```php
use SilverStripe\Forms\CheckboxField;
use SilverStripe\Admin\ModelAdmin;
class MyAdmin extends ModelAdmin
{
public function getSearchContext()
{
$context = parent::getSearchContext();
@ -298,7 +289,6 @@ example, to add a new component.
return $form;
}
}
```
The above example will add the component to all `GridField`s (of all managed models). Alternatively we can also add it
@ -333,7 +323,6 @@ to only one specific `GridField`:
return $form;
}
}
```
## Data Import
@ -369,7 +358,6 @@ To customize the exported columns, create a new method called `getExportFields`
];
}
}
```
## Related Documentation

View File

@ -62,7 +62,7 @@ The CMS interface can be accessed by default through the `admin/` URL. You can c
After:
- '#adminroutes'
---
Director:
SilverStripe\Control\Director:
rules:
'admin': ''
'newAdmin': 'AdminRootController'
@ -75,14 +75,13 @@ In PHP you should use:
```php
AdminRootController::admin_url()
SilverStripe\Admin\AdminRootController::admin_url()
```
When writing templates use:
```ss
$AdminURL
```
@ -90,7 +89,6 @@ And in JavaScript, this is avaible through the `ss` namespace
```js
ss.config.adminUrl
```
@ -172,7 +170,7 @@ Basic example form in a CMS controller subclass:
class MyAdmin extends LeftAndMain
{
function getEditForm() {
public function getEditForm() {
return Form::create(
$this,
'EditForm',
@ -306,6 +304,7 @@ routing mechanism for this section. However, there are two major differences:
Firstly, `reactRouter` must be passed as a boolean flag to indicate that this section is
controlled by the react section, and thus should suppress registration of a page.js route
for this section.
```php
public function getClientConfig()
{
@ -313,14 +312,13 @@ for this section.
'reactRouter' => true
]);
}
```
Secondly, you should ensure that your react CMS section triggers route registration on the client side
with the reactRouteRegister component. This will need to be done on the `DOMContentLoaded` event
to ensure routes are registered before window.load is invoked.
```js
```js
import { withRouter } from 'react-router';
import ConfigHelpers from 'lib/Config';
import reactRouteRegister from 'lib/ReactRouteRegister';
@ -340,8 +338,8 @@ to ensure routes are registered before window.load is invoked.
```
Child routes can be registered post-boot by using `ReactRouteRegister` in the same way.
```js
```js
// Register a nested url under `sectionConfig.url`
const sectionConfig = ConfigHelpers.getSection('MyAdmin');
reactRouteRegister.add({
@ -434,9 +432,11 @@ sending back different `X-Pjax` headers and content.
On the client, you can set your preference through the `data-pjax-target` attributes
on links or through the `X-Pjax` header. For firing off an Ajax request that is
tracked in the browser history, use the `pjax` attribute on the state data.
```js
$('.cms-container').loadPanel(ss.config.adminUrl+'pages', null, {pjax: 'Content'});
```
## Loading lightweight PJAX fragments
Normal navigation between URLs in the admin section of the Framework occurs through `loadPanel` and `submitForm`.
@ -450,17 +450,21 @@ unrelated to the main flow.
In this case you can use the `loadFragment` call supplied by `LeftAndMain.js`. You can trigger as many of these in
parallel as you want. This will not disturb the main navigation.
```js
$('.cms-container').loadFragment(ss.config.adminUrl+'foobar/', 'Fragment1');
$('.cms-container').loadFragment(ss.config.adminUrl+'foobar/', 'Fragment2');
$('.cms-container').loadFragment(ss.config.adminUrl+'foobar/', 'Fragment3');
```
The ongoing requests are tracked by the PJAX fragment name (Fragment1, 2, and 3 above) - resubmission will
result in the prior request for this fragment to be aborted. Other parallel requests will continue undisturbed.
You can also load multiple fragments in one request, as long as they are to the same controller (i.e. URL):
```js
$('.cms-container').loadFragment(ss.config.adminUrl+'foobar/', 'Fragment2,Fragment3');
```
This counts as a separate request type from the perspective of the request tracking, so will not abort the singular
`Fragment2` nor `Fragment3`.
@ -470,6 +474,7 @@ has been found on an element (this element will get completely replaced). Afterw
will be triggered. In case of a request error a `loadfragmenterror` will be raised and DOM will not be touched.
You can hook up a response handler that obtains all the details of the XHR request via Entwine handler:
```js
'from .cms-container': {
onafterloadfragment: function(e, data) {
@ -478,7 +483,9 @@ You can hook up a response handler that obtains all the details of the XHR reque
}
}
```
Alternatively you can use the jQuery deferred API:
```js
$('.cms-container')
.loadFragment(ss.config.adminUrl+'foobar/', 'Fragment1')
@ -487,6 +494,7 @@ Alternatively you can use the jQuery deferred API:
alert(status);
});
```
## Ajax Redirects
Sometimes, a server response represents a new URL state, e.g. when submitting an "add record" form,

View File

@ -23,7 +23,6 @@ The easiest way to update the layout of the CMS is to call `redraw` on the top-l
```js
$('.cms-container').redraw();
```
@ -65,7 +64,6 @@ Layout manager will automatically apply algorithms to the children of `.cms-cont
```html
<div class="cms-content-tools west cms-panel cms-panel-layout"
data-expandOnClick="true"
data-layout-type="border"
@ -112,7 +110,6 @@ Use provided factory method to generate algorithm instances.
```js
jLayout.threeColumnCompressor(<column-spec-object>, <options-object>);
```

View File

@ -52,7 +52,6 @@ Note how the configuration happens in different entwine namespaces
```js
(function($) {
$.entwine('ss.preview', function($){
$('.cms-preview').entwine({
@ -81,8 +80,7 @@ to the `LeftAndMain.extra_requirements_javascript` [configuration value](../conf
```yml
LeftAndMain:
SilverStripe\Admin\LeftAndMain:
extra_requirements_javascript:
- mysite/javascript/MyLeftAndMain.Preview.js
```

View File

@ -8,6 +8,8 @@ SilverStripe lets you customise the style of content in the CMS. This is done by
```php
use SilverStripe\Forms\HTMLEditor\HtmlEditorConfig;
HtmlEditorConfig::get('cms')->setOption('content_css', project() . '/css/editor.css');
```
@ -25,7 +27,6 @@ add the color 'red' as an option within the `WYSIWYG` add the following to the `
```css
.red {
color: red;
}

View File

@ -45,8 +45,7 @@ plugin. See "[How jQuery Works](http://docs.jquery.com/How_jQuery_Works)" for a
You should write all your custom jQuery code in a closure.
```javascript
```js
(function($) {
$(document).ready(function(){
// your code here.
@ -75,7 +74,6 @@ Example: A plugin to highlight a collection of elements with a configurable fore
```js
// create closure
(function($) {
// plugin definition
@ -107,7 +105,6 @@ Usage:
```js
(function($) {
// Highlight all buttons with default colours
jQuery(':button').highlight();
@ -137,9 +134,7 @@ See the [official developer guide](http://jqueryui.com/docs/Developer_Guide) and
Example: Highlighter
```js
(function($) {
$.widget("ui.myHighlight", {
getBlink: function () {
@ -171,7 +166,6 @@ Usage:
```js
(function($) {
// call with default options
$(':button').myHighlight();
@ -205,7 +199,6 @@ Example: Highlighter
```js
(function($) {
$(':button').entwine({
Foreground: 'red',
@ -222,7 +215,6 @@ Usage:
```js
(function($) {
// call with default options
$(':button').entwine().highlight();
@ -255,7 +247,6 @@ Global properties are evil. They are accessible by other scripts, might be overw
```js
// you can't rely on '$' being defined outside of the closure
(function($) {
var myPrivateVar; // only available inside the closure
@ -273,7 +264,6 @@ the `window.onload` and `document.ready` events.
```js
// DOM elements might not be available here
$(document).ready(function() {
// The DOM is fully loaded here
@ -292,9 +282,7 @@ Caution: Only applies to certain events, see the [jQuery.on() documentation](htt
Example: Add a 'loading' classname to all pressed buttons
```js
// manual binding, only applies to existing elements
$('input[[type=submit]]').on('click', function() {
$(this).addClass('loading');
@ -313,7 +301,6 @@ makes sense). Encapsulate your code by nesting your jQuery commands inside a `jQ
```js
$('div.MyGridField').each(function() {
// This is the over code for the tr elements inside a GridField.
$(this).find('tr').hover(
@ -333,7 +320,6 @@ Through CSS properties
```js
$('form :input').bind('change', function(e) {
$(this.form).addClass('isChanged');
});
@ -344,9 +330,7 @@ Through CSS properties
Through jQuery.data()
```js
$('form :input').bind('change', function(e) {
$(this.form).data('isChanged', true);
});
@ -364,16 +348,11 @@ rendering a form element through the SilverStripe templating engine.
Example: Restricted numeric value field
```ss
<input type="text" class="restricted-text {min:4,max:10}" />
```
```js
$('.restricted-text').bind('change', function(e) {
if(
e.target.value < $(this).metadata().min
@ -407,7 +386,6 @@ Template:
```ss
<ul>
<% loop $Results %>
<li id="Result-$ID">$Title</li>
@ -419,6 +397,9 @@ PHP:
```php
use SilverStripe\Control\HTTPResponse;
use SilverStripe\View\ViewableData;
class MyController
{
public function autocomplete($request)
@ -436,14 +417,12 @@ PHP:
])->renderWith('AutoComplete');
}
}
```
HTML
```ss
<form action"#">
<div class="autocomplete {url:'MyController/autocomplete'}">
<input type="text" name="title" />
@ -457,7 +436,6 @@ JavaScript:
```js
$('.autocomplete input').on('change', function() {
var resultsEl = $(this).siblings('.results');
resultsEl.load(
@ -498,7 +476,6 @@ Example: Trigger custom 'validationfailed' event on form submission for each emp
```js
$('form').bind('submit', function(e) {
// $(this) refers to form
$(this).find(':input').each(function() {
@ -555,9 +532,7 @@ JSDoc-toolkit is a command line utility, see [usage](http://code.google.com/p/js
Example: jQuery.entwine
```js
/**
* Available Custom Events:
@ -616,9 +591,7 @@ start with JSpec, as it provides a much more powerful testing framework.
Example: QUnit test (from [jquery.com](http://docs.jquery.com/QUnit#Using_QUnit)):
```js
test("a basic test example", function() {
ok( true, "this test is fine" );
var value = "hello";
@ -627,6 +600,7 @@ Example: QUnit test (from [jquery.com](http://docs.jquery.com/QUnit#Using_QUnit)
```
Example: JSpec Shopping cart test (from [visionmedia.github.com](http://visionmedia.github.com/jspec/))
```
describe 'ShoppingCart'
before_each
@ -641,6 +615,7 @@ Example: JSpec Shopping cart test (from [visionmedia.github.com](http://visionme
end
end
```
## Related
* [Unobtrusive Javascript](http://www.onlinetools.org/articles/unobtrusivejavascript/chapter1.html)

View File

@ -80,7 +80,6 @@ First of all, you can toggle the state of the button - execute this code in the
```js
jQuery('.cms-edit-form .btn-toolbar #Form_EditForm_action_cleanup').button('toggleAlternate');
```
@ -88,7 +87,6 @@ Another, more useful, scenario is to check the current state.
```js
jQuery('.cms-edit-form .btn-toolbar #Form_EditForm_action_cleanup').button('option', 'showingAlternate');
```
@ -96,7 +94,6 @@ You can also force the button into a specific state by using UI options.
```js
jQuery('.cms-edit-form .btn-toolbar #Form_EditForm_action_cleanup').button({showingAlternate: true});
```
@ -106,7 +103,6 @@ CMS core that tracks the changes to the input fields and reacts by enabling the
```js
/**
* Enable save buttons upon detecting changes to content.
* "changed" class is added by jQuery.changetracker.
@ -156,7 +152,6 @@ cases.
```js
(function($) {
$.entwine('mysite', function($){

View File

@ -11,6 +11,8 @@ at the last position within the field, and expects unescaped HTML content.
```php
use SilverStripe\Forms\TextField;
TextField::create('MyText', 'My Text Label')
->setDescription('More <strong>detailed</strong> help');
```

View File

@ -96,7 +96,6 @@ button configuration.
CMSMenu::add_link($id, $title, $link, $priority, $attributes);
}
}
```
To have the link appear, make sure you add the extension to the `LeftAndMain`

View File

@ -18,23 +18,24 @@ Here's a brief example on how to add sorting and a new column for a
hypothetical `NewsPageHolder` type, which contains `NewsPage` children.
```php
use Page;
**mysite/code/NewsPageHolder.php**
// mysite/code/NewsPageHolder.php
```php
class NewsPageHolder extends Page
{
private static $allowed_children = ['NewsPage'];
}
```
// mysite/code/NewsPage.php
**mysite/code/NewsPage.php**
```php
class NewsPage extends Page
{
private static $has_one = [
'Author' => 'Member',
];
}
```
We'll now add an `Extension` subclass to `LeftAndMain`, which is the main CMS controller.
@ -43,15 +44,14 @@ before its rendered. In this case, we limit our logic to the desired page type,
although it's just as easy to implement changes which apply to all page types,
or across page types with common characteristics.
**mysite/code/NewsPageHolderCMSMainExtension.php**
```php
use Page;
use SilverStripe\Core\Extension;
// mysite/code/NewsPageHolderCMSMainExtension.php
class NewsPageHolderCMSMainExtension extends Extension
{
function updateListView($listView) {
public function updateListView($listView) {
$parentId = $listView->getController()->getRequest()->requestVar('ParentID');
$parent = ($parentId) ? Page::get()->byId($parentId) : new Page();
@ -76,10 +76,12 @@ or across page types with common characteristics.
```
Now you just need to enable the extension in your [configuration file](../../configuration).
```yml
// mysite/_config/config.yml
LeftAndMain:
SilverStripe\Admin\LeftAndMain:
extensions:
- NewsPageHolderCMSMainExtension
```
You're all set! Don't forget to flush the caches by appending `?flush=all` to the URL.

View File

@ -61,6 +61,7 @@ Let's add another customisation to TextField. If the text goes beyond a specifie
length, let's throw a warning in the UI.
__my-module/js/components/TextLengthChecker.js__
```js
const TextLengthCheker = (TextField) => (props) => {
const {limit, value } = props;
@ -86,6 +87,7 @@ For the purposes of demonstration, let's imagine this customisation comes from a
module.
__my-module/js/main.js__
```js
import Injector from 'lib/Injector';
import TextLengthChecker from './components/TextLengthChecker';

View File

@ -33,8 +33,8 @@ Inside the *mysite/code* folder create a file called *CustomSideReport.php*. Ins
The following example will create a report to list every page on the current site.
###CustomSideReport.php
```php
use Page;
use SilverStripe\Reports\Report;
class CustomSideReport_NameOfReport extends Report
@ -62,7 +62,6 @@ The following example will create a report to list every page on the current sit
return $fields;
}
}
```
More useful reports can be created by changing the `DataList` returned in the `sourceRecords` function.

View File

@ -31,7 +31,6 @@ the CMS logic. Add a new section into the `<ul class="cms-menu-list">`
```ss
...
<ul class="cms-menu-list">
<!-- ... -->
@ -57,7 +56,6 @@ with the CMS interface. Paste the following content into a new file called
```css
.bookmarked-link.first {margin-top: 1em;}
```
@ -66,8 +64,7 @@ Load the new CSS file into the CMS, by setting the `LeftAndMain.extra_requiremen
```yml
LeftAndMain:
SilverStripe\Admin\LeftAndMain:
extra_requirements_css:
- mysite/css/BookmarkedPages.css
```
@ -105,8 +102,7 @@ Enable the extension in your [configuration file](../../configuration)
```yml
SiteTree:
SilverStripe\CMS\Model\SiteTree:
extensions:
- BookmarkedPageExtension
```
@ -125,7 +121,6 @@ Add the following code to a new file `mysite/code/BookmarkedLeftAndMainExtension
```php
use Page;
use SilverStripe\Admin\LeftAndMainExtension;
class BookmarkedPagesLeftAndMainExtension extends LeftAndMainExtension
@ -142,8 +137,7 @@ Enable the extension in your [configuration file](../../configuration)
```yml
LeftAndMain:
SilverStripe\Admin\LeftAndMain:
extensions:
- BookmarkedPagesLeftAndMainExtension
```
@ -154,7 +148,6 @@ and replace it with the following:
```ss
<ul class="cms-menu-list">
<!-- ... -->
<% loop $BookmarkedPages %>
@ -267,8 +260,7 @@ The extension then needs to be registered:
```yaml
LeftAndMain:
SilverStripe\Admin\LeftAndMain:
extensions:
- CustomActionsExtension
```

View File

@ -21,7 +21,6 @@ Now enable this extension through your `[config.yml](/topics/configuration)` fil
```yml
MyAdmin:
extensions:
- MyAdminExtension

View File

@ -10,6 +10,9 @@ This can be accessed in user code via Injector
```php
use SilverStripe\Core\Kernel;
use SilverStripe\Core\Injector\Injector;
$kernel = Injector::inst()->get(Kernel::class);
echo "Current environment: " . $kernel->getEnvironment();
```
@ -70,7 +73,6 @@ You can customise it as required.
```php
<?php
use SilverStripe\Control\HTTPApplication;
use SilverStripe\Control\HTTPRequestBuilder;

View File

@ -8,8 +8,8 @@ needs to interface over the command line.
The main entry point for any command line execution is `cli-script.php` in the framework module.
For example, to run a database rebuild from the command line, use this command:
```bash
```bash
cd your-webroot/
php vendor/silverstripe/framework/cli-script.php dev/build
```
@ -23,7 +23,7 @@ to have.
## Sake - SilverStripe Make
Sake is a simple wrapper around `cli-script.php`. It also tries to detect which `php` executable to use if more than one
are available.
are available. It is accessible via `vendor/bin/sake`.
<div class="info" markdown='1'>
If you are using a Debian server: Check you have the php-cli package installed for sake to work. If you get an error
@ -32,11 +32,13 @@ when running the command php -v, then you may not have php-cli installed so sake
### Installation
`sake` can be invoked using `./vendor/silverstripe/framework/sake`. For easier access, copy the `sake` file into `/usr/bin/sake`.
`sake` can be invoked using `./vendor/bin/sake`. For easier access, copy the `sake` file into `/usr/bin/sake`.
```
cd your-webroot/
sudo ./vendor/silverstripe/framework/sake installsake
sudo ./vendor/bin/sake installsake
```
<div class="warning">
This currently only works on UNIX like systems, not on Windows.
</div>
@ -55,11 +57,10 @@ SS_BASE_URL="http://localhost/base-url"
### Usage
Sake can run any controller by passing the relative URL to that controller.
`sake` can run any controller by passing the relative URL to that controller.
```bash
sake /
# returns the homepage
@ -67,15 +68,15 @@ Sake can run any controller by passing the relative URL to that controller.
# shows a list of development operations
```
Sake is particularly useful for running build tasks.
```bash
`sake` is particularly useful for running build tasks.
```bash
sake dev/build "flush=1"
```
It can also be handy if you have a long running script..
```bash
```bash
sake dev/tasks/MyReallyLongTask
```
@ -86,7 +87,7 @@ It can also be handy if you have a long running script..
Make a task or controller class that runs a loop. To avoid memory leaks, you should make the PHP process exit when it
hits some reasonable memory limit. Sake will automatically restart your process whenever it exits.
Include some appropriate sleep()s so that your process doesn't hog the system. The best thing to do is to have a short
Include some appropriate `sleep()`s so that your process doesn't hog the system. The best thing to do is to have a short
sleep when the process is in the middle of doing things, and a long sleep when doesn't have anything to do.
This code provides a good template:
@ -115,14 +116,11 @@ This code provides a good template:
}
}
}
```
Then the process can be managed through `sake`
```bash
sake -start MyProcess
sake -stop MyProcess
```
@ -135,19 +133,15 @@ Then the process can be managed through `sake`
Parameters can be added to the command. All parameters will be available in `$_GET` array on the server.
```bash
cd your-webroot/
php vendor/silverstripe/framework/cli-script.php myurl myparam=1 myotherparam=2
```
Or if you're using `sake`
```bash
sake myurl "myparam=1&myotherparam=2"
vendor/bin/sake myurl "myparam=1&myotherparam=2"
```
## Running Regular Tasks With Cron
@ -158,5 +152,5 @@ On a UNIX machine, you can typically run a scheduled task with a [cron job](http
The following will run `MyTask` every minute.
```bash
* * * * * /your/site/folder/sake dev/tasks/MyTask
* * * * * /your/site/folder/vendor/bin/sake dev/tasks/MyTask
```

View File

@ -15,6 +15,8 @@ Sets the value of cookie with configuration.
```php
use SilverStripe\Control\Cookie;
Cookie::set($name, $value, $expiry = 90, $path = null, $domain = null, $secure = false, $httpOnly = false);
// Cookie::set('MyApplicationPreference', 'Yes');
@ -54,6 +56,10 @@ from the browser.
```php
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Control\Cookie;
use SilverStripe\Control\CookieJar;
$myCookies = [
'cookie1' => 'value1',
];
@ -97,12 +103,11 @@ If you need to implement your own Cookie_Backend you can use the injector system
```yml
---
Name: mycookie
After: '#cookie'
---
Injector:
SilverStripe\Core\Injector\Injector:
Cookie_Backend:
class: MyCookieJar
```

View File

@ -29,6 +29,8 @@ class MyController extends Controller
Otherwise, if you're not in a controller, get the request as a service.
```php
use SilverStripe\Control\HTTPRequest;
$request = Injector::inst()->get(HTTPRequest::class);
$session = $request->getSession();
```
@ -79,7 +81,6 @@ You can also get all the values in the session at once. This is useful for debug
```php
$session->getAll();
// returns an array of all the session values.
```
## clear
@ -102,7 +103,6 @@ In certain circumstances, you may want to use a different `session_name` cookie
```yml
SilverStripe\Control\Session:
cookie_secure: true
```

View File

@ -6,7 +6,7 @@ This version introduces many breaking changes, which in most projects can be man
of automatic upgrade processes as well as manual code review. This document reviews these changes and will
guide developers in preparing existing 3.x code for compatibility with 4.0
## <a name="overview"></a>Overview
## Overview {#overview}
* Minimum version dependencies have increased; PHP 5.5 and Internet Explorer 11 (or other modern browser)
is required.
@ -17,7 +17,7 @@ guide developers in preparing existing 3.x code for compatibility with 4.0
arrangement of templates, as well as other references to classes via string literals or configuration.
Automatic upgrading tools have been developed to cope with the bulk of these changes (see
[upgrading notes](#upgrading)).
* Object class has been replaced with traits ([details](object-replace)).
* Object class has been replaced with traits ([details](#object-replace)).
* Asset storage has been abstracted, and a new concept of `DBFile` references via database column references
now exists in addition to references via the existing `File` dataobject. File security and protected files
are now a core feature ([details](#asset-storage))
@ -52,13 +52,13 @@ guide developers in preparing existing 3.x code for compatibility with 4.0
* Core modules are installed in the `vendor/` folder by default (other modules can opt-in, see [guide](/developer_guides/extending/how_tos/publish_a_module))
* Renamed constant for temp folder from `TEMP_FOLDER` to `TEMP_PATH` for naming consistency with other path variables and constants
## <a name="upgrading"></a>Upgrading Guide
## Upgrading Guide {#upgrading}
The below sections describe how to go about updating an existing site to be prepared for upgrade to 4.0.
Most of these upgrading tasks will involve manual code review, although in some cases there are
some automated processes that users can run.
### <a name="deps"></a>Composer dependency update
### Composer dependency update {#deps}
As a first step, you need to update your composer dependencies.
The easiest way is to start with a new `composer.json` file
@ -126,7 +126,7 @@ you should raise an issue on the repository asking for 4.0 compatibility.
For now, you should attempt to continue the upgrade without the module
and temporarily disable its functionality.
### <a name="upgrader-tool"></a>Install the upgrader tool
### Install the upgrader tool {#upgrader-tool}
A lot of upgrade work can be automated, and we've written an
[upgrader tool](https://github.com/silverstripe/silverstripe-upgrader/) for this purpose.
@ -136,7 +136,7 @@ Install it via composer:
composer global require silverstripe/upgrader
```
### <a name="index-php-rewrites"></a>index.php and .htaccess rewrites
### index.php and .htaccess rewrites {#index-php-rewrites}
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`.
@ -167,7 +167,7 @@ for a clean installation. If you have applied customisations to your `.htaccess`
file (e.g. a custom `main.php`, HTTP header configuration, deny file access),
you'll need to manually reapply these to the copied default file.
### <a name="namespaced-classes"></a>Renamed and namespaced classes
### Renamed and namespaced classes {#namespaced-classes}
Nearly all core PHP classes have been namespaced. For example, `DataObject` is now called `SilverStripe\ORM\DataObject`.
The below tasks describe how to upgrade an existing site to remain compatible with the newly upgraded classes.
@ -189,7 +189,7 @@ For a full list of renamed classes, check the `.upgrade.yml` definitions in each
The rename won't affect class-based permission codes or database table names.
### <a name="env"></a>`_ss_environment.php` changed to`.env`
### `_ss_environment.php` changed to`.env` {#env}
The php configuration `_ss_environment.php` file has been replaced in favour of a non-executable
`.env` file, which follows a syntax similar to an `.ini` file for key/value pair assignment. Like
@ -215,10 +215,8 @@ define('SS_DATABASE_PASSWORD', '');
define('SS_DATABASE_SERVER', '127.0.0.1');
```
`.env`:
```
## Environment
SS_ENVIRONMENT_TYPE="dev"
@ -234,7 +232,6 @@ SS_DATABASE_PASSWORD=""
SS_DATABASE_SERVER="127.0.0.1"
```
The removal of the `_ss_environment.php` file means that conditional logic is no longer available in the environment
variable set-up process. This generally 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.
@ -253,9 +250,9 @@ which is no longer necessary.
To access environment variables you can use the `SilverStripe\Core\Environment::getEnv()` method.
See [Environment Management docs](/getting-started/environment_management/) for full details.
See [Environment Management docs](/getting_started/environment_management/) for full details.
### <a name="migrate-file"></a>Migrate File DataObject
### Migrate File DataObject {#migrate-file}
Since the structure of `File` dataobjects 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)).
@ -274,7 +271,7 @@ SilverStripe\Assets\FileMigrationHelper:
delete_invalid_files: false
```
### <a name="inspect-hints"></a>Get upgrade tips on your code
### Get upgrade tips on your code {#inspect-hints}
While there's some code we can automatically rewrite, other uses of changed SilverStripe APIs aren't that obvious.
You can use our heuristics to get some hints on where you need to review code manually.
@ -288,7 +285,7 @@ This task should be run *after* `upgrade-code upgrade`.
These hints only cover a part of the upgrade work,
but can serve as a good indicator for where to start.
### <a name="literal-table-names"></a>Rewrite literal table names
### Rewrite literal table names {#literal-table-names}
In 3.x the class name of any DataObject matched the table name, but in 4.x all classes are namespaced, and it is
necessary to map between table and class for querying the database.
@ -311,7 +308,7 @@ public function countDuplicates($model, $fieldToCheck)
}
```
### <a name="literal-class-names"></a>Rewrite literal class names
### Rewrite literal class names {#literal-class-names}
You'll need to update any strings that represent class names and make sure they're fully
qualified. In particular, relationship definitions such as `has_one` and `has_many` will need
@ -341,7 +338,7 @@ In the context of YAML, the magic constant `::class` does not apply. Fully quali
property: value
```
### <a name="controllers-own-files"></a>Move controllers to their own files
### Move controllers to their own files {#controllers-own-files}
The convention for naming controllers is now `[MyPageType]Controller`, where it used to be `[MyPageType]_Controller`. This change was made to be more compatible with the PSR-2 standards.
@ -351,7 +348,7 @@ other thirdparty code that extend `PageController` are likely to assume that cla
By default, a controller for a page type *must* reside in the same namespace as its page. To use different logic, override `SiteTree::getControllerName()`.
### <a name="template-locations"></a>Template locations and references
### Template locations and references {#template-locations}
Templates are now more strict about their locations.
Case is now also checked on case-sensitive filesystems.
@ -372,7 +369,7 @@ if the former is not present.
Please refer to our [template syntax](/developer_guides/templates/syntax) for details.
### <a name="private-static"></a>Config settings should be set to `private static`
### Config settings should be set to `private static` {#private-static}
Class configuration defined as `static` properties need to be marked as `private` to take effect:
@ -383,7 +380,7 @@ Class configuration defined as `static` properties need to be marked as `private
];
```
### <a name="module-paths"></a>Module paths can't be hardcoded
### Module paths can't be hardcoded {#module-paths}
You should no longer rely on modules being placed in a deterministic folder (e.g. `/framework`),
and use getters on the [Module](api:SilverStripe\Core\Manifest\Module) object instead.
@ -453,7 +450,7 @@ To ensure consistency, we've also deprecated support for path constants:
* Deprecated `THEMES_DIR` and `THEMES_PATH`
* Deprecated `MODULES_PATH` and `MODULES_DIR`
### <a name="vendor-folder"></a>Adapt tooling to modules in vendor folder
### Adapt tooling to modules in vendor folder {#vendor-folder}
SilverStripe modules can now be installed like any other composer package: In the `vendor/` folder
instead of the webroot. Modules need to opt in to this behaviour after they've ensured
@ -479,7 +476,7 @@ and this environment supports symlinks, you don't need to change anything.
If you deploy release archives, either ensure those archives can correctly extract symlinks,
or explicitly switch to the "copy" mode to avoid symlinks.
### <a name="psr3-logging"></a>SS_Log replaced with PSR-3 logging
### SS_Log replaced with PSR-3 logging {#psr3-logging}
One of the great changes that comes with SilverStripe 4 is the introduction of
[PSR-3](http://www.php-fig.org/psr/psr-3/) compatible logger interfaces. This
@ -532,7 +529,7 @@ SilverStripe\Core\Injector\Injector:
`WebDesignGroup\ShopSite\Logging\ErrorPageFormatter` should be a class that
implements the `Monolog\Formatter\FormatterInterface` interface.
### <a name="config-php"></a>Upgrade `mysite/_config.php`
### Upgrade `mysite/_config.php` {#config-php}
The globals `$database` and `$databaseConfig` are deprecated. You should upgrade your
site `_config.php` files to use the [.env configuration](#env)
@ -561,7 +558,7 @@ SilverStripe\Core\Manifest\ModuleManifest:
project: mysite
```
### <a name="object-replace"></a>Object class replaced by traits
### Object class replaced by traits {#object-replace}
Object has been superseded by traits.
@ -630,7 +627,7 @@ Upgrade extension use
+$extensions = DataObject::get_extensions(File::class); // alternate
```
### <a name="session"></a>Session object removes static methods
### Session object removes static methods {#session}
Session object is no longer statically accessible via `Session::inst()`. Instead, Session
is a member of the current request.
@ -648,7 +645,7 @@ In some places it may still be necessary to access the session object where no r
In rare cases it is still possible to access the request of the current controller via
`Controller::curr()->getRequest()` to gain access to the current session.
### <a name="extensions-singletons"></a>Extensions are now singletons
### Extensions are now singletons {#extensions-singletons}
Extensions are now all singletons, meaning that state stored as protected vars
within extensions are now shared across all object instances that use this extension.
@ -707,7 +704,7 @@ class MyClass extends DataObject {
}
```
### <a name="static-asset-paths"></a>Static references to asset paths
### Static references to asset paths {#static-asset-paths}
All static files (images, javascript, stylesheets, fonts) used for the CMS and forms interfaces
in `framework` and `cms` have moved locations. These assets are now placed in a `client/` subfolder,
@ -781,7 +778,7 @@ framework/thirdparty/jquery/jquery.js => framework/admin/thirdparty/jquery/jquer
If you have customised the CMS UI (via JavaScript or CSS), please read our guide to
[customise the admin interface](/developer_guides/customising_the_admin_interface/).
### <a name="template-casting"></a>Explicit text casting on template variables
### Explicit text casting on template variables {#template-casting}
Now whenever a `$Variable` is used in a template, regardless of whether any casts or methods are
suffixed to the reference, it will be cast to either an explicit DBField for that field, or
@ -816,7 +813,7 @@ class MyObject extends ViewableData
If you need to encode a field (such as `HTMLText`) for use in HTML attributes, use `.ATT`
instead, or if used in an actual XML file use `.CDATA` (see [template casting](/developer_guides/templates/casting)).
### <a name="uploadfield"></a>Replace UploadField with injected service
### Replace UploadField with injected service {#uploadfield}
This field has been superceded by a new class provided by the
[asset-admin](https://github.com/silverstripe/silverstripe-asset-admin) module, which provides a more
@ -844,7 +841,7 @@ class MyClass extends DataObject
}
```
### <a name="i18n"></a>i18n placeholders, plurals and i18nEntityProvider
### i18n placeholders, plurals and i18nEntityProvider {#i18n}
In many cases, localisation strings which worked in 3.x will continue to work in 4.0, however certain patterns
have been deprecated and will be removed in 5.0. These include:
@ -937,7 +934,7 @@ In templates this can also be invoked as below:
<%t MyObject.PLURALS 'An item|{count} items' count=$Count %>
```
### <a name="member-date-time-fields"></a>Removed Member.DateFormat and Member.TimeFormat database settings
### Removed Member.DateFormat and Member.TimeFormat database settings {#member-date-time-fields}
We're using [native HTML5 date and time pickers](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date)
in `DateField` and `TimeField` now ([discussion](https://github.com/silverstripe/silverstripe-framework/issues/6626)),
@ -950,7 +947,7 @@ Consequently, we've also removed `MemberDatetimeOptionsetField`.
the [IntlDateFormatter defaults](http://php.net/manual/en/class.intldateformatter.php) for the selected locale.
### <a name="asset-storage"></a>New asset storage mechanism
### New asset storage mechanism {#asset-storage}
File system has been abstracted into an abstract interface. By default, the out of the box filesystem
uses [Flysystem](http://flysystem.thephpleague.com/) with a local storage mechanism (under the assets directory).
@ -975,7 +972,7 @@ Depending on your server configuration, it may also be necessary to adjust your
permissions. Please see the [common installation problems](/getting_started/installation/common_problems)
guide for configuration instruction.
### <a name="image-handling"></a>Image handling
### Image handling {#image-handling}
As all image-specific manipulations has been refactored from `Image` into an `ImageManipulations` trait, which
is applied to both `File` and `DBFile`. These both implement a common interface `AssetContainer`, which
@ -1015,7 +1012,7 @@ class MyObject extends SilverStripe\ORM\DataObject
}
```
### <a name="write-file-dataobject"></a>Writing to `File` dataobjects or the assets folder
### Writing to `File` dataobjects or the assets folder {#write-file-dataobject}
In the past all that was necessary to write a `File` DataObject to the database was to ensure a physical file
existed in the assets folder, and that the Filename of the DataObject was set to the same location.
@ -1062,7 +1059,7 @@ You can disable File versioning by adding the following to your `_config.php`
SilverStripe\Assets\File::remove_extension('Versioned');
```
### <a name="image-manipulations"></a>Custom image manipulations
### Custom image manipulations {#image-manipulations}
As file storage and handling has been refactored into the abstract interface, many other components which were
once specific to Image.php have now been moved into a shared `ImageManipulation` trait. Manipulations of file content,
@ -1130,7 +1127,7 @@ There are a few differences in this new API:
A generic `manipulate` method may be used, although the callback for this method both is given, and should return,
an `AssetStore` instance and file tuple (Filename, Hash, and Variant) rather than an Image_Backend.
### <a name="file-shortcode"></a>File or Image shortcode handler
### File or Image shortcode handler {#file-shortcode}
The `handle_shortcode` methods have been removed from the core File and Image classes
and moved to separate classes in their own respective namespace.
@ -1155,7 +1152,7 @@ class MyShortcodeUser extends Object
}
```
### <a name="compositedbfield"></a>Composite db fields
### Composite db fields {#compositedbfield}
The `CompositeDBField` interface has been replaced with an abstract class, `DBComposite`. In many cases, custom code
that handled saving of content into composite fields can be removed, as it is now handled by the base class.
@ -1181,23 +1178,18 @@ class MyAddressField extends
}
```
### <a name="dataobject-db-database-fields"></a>`DataObject::database_fields` or `DataObject::db`
### Removed `DataObject::database_fields` or `DataObject::db` {#dataobject-db-database-fields}
These methods have been updated to include base fields (such as ID, ClassName, Created, and LastEdited), as
well as composite DB fields.
The methods `DataObject::database_fields()`, `DataObject::custom_database_fields()` and `DataObject::db()` have
been removed.
`DataObject::database_fields` does not have a second parameter anymore, and can be called directly on an object
or class. E.g. `Member::database_fields()`.
If user code requires the list of fields excluding base fields, then use custom_database_fields instead, or
make sure to call `unset($fields['ID']);` if this field should be excluded.
Instead, to get all database fields for a dataobject, including base fields (such as ID, ClassName, Created, and LastEdited), use `DataObject::getSchema()->databaseFields($className, $aggregate = true)`.
To omit the base fields, pass a value of `false` as the `$aggregate` parameter, e.g. `DataObject::getSchema()->databaseFields(Member::class, false)`.
`DataObject:db()` will return all logical fields, including foreign key ids and composite DB Fields, alongside
any child fields of these composites. This method can now take a second parameter $includesTable, which
when set to true (with a field name as the first parameter), will also include the table prefix in
`Table.ClassName(args)` format.
Composite database fields are omitted from the `databaseFields()` method. To get those, use `DataObject::getSchema()->compositeFields($className, $aggregate = true)`.
### <a name="sqlquery"></a>Rewrite SQLQuery to more specific classes
### Rewrite SQLQuery to more specific classes {#sqlquery}
Instead of `SQLQuery`, you should now use `SQLSelect`, `SQLUpdate`, `SQLInsert`
or `SQLDelete` - check the [3.2.0](3.2.0#sqlquery) upgrading notes for details.
@ -1215,7 +1207,7 @@ Example:
}
```
### <a name="buildtask-segment"></a>Upgrade BuildTask classes
### Upgrade BuildTask classes {#buildtask-segment}
Similarly to the `$table_name` configuration property for DataObjects, you should define a `private static $segment` for `BuildTask`
instances to ensure that you can still run your task via `sake dev/tasks/MyTask`. Without defining it, the default
@ -1230,7 +1222,7 @@ class MyTask extends BuildTask
}
```
### <a name="errorpage"></a>Moved ErrorPage into a new module
### Moved ErrorPage into a new module {#errorpage}
ErrorPage has been moved to a separate [silverstripe/errorpage module](http://addons.silverstripe.org/add-ons/silverstripe/errorpage)
to allow for alternative approaches to managing error responses.
@ -1244,7 +1236,7 @@ By default, SilverStripe will display a plaintext "not found" message when the m
Check the [module upgrading guide](http://addons.silverstripe.org/add-ons/silverstripe/errorpage)
for more configuration API changes on the `ErrorPage` class.
### <a name="assets-server-config"></a>Server configuration files for assets
### Server configuration files for assets {#assets-server-config}
Server configuration files for `/assets` are no longer static, and are regenerated via a set of
standard SilverStripe templates on flush. These templates include:
@ -1265,7 +1257,7 @@ If upgrading from an existing installation, make sure to invoke `?flush=all` at
See our ["File Security" guide](/developer_guides/files/file_security) for more information.
### <a name="tinymce"></a>TinyMCE v4
### TinyMCE v4 {#tinymce}
Please see the [tinymce upgrading guide](http://archive.tinymce.com/wiki.php/Tutorial:Migration_guide_from_3.x)
to assist with upgrades to customisations to TinyMCE v3.
@ -1306,7 +1298,7 @@ $editor->setOption('charmap_append', [
]);
```
### <a name="dataobject-versioned"></a>DataObjects with the `Versioned` extension
### DataObjects with the `Versioned` extension {#dataobject-versioned}
In most cases, versioned models with the default versioning parameters will not need to be changed. However,
there are now additional restrictions on the use of custom stage names.
@ -1354,7 +1346,7 @@ These methods are deprecated:
* `Versioned::publish` Replaced by `Versioned::copyVersionToStage`
* `Versioned::doPublish` Replaced by `Versioned::publishRecursive`
### <a name="ownership"></a>New Ownership API
### New Ownership API {#ownership}
In order to support the recursive publishing of dataobjects, a new API has been developed to allow
developers to declare dependencies between objects. This is done to ensure that the published state
@ -1399,14 +1391,14 @@ setting on the child object.
For more information, see the [DataObject ownership](https://docs.silverstripe.org/en/4/developer_guides/model/versioning/#dataobject-ownership) documentation and the [versioning](/developer_guides/model/versioning) documentation
### <a name="changeset"></a>ChangeSet batch publishing
### ChangeSet batch publishing {#changeset}
ChangeSet objects have been added, which allow groups of objects to be published in
a single atomic transaction.
This API will utilise the ownership API to ensure that changes to any object include
all necessary changes to owners or owned entities within the same changeset.
### <a name="image-shortcode"></a>New `[image]` shortcode in `HTMLText` fields
### New `[image]` shortcode in `HTMLText` fields {#image-shortcode}
The new Ownership API relies on relationships between objects.
Many of these relationships are already made explicit through `has_one`, `has_many` and `many_many`.
@ -1416,7 +1408,7 @@ of the `Image` record rather than its path on the filesystem. The shortcode will
when the field is rendered. Newly inserted images will automatically receive the shortcode and ownership tracking,
and existing `<img>` will continue to work.
### <a name="dbfield-rename"></a>Renamed DBField and subclasses
### Renamed DBField and subclasses {#dbfield-rename}
All `DBField` subclasses are namespaced, have a `DB` prefix, and drop any existing `SS_` prefix.
For example, `Text` becomes `SilverStripe\ORM\FieldType\DBText`,
@ -1448,17 +1440,17 @@ class MyObject extends DataObject
}
```
### <a name="restfulservice"></a>Removed RestfulService
### Removed RestfulService {#restfulservice}
The `RestfulService` API was a (poor) attempt at a built-in HTTP client.
We've removed it, and recommend using [Guzzle](http://docs.guzzlephp.org/en/latest/) instead.
### <a name="oembed"></a>Removed Oembed
### Removed Oembed {#oembed}
Instead of Oembed, the framework now relies on [oscarotero/Embed](https://github.com/oscarotero/Embed) to handle getting the shortcode-data for embedding.
If you have custom embedding-code relying on `Oembed`, please refer to the documentation provided by this package.
### <a name="admin-url"></a>Configurable Admin URL
### Configurable Admin URL {#admin-url}
The default `admin/` URL to access the CMS interface can now be changed via a custom Director routing rule for
`AdminRootController`. If your website or module has hard coded `admin` URLs in PHP, templates or JavaScript, make sure
@ -1466,7 +1458,7 @@ to update those with the appropriate function or config call. See
[CMS architecture](/developer_guides/customising_the_admin_interface/cms-architecture#the-admin-url) for language
specific functions.
### <a name="custom-authenticators"></a>Custom Authenticators
### Custom Authenticators {#custom-authenticators}
The methods `register()` and `unregister()` on `Authenticator` are deprecated in favour
of the `Config` system. This means that any custom `Authenticator` needs to be registered
@ -1496,7 +1488,7 @@ which are called from the `AuthenticationHandler`.
If there is a valid `Member`, it is set on `Security::setCurrentUser()`, which defaults to `null`.
IdentityStores are responsible for logging members in and out (e.g. destroy cookies and sessions, or instantiate them).
### <a name="config"></a>Config is now immutable
### Config is now immutable {#config}
Performance optimisations have been made to Config which, under certain circumstances, require developer
care when modifying or caching config values. The top level config object is now immutable on application
@ -1514,7 +1506,7 @@ One removed feature is the `Config::FIRST_SET` option. Either use uninherited co
directly, or use the inherited config lookup. As falsey values now overwrite all parent class values, it is
now generally safer to use the default inherited config, where in the past you would need to use `FIRST_SET`.
### <a name="cache"></a>Replace Zend_Cache with symfony/cache
### Replace Zend_Cache with symfony/cache {#cache}
We have replaced the unsupported `Zend_Cache` library with [symfony/cache](https://github.com/symfony/cache).
This also allowed us to remove SilverStripe's `Cache` API and use dependency injection with a standard
@ -1599,7 +1591,7 @@ SilverStripe\Core\Injector\Injector:
client: '%$MemcachedClient
```
### <a name="usercode-style-upgrades"></a>User-code style upgrades
### User-code style upgrades {#usercode-style-upgrades}
Although it is not mandatory to upgrade project code to follow SilverStripe and
PSR-2 standard it is highly recommended to ensure that code is consistent. The below sections
@ -1675,7 +1667,7 @@ class GalleryPage extends Page
}
```
### <a name="class-name-remapping"></a>Class name remapping
### Class name remapping {#class-name-remapping}
If you've namespaced one of your custom page types, you may notice a message in the CMS
telling you it's obsolete. This is likely because the `ClassName`
@ -1699,7 +1691,7 @@ SilverStripe\ORM\DatabaseAdmin:
The next time you run a dev/build the class name for all `GalleryPage` pages will
be automatically updated to the new `WebDesignGroup\ShopSite\GalleryPage`
### <a name="psr2"></a>PSR-2 Coding Standard compliance
### PSR-2 Coding Standard compliance {#psr2}
You can use the [php codesniffer](https://github.com/squizlabs/PHP_CodeSniffer) tool
to not only detect and lint PSR-2 coding errors, but also do some minimal automatic
@ -1717,7 +1709,7 @@ code style migration.
Repeat the final step and manually repair suggested changes, as necessary,
until you no longer have any linting issues.
### <a name="psr4"></a>PSR-4 autoloading for project code
### PSR-4 autoloading for project code {#psr4}
While not critical to an upgrade, SilverStripe 4.0 has adopted the [PS-4 autoloading](http://www.php-fig.org/psr/psr-4/)
standard for the core modules, so it's probably a good idea to be consistent.
@ -1751,9 +1743,9 @@ Please note that there are changes to template structure which in some cases
require templates to be in a folder location that matches the namespace of the class
that it belongs to, e.g. `themes/mytheme/templates/MyVendor/Foobar/Model/MyModel.ss`.
## <a name="api-changes"></a>API Changes
## API Changes {#api-changes}
### <a name="overview-general"></a>General
### General {#overview-general}
* Minimum PHP version raised to 5.6 (with support for PHP 7.x)
* Dropped support for PHP safe mode (removed php 5.4).
@ -1771,7 +1763,7 @@ that it belongs to, e.g. `themes/mytheme/templates/MyVendor/Foobar/Model/MyModel
* Admin URL can now be configured via custom Director routing rule
* `Controller::init` visibility changed to protected. Use `Controller::doInit()` instead.
* `Controller::join_links` supports an array of link sections.
* <a name="object-usecustomclass"></a>`Object::useCustomClass` has been removed. You should use the config API with Injector instead.
* `Object::useCustomClass` has been removed. You should use the config API with Injector instead. {#object-usecustomclass}
* `Object::invokeWithExtensions` now has the same method signature as `Object::extend` and behaves the same way.
* `ServiceConfigurationLocator` is now an interface not a class.
* `i18nTextCollectorTask` merge is now true by default.
@ -1823,7 +1815,7 @@ instance rather than a `HTMLEditorField_Toolbar`
* Removed `Director.alternate_protocol`. Use `Director.alternate_base_url` instead.
* 'BlockUntrustedIPS' env setting has been removed.
All IPs are untrusted unless `SS_TRUSTED_PROXY_IPS` is set to '*'
See [Environment Management docs](/getting-started/environment_management/) for full details.
See [Environment Management docs](/getting_started/environment_management/) for full details.
* `SS_TRUSTED_PROXY_HOST_HEADER`, `SS_TRUSTED_PROXY_PROTOCOL_HEADER`, and `SS_TRUSTED_PROXY_IP_HEADER`
are no longer supported. These settings should go into the Injector service configuration for
TrustedProxyMiddleware instead.
@ -1925,7 +1917,7 @@ TrustedProxyMiddleware instead.
* Deprecated `Config::inst()->update()`. Use `Config::modify()->set()` or `Config::modify()->merge()`
instead.
### <a name="overview-orm"></a>ORM
### ORM {#overview-orm}
* Deprecated `SQLQuery` in favour `SQLSelect` ([details](#sqlquery))
* Added `DataObject.many_many` 'through' relationships now support join dataobjects in place of
@ -2050,7 +2042,7 @@ usercode before invocation.
* Moved `DataObject::manyManyExtraFieldsForComponent()` to `DataObjectSchema`
* Deprecated `DataObject::$destroyed`
* Removed `DataObject::validateModelDefinitions`. Relations are now validated within `DataObjectSchema`
* <a name="dataobject-has-own"></a>Removed `DataObject` methods `hasOwnTableDatabaseField`, `has_own_table_database_field` and
* Removed `DataObject` methods `hasOwnTableDatabaseField`, `has_own_table_database_field` and {#dataobject-has-own}
`hasDatabaseFields` are superceded by `DataObjectSchema::fieldSpec`.
Use `$schema->fieldSpec($class, $field, DataObjectSchema::DB_ONLY | DataObjectSchema::UNINHERITED )`.
Exclude `uninherited` option to search all tables in the class hierarchy.
@ -2093,7 +2085,7 @@ usercode before invocation.
* Removed `Hierarchy::naturalPrev()`
* Removed `Hierarchy::markingFinished()`
### <a name="overview-filesystem"></a>Filesystem
### Filesystem {#overview-filesystem}
* Image manipulations have been moved into a new `[ImageManipulation](api:SilverStripe\Assets\ImageManipulation)` trait.
* Removed `CMSFileAddController`
@ -2121,7 +2113,6 @@ usercode before invocation.
cache or combined files).
* `Requirements_Minifier` API can be used to declare any new mechanism for minifying combined required files.
By default this api is provided by the `JSMinifier` class, but user code can substitute their own.
* `AssetField` form field to provide an `UploadField` style uploader for the new `DBFile` database field.
* `AssetControlExtension` is applied by default to all DataObjects, in order to support the management
of linked assets and file protection.
* `ProtectedFileController` class is used to serve up protected assets.
@ -2167,7 +2158,7 @@ appropriate mime types. The following file manipulations classes and methods hav
* Removed `Filesystem::sync()`
* Removed `AssetAdmin::doSync()`
### <a name="overview-template"></a>Templates and Form
### Templates and Form {#overview-template}
* Upgrade to TinyMCE 4.x
* Templates now use a standard template lookup system via `SSViewer::get_templates_by_class()`
@ -2198,6 +2189,8 @@ appropriate mime types. The following file manipulations classes and methods hav
submission, in contrast to `FormField::setValue($value, $data)` which is intended to load its
value from the ORM. The second argument to `setValue()` has been added.
* `FormField::create_tag()` moved to `SilverStripe\View\HTML->createTag()`.
* `CompositeField::setID()` is removed. ID is generated from name indirectly.
Use SilverStripe\Form\FormField::setName() instead
* Changed `ListboxField` to multiple only. Previously, this field would operate as either a
single select (default) or multi-select through `setMultiple()`.
Now this field should only be used for multi-selection. Single-selection should be done using
@ -2206,7 +2199,7 @@ appropriate mime types. The following file manipulations classes and methods hav
instead of a list of grouped values. The method now expectes
a non-associative array of values (not titles) or an `SS_List`.
<a name="requirements"></a>The following methods and properties on `Requirements_Backend` have been renamed:
The following methods and properties on `Requirements_Backend` have been renamed: {#requirements}
* Renamed `$combine_files` to `$combinedFiles`
* Renamed `$combine_js_with_min` to `$minifyCombinedFiles`
@ -2234,7 +2227,7 @@ appropriate mime types. The following file manipulations classes and methods hav
A new config `Requirements_Backend.combine_in_dev` has been added in order to allow combined files to be
forced on during development. If this is off, combined files is only enabled in live environments.
<a name="form-validation"></a>Form validation has been refactored significantly. A new `FormMessage` trait has been created to
Form validation has been refactored significantly. A new `FormMessage` trait has been created to {#form-validation}
handle `FormField` and `Form` messages. This trait has a new`setMessage()` API to assign a message, type, and cast.
Use `getMessage()`, `getMessageType()`, `getMessageCast()` and `getMessageCastingHelper()` to retrieve them.
@ -2282,7 +2275,7 @@ Use `getMessage()`, `getMessageType()`, `getMessageCast()` and `getMessageCastin
* Changed constructor to remove second argument (`$message`). It now only accepts `$result`,
which may be a string, and optional `$code`
<a name="datetimefield"></a>New `DatetimeField` methods replace `getConfig()` / `setConfig()`:
New `DatetimeField` methods replace `getConfig()` / `setConfig()`: {#datetimefield}
* Added `getTimezone()` / `setTimezone()`
* Added `getDateTimeOrder()` / `setDateTimeOrder()`
@ -2300,7 +2293,7 @@ The `DatetimeField` has changed behaviour:
* It no longer accepts `setValue()` as an array with 'date' and 'time' keys
* Added `getHTML5()` / `setHTML5()`
<a name="datefield"></a>New `DateField` methods replace `getConfig()` / `setConfig()`:
New `DateField` methods replace `getConfig()` / `setConfig()`: {#datefield}
* Added `getDateFormat()` / `setDateFormat()`
* Added `getMinDate()` / `setMinDate()`
@ -2316,7 +2309,7 @@ The `DateField` has changed behavior:
* The `dmyfields` option has been replced with native HTML5 behaviour (as one single `<input type=date>`).
* `getClientLocale` / `setClientLocale` have been removed (handled by `DateField->locale` and browser settings)
<a name="timefield"></a>New `TimeField` methods replace `getConfig()` / `setConfig()`
New `TimeField` methods replace `getConfig()` / `setConfig()` {#timefield}
* Added `getTimeFormat()` / `setTimeFormat()`
* Added `getLocale()` / `setLocale()`
@ -2358,7 +2351,7 @@ Further API changes:
* Removed `HTMLEditorField_Flash`
* Removed `HTMLEditorField_Image`
### <a name="overview-i18n"></a>i18n
### i18n {#overview-i18n}
* Upgrade of i18n to symfony/translation
* Localisation based on language-only (without any specific locale) is now supported
@ -2391,7 +2384,7 @@ Further API changes:
* Removed `i18n::get_common_locales()`
* Removed `i18n.common_locales`
### <a name="overview-mailer"></a>Email
### Email {#overview-mailer}
* Changed `Mailer` to an interface
* `Email` re-written to be powered by [SwiftMailer](https://github.com/swiftmailer/swiftmailer)
@ -2400,7 +2393,7 @@ Further API changes:
* Added `Email->setPlainTemplate()` for rendering plain versions of email
* Renamed `Email->populateTemplate()` to `Email->setData()`
### <a name="overview-testing"></a>SapphireTest
### SapphireTest {#overview-testing}
* `is_running_tests()` is no longer public and user code should not rely on this. Test-specific behaviour
should be implemented in `setUp()` and `tearDown()`
@ -2411,7 +2404,7 @@ Further API changes:
* Renamed `$extraDataObjects` to `$extra_dataobjects` (and made static)
* Renamed `$extraControllers` to `$extra_controllers` (and made static)
### <a name="overview-security"></a>Security
### Security {#overview-security}
* `LoginForm` now has an abstract method `getAuthenticatorName()`. If you have made subclasses of this,
you will need to define this method and return a short name describing the login method.

View File

@ -0,0 +1,42 @@
# 4.0.0-rc2
<!--- Changes below this line will be automatically regenerated -->
## Change Log
### Features and Enhancements
* 2017-10-31 [34ed2cf](https://github.com/silverstripe/silverstripe-admin/commit/34ed2cf39fcdd5ca7b5fd9243903cf4f39535da9) add more font icons (Christopher Joe)
* 2017-10-31 [c82752f](https://github.com/silverstripe/silverstripe-campaign-admin/commit/c82752ffc6521405d5064e66ffd7ca122aaa69cb) a help panel to describe what campaigns are for - extensible for later to add links to userhelp docs (Christopher Joe)
* 2017-10-31 [bde7395](https://github.com/silverstripe/silverstripe-asset-admin/commit/bde73952d804fb8cbfd1d785b1f2b04ed42b5c4d) Add icon to add to campaign button (Damian Mooyman)
* 2017-10-30 [0c178f934](https://github.com/silverstripe/silverstripe-framework/commit/0c178f934de942b8b3f6b8fda78b1228656d9906) Adjust tinymce footer, remove branding and restore path (Damian Mooyman)
* 2017-10-26 [f6b7cf888](https://github.com/silverstripe/silverstripe-framework/commit/f6b7cf88893fdc5bc50f1c10d59696b41f924dc2) disable current user from removing their admin permission (Christopher Joe)
* 2017-10-26 [6086b0d](https://github.com/silverstripe/silverstripe-admin/commit/6086b0d48327b3e66191f2c949912a0a7841d0ed) Allow popover menu icon size class to be adjustable via class props (Robbie Averill)
* 2017-10-24 [324bdad48](https://github.com/silverstripe/silverstripe-framework/commit/324bdad48c7ad3c3faa75388e22a34dfdf7ae4b9) Ensure DBVarchar scaffolds text field with TextField with appropriate max length (Damian Mooyman)
### Bugfixes
* 2017-11-02 [37508a9](https://github.com/silverstripe/silverstripe-campaign-admin/commit/37508a91fa75512083b36ef311d3bc5d1ec70092) Behat tests (Damian Mooyman)
* 2017-11-02 [052d039](https://github.com/silverstripe/silverstripe-campaign-admin/commit/052d039d0cccd0215cfeddc915865e9c89644d02) error detection is incorrect for successful messages (Christopher Joe)
* 2017-11-01 [df50c8da0](https://github.com/silverstripe/silverstripe-framework/commit/df50c8da03033c3282a9589b292b00002f4f08e8) Use parse_str in place of guzzle library (Damian Mooyman)
* 2017-11-01 [4b80a3c](https://github.com/silverstripe/silverstripe-versioned/commit/4b80a3c4c6646ef2627432f2e0525a663b8d9832) Reset reading stage when user logs out of the CMS (Robbie Averill)
* 2017-11-01 [897cba55c](https://github.com/silverstripe/silverstripe-framework/commit/897cba55cbf6bf2fae6ec0bc2f464b4b61b4dd22) Move Member log out extension points to non-deprecated methods (Robbie Averill)
* 2017-11-01 [f7e8277](https://github.com/silverstripe/silverstripe-admin/commit/f7e827777dc95e66d75f1bcfe806724ca89f0684) Do not hide all columns in the gridfield when in mobile view (Christopher Joe)
* 2017-11-01 [c331deda](https://github.com/silverstripe/silverstripe-cms/commit/c331dedae929478c971348a5cfd431d99ea09bbd) Fix ambiguous query for content report (Christopher Joe)
* 2017-11-01 [b85a10b](https://github.com/silverstripe/silverstripe-graphql/commit/b85a10bf5d2e71c24d185f9017b3f15ec573030d) Prevent PHP7 warnings on sizeof(Callable) (Damian Mooyman)
* 2017-10-31 [42acb9f](https://github.com/silverstripe/silverstripe-admin/commit/42acb9f8f740e05745a8bed64b477a6ffe49c0f8) Adjust optionset gutter to offset different base REM (Damian Mooyman)
* 2017-10-30 [7b75690](https://github.com/silverstripe/silverstripe-admin/commit/7b7569051d76a18324dde344c7e62d978ce38cd8) Restore missing i18n localisations (Damian Mooyman)
* 2017-10-30 [0dfdb5a](https://github.com/silverstripe/silverstripe-errorpage/commit/0dfdb5a608d46d12b90562f7440328dc5f8f5533) Prevent cms themes + user from interfering with error static cache (Damian Mooyman)
* 2017-10-30 [4fb53060](https://github.com/silverstripe/silverstripe-cms/commit/4fb5306008328f38886c7ddb7b0b2a31addffc47) Safely check for is_site_url before parsing a shortcode (Damian Mooyman)
* 2017-10-27 [4d063289](https://github.com/silverstripe/silverstripe-cms/commit/4d0632892bf1c4bc56041881fccb9e0ff9c1d5db) Add warning state to revert action in CMS page history (Robbie Averill)
* 2017-10-27 [33161a4](https://github.com/silverstripe/silverstripe-admin/commit/33161a4735c6527ebedc1c3b0dce96430162b24f) Remove magnifying glass icon from GridField view link (Robbie Averill)
* 2017-10-26 [6145f4b](https://github.com/silverstripe/silverstripe-assets/commit/6145f4b07dea1e0af10f915e117afd4d5ab3f250) Prevent massive recursion of publish writes (Damian Mooyman)
* 2017-10-26 [9d3277f3d](https://github.com/silverstripe/silverstripe-framework/commit/9d3277f3d3937845d893ce9a93863a63b99b4546) Fix forceWWW and forceSSL not working in _config.php (Damian Mooyman)
* 2017-10-26 [7ee5f2f](https://github.com/silverstripe/silverstripe-assets/commit/7ee5f2fcedc375e1cbe1da97170db1f9f2b90b4e) Fix migrations to delete the migrated files (Christopher Joe)
* 2017-10-26 [68c3279fd](https://github.com/silverstripe/silverstripe-framework/commit/68c3279fd9b342f9664146c0131d185ca17c340a) Ensure readonly tree dropdown is safely encoded (Damian Mooyman)
* 2017-10-25 [8725672ea](https://github.com/silverstripe/silverstripe-framework/commit/8725672eaec2a85bfb1e6af05c37ad0e8a6e1790) changelog anchors (Ingo Schommer)
* 2017-10-25 [4276951](https://github.com/silverstripe/silverstripe-versioned/commit/427695106c49a538ca58baf5c7628f5a884121c2) Combine if statements to simplify logic (Robbie Averill)
* 2017-10-25 [da4989e8f](https://github.com/silverstripe/silverstripe-framework/commit/da4989e8f624247cb3618c1244d7c19055672a6c) Do not escape the readonly values since they get escaped when rendered (Robbie Averill)
* 2017-10-23 [abfeb0c](https://github.com/silverstripe/silverstripe-versioned/commit/abfeb0cccddd0af9254756208d5374b26d2bae7c) Dont override ItemRequest if already versioned (Will Rossiter)
* 2017-10-22 [6f341d3](https://github.com/silverstripe/silverstripe-admin/commit/6f341d364ee3ddb8b7468c8b24ef3d7f7466fad0) amend preview URL correctly, more flexibly (NightjarNZ)
* 2016-06-22 [e0c829f47](https://github.com/silverstripe/silverstripe-framework/commit/e0c829f471f464d4ab23ab5b18775f2d16ccba6e) es issue 5188: X-Forwarded Proto (Ian Walls)

View File

@ -212,11 +212,14 @@ Further guidelines:
* Mention important changed classes and methods in the commit summary.
Example: Bad commit message
```
finally fixed this dumb rendering bug that Joe talked about ... LOL
also added another form field for password validation
```
Example: Good commit message
```
BUG Formatting through prepValueForDB()

View File

@ -92,8 +92,9 @@ notices are always disabled on both live and test.
`.env`
```
SS_DEPRECATION_ENABLED="0"
```
## Security Releases

View File

@ -39,6 +39,7 @@ As a core contributor it is necessary to have installed the following set of too
* A good `.env` setup in your localhost webroot.
Example `.env`:
```
# Environent
SS_TRUSTED_PROXY_IPS="*"
@ -168,9 +169,11 @@ doe not make any upstream changes (so it's safe to run without worrying about
any mistakes migrating their way into the public sphere).
Invoked by running `cow release` in the format as below:
```
cow release <version> -vvv
```
This command has the following parameters:
* `<version>` The version that is to be released. E.g. 3.2.4 or 4.0.0-alpha4
@ -238,6 +241,7 @@ building an archive, and uploading to
[www.silverstripe.org](http://www.silverstripe.org/software/download/) download page.
Invoked by running `cow release:publish` in the format as below:
```
cow release:publish <version> -vvv
```

View File

@ -90,31 +90,37 @@ sparingly.
</div>
Code for a Tip box:
```
<div class="hint" markdown='1'>
...
</div>
```
<div class="notice" markdown='1'>
"Notification box": A notification box is good for technical notifications relating to the main text. For example, notifying users about a deprecated feature.
</div>
Code for a Notification box:
```
<div class="notice" markdown='1'>
...
</div>
```
<div class="warning" markdown='1'>
"Warning box": A warning box is useful for highlighting a severe bug or a technical issue requiring a user's attention. For example, suppose a rare edge case sometimes leads to a variable being overwritten incorrectly. A warning box can be used to alert the user to this case so they can write their own code to handle it.
</div>
Code for a Warning box:
```
<div class="warning" markdown='1'>
...
</div>
```
See [markdown extra documentation](http://michelf.com/projects/php-markdown/extra/#html) for more restrictions
on placing HTML blocks inside Markdown.

View File

@ -71,8 +71,8 @@ under the "silverstripe" user, see
Translations need to be reviewed before being committed, which is a process that happens roughly once per month. We're
merging back translations into all supported release branches as well as the `master` branch. The following script
should be applied to the oldest release branch, and then merged forward into newer branches:
```bash
```bash
tx pull
# Manually review changes through git diff, then commit
@ -89,8 +89,8 @@ You can download your work right from Transifex in order to speed up the process
SilverStripe also supports translating strings in JavaScript (see [i18n](/developer_guides/i18n)), but there's a
conversion step involved in order to get those translations syncing with Transifex. Our translation files stored in
`mymodule/javascript/lang/*.js` call `ss.i18n.addDictionary()` to add files.
```js
```js
ss.i18n.addDictionary('de', {'MyNamespace.MyKey': 'My Translation'});
```
@ -101,8 +101,8 @@ But Transifex only accepts structured formats like JSON.
```
First of all, you need to create those source files in JSON, and store them in `mymodule/javascript/lang/src/*.js`. In your `.tx/config` you can configure this path as a separate master location.
```ruby
```ruby
[main]
host = https://www.transifex.com
@ -122,12 +122,14 @@ First of all, you need to create those source files in JSON, and store them in `
Then you can upload the source files via a normal `tx push`. Once translations come in, you need to convert the source
files back into the JS files SilverStripe can actually read. This requires an installation of our
[buildtools](https://github.com/silverstripe/silverstripe-buildtools).
```
tx pull
(cd .. && phing -Dmodule=mymodule translation-generate-javascript-for-module)
git add javascript/lang/*
git commit -m "Updated javascript translations"
```
# Related
* [i18n](/developer_guides/i18n/): Developer-level documentation of Silverstripe's i18n capabilities

View File

@ -62,7 +62,9 @@ Try to avoid using PHP's ability to mix HTML into the code.
{
return "<h2>Bad Example</h2>";
}
```
```ss
// Template code
$Title
```
@ -76,7 +78,9 @@ Better: Keep HTML in template files:
{
return "Better Example";
}
```
```ss
// Template code
<h2>$Title</h2>
```
@ -119,7 +123,7 @@ Example:
* @param string $colour The colour of cool things that you want
* @return DataList A list of everything cool
*/
public function myMethod($foo)
public function myMethod($colour)
{
// ...
}
@ -148,7 +152,7 @@ with the column or table name escaped with double quotes as below.
```php
MyClass::get()->where(["\"Score\" > ?" => 50]);
MyClass::get()->where(['"Score" > ?' => 50]);
```

View File

@ -6,7 +6,7 @@ ar:
SHOWONCLICKTITLE: 'تغيير كلمة المرور'
SilverStripe\Forms\DateField:
NOTSET: 'غير محدد'
TODAY: 'اليوم'
TODAY: اليوم
VALIDDATEFORMAT2: 'الرجاء إدخال صيغة تاريخ صحيحة ({صيغة})'
VALIDDATEMAXDATE: 'التسجيل الخاص بك قد يكون أقدم أو مطابق لأقصى تاريخ مسموح به ({تاريخ})'
VALIDDATEMINDATE: 'التسجيل الخاص بك قد يكون أحدث أو مطابق للحد الأدنى للتاريخ المسموح به ({تاريخ})'
@ -18,14 +18,14 @@ ar:
VALIDATIONPASSWORDSDONTMATCH: 'رقم المرور غير صحيح'
VALIDATIONPASSWORDSNOTEMPTY: 'أرقام المرور لا يمكن أن تكون فارغة'
VALIDATIONSTRONGPASSWORD: 'كلمات المرور يجب أن تحتوي على رقم و حرف على الأقل'
VALIDATOR: 'المحقق'
VALIDATOR: المحقق
VALIDCURRENCY: 'يرجى إدخال عملة صحيحة'
SilverStripe\Forms\FormField:
NONE: لايوجد
SilverStripe\Forms\GridField\GridField:
Add: 'إضافة {اسم}'
CSVEXPORT: 'تصدير إلى CSV'
Filter: 'مرشح'
Filter: مرشح
FilterBy: 'ترشيح باستخدام'
Find: ابحث
LinkExisting: 'الرابط موجود'
@ -54,8 +54,8 @@ ar:
SilverStripe\Forms\GridField\GridFieldEditButton_ss:
EDIT: تعديل
SilverStripe\Forms\MoneyField:
FIELDLABELAMOUNT: 'الكمية'
FIELDLABELCURRENCY: 'العملة'
FIELDLABELAMOUNT: الكمية
FIELDLABELCURRENCY: العملة
SilverStripe\Forms\NullableField:
IsNullLabel: باطل
SilverStripe\Forms\NumericField:
@ -120,7 +120,7 @@ ar:
ValidationIdentifierFailed: 'لا يمكن الكتابة فوق رقم العضوية الحالي {معرف} مع معرف مطابق ({اسم} = {قيمة}))'
WELCOMEBACK: 'مرحبا بك مرة أخرى، {الاسم الأول}'
YOUROLDPASSWORD: 'رقم المرور السابق'
belongs_many_many_Groups: 'المجموعات'
belongs_many_many_Groups: المجموعات
db_Locale: 'واجهة الموقع'
db_LockedOutUntil: 'مغلق حتى تاريخ'
db_Password: 'الرقم السري'

View File

@ -2,10 +2,10 @@ bg:
SilverStripe\Admin\LeftAndMain:
VersionUnknown: непозната
SilverStripe\AssetAdmin\Forms\UploadField:
Dimensions: 'Размери'
Dimensions: Размери
EDIT: Промени
EDITINFO: 'Редактирай този файл'
REMOVE: 'Премахни'
REMOVE: Премахни
SilverStripe\Control\ChangePasswordEmail_ss:
CHANGEPASSWORDFOREMAIL: 'Паролата за акаунт с адрес {email} беше променена. Ако не Вие сте променили паролата си, направете го от адреса долу'
CHANGEPASSWORDTEXT1: 'Променихте паролата си за'
@ -99,7 +99,7 @@ bg:
Deleted: 'Изтрити {type} {name}'
Save: Запис
SilverStripe\Forms\GridField\GridFieldEditButton_ss:
EDIT: 'Редактиране'
EDIT: Редактиране
SilverStripe\Forms\GridField\GridFieldPaginator:
OF: от
Page: Страница
@ -185,18 +185,18 @@ bg:
MEMBERS: Потребители
NEWGROUP: 'Нова група'
NoRoles: 'Няма намерени роли'
PERMISSIONS: 'Разрешения'
PERMISSIONS: Разрешения
PLURALNAME: Групи
PLURALS:
one: Група
other: '{count} групи'
Parent: 'Група източник'
ROLES: 'Роли'
ROLES: Роли
ROLESDESCRIPTION: 'Ролите са предварително зададени сетове от разрешения и могат да бъдат присвоявани на групи.<br />Ако е нужно, те се наследяват от родителските групи.'
RolesAddEditLink: 'Управление на ролите'
SINGULARNAME: Група
Sort: Сортиране
has_many_Permissions: 'Разрешения'
has_many_Permissions: Разрешения
many_many_Members: Потребители
SilverStripe\Security\LoginAttempt:
Email: 'Email адрес'
@ -212,7 +212,7 @@ bg:
BUTTONCHANGEPASSWORD: 'Променете паролата'
BUTTONLOGIN: Влез
BUTTONLOGINOTHER: 'Влез като някой друг'
BUTTONLOGOUT: 'Изход'
BUTTONLOGOUT: Изход
BUTTONLOSTPASSWORD: 'Загубих си паролата'
CONFIRMNEWPASSWORD: 'Потвърдете новата парола'
CONFIRMPASSWORD: 'Потвърдете паролата'
@ -274,11 +274,11 @@ bg:
FULLADMINRIGHTS: 'Пълни административни права'
FULLADMINRIGHTS_HELP: 'Включва и отменя другите присвоени разрешения.'
PERMISSIONS_CATEGORY: 'Роли и права за достъп'
PLURALNAME: 'Разрешения'
PLURALNAME: Разрешения
PLURALS:
one: 'Разрешение'
one: Разрешение
other: '{count} разрешения'
SINGULARNAME: 'Разрешение'
SINGULARNAME: Разрешение
UserPermissionsIntro: 'Присвояването на групи на този потребител ще коригира разрешенията, които има. Вижте раздела за групи за подробности относно разрешенията на отделните групи.'
SilverStripe\Security\PermissionCheckboxSetField:
AssignedTo: 'приписана на "{title}"'
@ -287,11 +287,11 @@ bg:
FromRoleOnGroup: 'унаследено от роля "{roletitle}" на група "{grouptitle}"'
SilverStripe\Security\PermissionRole:
OnlyAdminCanApply: 'Може да се приложи само от администратор'
PLURALNAME: 'Роли'
PLURALNAME: Роли
PLURALS:
one: 'Роля'
one: Роля
other: '{count} роли'
SINGULARNAME: 'Роля'
SINGULARNAME: Роля
Title: Заглавие
SilverStripe\Security\PermissionRoleCode:
PLURALNAME: 'Кодове за роли с разрешения'
@ -315,7 +315,7 @@ bg:
ENTERNEWPASSWORD: 'Моля, въведете нова парола.'
ERRORPASSWORDPERMISSION: 'Трябва да сте влезли, за да можете да промените вашата парола!'
LOGIN: 'Влезте в системата'
LOGOUT: 'Изход'
LOGOUT: Изход
LOSTPASSWORDHEADER: 'Забравена парола'
NOTEPAGESECURED: 'Тази страница е защитена. Въведете вашите данни по-долу, за да продължите.'
NOTERESETLINKINVALID: '<p>Връзката за нулиране на парола не е вярна или е просрочена.</p><p>Можете да заявите нова <a href="{link1}">тук</a> или да промените паролата си след като <a href="{link2}">влезете</a>.</p>'

View File

@ -39,7 +39,7 @@ bs:
FIRSTNAME: Ime
INTERFACELANG: 'Jezik sučelja'
NEWPASSWORD: 'Nova šifra'
PASSWORD: 'Šifra'
PASSWORD: Šifra
SUBJECTPASSWORDCHANGED: 'Vaša šifra je promijenjena'
SUBJECTPASSWORDRESET: 'Link za ponovno podešavanje Vaše šifre'
SURNAME: Prezime

View File

@ -100,6 +100,8 @@ en:
Save: Save
SilverStripe\Forms\GridField\GridFieldEditButton_ss:
EDIT: Edit
SilverStripe\Forms\GridField\GridFieldGroupDeleteAction:
UnlinkSelfFailure: 'Cannot remove yourself from this group, you will lose admin rights'
SilverStripe\Forms\GridField\GridFieldPaginator:
OF: of
Page: Page
@ -250,6 +252,7 @@ en:
SUBJECTPASSWORDCHANGED: 'Your password has been changed'
SUBJECTPASSWORDRESET: 'Your password reset link'
SURNAME: Surname
VALIDATIONADMINLOSTACCESS: 'Cannot remove all admin groups from your profile'
ValidationIdentifierFailed: 'Can''t overwrite existing member #{id} with identical identifier ({name} = {value}))'
WELCOMEBACK: 'Welcome Back, {firstname}'
YOUROLDPASSWORD: 'Your old password'

View File

@ -13,8 +13,8 @@ fa_IR:
SilverStripe\Forms\CurrencyField:
CURRENCYSYMBOL:
SilverStripe\Forms\DateField:
NOTSET: 'نامشخص'
TODAY: 'امروز'
NOTSET: نامشخص
TODAY: امروز
VALIDDATEFORMAT2: 'لطفاً یک قالب تاریخ معتبر وارد نمایید ({format})'
VALIDDATEMAXDATE: 'تاریخ شما باید قدیمی‌تر یا برابر با حداکثر تاریخ مجاز ({date}) باشد'
VALIDDATEMINDATE: 'تاریخ شما باید جدیدتر یا برابر با حداقل تاریخ مجاز ({date}) باشد'
@ -32,7 +32,7 @@ fa_IR:
VALIDATOR: اعتبارسنج
VALIDCURRENCY: 'لطفاً یک واحد پول معتبر وارد نمایید'
SilverStripe\Forms\FormField:
NONE: 'هیچ‌کدام'
NONE: هیچ‌کدام
SilverStripe\Forms\GridField\GridField:
Add: 'افزودن {name}'
CSVEXPORT: 'خروجی‌گیری به CSV'
@ -65,7 +65,7 @@ fa_IR:
SilverStripe\Forms\GridField\GridFieldEditButton_ss:
EDIT: ویرایش
SilverStripe\Forms\MoneyField:
FIELDLABELAMOUNT: 'مقدار'
FIELDLABELAMOUNT: مقدار
FIELDLABELCURRENCY: 'واحد پول'
SilverStripe\Forms\NullableField:
IsNullLabel: 'خالی است'
@ -89,12 +89,12 @@ fa_IR:
PASSWORDEXPIRED: '<p>گذرواژه شما منقضی شده‌است. <a target="_top" href="{link}">لطفاً یکی دیگر برگزینید.</a></p>'
SilverStripe\Security\CMSSecurity:
INVALIDUSER: '<p>کاربر نامعتبر. جهت ادامه <a target="_top" href="{link}">لطفاً اینجا مجدداً وارد شوید.</a></p>'
SUCCESS: 'موفقیت'
SUCCESS: موفقیت
SUCCESSCONTENT: '<p>ورود موفق. اگر به‌طور خودکار ارجاع نشدید <a target="_top" href="{link}">اینجا را کلیک کنید.</a></p>'
SilverStripe\Security\Group:
AddRole: 'اعمال یک وظیفه به گروه'
Code: 'كد گروه'
DefaultGroupTitleAdministrators: 'مدیران'
DefaultGroupTitleAdministrators: مدیران
DefaultGroupTitleContentAuthors: 'نویسندگان مطالب'
Description: توضحیات
GroupReminder: 'اگر یک گروه مادر را برگزینید،‌ این گروه تمامی وظایف آن را می‌پذیرد'
@ -128,7 +128,7 @@ fa_IR:
ENTEREMAIL: 'لطفاً یک نشانی ایمیل وارد نمایید تا لینک ازنوسازی گذرواژه را دریافت کنید.'
ERRORNEWPASSWORD: 'شما گذرواژه جدید خود را متفاوت وارد کرده‌اید، دوباره امتحان نمایید'
ERRORPASSWORDNOTMATCH: 'گذرواژه کنونی همانند نیست، لطفاً مجدداً تلاش نمایید'
FIRSTNAME: 'نام'
FIRSTNAME: نام
INTERFACELANG: 'زبان برنامه'
KEEPMESIGNEDIN: 'مرا واردشده نگه‌دار'
LOGGEDINAS: 'شما به {name} عنوان وارد شده‌اید.'
@ -155,8 +155,8 @@ fa_IR:
AdminGroup: 'مدیر کل'
CONTENT_CATEGORY: 'دسترسی محتوا'
FULLADMINRIGHTS: 'توانایی‌های کامل مدیریتی:'
PLURALNAME: 'مجوز‌ها'
SINGULARNAME: 'مجوز'
PLURALNAME: مجوز‌ها
SINGULARNAME: مجوز
SilverStripe\Security\PermissionCheckboxSetField:
AssignedTo: 'اختصاص داده‌شده به "{title}"'
FromGroup: 'از گروه "{title}" ارث برده است'

View File

@ -104,6 +104,15 @@ fi:
OF: /
Page: Sivu
View: Näytä
SilverStripe\Forms\GridField\GridFieldVersionedState:
ADDEDTODRAFTHELP: 'Kohdetta ei ole julkaistu vielä'
ADDEDTODRAFTSHORT: Luonnos
ARCHIVEDPAGEHELP: 'Kohde on poistettu luonnoksista ja julkaisusta'
ARCHIVEDPAGESHORT: Arkistoitu
MODIFIEDONDRAFTHELP: 'Kohteella on julkaisemattomia muutoksia'
MODIFIEDONDRAFTSHORT: Muokattu
ONLIVEONLYSHORT: 'Vain julkaistuna'
ONLIVEONLYSHORTHELP: 'Kohde on julkaistu, mutta poistettu luonnoksista'
SilverStripe\Forms\MoneyField:
FIELDLABELAMOUNT: Määrä
FIELDLABELCURRENCY: Valuutta

View File

@ -11,4 +11,4 @@ hi:
ERRORNOTADMIN: 'वह उपयोगकर्ता एक व्यवस्थापक नहीं है.'
SilverStripe\Security\Group:
RolesAddEditLink: 'भूमिकाओं का प्रबंधन करे '
has_many_Permissions: 'अनुमतियाँ'
has_many_Permissions: अनुमतियाँ

View File

@ -13,13 +13,13 @@ ja:
SilverStripe\Forms\DropdownField:
CHOOSE: (選択)
SilverStripe\Forms\EmailField:
VALIDATION: 'メールアドレスを入力してください'
VALIDATION: メールアドレスを入力してください
SilverStripe\Forms\Form:
VALIDATIONPASSWORDSDONTMATCH: パスワードが一致しません
VALIDATIONPASSWORDSNOTEMPTY: パスワードが空欄です
VALIDATIONSTRONGPASSWORD: 'パスワードは少なくとも1桁の数字と1つの英字を含んでいる必要があります'
VALIDATIONSTRONGPASSWORD: パスワードは少なくとも1桁の数字と1つの英字を含んでいる必要があります
VALIDATOR: 検証
VALIDCURRENCY: '有効な通貨を入力してください'
VALIDCURRENCY: 有効な通貨を入力してください
SilverStripe\Forms\FormField:
NONE: 何もありません
SilverStripe\Forms\GridField\GridField:
@ -30,7 +30,7 @@ ja:
Find: 探す
LinkExisting: 既存のリンク
NewRecord: '新しい{type}'
NoItemsFound: '項目が見つかりませんでした'
NoItemsFound: 項目が見つかりませんでした
PRINTEDAT: で印刷
PRINTEDBY: によって印刷
PlaceHolder: '{type}を探す'
@ -73,28 +73,28 @@ ja:
SilverStripe\ORM\Hierarchy\Hierarchy:
InfiniteLoopNotAllowed: '無限ループが"{型}"階層内で見つかりました。 これを解決するために親を変更してください。'
SilverStripe\Security\BasicAuth:
ENTERINFO: 'ユーザー名とパスワードを入力してください'
ERRORNOTADMIN: 'このユーザーは管理者(アドミニストレーター)ではありません'
ENTERINFO: ユーザー名とパスワードを入力してください
ERRORNOTADMIN: このユーザーは管理者(アドミニストレーター)ではありません
ERRORNOTREC: 'ユーザー名 / パスワードは認識されませんでした'
SilverStripe\Security\Group:
AddRole: 'このグループに役割を追加'
AddRole: このグループに役割を追加
Code: グループコード
DefaultGroupTitleAdministrators: '管理者'
DefaultGroupTitleContentAuthors: 'コンテンツの作成者'
DefaultGroupTitleAdministrators: 管理者
DefaultGroupTitleContentAuthors: コンテンツの作成者
Description: 説明文
GroupReminder: 'あなたが親グループを選択した場合、このグループはそのすべての役割を選択します'
GroupReminder: あなたが親グループを選択した場合、このグループはそのすべての役割を選択します
Locked: ロックしますか?
NoRoles: 役割が見つかりませんでした
Parent: '元グループ'
Parent: 元グループ
RolesAddEditLink: 役割の管理
Sort: '並び順'
Sort: 並び順
has_many_Permissions: 承認
many_many_Members: メンバー
SilverStripe\Security\LoginAttempt:
IP: IPアドレス
Status: ステータス
SilverStripe\Security\Member:
ADDGROUP: 'グループを追加'
ADDGROUP: グループを追加
BUTTONCHANGEPASSWORD: パスワードの変更
BUTTONLOGIN: ログイン
BUTTONLOGINOTHER: 他の誰かとしてログイン
@ -102,12 +102,12 @@ ja:
CONFIRMNEWPASSWORD: 新しいパスワードを確認します
CONFIRMPASSWORD: パスワード(確認のためもう一度)
EMAIL: メールアドレス
EMPTYNEWPASSWORD: 'パスワードが空です。もう一度入力して下さい。'
ENTEREMAIL: 'パスワードをリセットするためにメールアドレスを入力してください。'
EMPTYNEWPASSWORD: パスワードが空です。もう一度入力して下さい。
ENTEREMAIL: パスワードをリセットするためにメールアドレスを入力してください。
ERRORLOCKEDOUT2: '複数回ログインに失敗したため、あなたのアカウントは一時的に使用不可能になっています。 {count} 分後に再びログインしてください。'
ERRORNEWPASSWORD: '入力されたパスワードが一致しません。再度お試しください'
ERRORPASSWORDNOTMATCH: '登録されているパスワードと一致しません、もう一度入力し直してください'
ERRORWRONGCRED: 'メールアドレスまたはパスワードが正しくありません、もう一度入力し直してください'
ERRORNEWPASSWORD: 入力されたパスワードが一致しません。再度お試しください
ERRORPASSWORDNOTMATCH: 登録されているパスワードと一致しません、もう一度入力し直してください
ERRORWRONGCRED: メールアドレスまたはパスワードが正しくありません、もう一度入力し直してください
FIRSTNAME:
INTERFACELANG: 画面言語
LOGGEDINAS: '{name}としてログインしています。'
@ -126,32 +126,32 @@ ja:
db_PasswordExpiry: パスワードの有効期限
SilverStripe\Security\PasswordValidator:
LOWCHARSTRENGTH: '次の文字のいくつかを追加してパスワードを強化してください: {chars}'
PREVPASSWORD: 'このパスワードは過去に使用されています、新しいパスワードを選択してください'
PREVPASSWORD: このパスワードは過去に使用されています、新しいパスワードを選択してください
TOOSHORT: パスワードが短すぎます、%文字以上でなければなりません
SilverStripe\Security\Permission:
AdminGroup: '管理者'
AdminGroup: 管理者
CONTENT_CATEGORY: コンテンツに関する権限
FULLADMINRIGHTS: '完全な管理権'
FULLADMINRIGHTS: 完全な管理権
FULLADMINRIGHTS_HELP: 暗黙的に定義または他のすべての割り当てられた権限を無効にする。
UserPermissionsIntro: 'このユーザーにグループを割り当てると、彼らが持っている権限を調整します。個々のグループの権限の詳細については、グループセクションを参照してください。'
UserPermissionsIntro: このユーザーにグループを割り当てると、彼らが持っている権限を調整します。個々のグループの権限の詳細については、グループセクションを参照してください。
SilverStripe\Security\PermissionCheckboxSetField:
AssignedTo: '"{title}" に割り当てられた'
FromGroup: 'グループ"{title}"から継承'
FromRole: '役割"{title}"から継承'
FromRoleOnGroup: 'グループ "{roletitle}" のロール "{grouptitle}"から継承'
SilverStripe\Security\PermissionRole:
OnlyAdminCanApply: '管理者のみ適用可能'
OnlyAdminCanApply: 管理者のみ適用可能
Title: タイトル
SilverStripe\Security\Security:
ALREADYLOGGEDIN: 'あなたはこのページにアクセスできません。別のアカウントを持っていたら 再ログインを行ってください。'
BUTTONSEND: 'パスワードリセットのリンクを送信してください'
BUTTONSEND: パスワードリセットのリンクを送信してください
CHANGEPASSWORDBELOW: 以下のパスワードを変更できます
CHANGEPASSWORDHEADER: パスワードを変更しました
ENTERNEWPASSWORD: '新しいパスワードを入力してください'
ENTERNEWPASSWORD: 新しいパスワードを入力してください
ERRORPASSWORDPERMISSION: パスワードを変更する為に、ログインしなければなりません!
LOGIN: ログイン
NOTEPAGESECURED: 'このページはセキュリティで保護されております証明書キーを下記に入力してください。こちらからすぐに送信します'
NOTEPAGESECURED: このページはセキュリティで保護されております証明書キーを下記に入力してください。こちらからすぐに送信します
NOTERESETLINKINVALID: '<p>パスワードのリセットリンクは有効でないか期限切れです。</p><p> 新しいパスワードを要求することができます <a href="{link1}"> ここ </a> もしくはパスワードを変更することができます <a href="{link2}"> ログインした後 </a>.</p>'
NOTERESETPASSWORD: 'メールアドレスを入力してください、パスワードをリセットするURLを送信致します'
NOTERESETPASSWORD: メールアドレスを入力してください、パスワードをリセットするURLを送信致します
PASSWORDSENTHEADER: 'パスワードリセットリンクは ''{email}'' に送信されました'
PASSWORDSENTTEXT: 'ありがとうございました! リセットリンクは、''{email}'' に、このアカウントが存在することを前提として送信されました。'

View File

@ -7,8 +7,8 @@ km:
IsNullLabel: ទទេ
SilverStripe\Security\Group:
Code: លេខកូដក្រុម
Locked: 'មិនអាចប្រើ'
Parent: 'ចំណាត់ក្រុមដើម'
Locked: មិនអាចប្រើ
Parent: ចំណាត់ក្រុមដើម
has_many_Permissions: ការអនុញ្ញាតិ្ត
many_many_Members: សមាជិក
SilverStripe\Security\Member:
@ -16,8 +16,8 @@ km:
INTERFACELANG: ភាសាប្រើសំរាប់ទំព័រមុខ
SUBJECTPASSWORDCHANGED: ពាក្យសំងាត់របស់អ្នកបានផ្លាស់ប្តូរ
SUBJECTPASSWORDRESET: លីងសំរាប់ប្តូរពាក្យសំងាត់របស់អ្នក
belongs_many_many_Groups: 'ចំណាត់ក្រុម'
db_LockedOutUntil: 'ដោះចេញរហូតដល់'
db_PasswordExpiry: 'កាលបរិច្ឆេទផុតកំណត់ពាក្យសំងាត់'
belongs_many_many_Groups: ចំណាត់ក្រុម
db_LockedOutUntil: ដោះចេញរហូតដល់
db_PasswordExpiry: កាលបរិច្ឆេទផុតកំណត់ពាក្យសំងាត់
SilverStripe\Security\Security:
ALREADYLOGGEDIN: 'អ្នកមិនអាចមើលទំព័រនេះបានទេ។ សូមប្រើប្រាស់ព័ត៌មានសំរាប់ថ្មី មួយទៀតសំរាប់ចូលមើល។ សូមចូលតាម'

View File

@ -1,7 +1,7 @@
ru:
SilverStripe\AssetAdmin\Forms\UploadField:
Dimensions: 'Размеры'
EDIT: 'Редактировать'
Dimensions: Размеры
EDIT: Редактировать
EDITINFO: 'Редактировать этот файл'
REMOVE: Удалить
SilverStripe\Control\ChangePasswordEmail_ss:
@ -93,9 +93,9 @@ ru:
Delete: Удалить
DeletePermissionsFailure: 'Нет прав на удаление'
Deleted: 'Удалено {type} {name}'
Save: 'Сохранить'
Save: Сохранить
SilverStripe\Forms\GridField\GridFieldEditButton_ss:
EDIT: 'Редактировать'
EDIT: Редактировать
SilverStripe\Forms\GridField\GridFieldPaginator:
OF: из
Page: Страница
@ -203,7 +203,7 @@ ru:
many: '{count} групп'
other: '{count} групп'
Parent: 'Родительская группа'
ROLES: 'Роли'
ROLES: Роли
ROLESDESCRIPTION: 'Роли представляют собой сочетания различных прав доступа, которые могут быть присвоены группам.<br />При необходимости они наследуются от групп более высокого уровня.'
RolesAddEditLink: 'Добавить/редактировать роли'
SINGULARNAME: Группа
@ -294,7 +294,7 @@ ru:
PERMISSIONS_CATEGORY: 'Роли и права доступа'
PLURALNAME: 'Права доступа'
PLURALS:
one: 'Разрешение'
one: Разрешение
few: '{count} разрешения'
many: '{count} разрешений'
other: '{count} разрешений'
@ -307,13 +307,13 @@ ru:
FromRoleOnGroup: 'перенято из роли "{roletitle}" для группы "{grouptitle}"'
SilverStripe\Security\PermissionRole:
OnlyAdminCanApply: 'Может применяться только администратором'
PLURALNAME: 'Роли'
PLURALNAME: Роли
PLURALS:
one: 'Роль'
one: Роль
few: '{count} роли'
many: '{count} ролей'
other: '{count} ролей'
SINGULARNAME: 'Роль'
SINGULARNAME: Роль
Title: Название
SilverStripe\Security\PermissionRoleCode:
PLURALNAME: 'Коды ролей доступа'
@ -340,7 +340,7 @@ ru:
CONFIRMLOGOUT: 'Нажмите кнопку ниже чтобы подтвердить что вы хотите выйти из системы.'
ENTERNEWPASSWORD: 'Пожалуйста, введите новый пароль.'
ERRORPASSWORDPERMISSION: 'Вы должны войти в систему, чтобы изменить Ваш пароль!'
LOGIN: 'Вход'
LOGIN: Вход
LOGOUT: Выйти
LOSTPASSWORDHEADER: 'Восстановление пароля'
NOTEPAGESECURED: 'Эта страница защищена. Пожалуйста, введите свои учетные данные для входа.'

View File

@ -14,7 +14,7 @@ si:
Code: 'කාන්ඩ සංකේතය'
Locked: 'අගුලුලාද?'
Parent: 'මවු කාන්ඩය'
has_many_Permissions: 'අවසර'
has_many_Permissions: අවසර
many_many_Members: සාමාජිකයින්
SilverStripe\Security\Member:
BUTTONCHANGEPASSWORD: 'මුර පදය අලුත් කරන්න'

View File

@ -37,7 +37,7 @@ sr@latin:
PRINTEDBY: Odštampao
PlaceHolder: 'Pronađi {type}'
PlaceHolderWithLabels: 'Pronađi {type} po {name}'
Print: 'Štampaj'
Print: Štampaj
RelationSearch: 'Pretraživanje relacije'
ResetFilter: 'Vrati u pređašnje stanje'
SilverStripe\Forms\GridField\GridFieldDeleteAction:

View File

@ -37,7 +37,7 @@ sr_RS@latin:
PRINTEDBY: Odštampao
PlaceHolder: 'Pronađi {type}'
PlaceHolderWithLabels: 'Pronađi {type} po {name}'
Print: 'Štampaj'
Print: Štampaj
RelationSearch: 'Pretraživanje relacije'
ResetFilter: 'Vrati u pređašnje stanje'
SilverStripe\Forms\GridField\GridFieldDeleteAction:

View File

@ -78,7 +78,7 @@ th:
ENTEREMAIL: กรุณากรอกที่อยู่อีเมลเพื่อขอรับลิงก์สำหรับรีเซ็ตรหัสผ่านใหม่
ERRORPASSWORDNOTMATCH: 'รหัสผ่านไม่ตรงกัน กรุณาลองใหม่อีกครั้ง'
FIRSTNAME: ชื่อจริง
INTERFACELANG: 'ภาษาสำหรับหน้าจอติดต่อผู้ใช้'
INTERFACELANG: ภาษาสำหรับหน้าจอติดต่อผู้ใช้
NEWPASSWORD: รหัสผ่านใหม่
PASSWORD: รหัสผ่าน
SUBJECTPASSWORDCHANGED: รหัสผ่านได้รับการเปลี่ยนแปลงแล้ว
@ -86,7 +86,7 @@ th:
SURNAME: นามสกุล
YOUROLDPASSWORD: รหัสผ่านเก่าของคุณ
belongs_many_many_Groups: กลุ่ม
db_Locale: 'ภาษาสำหรับส่วนอินเทอร์เฟซ'
db_Locale: ภาษาสำหรับส่วนอินเทอร์เฟซ
db_LockedOutUntil: ออกจากระบบจนกว่า
db_Password: รหัสผ่าน
db_PasswordExpiry: วันที่รหัสผ่านหมดอายุ

View File

@ -6,7 +6,7 @@ zh:
ATLEAST: '密码长度必须至少 {min} 个字符。'
BETWEEN: '密码长度必须含 {min} 到 {max} 个字符。'
MAXIMUM: '密码长度必须至多 {max} 个字符。'
SHOWONCLICKTITLE: '更改密码'
SHOWONCLICKTITLE: 更改密码
SilverStripe\Forms\CurrencyField:
CURRENCYSYMBOL: 货币字符
SilverStripe\Forms\DateField:
@ -19,16 +19,16 @@ zh:
CHOOSE: (选择)
SOURCE_VALIDATION: '请选择列表内提供的选项。{value}不是一个有效的选项'
SilverStripe\Forms\EmailField:
VALIDATION: '请输入一个电子邮件地址'
VALIDATION: 请输入一个电子邮件地址
SilverStripe\Forms\Form:
CSRF_FAILED_MESSAGE: 似乎是一个技术问题。请点击返回按钮,刷新浏览器,然后再试一次。
VALIDATIONPASSWORDSDONTMATCH: '密码不匹配'
VALIDATIONPASSWORDSNOTEMPTY: '密码不得为空'
VALIDATIONSTRONGPASSWORD: '密码必须至少有一个数字和一个字母数字字符'
VALIDATIONPASSWORDSDONTMATCH: 密码不匹配
VALIDATIONPASSWORDSNOTEMPTY: 密码不得为空
VALIDATIONSTRONGPASSWORD: 密码必须至少有一个数字和一个字母数字字符
VALIDATOR: 验证器
VALIDCURRENCY: '请输入一个有效的货币'
VALIDCURRENCY: 请输入一个有效的货币
SilverStripe\Forms\FormField:
NONE: '无'
NONE:
SilverStripe\Forms\GridField\GridField:
Add: '添加 {name}'
CSVEXPORT: '导出到 CSV'
@ -43,19 +43,19 @@ zh:
PlaceHolder: '查找 {type}'
PlaceHolderWithLabels: '通过 {name} 查找 {type}'
Print: 打印
RelationSearch: '关系搜索'
RelationSearch: 关系搜索
ResetFilter: 重设
SilverStripe\Forms\GridField\GridFieldDeleteAction:
DELETE_DESCRIPTION: '删除'
Delete: '删除'
DeletePermissionsFailure: '没有删除权限'
DELETE_DESCRIPTION: 删除
Delete: 删除
DeletePermissionsFailure: 没有删除权限
EditPermissionsFailure: 没有解除记录链接的权限
UnlinkRelation: 解除链接
SilverStripe\Forms\GridField\GridFieldDetailForm:
CancelBtn: 取消
Create: 创建
Delete: '删除'
DeletePermissionsFailure: '没有删除权限'
Delete: 删除
DeletePermissionsFailure: 没有删除权限
Deleted: '已删除的 {type} {name}'
Save: 保存
SilverStripe\Forms\GridField\GridFieldEditButton_ss:
@ -82,7 +82,7 @@ zh:
SilverStripe\ORM\Hierarchy\Hierarchy:
InfiniteLoopNotAllowed: '"{type}" 层次结构中发现无限循环。请更改父类型来解决此问题'
SilverStripe\Security\BasicAuth:
ENTERINFO: '请输入一个用户名和密码。'
ENTERINFO: 请输入一个用户名和密码。
ERRORNOTADMIN: 那个用户不是一名管理员。
ERRORNOTREC: '那个用户名 / 密码无法被辨认'
SilverStripe\Security\CMSMemberLoginForm:
@ -92,12 +92,12 @@ zh:
SUCCESS: 成功
SUCCESSCONTENT: '<p>登录成功。如果您没有自动重定向<a target="_top" href="{link}">点击此处</a></p>'
SilverStripe\Security\Group:
AddRole: '在这个小组中添加一个角色'
Code: '小组代码'
AddRole: 在这个小组中添加一个角色
Code: 小组代码
DefaultGroupTitleAdministrators: 管理员
DefaultGroupTitleContentAuthors: '内容作者'
DefaultGroupTitleContentAuthors: 内容作者
Description: 描述
GroupReminder: '如果您选择了某父组,该组别将会承担起所有功能角色'
GroupReminder: 如果您选择了某父组,该组别将会承担起所有功能角色
HierarchyPermsError: '无法为父组 "{group}" 分配特权权限(要求具备 ADMIN 访问)'
Locked: 锁定?
NoRoles: 没有找到角色
@ -110,47 +110,47 @@ zh:
IP: 'IP 地址'
Status: 状态
SilverStripe\Security\Member:
ADDGROUP: '添加组别'
BUTTONCHANGEPASSWORD: '更改密码'
ADDGROUP: 添加组别
BUTTONCHANGEPASSWORD: 更改密码
BUTTONLOGIN: 登录
BUTTONLOGINOTHER: 以不同身份登录
BUTTONLOSTPASSWORD: '我丢失了密码'
CONFIRMNEWPASSWORD: '确认新密码'
CONFIRMPASSWORD: '确认密码'
BUTTONLOSTPASSWORD: 我丢失了密码
CONFIRMNEWPASSWORD: 确认新密码
CONFIRMPASSWORD: 确认密码
EMAIL: 电子邮件
EMPTYNEWPASSWORD: '新密码不能为空,请重试'
ENTEREMAIL: '请输入电子邮件地址以获取密码重置链接'
EMPTYNEWPASSWORD: 新密码不能为空,请重试
ENTEREMAIL: 请输入电子邮件地址以获取密码重置链接
ERRORLOCKEDOUT2: '由于登录失败次数过多,您的账户暂时被冻结。请在 {count} 分钟后重试。'
ERRORNEWPASSWORD: '您输入的新密码不同,请重试'
ERRORPASSWORDNOTMATCH: '您的现有密码不匹配,请重试'
ERRORNEWPASSWORD: 您输入的新密码不同,请重试
ERRORPASSWORDNOTMATCH: 您的现有密码不匹配,请重试
ERRORWRONGCRED: 所提供的资料似乎是不正确的。请再试一次。
FIRSTNAME: 名字
INTERFACELANG: 界面语言
LOGGEDINAS: '您以 {name} 身份登录。'
NEWPASSWORD: '新密码'
PASSWORD: '密码'
NEWPASSWORD: 新密码
PASSWORD: 密码
PASSWORDEXPIRED: '您的密码已过期。 请选择一个新的。'
SUBJECTPASSWORDCHANGED: '您的密码已更改'
SUBJECTPASSWORDRESET: '您的密码重设链接'
SUBJECTPASSWORDCHANGED: 您的密码已更改
SUBJECTPASSWORDRESET: 您的密码重设链接
SURNAME: 姓氏
ValidationIdentifierFailed: '不能用相同的标识符 ({name} = {value})) 重写现有成员 #{id}'
WELCOMEBACK: '欢迎回来, {firstname}'
YOUROLDPASSWORD: '您的旧密码'
YOUROLDPASSWORD: 您的旧密码
belongs_many_many_Groups: 群组
db_Locale: 界面区域设置
db_LockedOutUntil: 保持锁定直到
db_Password: '密码'
db_PasswordExpiry: '密码失效日期'
db_Password: 密码
db_PasswordExpiry: 密码失效日期
SilverStripe\Security\PasswordValidator:
LOWCHARSTRENGTH: '请添加下列部分字符以提升密码强度:{chars}'
PREVPASSWORD: '您已经使用过这个密码,请选用新的密码'
PREVPASSWORD: 您已经使用过这个密码,请选用新的密码
TOOSHORT: '密码长度过短,必须为 {minimum} 个字符或更长'
SilverStripe\Security\Permission:
AdminGroup: 管理员
CONTENT_CATEGORY: '内容权限'
FULLADMINRIGHTS: '完全管理权'
FULLADMINRIGHTS_HELP: '包含并支配其它所有已分配权限'
UserPermissionsIntro: '把群组分配给该用户会改变其权限。请查看群组部分以获取关于个体组别的权限详情。'
CONTENT_CATEGORY: 内容权限
FULLADMINRIGHTS: 完全管理权
FULLADMINRIGHTS_HELP: 包含并支配其它所有已分配权限
UserPermissionsIntro: 把群组分配给该用户会改变其权限。请查看群组部分以获取关于个体组别的权限详情。
SilverStripe\Security\PermissionCheckboxSetField:
AssignedTo: '已分配至 "{title}"'
FromGroup: '从小组 "{title}"继承'
@ -158,20 +158,20 @@ zh:
FromRoleOnGroup: '从 "{roletitle}" 小组的 "{grouptitle}" 角色继承'
SilverStripe\Security\PermissionRole:
OnlyAdminCanApply: 只有管理员可以应用
Title: '标题'
Title: 标题
SilverStripe\Security\PermissionRoleCode:
PermsError: '无法为代码 "{code}"分配特权权限(要求具备 ADMIN 访问)'
SilverStripe\Security\Security:
ALREADYLOGGEDIN: '您无法进入这个页面。如果您有另一个帐号可以进入这个页面,您可以在下面再次登录。'
BUTTONSEND: '将密码重设链接发送给我'
CHANGEPASSWORDBELOW: '您可以在下面更改您的密码。'
CHANGEPASSWORDHEADER: '更改您的密码'
ENTERNEWPASSWORD: '请输入一个新密码。'
ERRORPASSWORDPERMISSION: '您必须登录才能更改您的密码!'
ALREADYLOGGEDIN: 您无法进入这个页面。如果您有另一个帐号可以进入这个页面,您可以在下面再次登录。
BUTTONSEND: 将密码重设链接发送给我
CHANGEPASSWORDBELOW: 您可以在下面更改您的密码。
CHANGEPASSWORDHEADER: 更改您的密码
ENTERNEWPASSWORD: 请输入一个新密码。
ERRORPASSWORDPERMISSION: 您必须登录才能更改您的密码!
LOGIN: 登录
LOSTPASSWORDHEADER: '忘记密码'
NOTEPAGESECURED: '该页面受安全保护。请在下面输入您的证书,然后我们会立即将您引导至该页面。'
LOSTPASSWORDHEADER: 忘记密码
NOTEPAGESECURED: 该页面受安全保护。请在下面输入您的证书,然后我们会立即将您引导至该页面。
NOTERESETLINKINVALID: '<p>密码重设链接无效或已过期。</p><p>您可以在<a href="{link1}">这里</a> 要求一个新的或在<a href="{link2}">登录</a>后更改您的密码。</p>'
NOTERESETPASSWORD: '请输入您的电子邮件地址,然后我们会将一个链接发送给您,您可以用它来重设您的密码'
NOTERESETPASSWORD: 请输入您的电子邮件地址,然后我们会将一个链接发送给您,您可以用它来重设您的密码
PASSWORDSENTHEADER: '密码重设链接已发送至''{email}'''
PASSWORDSENTTEXT: '谢谢!复位链接已发送到 ''{email}'',假定此电子邮件地址存在一个帐户。'

View File

@ -1,48 +1,48 @@
zh_CN:
SilverStripe\Forms\ConfirmedPasswordField:
SHOWONCLICKTITLE: '更改密码'
SHOWONCLICKTITLE: 更改密码
SilverStripe\Forms\DropdownField:
CHOOSE: (选择)
SilverStripe\Forms\Form:
VALIDATIONPASSWORDSDONTMATCH: '(密码相互不匹配)'
VALIDATIONPASSWORDSNOTEMPTY: '密码不能空白'
VALIDATIONPASSWORDSDONTMATCH: (密码相互不匹配)
VALIDATIONPASSWORDSNOTEMPTY: 密码不能空白
SilverStripe\Security\BasicAuth:
ENTERINFO: '请输入用户名和密码'
ENTERINFO: 请输入用户名和密码
ERRORNOTADMIN: 此用户没有管理员权限。
ERRORNOTREC: '没有找到此用户名/密码'
ERRORNOTREC: 没有找到此用户名/密码
SilverStripe\Security\Group:
Code: '团队代码'
Code: 团队代码
Locked: 锁定?
Parent: 主团队
has_many_Permissions: 权限
many_many_Members: 成员
SilverStripe\Security\Member:
BUTTONCHANGEPASSWORD: '更改密码'
BUTTONCHANGEPASSWORD: 更改密码
BUTTONLOGIN: 登录
BUTTONLOGINOTHER: '使用其他帐户登录'
BUTTONLOSTPASSWORD: '忘记密码'
CONFIRMNEWPASSWORD: '确认新密码'
CONFIRMPASSWORD: '确认密码'
BUTTONLOGINOTHER: 使用其他帐户登录
BUTTONLOSTPASSWORD: 忘记密码
CONFIRMNEWPASSWORD: 确认新密码
CONFIRMPASSWORD: 确认密码
EMAIL: 电子邮件
ERRORNEWPASSWORD: '您输入了一个不同的新密码,请重新输入'
ERRORPASSWORDNOTMATCH: '您当前的密码不正确,请再次输入'
ERRORNEWPASSWORD: 您输入了一个不同的新密码,请重新输入
ERRORPASSWORDNOTMATCH: 您当前的密码不正确,请再次输入
FIRSTNAME:
INTERFACELANG: 界面语言
NEWPASSWORD: '新密码'
PASSWORD: '密码'
SUBJECTPASSWORDCHANGED: '您的密码已更改'
SUBJECTPASSWORDRESET: '重设您的密码链接'
NEWPASSWORD: 新密码
PASSWORD: 密码
SUBJECTPASSWORDCHANGED: 您的密码已更改
SUBJECTPASSWORDRESET: 重设您的密码链接
SURNAME:
YOUROLDPASSWORD: '您的旧密码'
YOUROLDPASSWORD: 您的旧密码
belongs_many_many_Groups: 团队
db_LockedOutUntil: 禁止直至
db_PasswordExpiry: '密码过期日期'
db_PasswordExpiry: 密码过期日期
SilverStripe\Security\Security:
ALREADYLOGGEDIN: '您无访问此页的权限。如果您拥有另一个可访问次页的帐户,请在下面登录。'
BUTTONSEND: '给我发送密码重设链接'
CHANGEPASSWORDBELOW: '您可在下面更改您的密码'
CHANGEPASSWORDHEADER: '更改您的密码'
ENTERNEWPASSWORD: '请输入新密码'
ERRORPASSWORDPERMISSION: '您必需登录以更改您的密码'
NOTEPAGESECURED: '此页是受安全保护的。输入您的登录信息,我们会将您送达。'
NOTERESETPASSWORD: '输入您的电邮地址,我们会给您发送一个您可重设密码的链接'
ALREADYLOGGEDIN: 您无访问此页的权限。如果您拥有另一个可访问次页的帐户,请在下面登录。
BUTTONSEND: 给我发送密码重设链接
CHANGEPASSWORDBELOW: 您可在下面更改您的密码
CHANGEPASSWORDHEADER: 更改您的密码
ENTERNEWPASSWORD: 请输入新密码
ERRORPASSWORDPERMISSION: 您必需登录以更改您的密码
NOTEPAGESECURED: 此页是受安全保护的。输入您的登录信息,我们会将您送达。
NOTERESETPASSWORD: 输入您的电邮地址,我们会给您发送一个您可重设密码的链接

View File

@ -2,21 +2,21 @@ zh_TW:
SilverStripe\Forms\DropdownField:
CHOOSE: (選擇)
SilverStripe\Forms\Form:
VALIDATIONPASSWORDSDONTMATCH: '密碼不相配'
VALIDATIONPASSWORDSDONTMATCH: 密碼不相配
VALIDATIONPASSWORDSNOTEMPTY: 密碼不能是空的
SilverStripe\Security\BasicAuth:
ENTERINFO: '請輸入帳號密碼。'
ERRORNOTADMIN: '那個使用者不是管理員。'
ENTERINFO: 請輸入帳號密碼。
ERRORNOTADMIN: 那個使用者不是管理員。
ERRORNOTREC: 那組帳號密碼不對。
SilverStripe\Security\Member:
BUTTONCHANGEPASSWORD: 更改密碼
BUTTONLOGIN: '登入'
BUTTONLOGINOTHER: '用別的帳戶登入'
BUTTONLOGIN: 登入
BUTTONLOGINOTHER: 用別的帳戶登入
BUTTONLOSTPASSWORD: 忘記密碼
CONFIRMNEWPASSWORD: 確認新密碼
CONFIRMPASSWORD: 確認密碼
EMAIL: 電子郵件
ERRORNEWPASSWORD: '新密碼不相配,請再試一次。'
ERRORNEWPASSWORD: 新密碼不相配,請再試一次。
ERRORPASSWORDNOTMATCH: 舊密碼不對,請再試一次。
FIRSTNAME:
INTERFACELANG: 介面語言
@ -27,11 +27,11 @@ zh_TW:
SURNAME:
YOUROLDPASSWORD: 舊密碼
SilverStripe\Security\Security:
ALREADYLOGGEDIN: '你不能瀏覽此頁。請用別的帳戶登入。'
ALREADYLOGGEDIN: 你不能瀏覽此頁。請用別的帳戶登入。
BUTTONSEND: 寄給我密碼重設網址。
CHANGEPASSWORDBELOW: 請在下面更改密碼。
CHANGEPASSWORDHEADER: 更改密碼
ENTERNEWPASSWORD: '請輸入新的密碼。'
ERRORPASSWORDPERMISSION: '你必須先登入才能改密碼!'
NOTEPAGESECURED: '那的網頁是被保護的。請先登入。'
NOTERESETPASSWORD: '請輸入您的電子郵件。我們將寄給你重設密媽的網址。'
ENTERNEWPASSWORD: 請輸入新的密碼。
ERRORPASSWORDPERMISSION: 你必須先登入才能改密碼!
NOTEPAGESECURED: 那的網頁是被保護的。請先登入。
NOTERESETPASSWORD: 請輸入您的電子郵件。我們將寄給你重設密媽的網址。

View File

@ -3,6 +3,7 @@
namespace SilverStripe\Control;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Control\Middleware\CanonicalURLMiddleware;
use SilverStripe\Control\Middleware\HTTPMiddlewareAware;
use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\Environment;
@ -595,55 +596,36 @@ class Director implements TemplateGlobalProvider
* Turns an absolute URL or folder into one that's relative to the root of the site. This is useful
* when turning a URL into a filesystem reference, or vice versa.
*
* @param string $url Accepts both a URL or a filesystem path.
* Note: You should check {@link Director::is_site_url()} if making an untrusted url relative prior
* to calling this function.
*
* @param string $url Accepts both a URL or a filesystem path.
* @return string
*/
public static function makeRelative($url)
{
// Allow for the accidental inclusion whitespace and // in the URL
$url = trim(preg_replace('#([^:])//#', '\\1/', $url));
$url = preg_replace('#([^:])//#', '\\1/', trim($url));
$base1 = self::absoluteBaseURL();
$baseDomain = substr($base1, strlen(self::protocol()));
// If using a real url, remove protocol / hostname / auth / port
if (preg_match('#^(?<protocol>https?:)?//(?<hostpart>[^/]*)(?<url>(/.*)?)$#i', $url, $matches)) {
$url = $matches['url'];
}
// Only bother comparing the URL to the absolute version if $url looks like a URL.
if (preg_match('/^https?[^:]*:\/\//', $url, $matches)) {
$urlProtocol = $matches[0];
$urlWithoutProtocol = substr($url, strlen($urlProtocol));
// If we are already looking at baseURL, return '' (substr will return false)
if ($url == $base1) {
// Empty case
if (trim($url, '\\/') === '') {
return '';
} elseif (substr($url, 0, strlen($base1)) == $base1) {
return substr($url, strlen($base1));
} elseif (substr($base1, -1) == "/" && $url == substr($base1, 0, -1)) {
// Convert http://www.mydomain.com/mysitedir to ''
return "";
}
if (substr($urlWithoutProtocol, 0, strlen($baseDomain)) == $baseDomain) {
return substr($urlWithoutProtocol, strlen($baseDomain));
// Remove base folder or url
foreach ([self::baseFolder(), self::baseURL()] as $base) {
// Ensure single / doesn't break comparison (unless it would make base empty)
$base = rtrim($base, '\\/') ?: $base;
if (stripos($url, $base) === 0) {
return ltrim(substr($url, strlen($base)), '\\/');
}
}
// test for base folder, e.g. /var/www
$base2 = self::baseFolder();
if (substr($url, 0, strlen($base2)) == $base2) {
return substr($url, strlen($base2));
}
// Test for relative base url, e.g. mywebsite/ if the full URL is http://localhost/mywebsite/
$base3 = self::baseURL();
if (substr($url, 0, strlen($base3)) == $base3) {
return substr($url, strlen($base3));
}
// Test for relative base url, e.g mywebsite/ if the full url is localhost/myswebsite
if (substr($url, 0, strlen($baseDomain)) == $baseDomain) {
return substr($url, strlen($baseDomain));
}
// Nothing matched, fall back to returning the original URL
return $url;
}
@ -855,62 +837,29 @@ class Director implements TemplateGlobalProvider
*
* @param array $patterns Array of regex patterns to match URLs that should be HTTPS.
* @param string $secureDomain Secure domain to redirect to. Defaults to the current domain.
* @return bool true if already on SSL, false if doesn't match patterns (or cannot redirect)
* @throws HTTPResponse_Exception Throws exception with redirect, if successful
* @param HTTPRequest|null $request Request object to check
*/
public static function forceSSL($patterns = null, $secureDomain = null)
public static function forceSSL($patterns = null, $secureDomain = null, HTTPRequest $request = null)
{
// Already on SSL
if (static::is_https()) {
return true;
}
// Can't redirect without a url
if (!isset($_SERVER['REQUEST_URI'])) {
return false;
}
$handler = CanonicalURLMiddleware::singleton()->setForceSSL(true);
if ($patterns) {
$matched = false;
$relativeURL = self::makeRelative(Director::absoluteURL($_SERVER['REQUEST_URI']));
// protect portions of the site based on the pattern
foreach ($patterns as $pattern) {
if (preg_match($pattern, $relativeURL)) {
$matched = true;
break;
$handler->setForceSSLPatterns($patterns);
}
if ($secureDomain) {
$handler->setForceSSLDomain($secureDomain);
}
if (!$matched) {
return false;
}
}
// if an domain is specified, redirect to that instead of the current domain
if (!$secureDomain) {
$secureDomain = static::host();
}
$url = 'https://' . $secureDomain . $_SERVER['REQUEST_URI'];
// Force redirect
self::force_redirect($url);
return true;
$handler->throwRedirectIfNeeded($request);
}
/**
* Force a redirect to a domain starting with "www."
*
* @param HTTPRequest $request
*/
public static function forceWWW()
public static function forceWWW(HTTPRequest $request = null)
{
if (!Director::isDev() && !Director::isTest() && strpos(static::host(), 'www') !== 0) {
$destURL = str_replace(
Director::protocol(),
Director::protocol() . 'www.',
Director::absoluteURL($_SERVER['REQUEST_URI'])
);
self::force_redirect($destURL);
}
$handler = CanonicalURLMiddleware::singleton()->setForceWWW(true);
$handler->throwRedirectIfNeeded($request);
}
/**
@ -947,7 +896,7 @@ class Director implements TemplateGlobalProvider
* Can also be checked with {@link Director::isDev()}, {@link Director::isTest()}, and
* {@link Director::isLive()}.
*
* @return bool
* @return string
*/
public static function get_environment_type()
{

View File

@ -0,0 +1,331 @@
<?php
namespace SilverStripe\Control\Middleware;
use SilverStripe\Control\Controller;
use SilverStripe\Control\Director;
use SilverStripe\Control\HTTP;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Control\HTTPResponse_Exception;
use SilverStripe\Core\CoreKernel;
use SilverStripe\Core\Injector\Injectable;
use SilverStripe\Core\Injector\Injector;
/**
* Allows events to be registered and passed through middleware.
* Useful for event registered prior to the beginning of a middleware chain.
*/
class CanonicalURLMiddleware implements HTTPMiddleware
{
use Injectable;
/**
* Set if we should redirect to WWW
*
* @var bool
*/
protected $forceWWW = false;
/**
* Set if we should force SSL
*
* @var bool
*/
protected $forceSSL = false;
/**
* Redirect type
*
* @var int
*/
protected $redirectType = 301;
/**
* Environment variables this middleware is enabled in, or a fixed boolean flag to
* apply to all environments
*
* @var array|bool
*/
protected $enabledEnvs = [
CoreKernel::LIVE
];
/**
* If forceSSL is enabled, this is the list of patterns that the url must match (at least one)
*
* @var array Array of regexps to match against relative url
*/
protected $forceSSLPatterns = [];
/**
* SSL Domain to use
*
* @var string
*/
protected $forceSSLDomain = null;
/**
* @return array
*/
public function getForceSSLPatterns()
{
return $this->forceSSLPatterns ?: [];
}
/**
* @param array $forceSSLPatterns
* @return $this
*/
public function setForceSSLPatterns($forceSSLPatterns)
{
$this->forceSSLPatterns = $forceSSLPatterns;
return $this;
}
/**
* @return string
*/
public function getForceSSLDomain()
{
return $this->forceSSLDomain;
}
/**
* @param string $forceSSLDomain
* @return $this
*/
public function setForceSSLDomain($forceSSLDomain)
{
$this->forceSSLDomain = $forceSSLDomain;
return $this;
}
/**
* @return bool
*/
public function getForceWWW()
{
return $this->forceWWW;
}
/**
* @param bool $forceWWW
* @return $this
*/
public function setForceWWW($forceWWW)
{
$this->forceWWW = $forceWWW;
return $this;
}
/**
* @return bool
*/
public function getForceSSL()
{
return $this->forceSSL;
}
/**
* @param bool $forceSSL
* @return $this
*/
public function setForceSSL($forceSSL)
{
$this->forceSSL = $forceSSL;
return $this;
}
/**
* Generate response for the given request
*
* @param HTTPRequest $request
* @param callable $delegate
* @return HTTPResponse
*/
public function process(HTTPRequest $request, callable $delegate)
{
// Handle any redirects
$redirect = $this->getRedirect($request);
if ($redirect) {
return $redirect;
}
return $delegate($request);
}
/**
* Given request object determine if we should redirect.
*
* @param HTTPRequest $request Pre-validated request object
* @return HTTPResponse|null If a redirect is needed return the response
*/
protected function getRedirect(HTTPRequest $request)
{
// Check global disable
if (!$this->isEnabled()) {
return null;
}
// Get properties of current request
$host = $request->getHost();
$scheme = $request->getScheme();
// Check https
if ($this->requiresSSL($request)) {
$scheme = 'https';
// Promote ssl domain if configured
$host = $this->getForceSSLDomain() ?: $host;
}
// Check www.
if ($this->getForceWWW() && strpos($host, 'www.') !== 0) {
$host = "www.{$host}";
}
// No-op if no changes
if ($request->getScheme() === $scheme && $request->getHost() === $host) {
return null;
}
// Rebuild url for request
$url = Controller::join_links("{$scheme}://{$host}", Director::baseURL(), $request->getURL(true));
// Force redirect
$response = new HTTPResponse();
$response->redirect($url, $this->getRedirectType());
HTTP::add_cache_headers($response);
return $response;
}
/**
* Handles redirection to canonical urls outside of the main middleware chain
* using HTTPResponseException.
* Will not do anything if a current HTTPRequest isn't available
*
* @param HTTPRequest|null $request Allow HTTPRequest to be used for the base comparison
* @throws HTTPResponse_Exception
*/
public function throwRedirectIfNeeded(HTTPRequest $request = null)
{
$request = $this->getOrValidateRequest($request);
if (!$request) {
return;
}
$response = $this->getRedirect($request);
if ($response) {
throw new HTTPResponse_Exception($response);
}
}
/**
* Return a valid request, if one is available, or null if none is available
*
* @param HTTPRequest $request
* @return mixed|null
*/
protected function getOrValidateRequest(HTTPRequest $request = null)
{
if ($request instanceof HTTPRequest) {
return $request;
}
if (Injector::inst()->has(HTTPRequest::class)) {
return Injector::inst()->get(HTTPRequest::class);
}
return null;
}
/**
* Check if a redirect for SSL is necessary
*
* @param HTTPRequest $request
* @return bool
*/
protected function requiresSSL(HTTPRequest $request)
{
// Check if force SSL is enabled
if (!$this->getForceSSL()) {
return false;
}
// Already on SSL
if ($request->getScheme() === 'https') {
return false;
}
// Veto if any existing patterns fail
$patterns = $this->getForceSSLPatterns();
if (!$patterns) {
return true;
}
// Filter redirect based on url
$relativeURL = $request->getURL(true);
foreach ($patterns as $pattern) {
if (preg_match($pattern, $relativeURL)) {
return true;
}
}
// No patterns match
return false;
}
/**
* @return int
*/
public function getRedirectType()
{
return $this->redirectType;
}
/**
* @param int $redirectType
* @return $this
*/
public function setRedirectType($redirectType)
{
$this->redirectType = $redirectType;
return $this;
}
/**
* Get enabled flag, or list of environments to enable in
*
* @return array|bool
*/
public function getEnabledEnvs()
{
return $this->enabledEnvs;
}
/**
* @param array|bool $enabledEnvs
* @return $this
*/
public function setEnabledEnvs($enabledEnvs)
{
$this->enabledEnvs = $enabledEnvs;
return $this;
}
/**
* Ensure this middleware is enabled
*/
protected function isEnabled()
{
// At least one redirect must be enabled
if (!$this->getForceWWW() && !$this->getForceSSL()) {
return false;
}
// Filter by env vars
$enabledEnvs = $this->getEnabledEnvs();
if (is_bool($enabledEnvs)) {
return $enabledEnvs;
}
return empty($enabledEnvs) || in_array(Director::get_environment_type(), $enabledEnvs);
}
}

View File

@ -2,7 +2,6 @@
namespace SilverStripe\Core\Startup;
use function GuzzleHttp\Psr7\parse_query;
use SilverStripe\Control\Controller;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse;
@ -138,7 +137,7 @@ class ParameterConfirmationToken
// Filter backURL if it contains the given request parameter
list(,$query) = explode('?', $backURL);
$queryArgs = parse_query($query);
parse_str($query, $queryArgs);
$name = $this->getName();
if (isset($queryArgs[$name])) {
return $queryArgs[$name];

View File

@ -3,6 +3,7 @@
namespace SilverStripe\Dev;
use SilverStripe\Control\Director;
use SilverStripe\Core\Environment;
use SilverStripe\Core\Manifest\ClassLoader;
use SilverStripe\Core\Manifest\Module;
use SilverStripe\Core\Manifest\ModuleLoader;
@ -148,7 +149,7 @@ class Deprecation
if (isset(self::$enabled)) {
return self::$enabled;
}
return getenv('SS_DEPRECATION_ENABLED') ?: true;
return Environment::getEnv('SS_DEPRECATION_ENABLED') ?: true;
}
/**

View File

@ -119,6 +119,7 @@ class GridFieldDeleteAction implements GridField_ColumnProvider, GridField_Actio
if (!$record->canEdit()) {
return null;
}
$title = _t(__CLASS__.'.UnlinkRelation', "Unlink");
$field = GridField_FormAction::create(
$gridField,
@ -128,7 +129,8 @@ class GridFieldDeleteAction implements GridField_ColumnProvider, GridField_Actio
array('RecordID' => $record->ID)
)
->addExtraClass('btn btn--no-text btn--icon-md font-icon-link-broken grid-field__icon-action gridfield-button-unlink')
->setAttribute('title', _t('SilverStripe\\Forms\\GridField\\GridFieldDeleteAction.UnlinkRelation', "Unlink"));
->setAttribute('title', $title)
->setAttribute('aria-label', $title);
} else {
if (!$record->canDelete()) {
return null;
@ -142,8 +144,8 @@ class GridFieldDeleteAction implements GridField_ColumnProvider, GridField_Actio
array('RecordID' => $record->ID)
)
->addExtraClass('gridfield-button-delete btn--icon-md font-icon-trash-bin btn--no-text grid-field__icon-action')
->setAttribute('title', _t('SilverStripe\\Forms\\GridField\\GridFieldDeleteAction.Delete', "Delete"))
->setDescription(_t('SilverStripe\\Forms\\GridField\\GridFieldDeleteAction.DELETE_DESCRIPTION', 'Delete'));
->setAttribute('title', _t(__CLASS__.'.Delete', "Delete"))
->setDescription(_t(__CLASS__.'.DELETE_DESCRIPTION', 'Delete'));
}
return $field->Field();
}
@ -153,8 +155,8 @@ class GridFieldDeleteAction implements GridField_ColumnProvider, GridField_Actio
*
* @param GridField $gridField
* @param string $actionName
* @param mixed $arguments
* @param array $data - form data
* @param array $arguments
* @param array $data Form data
* @throws ValidationException
*/
public function handleAction(GridField $gridField, $actionName, $arguments, $data)
@ -169,7 +171,7 @@ class GridFieldDeleteAction implements GridField_ColumnProvider, GridField_Actio
if ($actionName == 'deleterecord') {
if (!$item->canDelete()) {
throw new ValidationException(
_t('SilverStripe\\Forms\\GridField\\GridFieldDeleteAction.DeletePermissionsFailure', "No delete permissions")
_t(__CLASS__.'.DeletePermissionsFailure', "No delete permissions")
);
}
@ -177,7 +179,7 @@ class GridFieldDeleteAction implements GridField_ColumnProvider, GridField_Actio
} else {
if (!$item->canEdit()) {
throw new ValidationException(
_t('SilverStripe\\Forms\\GridField\\GridFieldDeleteAction.EditPermissionsFailure', "No permission to unlink record")
_t(__CLASS__.'.EditPermissionsFailure', "No permission to unlink record")
);
}

View File

@ -0,0 +1,90 @@
<?php
namespace SilverStripe\Forms\GridField;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\ValidationException;
use SilverStripe\Security\Member;
use SilverStripe\Security\Permission;
use SilverStripe\Security\Security;
/**
* Adds a delete action for the gridfield to remove a relationship from group.
* This is a special case where it captures whether the current user is the record being removed and
* prevents removal from happening.
*/
class GridFieldGroupDeleteAction extends GridFieldDeleteAction
{
/**
* @var int
*/
protected $groupID;
public function __construct($groupID)
{
$this->groupID = $groupID;
parent::__construct(true);
}
/**
*
* @param GridField $gridField
* @param DataObject $record
* @param string $columnName
* @return string the HTML for the column
*/
public function getColumnContent($gridField, $record, $columnName)
{
if ($this->canUnlink($record)) {
return parent::getColumnContent($gridField, $record, $columnName);
}
return null;
}
/**
* Handle the actions and apply any changes to the GridField
*
* @param GridField $gridField
* @param string $actionName
* @param array $arguments
* @param array $data Form data
* @throws ValidationException
*/
public function handleAction(GridField $gridField, $actionName, $arguments, $data)
{
$record = $gridField->getList()->find('ID', $arguments['RecordID']);
if (!$record || !$actionName == 'unlinkrelation' || $this->canUnlink($record)) {
parent::handleAction($gridField, $actionName, $arguments, $data);
return;
}
throw new ValidationException(
_t(__CLASS__ . '.UnlinkSelfFailure', 'Cannot remove yourself from this group, you will lose admin rights')
);
}
/**
* @param $record - the record of the User to unlink with
* @return bool
*/
protected function canUnlink($record)
{
$currentUser = Security::getCurrentUser();
if ($currentUser
&& $record instanceof Member
&& (int)$record->ID === (int)$currentUser->ID
&& Permission::checkMember($record, 'ADMIN')
) {
$adminGroups = array_intersect(
$record->Groups()->column(),
Permission::get_groups_by_permission('ADMIN')->column()
);
if (count($adminGroups) === 1 && array_search($this->groupID, $adminGroups) !== false) {
return false;
}
}
return true;
}
}

View File

@ -92,10 +92,12 @@ class TinyMCECombinedGenerator implements TinyMCEScriptGenerator, Flushable
foreach ($config->getPlugins() as $plugin => $path) {
// Add external plugin
if ($path) {
// Skip external urls
if (is_string($path) && !Director::is_site_url($path)) {
continue;
}
// Convert URLS to relative paths
if (is_string($path)
&& (Director::is_absolute_url($path) || strpos($path, '/') === 0)
) {
if (is_string($path)) {
$path = Director::makeRelative($path);
}
// Ensure file exists

View File

@ -225,12 +225,14 @@ class TinyMCEConfig extends HTMLEditorConfig
'priority' => 0, // used for Per-member config override
'browser_spellcheck' => true,
'body_class' => 'typography',
'elementpath' => false, // https://www.tinymce.com/docs/configure/editor-appearance/#elementpath
'statusbar' => true,
'elementpath' => true, // https://www.tinymce.com/docs/configure/editor-appearance/#elementpath
'relative_urls' => true,
'remove_script_host' => true,
'convert_urls' => false, // Prevent site-root images being rewritten to base relative
'menubar' => false,
'language' => 'en',
'branding' => false,
);
/**

View File

@ -2,10 +2,10 @@
namespace SilverStripe\Forms;
use SilverStripe\ORM\ArrayLib;
use SilverStripe\ORM\FieldType\DBField;
use SilverStripe\ORM\DataObjectInterface;
use SilverStripe\Core\Convert;
use SilverStripe\ORM\ArrayLib;
use SilverStripe\ORM\DataObjectInterface;
use SilverStripe\ORM\FieldType\DBField;
/**
* Read-only complement of {@link DropdownField}.
@ -36,24 +36,22 @@ class LookupField extends MultiSelectField
$values = $this->getValueArray();
// Get selected values
$mapped = array();
$mapped = [];
foreach ($values as $value) {
if (isset($source[$value])) {
$mapped[] = $source[$value];
$mapped[] = Convert::raw2xml($source[$value]);
}
}
// Don't check if string arguments are matching against the source,
// as they might be generated HTML diff views instead of the actual values
if ($this->value && is_string($this->value) && empty($mapped)) {
$mapped = array(trim($this->value));
$values = array();
$mapped[] = Convert::raw2xml(trim($this->value));
$values = [];
}
if ($mapped) {
$attrValue = implode(', ', array_values($mapped));
$attrValue = Convert::raw2xml($attrValue);
$inputValue = implode(', ', array_values($values));
} else {
$attrValue = '<i>('._t('SilverStripe\\Forms\\FormField.NONE', 'none').')</i>';

View File

@ -22,7 +22,9 @@ class TextField extends FormField
* @param string $name
* @param null|string $title
* @param string $value
* @param null|int $maxLength
* @param null|int $maxLength Max characters to allow for this field. If this value is stored
* against a DB field with a fixed size it's recommended to set an appropriate max length
* matching this size.
* @param null|Form $form
*/
public function __construct($name, $title = null, $value = '', $maxLength = null, $form = null)
@ -40,8 +42,7 @@ class TextField extends FormField
/**
* @param int $maxLength
*
* @return static
* @return $this
*/
public function setMaxLength($maxLength)
{

View File

@ -2,18 +2,16 @@
namespace SilverStripe\Forms;
use Exception;
use InvalidArgumentException;
use SilverStripe\Assets\Folder;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Core\Convert;
use SilverStripe\ORM\DataList;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\ORM\Hierarchy\Hierarchy;
use SilverStripe\ORM\Hierarchy\MarkedSet;
use SilverStripe\View\ViewableData;
use Exception;
use InvalidArgumentException;
/**
* Dropdown-like field that allows you to select an item from a hierarchical
@ -425,39 +423,6 @@ class TreeDropdownField extends FormField
return $this;
}
/**
* @param array $properties
* @return string
*/
public function Field($properties = array())
{
$record = $this->Value() ? $this->objectForKey($this->Value()) : null;
if ($record instanceof ViewableData) {
$title = $record->obj($this->getLabelField())->forTemplate();
} elseif ($record) {
$title = Convert::raw2xml($record->{$this->getLabelField()});
} else {
$title = $this->getEmptyString();
}
// TODO Implement for TreeMultiSelectField
$metadata = array(
'id' => $record ? $record->ID : null,
'ClassName' => $record ? $record->ClassName : $this->getSourceObject()
);
$properties = array_merge(
$properties,
array(
'Title' => $title,
'EmptyTitle' => $this->getEmptyString(),
'Metadata' => ($metadata) ? Convert::raw2json($metadata) : null,
)
);
return parent::Field($properties);
}
public function extraClass()
{
return implode(' ', array(parent::extraClass(), ($this->getShowSearch() ? "searchable" : null)));
@ -633,6 +598,9 @@ class TreeDropdownField extends FormField
}
/**
* HTML-encoded label for this node, including css classes and other markup.
*
* @deprecated 4.0...5.0 Use setTitleField()
* @param string $field
* @return $this
*/
@ -643,6 +611,9 @@ class TreeDropdownField extends FormField
}
/**
* HTML-encoded label for this node, including css classes and other markup.
*
* @deprecated 4.0...5.0 Use getTitleField()
* @return string
*/
public function getLabelField()
@ -651,7 +622,7 @@ class TreeDropdownField extends FormField
}
/**
* Field to use for item titles
* Field to use for plain text item titles.
*
* @return string
*/
@ -795,14 +766,16 @@ class TreeDropdownField extends FormField
$sourceObject = $this->getSourceObject();
$filters = array();
if (singleton($sourceObject)->hasDatabaseField($this->getLabelField())) {
$filters["{$this->getLabelField()}:PartialMatch"] = $this->search;
} else {
if (singleton($sourceObject)->hasDatabaseField('Title')) {
$filters["Title:PartialMatch"] = $this->search;
}
if (singleton($sourceObject)->hasDatabaseField('Name')) {
$filters["Name:PartialMatch"] = $this->search;
$sourceObjectInstance = DataObject::singleton($sourceObject);
$candidates = array_unique([
$this->getLabelField(),
$this->getTitleField(),
'Title',
'Name'
]);
foreach ($candidates as $candidate) {
if ($sourceObjectInstance->hasDatabaseField($candidate)) {
$filters["{$candidate}:PartialMatch"] = $this->search;
}
}
@ -810,7 +783,7 @@ class TreeDropdownField extends FormField
throw new InvalidArgumentException(sprintf(
'Cannot query by %s.%s, not a valid database column',
$sourceObject,
$this->getLabelField()
$this->getTitleField()
));
}
return DataObject::get($this->getSourceObject())->filterAny($filters);

View File

@ -8,18 +8,15 @@ class TreeDropdownField_Readonly extends TreeDropdownField
public function Field($properties = array())
{
$fieldName = $this->getLabelField();
$fieldName = $this->getTitleField();
if ($this->value) {
$keyObj = $this->objectForKey($this->value);
$obj = $keyObj ? $keyObj->$fieldName : '';
$title = $keyObj ? $keyObj->$fieldName : '';
} else {
$obj = null;
$title = null;
}
$source = array(
$this->value => $obj
);
$source = [ $this->value => $title ];
$field = new LookupField($this->name, $this->title, $source);
$field->setValue($this->value);
$field->setForm($this->form);

View File

@ -9,27 +9,28 @@ class TreeMultiselectField_Readonly extends TreeMultiselectField
public function Field($properties = array())
{
$titleArray = $itemIDs = array();
$titleList = $itemIDsList = "";
if ($items = $this->getItems()) {
// Build list of titles
$titleField = $this->getTitleField();
$items = $this->getItems();
$titleArray = [];
foreach ($items as $item) {
$titleArray[] = $item->Title;
$titleArray[] = $item->$titleField;
}
$titleList = implode(", ", $titleArray);
// Build list of values
$itemIDs = [];
foreach ($items as $item) {
$itemIDs[] = $item->ID;
}
if ($titleArray) {
$titleList = implode(", ", $titleArray);
}
if ($itemIDs) {
$itemIDsList = implode(",", $itemIDs);
}
}
// Readonly field for display
$field = new ReadonlyField($this->name . '_ReadonlyValue', $this->title);
$field->setValue($titleList);
$field->setForm($this->form);
// Store values to hidden field
$valueField = new HiddenField($this->name);
$valueField->setValue($itemIDsList);
$valueField->setForm($this->form);

View File

@ -548,9 +548,7 @@ abstract class DBField extends ViewableData implements DBIndexable
*/
public function scaffoldFormField($title = null, $params = null)
{
$field = new TextField($this->name, $title);
return $field;
return TextField::create($this->name, $title);
}
/**

View File

@ -124,12 +124,12 @@ class DBHTMLVarchar extends DBVarchar
public function scaffoldFormField($title = null, $params = null)
{
return new HTMLEditorField($this->name, $title, 1);
return HTMLEditorField::create($this->name, $title, 1);
}
public function scaffoldSearchField($title = null)
{
return new TextField($this->name, $title);
return TextField::create($this->name, $title);
}
public function getSchemaValue()

View File

@ -2,10 +2,11 @@
namespace SilverStripe\ORM\FieldType;
use SilverStripe\ORM\DB;
use SilverStripe\Core\Config\Config;
use SilverStripe\Forms\TextField;
use SilverStripe\Forms\NullableField;
use SilverStripe\Forms\TextField;
use SilverStripe\ORM\Connect\MySQLDatabase;
use SilverStripe\ORM\DB;
/**
* Class Varchar represents a variable-length string of up to 255 characters, designed to store raw text
@ -22,6 +23,11 @@ class DBVarchar extends DBString
"URL" => "Text",
);
/**
* Max size of this field
*
* @var int
*/
protected $size;
/**
@ -58,8 +64,8 @@ class DBVarchar extends DBString
*/
public function requireField()
{
$charset = Config::inst()->get('SilverStripe\ORM\Connect\MySQLDatabase', 'charset');
$collation = Config::inst()->get('SilverStripe\ORM\Connect\MySQLDatabase', 'collation');
$charset = Config::inst()->get(MySQLDatabase::class, 'charset');
$collation = Config::inst()->get(MySQLDatabase::class, 'collation');
$parts = array(
'datatype'=>'varchar',
@ -117,12 +123,14 @@ class DBVarchar extends DBString
public function scaffoldFormField($title = null, $params = null)
{
if (!$this->nullifyEmpty) {
// Set field with appropriate size
$field = TextField::create($this->name, $title);
$field->setMaxLength($this->getSize());
// Allow the user to select if it's null instead of automatically assuming empty string is
return new NullableField(new TextField($this->name, $title));
} else {
// Automatically determine null (empty string)
return parent::scaffoldFormField($title);
}
if (!$this->getNullifyEmpty()) {
return NullableField::create($field);
}
return $field;
}
}

View File

@ -11,8 +11,11 @@ use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
use SilverStripe\Forms\GridField\GridFieldButtonRow;
use SilverStripe\Forms\GridField\GridFieldConfig_RelationEditor;
use SilverStripe\Forms\GridField\GridFieldDeleteAction;
use SilverStripe\Forms\GridField\GridFieldDetailForm;
use SilverStripe\Forms\GridField\GridFieldExportButton;
use SilverStripe\Forms\GridField\GridFieldGroupDeleteAction;
use SilverStripe\Forms\GridField\GridFieldPageCount;
use SilverStripe\Forms\GridField\GridFieldPrintButton;
use SilverStripe\Forms\HiddenField;
use SilverStripe\Forms\HTMLEditor\HTMLEditorConfig;
@ -150,6 +153,9 @@ class Group extends DataObject
$config->addComponent(new GridFieldButtonRow('after'));
$config->addComponents(new GridFieldExportButton('buttons-after-left'));
$config->addComponents(new GridFieldPrintButton('buttons-after-left'));
$config->removeComponentsByType(GridFieldDeleteAction::class);
$config->addComponent(new GridFieldGroupDeleteAction($this->ID), GridFieldPageCount::class);
/** @var GridFieldAddExistingAutocompleter $autocompleter */
$autocompleter = $config->getComponentByType(GridFieldAddExistingAutocompleter::class);
/** @skipUpgrade */

View File

@ -10,6 +10,7 @@ use SilverStripe\Control\Controller;
use SilverStripe\Control\Director;
use SilverStripe\Control\Email\Email;
use SilverStripe\Control\Email\Mailer;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Convert;
use SilverStripe\Core\Injector\Injector;
@ -539,14 +540,30 @@ class Member extends DataObject
{
Deprecation::notice(
'5.0.0',
'This method is deprecated and now does not persist. Please use Security::setCurrentUser(null) or an IdenityStore'
'This method is deprecated and now does not persist. Please use Security::setCurrentUser(null) or an IdentityStore'
);
$this->extend('beforeMemberLoggedOut');
Injector::inst()->get(IdentityStore::class)->logOut(Controller::curr()->getRequest());
// Audit logging hook
$this->extend('afterMemberLoggedOut');
}
/**
* Audit logging hook, called before a member is logged out
*
* @param HTTPRequest|null $request
*/
public function beforeMemberLoggedOut(HTTPRequest $request = null)
{
$this->extend('beforeMemberLoggedOut', $request);
}
/**
* Audit logging hook, called after a member is logged out
*
* @param HTTPRequest|null $request
*/
public function afterMemberLoggedOut(HTTPRequest $request = null)
{
$this->extend('afterMemberLoggedOut', $request);
}
/**

View File

@ -101,8 +101,8 @@ class Member_Validator extends RequiredFields
// Only validate identifier field if it's actually set. This could be the case if
// somebody removes `Email` from the list of required fields.
if (isset($data[$identifierField])) {
$id = isset($data['ID']) ? (int)$data['ID'] : 0;
if (isset($data[$identifierField])) {
if (!$id && ($ctrl = $this->form->getController())) {
// get the record when within GridField (Member editing page in CMS)
if ($ctrl instanceof GridFieldDetailForm_ItemRequest && $record = $ctrl->getRecord()) {
@ -137,6 +137,38 @@ class Member_Validator extends RequiredFields
}
}
$currentUser = Security::getCurrentUser();
if ($currentUser
&& $id
&& $id === (int)$currentUser->ID
&& Permission::checkMember($currentUser, 'ADMIN')
) {
$stillAdmin = true;
if (!isset($data['DirectGroups'])) {
$stillAdmin = false;
} else {
$adminGroups = array_intersect(
$data['DirectGroups'],
Permission::get_groups_by_permission('ADMIN')->column()
);
if (count($adminGroups) === 0) {
$stillAdmin = false;
}
}
if (!$stillAdmin) {
$this->validationError(
'DirectGroups',
_t(
'SilverStripe\\Security\\Member.VALIDATIONADMINLOSTACCESS',
'Cannot remove all admin groups from your profile'
),
'required'
);
}
}
// Execute the validators on the extensions
$results = $this->extend('updatePHP', $data, $this->form);

View File

@ -9,6 +9,7 @@ use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DataObjectInterface;
use SilverStripe\ORM\SS_List;
use Traversable;
/**
* Shows a categorized list of available permissions (through {@link Permission::get_codes()}).
@ -298,7 +299,9 @@ class PermissionCheckboxSetField extends FormField
// Remove all "privileged" permissions if the currently logged-in user is not an admin
$privilegedPermissions = Permission::config()->privileged_permissions;
if (!Permission::check('ADMIN')) {
if ((is_array($this->value) || $this->value instanceof Traversable)
&& !Permission::check('ADMIN')
) {
foreach ($this->value as $id => $bool) {
if (in_array($id, $privilegedPermissions)) {
unset($this->value[$id]);
@ -321,7 +324,7 @@ class PermissionCheckboxSetField extends FormField
$record->write(); // We need a record ID to write permissions
}
if ($this->value) {
if (is_array($this->value) || $this->value instanceof Traversable) {
foreach ($this->value as $id => $bool) {
if ($bool) {
$perm = new $managedClass();

View File

@ -74,10 +74,19 @@ class RequestAuthenticationHandler implements AuthenticationHandler
*/
public function logOut(HTTPRequest $request = null)
{
$member = Security::getCurrentUser();
if ($member) {
$member->beforeMemberLoggedOut($request);
}
foreach ($this->getHandlers() as $handler) {
$handler->logOut($request);
}
Security::setCurrentUser(null);
if ($member) {
$member->afterMemberLoggedOut($request);
}
}
}

View File

@ -14,6 +14,8 @@ use DOMDocument;
*
* It's designed to allow dependancy injection to replace the standard HTML4 version with one that
* handles XHTML or HTML5 instead
*
* @mixin DOMDocument
*/
abstract class HTMLValue extends ViewableData
{

View File

@ -1 +1 @@
<a class="action action-detail view-link" href="$Link">View</a>
<a class="grid-field__icon-action font-icon-right-open btn--icon-large action action-detail view-link" href="$Link"><span class="sr-only">View</span></a>

View File

@ -5,18 +5,18 @@ Feature: Lost Password
Using my email
Background:
Given a "member" "Admin" with "Email"="admin@test.com"
Given a "member" "Admin" with "Email"="admin@example.org"
Scenario: I can request a password reset by email
Given I go to "Security/login"
When I follow "I've lost my password"
And I fill in "admin@test.com" for "Email"
And I fill in "admin@example.org" for "Email"
And I press the "Send me the password reset link" button
Then I should see "A reset link has been sent to 'admin@test.com'"
And there should be an email to "admin@test.com" titled "Your password reset link"
When I click on the "password reset link" link in the email to "admin@test.com"
Then I should see "A reset link has been sent to 'admin@example.org'"
And there should be an email to "admin@example.org" titled "Your password reset link"
When I click on the "password reset link" link in the email to "admin@example.org"
Then I should see "Please enter a new password"
When I fill in "newpassword" for "New Password"
And I fill in "newpassword" for "Confirm New Password"
And I press the "Change Password" button
Then the password for "admin@test.com" should be "newpassword"
Then the password for "admin@example.org" should be "newpassword"

View File

@ -5,23 +5,41 @@ Feature: Manage users
So that I can control access to the CMS
Background:
Given a "member" "ADMIN" belonging to "ADMIN Group" with "Email"="admin@test.com"
And a "member" "Staff" belonging to "Staff Group" with "Email"="staffmember@test.com"
Given a "member" "ADMIN" belonging to "ADMIN group" with "Email"="admin@example.org"
And the "member" "ADMIN" belonging to "ADMIN group2"
And a "member" "Staff" belonging to "Staff group" with "Email"="staffmember@example.org"
And the "group" "ADMIN group" has permissions "Full administrative rights"
And the "group" "ADMIN group2" has permissions "Full administrative rights"
And I am logged in with "ADMIN" permissions
And I go to "/admin/security"
Scenario: I cannot remove my admin access, but can remove myself from an admin group
When I click the "Groups" CMS tab
And I click "ADMIN group" in the "#Root_Groups" element
And I should see the "Unlink" button in the "Members" gridfield for the "ADMIN" row
Then I click "Groups" in the ".breadcrumbs-wrapper" element
And I click the "Groups" CMS tab
And I click "ADMIN group2" in the "#Root_Groups" element
And I should see the "Unlink" button in the "Members" gridfield for the "ADMIN" row
Then I click the "Unlink" button in the "Members" gridfield for the "ADMIN" row
And I should not see the "Unlink" button in the "Members" gridfield for the "ADMIN" row
Then I click "Groups" in the ".breadcrumbs-wrapper" element
And I click the "Groups" CMS tab
And I click "ADMIN group" in the "#Root_Groups" element
And I should not see the "Unlink" button in the "Members" gridfield for the "ADMIN" row
Scenario: I can list all users regardless of group
When I click the "Users" CMS tab
Then I should see "admin@test.com" in the "#Root_Users" element
And I should see "staffmember@test.com" in the "#Root_Users" element
Then I should see "admin@example.org" in the "#Root_Users" element
And I should see "staffmember@example.org" in the "#Root_Users" element
Scenario: I can list all users in a specific group
When I click the "Groups" CMS tab
# TODO Please check how performant this is
And I click "ADMIN group" in the "#Root_Groups" element
Then I should see "admin@test.com" in the "#Root_Members" element
And I should not see "staffmember@test.com" in the "#Root_Members" element
Then I should see "admin@example.org" in the "#Root_Members" element
And I should not see "staffmember@example.org" in the "#Root_Members" element
Scenario: I can add a user to the system
When I click the "Users" CMS tab
@ -29,16 +47,16 @@ Feature: Manage users
And I fill in the following:
| First Name | John |
| Surname | Doe |
| Email | john.doe@test.com |
| Email | john.doe@example.org |
And I press the "Create" button
Then I should see a "Saved member" message
When I go to "admin/security/"
Then I should see "john.doe@test.com" in the "#Root_Users" element
Then I should see "john.doe@example.org" in the "#Root_Users" element
Scenario: I can edit an existing user and add him to an existing group
When I click the "Users" CMS tab
And I click "staffmember@test.com" in the "#Root_Users" element
And I click "staffmember@example.org" in the "#Root_Users" element
And I select "ADMIN group" from "Groups"
And I press the "Save" button
Then I should see a "Saved Member" message
@ -46,11 +64,11 @@ Feature: Manage users
When I go to "admin/security"
And I click the "Groups" CMS tab
And I click "ADMIN group" in the "#Root_Groups" element
Then I should see "staffmember@test.com"
Then I should see "staffmember@example.org"
Scenario: I can delete an existing user
When I click the "Users" CMS tab
And I click "staffmember@test.com" in the "#Root_Users" element
And I click "staffmember@example.org" in the "#Root_Users" element
And I press the "Delete" button, confirming the dialog
Then I should see "admin@test.com"
And I should not see "staffmember@test.com"
Then I should see "admin@example.org"
And I should not see "staffmember@example.org"

View File

@ -5,15 +5,29 @@ Feature: Manage my own settings
In order to streamline my CMS experience
Background:
Given a "member" "Joe" belonging to "Admin Group" with "Email"="joe@test.com" and "Password"="secret"
And the "group" "Admin Group" has permissions "Full administrative rights"
And I log in with "joe@test.com" and "secret"
Given a "member" "Joe" belonging to "Admin group" with "Email"="joe@example.org" and "Password"="secret"
And the "group" "Admin group" has permissions "Full administrative rights"
And the "member" "Joe" belonging to "Admin group2"
And the "group" "Admin group2" has permissions "Full administrative rights"
And I log in with "joe@example.org" and "secret"
And I go to "admin/myprofile"
Scenario: I cannot remove all my admin groups
When I click the "Admin group" option in the "DirectGroups" listbox
And I click the "Admin group2" option in the "DirectGroups" listbox
And I press the "Save" button
Then I should see "Cannot remove all admin groups from your profile" in the "#Form_EditForm" element
Scenario: I can remove one of my admin groups
When I click the "Admin group" option in the "DirectGroups" listbox
And I press the "Save" button
Then I should see a "Saved" notice
And I should not see "Cannot remove all admin groups from your profile" in the "#Form_EditForm" element
Scenario: I can edit my personal details
Given I fill in "First Name" with "Jack"
And I fill in "Surname" with "Johnson"
And I fill in "Email" with "jack@test.com"
And I fill in "Email" with "jack@example.org"
When I press the "Save" button
Given I go to "admin/myprofile"
Then I should not see "Joe"
@ -35,7 +49,7 @@ Feature: Manage my own settings
And I fill in "Confirm Password" with "newsecret"
And I press the "Save" button
And I am not logged in
When I log in with "joe@test.com" and "newsecret"
When I log in with "joe@example.org" and "newsecret"
And I go to "admin/myprofile"
Then I should see the CMS

View File

@ -6,7 +6,7 @@ Feature: Manage Security Permissions for Groups
Background:
Given a "group" "test group"
And a "member" "ADMIN" belonging to "ADMIN Group" with "Email"="admin@test.com"
And a "member" "ADMIN" belonging to "ADMIN group" with "Email"="admin@example.org"
And the "group" "ADMIN group" has permissions "Full administrative rights"
And I am logged in with "ADMIN" permissions
And I go to "/admin/security"

View File

@ -352,4 +352,87 @@ JS;
// Destroy cookie to detach session
$this->getMainContext()->getSession()->setCookie('PHPSESSID', null);
}
/**
* @When /^I should see the "([^"]*)" button in the "([^"]*)" gridfield for the "([^"]*)" row$/
* @param string $buttonLabel
* @param string $gridFieldName
* @param string $rowName
*/
public function assertIShouldSeeTheGridFieldButtonForRow($buttonLabel, $gridFieldName, $rowName)
{
$button = $this->getGridFieldButton($gridFieldName, $rowName, $buttonLabel);
assertNotNull($button, sprintf('Button "%s" not found', $buttonLabel));
}
/**
* @When /^I should not see the "([^"]*)" button in the "([^"]*)" gridfield for the "([^"]*)" row$/
* @param string $buttonLabel
* @param string $gridFieldName
* @param string $rowName
*/
public function assertIShouldNotSeeTheGridFieldButtonForRow($buttonLabel, $gridFieldName, $rowName)
{
$button = $this->getGridFieldButton($gridFieldName, $rowName, $buttonLabel);
assertNull($button, sprintf('Button "%s" found', $buttonLabel));
}
/**
* @When /^I click the "([^"]*)" button in the "([^"]*)" gridfield for the "([^"]*)" row$/
* @param string $buttonLabel
* @param string $gridFieldName
* @param string $rowName
*/
public function stepIClickTheGridFieldButtonForRow($buttonLabel, $gridFieldName, $rowName)
{
$button = $this->getGridFieldButton($gridFieldName, $rowName, $buttonLabel);
assertNotNull($button, sprintf('Button "%s" not found', $buttonLabel));
$button->click();
}
/**
* Finds a button in the gridfield row
*
* @param $gridFieldName
* @param $rowName
* @param $buttonLabel
* @return $button
*/
protected function getGridFieldButton($gridFieldName, $rowName, $buttonLabel)
{
$page = $this->getSession()->getPage();
$gridField = $page->find('xpath', sprintf('//*[@data-name="%s"]', $gridFieldName));
assertNotNull($gridField, sprintf('Gridfield "%s" not found', $gridFieldName));
$name = $gridField->find('xpath', sprintf('//*[count(*)=0 and contains(.,"%s")]', $rowName));
if (!$name) {
return null;
}
$button = $name->getParent()->find('xpath', sprintf('//*[@aria-label="%s"]', $buttonLabel));
return $button;
}
/**
* @When /^I click the "([^"]*)" option in the "([^"]*)" listbox$/
* @param $optionLabel
* @param $fieldName
*/
public function stepIClickTheListBoxOption($optionLabel, $fieldName)
{
$page = $this->getSession()->getPage();
$listBox = $page->find('xpath', sprintf('//*[@name="%s[]"]', $fieldName));
assertNotNull($listBox, sprintf('The listbox %s is not found', $fieldName));
$option = $listBox->getParent()
->find('css', '.chosen-choices')
->find('xpath', sprintf('//*[count(*)=0 and contains(.,"%s")]', $optionLabel));
assertNotNull($option, sprintf('Option %s is not found', $optionLabel));
$button = $option->getParent()->find('css', 'a');
$button->click();
}
}

View File

@ -7,7 +7,7 @@ use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPRequestBuilder;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Control\HTTPResponse_Exception;
use SilverStripe\Control\Middleware\HTTPMiddleware;
use SilverStripe\Control\Middleware\CanonicalURLMiddleware;
use SilverStripe\Control\Middleware\RequestHandlerMiddlewareAdapter;
use SilverStripe\Control\Middleware\TrustedProxyMiddleware;
use SilverStripe\Control\RequestProcessor;
@ -30,6 +30,9 @@ class DirectorTest extends SapphireTest
{
parent::setUp();
Director::config()->set('alternate_base_url', 'http://www.mysite.com/');
// Ensure redirects enabled on all environments
CanonicalURLMiddleware::singleton()->setEnabledEnvs(true);
$this->expectedRedirect = null;
}
@ -239,26 +242,132 @@ class DirectorTest extends SapphireTest
$this->assertTrue(Director::is_relative_url('/relative/#=http://test.com'));
}
public function testMakeRelative()
/**
* @return array
*/
public function providerMakeRelative()
{
$siteUrl = Director::absoluteBaseURL();
$siteUrlNoProtocol = preg_replace('/https?:\/\//', '', $siteUrl);
return [
// Resilience to slash position
[
'http://www.mysite.com/base/folder',
'http://www.mysite.com/base/folder',
''
],
[
'http://www.mysite.com/base/folder',
'http://www.mysite.com/base/folder/',
''
],
[
'http://www.mysite.com/base/folder/',
'http://www.mysite.com/base/folder',
''
],
[
'http://www.mysite.com/',
'http://www.mysite.com/',
''
],
[
'http://www.mysite.com/',
'http://www.mysite.com',
''
],
[
'http://www.mysite.com',
'http://www.mysite.com/',
''
],
[
'http://www.mysite.com/base/folder',
'http://www.mysite.com/base/folder/page',
'page'
],
[
'http://www.mysite.com/',
'http://www.mysite.com/page/',
'page/'
],
// Parsing protocol safely
[
'http://www.mysite.com/base/folder',
'https://www.mysite.com/base/folder',
''
],
[
'https://www.mysite.com/base/folder',
'http://www.mysite.com/base/folder/testpage',
'testpage'
],
[
'http://www.mysite.com/base/folder',
'//www.mysite.com/base/folder/testpage',
'testpage'
],
// Dirty input
[
'http://www.mysite.com/base/folder',
' https://www.mysite.com/base/folder/testpage ',
'testpage'
],
[
'http://www.mysite.com/base/folder',
'//www.mysite.com/base//folder/testpage//subpage',
'testpage/subpage'
],
// Non-http protocol isn't modified
[
'http://www.mysite.com/base/folder',
'ftp://test.com',
'ftp://test.com'
],
// Alternate hostnames are redirected
[
'https://www.mysite.com/base/folder',
'http://mysite.com/base/folder/testpage',
'testpage'
],
[
'http://www.otherdomain.com/base/folder',
'//www.mysite.com/base/folder/testpage',
'testpage'
],
// Base folder is found
[
'http://www.mysite.com/base/folder',
BASE_PATH . '/some/file.txt',
'some/file.txt',
],
// querystring is protected
[
'http://www.mysite.com/base/folder',
'//www.mysite.com/base//folder/testpage//subpage?args=hello',
'testpage/subpage?args=hello'
],
[
'http://www.mysite.com/base/folder',
'//www.mysite.com/base//folder/?args=hello',
'?args=hello'
],
];
}
$this->assertEquals(Director::makeRelative("$siteUrl"), '');
$this->assertEquals(Director::makeRelative("https://$siteUrlNoProtocol"), '');
$this->assertEquals(Director::makeRelative("http://$siteUrlNoProtocol"), '');
$this->assertEquals(Director::makeRelative(" $siteUrl/testpage "), 'testpage');
$this->assertEquals(Director::makeRelative("$siteUrlNoProtocol/testpage"), 'testpage');
$this->assertEquals(Director::makeRelative('ftp://test.com'), 'ftp://test.com');
$this->assertEquals(Director::makeRelative('http://test.com'), 'http://test.com');
$this->assertEquals(Director::makeRelative('relative'), 'relative');
$this->assertEquals(Director::makeRelative("$siteUrl/?url=http://test.com"), '?url=http://test.com');
$this->assertEquals("test", Director::makeRelative("https://".$siteUrlNoProtocol."/test"));
$this->assertEquals("test", Director::makeRelative("http://".$siteUrlNoProtocol."/test"));
/**
* @dataProvider providerMakeRelative
* @param string $baseURL Site base URL
* @param string $requestURL Request URL
* @param string $relativeURL Expected relative URL
*/
public function testMakeRelative($baseURL, $requestURL, $relativeURL)
{
Director::config()->set('alternate_base_url', $baseURL);
$actualRelative = Director::makeRelative($requestURL);
$this->assertEquals(
$relativeURL,
$actualRelative,
"Expected relativeURL of {$requestURL} to be {$relativeURL}"
);
}
/**
@ -412,43 +521,101 @@ class DirectorTest extends SapphireTest
);
}
public function testForceWWW()
{
$this->expectExceptionRedirect('http://www.mysite.com/some-url');
Director::mockRequest(function ($request) {
Injector::inst()->registerService($request, HTTPRequest::class);
Director::forceWWW();
}, 'http://mysite.com/some-url');
}
public function testPromisedForceWWW()
{
Director::forceWWW();
// Flag is set but not redirected yet
$middleware = CanonicalURLMiddleware::singleton();
$this->assertTrue($middleware->getForceWWW());
// Middleware forces the redirection eventually
/** @var HTTPResponse $response */
$response = Director::mockRequest(function ($request) use ($middleware) {
return $middleware->process($request, function ($request) {
return null;
});
}, 'http://mysite.com/some-url');
// Middleware returns non-exception redirect
$this->assertEquals('http://www.mysite.com/some-url', $response->getHeader('Location'));
$this->assertEquals(301, $response->getStatusCode());
}
public function testForceSSLProtectsEntireSite()
{
$this->expectExceptionRedirect('https://www.mysite.com/some-url');
Director::mockRequest(function () {
Director::mockRequest(function ($request) {
Injector::inst()->registerService($request, HTTPRequest::class);
Director::forceSSL();
}, '/some-url');
}, 'http://www.mysite.com/some-url');
}
public function testPromisedForceSSL()
{
Director::forceSSL();
// Flag is set but not redirected yet
$middleware = CanonicalURLMiddleware::singleton();
$this->assertTrue($middleware->getForceSSL());
// Middleware forces the redirection eventually
/** @var HTTPResponse $response */
$response = Director::mockRequest(function ($request) use ($middleware) {
return $middleware->process($request, function ($request) {
return null;
});
}, 'http://www.mysite.com/some-url');
// Middleware returns non-exception redirect
$this->assertEquals('https://www.mysite.com/some-url', $response->getHeader('Location'));
$this->assertEquals(301, $response->getStatusCode());
}
public function testForceSSLOnTopLevelPagePattern()
{
// Expect admin to trigger redirect
$this->expectExceptionRedirect('https://www.mysite.com/admin');
Director::mockRequest(function () {
Director::mockRequest(function (HTTPRequest $request) {
Injector::inst()->registerService($request, HTTPRequest::class);
Director::forceSSL(array('/^admin/'));
}, '/admin');
}, 'http://www.mysite.com/admin');
}
public function testForceSSLOnSubPagesPattern()
{
// Expect to redirect to security login page
$this->expectExceptionRedirect('https://www.mysite.com/Security/login');
Director::mockRequest(function () {
Director::mockRequest(function (HTTPRequest $request) {
Injector::inst()->registerService($request, HTTPRequest::class);
Director::forceSSL(array('/^Security/'));
}, '/Security/login');
}, 'http://www.mysite.com/Security/login');
}
public function testForceSSLWithPatternDoesNotMatchOtherPages()
{
// Not on same url should not trigger redirect
Director::mockRequest(function () {
$this->assertFalse(Director::forceSSL(array('/^admin/')));
}, Director::baseURL() . 'normal-page');
$response = Director::mockRequest(function (HTTPRequest $request) {
Injector::inst()->registerService($request, HTTPRequest::class);
Director::forceSSL(array('/^admin/'));
}, 'http://www.mysite.com/normal-page');
$this->assertNull($response, 'Non-matching patterns do not trigger redirect');
// nested url should not triger redirect either
Director::mockRequest(function () {
$this->assertFalse(Director::forceSSL(array('/^admin/', '/^Security/')));
}, Director::baseURL() . 'just-another-page/sub-url');
$response = Director::mockRequest(function (HTTPRequest $request) {
Injector::inst()->registerService($request, HTTPRequest::class);
Director::forceSSL(array('/^admin/', '/^Security/'));
}, 'http://www.mysite.com/just-another-page/sub-url');
$this->assertNull($response, 'Non-matching patterns do not trigger redirect');
}
public function testForceSSLAlternateDomain()
@ -456,8 +623,35 @@ class DirectorTest extends SapphireTest
// Ensure that forceSSL throws the appropriate exception
$this->expectExceptionRedirect('https://secure.mysite.com/admin');
Director::mockRequest(function (HTTPRequest $request) {
Injector::inst()->registerService($request, HTTPRequest::class);
return Director::forceSSL(array('/^admin/'), 'secure.mysite.com');
}, Director::baseURL() . 'admin');
}, 'http://www.mysite.com/admin');
}
/**
* Test that combined forceWWW and forceSSL combine safely
*/
public function testForceSSLandForceWWW()
{
Director::forceWWW();
Director::forceSSL();
// Flag is set but not redirected yet
$middleware = CanonicalURLMiddleware::singleton();
$this->assertTrue($middleware->getForceWWW());
$this->assertTrue($middleware->getForceSSL());
// Middleware forces the redirection eventually
/** @var HTTPResponse $response */
$response = Director::mockRequest(function ($request) use ($middleware) {
return $middleware->process($request, function ($request) {
return null;
});
}, 'http://mysite.com/some-url');
// Middleware returns non-exception redirect
$this->assertEquals('https://www.mysite.com/some-url', $response->getHeader('Location'));
$this->assertEquals(301, $response->getStatusCode());
}
/**

View File

@ -194,4 +194,19 @@ class TreeDropdownFieldTest extends SapphireTest
$file3->Name.' is not found'
);
}
public function testReadonly()
{
$field = new TreeDropdownField('TestTree', 'Test tree', File::class);
$asdf = $this->objFromFixture(File::class, 'asdf');
$field->setValue($asdf->ID);
$readonlyField = $field->performReadonlyTransformation();
$this->assertEquals(
<<<"HTML"
<span class="readonly" id="TestTree">&lt;Special &amp; characters&gt;</span><input type="hidden" name="TestTree" value="{$asdf->ID}" />
HTML
,
(string)$readonlyField->Field()
);
}
}

View File

@ -11,6 +11,7 @@ SilverStripe\Assets\Folder:
SilverStripe\Assets\File:
asdf:
Filename: assets/FileTest.txt
Title: '<Special & characters>'
subfolderfile1:
Filename: assets/FileTest-subfolder/TestFile1InSubfolder.txt
Name: TestFile1InSubfolder

View File

@ -0,0 +1,31 @@
<?php
namespace SilverStripe\Forms\Tests;
use SilverStripe\Assets\File;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Forms\TreeMultiselectField;
class TreeMultiselectFieldTest extends SapphireTest
{
protected static $fixture_file = 'TreeDropdownFieldTest.yml';
public function testReadonly()
{
$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()
);
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace SilverStripe\ORM\Tests;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Forms\NullableField;
use SilverStripe\Forms\TextField;
class DBVarcharTest extends SapphireTest
{
protected static $extra_dataobjects = [
DBVarcharTest\TestObject::class,
];
public function testScaffold()
{
$obj = new DBVarcharTest\TestObject();
/** @var TextField $field */
$field = $obj->dbObject('Title')->scaffoldFormField();
$this->assertInstanceOf(TextField::class, $field);
$this->assertEquals(129, $field->getMaxLength());
/** @var NullableField $nullable */
$nullable = $obj->dbObject('NullableField')->scaffoldFormField();
$this->assertInstanceOf(NullableField::class, $nullable);
$innerField = $nullable->valueField;
$this->assertInstanceOf(TextField::class, $innerField);
$this->assertEquals(111, $innerField->getMaxLength());
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace SilverStripe\ORM\Tests\DBVarcharTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\ORM\DataObject;
class TestObject extends DataObject implements TestOnly
{
private static $table_name = 'DBVarcharTest_TestObject';
private static $db = [
'Title' => 'Varchar(129)',
'NullableField' => 'Varchar(111, ["nullifyEmpty" => false])'
];
}

View File

@ -106,7 +106,8 @@ class SearchContextTest extends SapphireTest
$context = $company->getDefaultSearchContext();
$this->assertEquals(
new FieldList(
new TextField("Name", 'Name'),
(new TextField("Name", 'Name'))
->setMaxLength(255),
new TextareaField("Industry", 'Industry'),
new NumericField("AnnualProfit", 'The Almighty Annual Profit')
),