Merge pull request #6446 from robbieaverill/feature/controllers-without-underscores

API Allow controller discovery without underscores (PSR-2 compliance)
This commit is contained in:
Daniel Hensby 2017-01-11 15:27:56 +00:00 committed by GitHub
commit 747c0770e7
24 changed files with 1077 additions and 867 deletions

View File

@ -1087,6 +1087,7 @@ mappings:
ConfigTest_TestNest: SilverStripe\Core\Tests\Config\ConfigTest\TestNest ConfigTest_TestNest: SilverStripe\Core\Tests\Config\ConfigTest\TestNest
ConfigTest: SilverStripe\Core\Tests\Config\ConfigTest ConfigTest: SilverStripe\Core\Tests\Config\ConfigTest
ConfigTest_Config_MemCache: SilverStripe\Core\Tests\Config\ConfigTest\ConfigTestMemCache ConfigTest_Config_MemCache: SilverStripe\Core\Tests\Config\ConfigTest\ConfigTestMemCache
Page_Controller: PageController
skipConfigs: skipConfigs:
- db - db
- casting - casting

View File

@ -335,13 +335,22 @@ types right now, we will go into much more detail in the [next tutorial](/tutori
Create a new file *HomePage.php* in *mysite/code*. Copy the following code into it: Create a new file *HomePage.php* in *mysite/code*. Copy the following code into it:
:::php ```php
<?php <?php
class HomePage extends Page {
}
class HomePage_Controller extends Page_Controller {
}
use Page;
use PageController;
class HomePage extends Page
{
}
class HomePageController extends PageController
{
}
```
Every page type also has a database table corresponding to it. Every time we modify the database, we need to rebuild it. Every page type also has a database table corresponding to it. Every time we modify the database, we need to rebuild it.
We can do this by going to `http://localhost/your_site_name/dev/build`. We can do this by going to `http://localhost/your_site_name/dev/build`.

View File

@ -18,59 +18,67 @@ We will create a poll on the home page that asks the user their favourite web br
## Creating the form ## Creating the form
The poll we will be creating on our homepage will ask the user for their name and favourite web browser. It will then collate the results into a bar graph. We create the form in a method on *HomePage_Controller*. The poll we will be creating on our homepage will ask the user for their name and favourite web browser. It will then collate the results into a bar graph. We create the form in a method on *HomePageController*.
**mysite/code/HomePage.php** **mysite/code/HomePageController.php**
```php ```php
class HomePage_Controller extends Page_Controller { use PageController;
private static $allowed_actions = array('BrowserPollForm'); use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\Form;
use SilverStripe\Forms\FormAction;
use SilverStripe\Forms\OptionSetField;
use SilverStripe\Forms\TextField;
// ... class HomePageController extends PageController
{
private static $allowed_actions = array('BrowserPollForm');
public function BrowserPollForm() { // ...
// Create fields
$fields = new FieldList(
new TextField('Name'),
new OptionsetField('Browser', 'Your Favourite Browser', array(
'Firefox' => 'Firefox',
'Chrome' => 'Chrome',
'Internet Explorer' => 'Internet Explorer',
'Safari' => 'Safari',
'Opera' => 'Opera',
'Lynx' => 'Lynx'
))
);
// Create actions public function BrowserPollForm()
$actions = new FieldList( {
new FormAction('doBrowserPoll', 'Submit') // Create fields
); $fields = new FieldList(
new TextField('Name'),
new OptionsetField('Browser', 'Your Favourite Browser', array(
'Firefox' => 'Firefox',
'Chrome' => 'Chrome',
'Internet Explorer' => 'Internet Explorer',
'Safari' => 'Safari',
'Opera' => 'Opera',
'Lynx' => 'Lynx'
))
);
return new Form($this, 'BrowserPollForm', $fields, $actions); // Create actions
} $actions = new FieldList(
new FormAction('doBrowserPoll', 'Submit')
);
... return new Form($this, 'BrowserPollForm', $fields, $actions);
} }
... // ...
}
// ...
``` ```
Let's step through this code. Let's step through this code.
```php ```php
// Create fields // Create fields
$fields = new FieldList( $fields = new FieldList(
new TextField('Name'), new TextField('Name'),
new OptionsetField('Browser', 'Your Favourite Browser', array( new OptionsetField('Browser', 'Your Favourite Browser', array(
'Firefox' => 'Firefox', 'Firefox' => 'Firefox',
'Chrome' => 'Chrome', 'Chrome' => 'Chrome',
'Internet Explorer' => 'Internet Explorer', 'Internet Explorer' => 'Internet Explorer',
'Safari' => 'Safari', 'Safari' => 'Safari',
'Opera' => 'Opera', 'Opera' => 'Opera',
'Lynx' => 'Lynx' 'Lynx' => 'Lynx'
)) ))
); );
``` ```
First we create our form fields. First we create our form fields.
@ -81,10 +89,10 @@ argument is passed, as in this case, it is assumed the label is the same as the
The second field we create is an [api:OptionsetField]. This is a dropdown, and takes a third argument - an The second field we create is an [api:OptionsetField]. This is a dropdown, and takes a third argument - an
array mapping the values to the options listed in the dropdown. array mapping the values to the options listed in the dropdown.
```php ```php
$actions = new FieldList( $actions = new FieldList(
new FormAction('doBrowserPoll', 'Submit'); new FormAction('doBrowserPoll', 'Submit');
); );
``` ```
After creating the fields, we create the form actions. Form actions appear as buttons at the bottom of the form. After creating the fields, we create the form actions. Form actions appear as buttons at the bottom of the form.
@ -94,7 +102,7 @@ All the form actions (in this case only one) are collected into a [api:FieldList
the fields. the fields.
```php ```php
return new Form($this, 'BrowserPollForm', $fields, $actions); return new Form($this, 'BrowserPollForm', $fields, $actions);
``` ```
Finally we create the [api:Form] object and return it. Finally we create the [api:Form] object and return it.
@ -107,14 +115,14 @@ Add the following code to the top of your home page template, just before `<div
**themes/simple/templates/Layout/HomePage.ss** **themes/simple/templates/Layout/HomePage.ss**
```ss ```ss
... ...
<div id="BrowserPoll"> <div id="BrowserPoll">
<h2>Browser Poll</h2> <h2>Browser Poll</h2>
$BrowserPollForm $BrowserPollForm
</div> </div>
<div class="Content"> <div class="Content">
... ...
``` ```
In order to make the graphs render correctly, In order to make the graphs render correctly,
@ -123,49 +131,49 @@ Add the following code to the existing `form.css` file:
**themes/simple/css/form.css** **themes/simple/css/form.css**
```css ```css
/* BROWSER POLL */ /* BROWSER POLL */
#BrowserPoll { #BrowserPoll {
float: right; float: right;
margin: 20px 10px 0 0; margin: 20px 10px 0 0;
width: 20%; width: 20%;
} }
form FieldList { form FieldList {
border:0; border: 0;
} }
#BrowserPoll .message { #BrowserPoll .message {
float:left; float: left;
display: block; display: block;
color:red; color: red;
background:#efefef; background: #efefef;
border:1px solid #ccc; border: 1px solid #ccc;
padding:5px; padding: 5px;
margin:5px; margin: 5px;
} }
#BrowserPoll h2 { #BrowserPoll h2 {
font-size: 1.5em; font-size: 1.5em;
line-height:2em; line-height:2em;
color: #0083C8; color: #0083C8;
} }
#BrowserPoll .field { #BrowserPoll .field {
padding:3px 0; padding:3px 0;
} }
#BrowserPoll input.text { #BrowserPoll input.text {
padding: 0; padding: 0;
font-size:1em; font-size:1em;
} }
#BrowserPoll .btn-toolbar { #BrowserPoll .btn-toolbar {
padding:5px 0; padding: 5px 0;
} }
#BrowserPoll .bar { #BrowserPoll .bar {
background-color: #015581; background-color: #015581;
} }
``` ```
@ -183,29 +191,37 @@ If you recall, in the [second tutorial](/tutorials/extending_a_basic_site) we sa
**mysite/code/BrowserPollSubmission.php** **mysite/code/BrowserPollSubmission.php**
```php ```php
<?php <?php
class BrowserPollSubmission extends DataObject {
private static $db = array( use SilverStripe\ORM\DataObject;
'Name' => 'Text',
'Browser' => 'Text' class BrowserPollSubmission extends DataObject
); {
} private static $db = array(
'Name' => 'Text',
'Browser' => 'Text'
);
}
``` ```
If we then rebuild the database ([http://localhost/your_site_name/dev/build](http://localhost/your_site_name/dev/build)), we will see that the *BrowserPollSubmission* table is created. Now we just need to define 'doBrowserPoll' on *HomePage_Controller*: If we then rebuild the database ([http://localhost/your_site_name/dev/build](http://localhost/your_site_name/dev/build)), we will see that the *BrowserPollSubmission* table is created. Now we just need to define 'doBrowserPoll' on *HomePageController*:
**mysite/code/HomePage.php** **mysite/code/HomePageController.php**
```php ```php
class HomePage_Controller extends Page_Controller { use BrowserPollSubmission;
// ... use PageController;
public function doBrowserPoll($data, $form) {
$submission = new BrowserPollSubmission(); class HomePageController extends PageController
$form->saveInto($submission); {
$submission->write(); // ...
return $this->redirectBack(); public function doBrowserPoll($data, $form) {
} $submission = new BrowserPollSubmission();
} $form->saveInto($submission);
$submission->write();
return $this->redirectBack();
}
}
``` ```
A function that processes a form submission takes two arguments - the first is the data in the form, the second is the [api:Form] object. A function that processes a form submission takes two arguments - the first is the data in the form, the second is the [api:Form] object.
@ -218,16 +234,17 @@ SilverStripe forms all have automatic validation on fields where it is logical.
SilverStripe provides the *RequiredFields* validator, which ensures that the fields specified are filled in before the form is submitted. To use it we create a new *RequiredFields* object with the name of the fields we wish to be required as the arguments, then pass this as a fifth argument to the Form constructor. SilverStripe provides the *RequiredFields* validator, which ensures that the fields specified are filled in before the form is submitted. To use it we create a new *RequiredFields* object with the name of the fields we wish to be required as the arguments, then pass this as a fifth argument to the Form constructor.
Change the end of the 'BrowserPollForm' function so it looks like this: Add a namespace import for `SilverStripe\Forms\RequiredFields`, then change the end of the 'BrowserPollForm' function so it looks like this:
**mysite/code/HomePage.php** **mysite/code/HomePage.php**
```php ```php
public function BrowserPollForm() { public function BrowserPollForm()
// ... {
$validator = new RequiredFields('Name', 'Browser'); // ...
return new Form($this, 'BrowserPollForm', $fields, $actions, $validator); $validator = new RequiredFields('Name', 'Browser');
} return new Form($this, 'BrowserPollForm', $fields, $actions, $validator);
}
``` ```
If we then open the homepage and attempt to submit the form without filling in the required fields errors should appear. If we then open the homepage and attempt to submit the form without filling in the required fields errors should appear.
@ -242,34 +259,40 @@ The first thing to do is make it so a user can only vote once per session. If th
We can do this using a session variable. The [api:Session] class handles all session variables in SilverStripe. First modify the 'doBrowserPoll' to set the session variable 'BrowserPollVoted' when a user votes. We can do this using a session variable. The [api:Session] class handles all session variables in SilverStripe. First modify the 'doBrowserPoll' to set the session variable 'BrowserPollVoted' when a user votes.
**mysite/code/HomePage.php** **mysite/code/HomePageController.php**
```php ```php
// ... // ...
class HomePage_Controller extends Page_Controller { class HomePageController extends PageController
// ... {
public function doBrowserPoll($data, $form) { // ...
$submission = new BrowserPollSubmission(); public function doBrowserPoll($data, $form)
$form->saveInto($submission); {
$submission->write(); $submission = new BrowserPollSubmission();
Session::set('BrowserPollVoted', true); $form->saveInto($submission);
return $this->redirectBack(); $submission->write();
} Session::set('BrowserPollVoted', true);
} return $this->redirectBack();
}
}
``` ```
Then we simply need to check if the session variable has been set in 'BrowserPollForm()', and to not return the form if Then we simply need to check if the session variable has been set in 'BrowserPollForm()', and to not return the form if
it is. it is.
```php ```php
// ... // ...
class HomePage_Controller extends Page_Controller { class HomePageController extends PageController
// ... {
public function BrowserPollForm() { // ...
if(Session::get('BrowserPollVoted')) return false; public function BrowserPollForm()
// ... {
} if (Session::get('BrowserPollVoted')) {
} return false;
}
// ...
}
}
``` ```
If you visit the home page now you will see you can only vote once per session; after that the form won't be shown. You can start a new session by closing and reopening your browser, If you visit the home page now you will see you can only vote once per session; after that the form won't be shown. You can start a new session by closing and reopening your browser,
@ -281,47 +304,49 @@ Now that we're collecting data, it would be nice to show the results on the webs
In the [second tutorial](/tutorials/extending_a_basic_site), we got a collection of news articles for the home page by using the 'ArticleHolder::get()' function, which returns a [api:DataList]. We can get all submissions in the same fashion, through `BrowserPollSubmission::get()`. This list will be the starting point for our result aggregation. In the [second tutorial](/tutorials/extending_a_basic_site), we got a collection of news articles for the home page by using the 'ArticleHolder::get()' function, which returns a [api:DataList]. We can get all submissions in the same fashion, through `BrowserPollSubmission::get()`. This list will be the starting point for our result aggregation.
Create the function 'BrowserPollResults' on the *HomePage_Controller* class. Add the appropriate namespace imports, then create the function 'BrowserPollResults' on the *HomePageController* class.
**mysite/code/HomePage.php** **mysite/code/HomePageController.php**
```php ```php
public function BrowserPollResults() { public function BrowserPollResults()
$submissions = new GroupedList(BrowserPollSubmission::get()); {
$total = $submissions->Count(); $submissions = new GroupedList(BrowserPollSubmission::get());
$total = $submissions->Count();
$list = new ArrayList(); $list = new ArrayList();
foreach($submissions->groupBy('Browser') as $browserName => $browserSubmissions) { foreach($submissions->groupBy('Browser') as $browserName => $browserSubmissions) {
$percentage = (int) ($browserSubmissions->Count() / $total * 100); $percentage = (int) ($browserSubmissions->Count() / $total * 100);
$list->push(new ArrayData(array( $list->push(new ArrayData(array(
'Browser' => $browserName, 'Browser' => $browserName,
'Percentage' => $percentage 'Percentage' => $percentage
))); )));
} }
return $list; return $list;
} }
``` ```
This code introduces a few new concepts, so let's step through it. This code introduces a few new concepts, so let's step through it.
```php ```php
$submissions = new GroupedList(BrowserPollSubmission::get()); $submissions = new GroupedList(BrowserPollSubmission::get());
``` ```
First we get all of the `BrowserPollSubmission` records from the database. This returns the submissions as a [api:DataList]. Then we wrap it inside a [api:GroupedList], which adds the ability to group those records. The resulting object will behave just like the original `DataList`, though (with the addition of a `groupBy()` method). First we get all of the `BrowserPollSubmission` records from the database. This returns the submissions as a [api:DataList]. Then we wrap it inside a [api:GroupedList], which adds the ability to group those records. The resulting object will behave just like the original `DataList`, though (with the addition of a `groupBy()` method).
```php ```php
$total = $submissions->Count(); $total = $submissions->Count();
``` ```
We get the total number of submissions, which is needed to calculate the percentages. We get the total number of submissions, which is needed to calculate the percentages.
```php ```php
$list = new ArrayList(); $list = new ArrayList();
foreach($submissions->groupBy('Browser') as $browserName => $browserSubmissions) { foreach ($submissions->groupBy('Browser') as $browserName => $browserSubmissions) {
$percentage = (int) ($browserSubmissions->Count() / $total * 100); $percentage = (int) ($browserSubmissions->Count() / $total * 100);
$list->push(new ArrayData(array( $list->push(new ArrayData(array(
'Browser' => $browserName, 'Browser' => $browserName,
'Percentage' => $percentage 'Percentage' => $percentage
))); )));
} }
``` ```
Now we create an empty [api:ArrayList] to hold the data we'll pass to the template. Its similar to [api:DataList], but can hold arbitrary objects rather than just DataObject` instances. Then we iterate over the 'Browser' submissions field. Now we create an empty [api:ArrayList] to hold the data we'll pass to the template. Its similar to [api:DataList], but can hold arbitrary objects rather than just DataObject` instances. Then we iterate over the 'Browser' submissions field.
@ -334,21 +359,21 @@ The final step is to create the template to display our data. Change the 'Browse
**themes/simple/templates/Layout/HomePage.ss** **themes/simple/templates/Layout/HomePage.ss**
```ss ```ss
<div id="BrowserPoll"> <div id="BrowserPoll">
<h2>Browser Poll</h2> <h2>Browser Poll</h2>
<% if $BrowserPollForm %> <% if $BrowserPollForm %>
$BrowserPollForm $BrowserPollForm
<% else %> <% else %>
<ul> <ul>
<% loop $BrowserPollResults %> <% loop $BrowserPollResults %>
<li> <li>
<div class="browser">$Browser: $Percentage%</div> <div class="browser">$Browser: $Percentage%</div>
<div class="bar" style="width:$Percentage%">&nbsp;</div> <div class="bar" style="width:$Percentage%">&nbsp;</div>
</li> </li>
<% end_loop %> <% end_loop %>
</ul> </ul>
<% end_if %> <% end_if %>
</div> </div>
``` ```
Here we first check if the *BrowserPollForm* is returned, and if it is display it. Otherwise the user has already voted, Here we first check if the *BrowserPollForm* is returned, and if it is display it. Otherwise the user has already voted,

View File

@ -22,11 +22,11 @@ make them editable in the CMS, and render them on the website.
This table shows some example data we'll be using: This table shows some example data we'll be using:
| Project | Student | Mentor | | Project | Student | Mentor |
| ------- | ------- | ------- | | ------- | ------- | ------- |
| Developer Toolbar | Jakob,Ofir | Mark,Sean | | Developer Toolbar | Jakob,Ofir | Mark,Sean |
| Behaviour Testing | Michal,Wojtek | Ingo, Sean | | Behaviour Testing | Michal,Wojtek | Ingo, Sean |
| Content Personalization | Yuki | Philipp | | Content Personalization | Yuki | Philipp |
| Module Management | Andrew | Marcus,Sam | | Module Management | Andrew | Marcus,Sam |
### Has-One and Has-Many Relationships: Project and Student ### Has-One and Has-Many Relationships: Project and Student
@ -38,29 +38,49 @@ Let's create the `Student` and `Project` objects.
**mysite/code/Student.php** **mysite/code/Student.php**
:::php ```php
<?php <?php
class Student extends DataObject {
private static $db = array( use SilverStripe\ORM\DataObject;
'Name' => 'Varchar',
'University' => 'Varchar', class Student extends DataObject
); {
private static $has_one = array( private static $db = array(
'Project' => 'Project' 'Name' => 'Varchar',
); 'University' => 'Varchar',
} );
private static $has_one = array(
'Project' => 'Project'
);
}
```
**mysite/code/Project.php** **mysite/code/Project.php**
:::php ```php
<?php <?php
class Project extends Page {
private static $has_many = array( use Page;
'Students' => 'Student'
); class Project extends Page
} {
class Project_Controller extends Page_Controller { private static $has_many = array(
} 'Students' => 'Student'
);
}
```
**mysite/code/ProjectController.php**
```php
<?php
use PageController;
class ProjectController extends PageController
{
}
```
The relationships are defined through the `$has_one` The relationships are defined through the `$has_one`
and `$has_many` properties on the objects. and `$has_many` properties on the objects.
@ -95,15 +115,30 @@ The restriction is enforced through the `$allowed_children` directive.
**mysite/code/ProjectsHolder.php** **mysite/code/ProjectsHolder.php**
:::php :::php
<?php <?php
class ProjectsHolder extends Page {
private static $allowed_children = array( use Page;
'Project'
); class ProjectsHolder extends Page {
} private static $allowed_children = array(
class ProjectsHolder_Controller extends Page_Controller { 'Project'
} );
}
```
**mysite/code/ProjectsHolderController.php
```php
<?php
use PageController;
class ProjectsHolderController extends PageController
{
}
```
You might have noticed that we don't specify the relationship You might have noticed that we don't specify the relationship
to a project. That's because it's already inherited from the parent implementation, to a project. That's because it's already inherited from the parent implementation,
@ -128,32 +163,42 @@ All customization to fields for a page type are managed through a method called
**mysite/code/Project.php** **mysite/code/Project.php**
:::php ```php
<?php <?php
class Project extends Page {
// ... use Page;
public function getCMSFields() { use SilverStripe\Forms\GridField;
// Get the fields from the parent implementation use SilverStripe\Forms\GridField\GridFieldConfig_RelationEditor;
$fields = parent::getCMSFields();
// Create a default configuration for the new GridField, allowing record editing class Project extends Page
$config = GridFieldConfig_RelationEditor::create(); {
// Set the names and data for our gridfield columns // ...
$config->getComponentByType('GridFieldDataColumns')->setDisplayFields(array( public function getCMSFields()
'Name' => 'Name', {
'Project.Title'=> 'Project' // Retrieve from a has-one relationship // Get the fields from the parent implementation
)); $fields = parent::getCMSFields();
// Create a gridfield to hold the student relationship // Create a default configuration for the new GridField, allowing record editing
$studentsField = new GridField( $config = GridFieldConfig_RelationEditor::create();
'Students', // Field name // Set the names and data for our gridfield columns
'Student', // Field title $config
$this->Students(), // List of all related students ->getComponentByType('SilverStripe\\Forms\\GridField\\GridFieldDataColumns')
$config ->setDisplayFields(array(
); 'Name' => 'Name',
// Create a tab named "Students" and add our field to it 'Project.Title'=> 'Project' // Retrieve from a has-one relationship
$fields->addFieldToTab('Root.Students', $studentsField); ));
return $fields; // Create a gridfield to hold the student relationship
} $studentsField = new GridField(
} 'Students', // Field name
'Student', // Field title
$this->Students(), // List of all related students
$config
);
// Create a tab named "Students" and add our field to it
$fields->addFieldToTab('Root.Students', $studentsField);
return $fields;
}
}
```
This creates a tabular field, which lists related student records, one row at a time. This creates a tabular field, which lists related student records, one row at a time.
It's empty by default, but you can add new students as required, It's empty by default, but you can add new students as required,
@ -169,11 +214,11 @@ One example of this is the configuration of column names on our table:
We call `setDisplayFields()` directly on the component responsible for their rendering. We call `setDisplayFields()` directly on the component responsible for their rendering.
<div class="note" markdown="1"> <div class="note" markdown="1">
Adding a `GridField` to a page type is a popular way to manage data, Adding a `GridField` to a page type is a popular way to manage data,
but not the only one. If your data requires a dedicated interface but not the only one. If your data requires a dedicated interface
with more sophisticated search and management logic, consider with more sophisticated search and management logic, consider
using the [ModelAdmin](/developer_guides/customising_the_admin_interface/modeladmin) using the [ModelAdmin](/developer_guides/customising_the_admin_interface/modeladmin)
interface instead. interface instead.
</div> </div>
![tutorial:tutorial5_project_creation.jpg](../_images/tutorial5_project_creation.jpg) ![tutorial:tutorial5_project_creation.jpg](../_images/tutorial5_project_creation.jpg)
@ -200,26 +245,37 @@ The first step is to create the `Mentor` object and set the relation with the `P
**mysite/code/Mentor.php** **mysite/code/Mentor.php**
:::php ```php
<?php <?php
class Mentor extends DataObject {
private static $db = array( use SilverStripe\ORM\DataObject;
'Name' => 'Varchar',
); class Mentor extends DataObject
private static $belongs_many_many = array( {
'Projects' => 'Project' private static $db = array(
); 'Name' => 'Varchar',
} );
private static $belongs_many_many = array(
'Projects' => 'Project'
);
}
```
**mysite/code/Project.php** **mysite/code/Project.php**
:::php ```php
class Project extends Page { <?php
// ...
private static $many_many = array( use Page;
'Mentors' => 'Mentor'
); class Project extends Page
} {
// ...
private static $many_many = array(
'Mentors' => 'Mentor'
);
}
```
This code will create a relationship between the `Project` table and the `Mentor` table by storing the ids of the respective `Project` and `Mentor` in a another table named "Project_Mentors" This code will create a relationship between the `Project` table and the `Mentor` table by storing the ids of the respective `Project` and `Mentor` in a another table named "Project_Mentors"
(after you've performed a `dev/build` command, of course). (after you've performed a `dev/build` command, of course).
@ -231,22 +287,31 @@ to configure it a bit differently.
**mysite/code/Project.php** **mysite/code/Project.php**
:::php ```php
class Project extends Page { <?php
// ...
public function getCMSFields() { use Page;
// ... use SilverStripe\Forms\GridField;
// Same setup, but for mentors use SilverStripe\Forms\GridField\GridFieldConfig_RelationEditor;
$mentorsField = new GridField(
'Mentors', class Project extends Page
'Mentors', {
$this->Mentors(), // ...
GridFieldConfig_RelationEditor::create() public function getCMSFields()
); {
$fields->addFieldToTab('Root.Mentors', $mentorsField); // ...
return $fields; // Same setup, but for mentors
} $mentorsField = new GridField(
} 'Mentors',
'Mentors',
$this->Mentors(),
GridFieldConfig_RelationEditor::create()
);
$fields->addFieldToTab('Root.Mentors', $mentorsField);
return $fields;
}
}
```
The important difference to our student management UI is the usage The important difference to our student management UI is the usage
of `$this->Mentor()` (rather than `Mentor::get()`). It will limit of `$this->Mentor()` (rather than `Mentor::get()`). It will limit
@ -286,44 +351,45 @@ a named list of object.
**themes/simple/templates/Layout/ProjectsHolder.ss** **themes/simple/templates/Layout/ProjectsHolder.ss**
:::ss ```ss
<% include SideBar %> <% include SideBar %>
<div class="content-container unit size3of4 lastUnit"> <div class="content-container unit size3of4 lastUnit">
<article> <article>
<h1>$Title</h1> <h1>$Title</h1>
<div class="content"> <div class="content">
$Content $Content
<table> <table>
<thead> <thead>
<tr> <tr>
<th>Project</th> <th>Project</th>
<th>Students</th> <th>Students</th>
<th>Mentors</th> <th>Mentors</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<% loop $Children %> <% loop $Children %>
<tr> <tr>
<td> <td>
<a href="$Link">$Title</a> <a href="$Link">$Title</a>
</td> </td>
<td> <td>
<% loop $Students %> <% loop $Students %>
$Name ($University)<% if $Last !=1 %>,<% end_if %> $Name ($University)<% if not $Last %>, <% end_if %>
<% end_loop %> <% end_loop %>
</td> </td>
<td> <td>
<% loop $Mentors %> <% loop $Mentors %>
$Name<% if $Last !=1 %>,<% end_if %> $Name<% if not $Last %>, <% end_if %>
<% end_loop %> <% end_loop %>
</td> </td>
</tr> </tr>
<% end_loop %> <% end_loop %>
</tbody> </tbody>
</table> </table>
</div> </div>
</article> </article>
</div> </div>
```
Navigate to the holder page through your website navigation, Navigate to the holder page through your website navigation,
or the "Preview" feature in the CMS. You should see a list of all projects now. or the "Preview" feature in the CMS. You should see a list of all projects now.
@ -344,36 +410,37 @@ we can access the "Students" and "Mentors" relationships directly in the templat
**themes/simple/templates/Layout/Project.ss** **themes/simple/templates/Layout/Project.ss**
:::ss ```ss
<% include SideBar %> <% include SideBar %>
<div class="content-container unit size3of4 lastUnit"> <div class="content-container unit size3of4 lastUnit">
<article> <article>
<h1>$Title</h1> <h1>$Title</h1>
<div class="content"> <div class="content">
$Content $Content
<h2>Students</h2> <h2>Students</h2>
<% if $Students %> <% if $Students %>
<ul> <ul>
<% loop $Students %> <% loop $Students %>
<li>$Name ($University)</li> <li>$Name ($University)</li>
<% end_loop %> <% end_loop %>
</ul> </ul>
<% else %> <% else %>
<p>No students found</p> <p>No students found</p>
<% end_if %> <% end_if %>
<h2>Mentors</h2> <h2>Mentors</h2>
<% if $Mentors %> <% if $Mentors %>
<ul> <ul>
<% loop $Mentors %> <% loop $Mentors %>
<li>$Name</li> <li>$Name</li>
<% end_loop %> <% end_loop %>
</ul> </ul>
<% else %> <% else %>
<p>No mentors found</p> <p>No mentors found</p>
<% end_if %> <% end_if %>
</div> </div>
</article> </article>
</div> </div>
```
Follow the link to a project detail from from your holder page, Follow the link to a project detail from from your holder page,
or navigate to it through the submenu provided by the theme. or navigate to it through the submenu provided by the theme.
@ -387,17 +454,23 @@ by introducing a new template for them.
**themes/simple/templates/Includes/StudentInfo.ss** **themes/simple/templates/Includes/StudentInfo.ss**
:::ss ```ss
$Name ($University) $Name ($University)
```
To use this template, we need to add a new method to our student class: To use this template, we need to add a new method to our student class:
:::php ```php
class Student extends DataObject { use SilverStripe\ORM\DataObject;
function getInfo() {
return $this->renderWith('StudentInfo'); class Student extends DataObject
} {
} public function getInfo()
{
return $this->renderWith('StudentInfo');
}
}
```
Replace the student template code in both `Project.ss` Replace the student template code in both `Project.ss`
and `ProjectHolder.ss` templates with the new placeholder, `$Info`. and `ProjectHolder.ss` templates with the new placeholder, `$Info`.

View File

@ -98,7 +98,8 @@ Variables can come from your database fields, or custom methods you define on yo
**mysite/code/Page.php** **mysite/code/Page.php**
:::php :::php
public function UsersIpAddress() { public function UsersIpAddress()
{
return $this->getRequest()->getIP(); return $this->getRequest()->getIP();
} }
@ -112,8 +113,8 @@ Variables can come from your database fields, or custom methods you define on yo
</div> </div>
The variables that can be used in a template vary based on the object currently in [scope](#scope). Scope defines what The variables that can be used in a template vary based on the object currently in [scope](#scope). Scope defines what
object the methods get called on. For the standard `Page.ss` template the scope is the current [api:Page_Controller] object the methods get called on. For the standard `Page.ss` template the scope is the current [api:PageController]
class. This object gives you access to all the database fields on [api:Page_Controller], its corresponding [api:Page] class. This object gives you access to all the database fields on [api:PageController], its corresponding [api:Page]
record and any subclasses of those two. record and any subclasses of those two.
**mysite/code/Layout/Page.ss** **mysite/code/Layout/Page.ss**
@ -407,12 +408,12 @@ In the `<% loop %>` section, we saw an example of two **scopes**. Outside the `<
the scope of the top level `Page`. But inside the loop, we were in the scope of an item in the list (i.e the `Child`) the scope of the top level `Page`. But inside the loop, we were in the scope of an item in the list (i.e the `Child`)
The scope determines where the value comes from when you refer to a variable. Typically the outer scope of a `Page.ss` The scope determines where the value comes from when you refer to a variable. Typically the outer scope of a `Page.ss`
layout template is the [api:Page_Controller] that is currently being rendered. layout template is the [api:PageController] that is currently being rendered.
When the scope is a `Page_Controller` it will automatically also look up any methods in the corresponding `Page` data When the scope is a `PageController` it will automatically also look up any methods in the corresponding `Page` data
record. In the case of `$Title` the flow looks like record. In the case of `$Title` the flow looks like
$Title --> [Looks up: Current Page_Controller and parent classes] --> [Looks up: Current Page and parent classes] $Title --> [Looks up: Current PageController and parent classes] --> [Looks up: Current Page and parent classes]
The list of variables you could use in your template is the total of all the methods in the current scope object, parent The list of variables you could use in your template is the total of all the methods in the current scope object, parent
classes of the current scope object, and any [api:Extension] instances you have. classes of the current scope object, and any [api:Extension] instances you have.

View File

@ -15,7 +15,7 @@ scope, and you can specify additional static methods to be available globally in
<div class="notice" markdown="1"> <div class="notice" markdown="1">
Want a quick way of knowing what scope you're in? Try putting `$ClassName` in your template. You should see a string Want a quick way of knowing what scope you're in? Try putting `$ClassName` in your template. You should see a string
such as `Page` of the object that's in scope. The methods you can call on that object then are any functions, database such as `Page` of the object that's in scope. The methods you can call on that object then are any functions, database
properties or relations on the `Page` class, `Page_Controller` class as well as anything from their subclasses **or** properties or relations on the `Page` class, `PageController` class as well as anything from their subclasses **or**
extensions. extensions.
</div> </div>

View File

@ -11,79 +11,88 @@ subclasses).
The following will render the given data into a template. Given the template: The following will render the given data into a template. Given the template:
**mysite/templates/Coach_Message.ss** **mysite/templates/Coach_Message.ss**
:::ss ```ss
<strong>$Name</strong> is the $Role on our team. <strong>$Name</strong> is the $Role on our team.
```
Our application code can render into that view using `renderWith`. This method is called on the [api:ViewableData] Our application code can render into that view using `renderWith`. This method is called on the [api:ViewableData]
instance with a template name or an array of templates to render. instance with a template name or an array of templates to render.
**mysite/code/Page.php** **mysite/code/Page.php**
:::php ```php
$arrayData = new ArrayData(array( $arrayData = new ArrayData(array(
'Name' => 'John', 'Name' => 'John',
'Role' => 'Head Coach' 'Role' => 'Head Coach'
)); ));
echo $arrayData->renderWith('Coach_Message'); echo $arrayData->renderWith('Coach_Message');
// returns "<strong>John</strong> is the Head Coach on our team." // returns "<strong>John</strong> is the Head Coach on our team."
```
<div class="info" markdown="1"> <div class="info" markdown="1">
Most classes in SilverStripe you want in your template extend `ViewableData` and allow you to call `renderWith`. This Most classes in SilverStripe you want in your template extend `ViewableData` and allow you to call `renderWith`. This
includes [api:Controller], [api:FormField] and [api:DataObject] instances. includes [api:Controller], [api:FormField] and [api:DataObject] instances.
</div> </div>
:::php ```php
$controller->renderWith(array("MyController", "MyBaseController")); $controller->renderWith(array('MyController', 'MyBaseController'));
Member::currentUser()->renderWith('Member_Profile'); Member::currentUser()->renderWith('Member_Profile');
```
`renderWith` can be used to override the default template process. For instance, to provide an ajax version of a `renderWith` can be used to override the default template process. For instance, to provide an ajax version of a
template. template.
:::php ```php
<?php <?php
class Page_Controller extends ContentController { use SilverStripe\CMS\Controllers\ContentController;
private static $allowed_actions = array('iwantmyajax'); class PageController extends ContentController
{
private static $allowed_actions = array('iwantmyajax');
public function iwantmyajax() { public function iwantmyajax()
if(Director::is_ajax()) { {
return $this->renderWith("AjaxTemplate"); if (Director::is_ajax()) {
} else { return $this->renderWith('AjaxTemplate');
return $this->httpError(404); } else {
} return $this->httpError(404);
} }
} }
}
```
Any data you want to render into the template that does not extend `ViewableData` should be wrapped in an object that Any data you want to render into the template that does not extend `ViewableData` should be wrapped in an object that
does, such as `ArrayData` or `ArrayList`. does, such as `ArrayData` or `ArrayList`.
:::php ```php
<?php <?php
class Page_Controller extends ContentController { use SilverStripe\CMS\Controllers\ContentController;
.. class PageController extends ContentController
{
// ..
public function iwantmyajax()
{
if (Director::is_ajax()) {
$experience = new ArrayList();
$experience->push(new ArrayData(array(
'Title' => 'First Job'
)));
public function iwantmyajax() { return $this->customise(new ArrayData(array(
if(Director::is_ajax()) { 'Name' => 'John',
$experience = new ArrayList(); 'Role' => 'Head Coach',
$experience->push(new ArrayData(array( 'Experience' => $experience
'Title' => 'First Job' )))->renderWith('AjaxTemplate');
))); } else {
return $this->httpError(404);
return $this->customise(new ArrayData(array( }
'Name' => 'John', }
'Role' => 'Head Coach', }
'Experience' => $experience ```
)))->renderWith("AjaxTemplate");
} else {
return $this->httpError(404);
}
}
}

View File

@ -34,7 +34,7 @@ at http://yoursite.com/teams/ and the `players` custom action is at http://yours
<div class="info" markdown="1"> <div class="info" markdown="1">
If you're using the `cms` module with and dealing with `Page` objects then for your custom `Page Type` controllers you If you're using the `cms` module with and dealing with `Page` objects then for your custom `Page Type` controllers you
would extend `ContentController` or `Page_Controller`. You don't need to define the routes value as the `cms` handles would extend `ContentController` or `PageController`. You don't need to define the routes value as the `cms` handles
routing. routing.
</div> </div>

View File

@ -4,11 +4,11 @@ summary: A more in depth look at how to map requests to particular controllers a
# Routing # Routing
Routing is the process of mapping URL's to [api:Controllers] and actions. In the introduction we defined a new custom route Routing is the process of mapping URL's to [api:Controllers] and actions. In the introduction we defined a new custom route
for our `TeamsController` mapping any `teams` URL to our `TeamsController` for our `TeamController` mapping any `teams` URL to our `TeamController`
<div class="info" markdown="1"> <div class="info" markdown="1">
If you're using the `cms` module with and dealing with `Page` objects then for your custom `Page Type` controllers you If you're using the `cms` module with and dealing with `Page` objects then for your custom `Page Type` controllers you
would extend `ContentController` or `Page_Controller`. You don't need to define the routes value as the `cms` handles would extend `ContentController` or `PageController`. You don't need to define the routes value as the `cms` handles
routing. routing.
</div> </div>
@ -17,16 +17,17 @@ These routes by standard, go into a `routes.yml` file in your applications `_con
**mysite/_config/routes.yml** **mysite/_config/routes.yml**
:::yml ```yml
--- ---
Name: mysiteroutes Name: mysiteroutes
After: framework/routes#coreroutes After: framework/routes#coreroutes
--- ---
Director: Director:
rules: rules:
'teams//$Action/$ID/$Name': 'TeamController' 'teams//$Action/$ID/$Name': 'TeamController'
'player/': 'PlayerController' 'player/': 'PlayerController'
'': 'HomeController' '': 'HomeController'
```
<div class="notice" markdown="1"> <div class="notice" markdown="1">
To understand the syntax for the `routes.yml` file better, read the [Configuration](../configuration) documentation. To understand the syntax for the `routes.yml` file better, read the [Configuration](../configuration) documentation.
@ -34,8 +35,9 @@ To understand the syntax for the `routes.yml` file better, read the [Configurati
## Parameters ## Parameters
:::yml ```yml
'teams//$Action/$ID/$Name': 'TeamController' 'teams//$Action/$ID/$Name': 'TeamController'
```
This route has defined that any URL beginning with `team` should create, and be handled by a `TeamController` instance. This route has defined that any URL beginning with `team` should create, and be handled by a `TeamController` instance.
@ -49,49 +51,49 @@ All Controllers have access to `$this->getRequest()` for the request object and
Here is what those parameters would look like for certain requests Here is what those parameters would look like for certain requests
:::php ```php
// GET /teams/ // GET /teams/
print_r($this->getRequest()->params()); print_r($this->getRequest()->params());
// Array // Array
// ( // (
// [Action] => null // [Action] => null
// [ID] => null // [ID] => null
// [Name] => null // [Name] => null
// ) // )
// GET /teams/players/ // GET /teams/players/
print_r($this->getRequest()->params()); print_r($this->getRequest()->params());
// Array // Array
// ( // (
// [Action] => 'players' // [Action] => 'players'
// [ID] => null // [ID] => null
// [Name] => null // [Name] => null
// ) // )
// GET /teams/players/1 // GET /teams/players/1
print_r($this->getRequest()->params()); print_r($this->getRequest()->params());
// Array // Array
// ( // (
// [Action] => 'players' // [Action] => 'players'
// [ID] => 1 // [ID] => 1
// [Name] => null // [Name] => null
// ) // )
```
You can also fetch one parameter at a time. You can also fetch one parameter at a time.
:::php ```php
// GET /teams/players/1/
// GET /teams/players/1/
echo $this->getRequest()->param('ID');
// returns '1'
echo $this->getRequest()->param('ID');
// returns '1'
```
## URL Patterns ## URL Patterns
@ -108,26 +110,29 @@ A rule must always start with alphabetical ([A-Za-z]) characters or a $Variable
| `!` | **Require Variable** - Placing this after a parameter variable requires data to be present for the rule to match | | `!` | **Require Variable** - Placing this after a parameter variable requires data to be present for the rule to match |
| `//` | **Shift Point** - Declares that only variables denoted with a $ are parsed into the $params AFTER this point in the regex | | `//` | **Shift Point** - Declares that only variables denoted with a $ are parsed into the $params AFTER this point in the regex |
:::yml ```yml
'teams/$Action/$ID/$OtherID': 'TeamController' 'teams/$Action/$ID/$OtherID': 'TeamController'
# /teams/ # /teams/
# /teams/players/ # /teams/players/
# /teams/ # /teams/
```
Standard URL handler syntax. For any URL that contains 'team' this rule will match and hand over execution to the Standard URL handler syntax. For any URL that contains 'team' this rule will match and hand over execution to the
matching controller. The `TeamsController` is passed an optional action, id and other id parameters to do any more matching controller. The `TeamsController` is passed an optional action, id and other id parameters to do any more
decision making. decision making.
:::yml ```yml
'teams/$Action!/$ID!/': 'TeamController' 'teams/$Action!/$ID!/': 'TeamController'
```
This does the same matching as the previous example, any URL starting with `teams` will look at this rule **but** both This does the same matching as the previous example, any URL starting with `teams` will look at this rule **but** both
`$Action` and `$ID` are required. Any requests to `team/` will result in a `404` error rather than being handed off to `$Action` and `$ID` are required. Any requests to `team/` will result in a `404` error rather than being handed off to
the `TeamController`. the `TeamController`.
:::yml ```yml
`admin/help//$Action/$ID`: 'AdminHelp' 'admin/help//$Action/$ID: 'AdminHelp'
```
Match an url starting with `/admin/help/`, but don't include `/help/` as part of the action (the shift point is set to Match an url starting with `/admin/help/`, but don't include `/help/` as part of the action (the shift point is set to
start parsing variables and the appropriate controller action AFTER the `//`). start parsing variables and the appropriate controller action AFTER the `//`).
@ -152,19 +157,22 @@ This is useful when you want to provide custom actions for the mapping of `teams
**mysite/code/controllers/TeamController.php** **mysite/code/controllers/TeamController.php**
:::php ```
<?php <?php
class TeamController extends Controller { use SilverStripe\Control\Controller;
private static $allowed_actions = array( class TeamController extends Controller
'payroll' {
); private static $allowed_actions = array(
'payroll'
);
private static $url_handlers = array( private static $url_handlers = array(
'staff/$ID/$Name' => 'payroll', 'staff/$ID/$Name' => 'payroll',
'coach/$ID/$Name' => 'payroll' 'coach/$ID/$Name' => 'payroll'
); );
```
The syntax for the `$url_handlers` array users the same pattern matches as the `YAML` configuration rules. The syntax for the `$url_handlers` array users the same pattern matches as the `YAML` configuration rules.
@ -175,28 +183,34 @@ class specifies the URL pattern in `$url_handlers`. Notice that it defines 5
parameters. parameters.
:::php ```php
class FeedController extends ContentController { use SilverStripe\CMS\Controllers\ContentController;
private static $allowed_actions = array('go'); class FeedController extends ContentController
private static $url_handlers = array( {
'go/$UserName/$AuthToken/$Timestamp/$OutputType/$DeleteMode' => 'go' private static $allowed_actions = array('go');
); private static $url_handlers = array(
public function go() { 'go/$UserName/$AuthToken/$Timestamp/$OutputType/$DeleteMode' => 'go'
$this->validateUser( );
$this->getRequest()->param('UserName'),
$this->getRequest()->param('AuthToken') public function go()
); {
/* more processing goes here */ $this->validateUser(
} $this->getRequest()->param('UserName'),
$this->getRequest()->param('AuthToken')
);
/* more processing goes here */
}
}
The YAML rule, in contrast, is simple. It needs to provide only enough The YAML rule, in contrast, is simple. It needs to provide only enough
information for the framework to choose the desired controller. information for the framework to choose the desired controller.
:::yaml ```yml
Director: Director:
rules: rules:
'feed': 'FeedController' 'feed': 'FeedController'
```
## Links ## Links

View File

@ -14,56 +14,66 @@ See the [Forms Tutorial](../../tutorials/forms/) for a step by step process of c
Creating a [api:Form] has the following signature. Creating a [api:Form] has the following signature.
:::php :::php
$form = new Form( $form = new Form(
$controller, // the Controller to render this form on $controller, // the Controller to render this form on
$name, // name of the method that returns this form on the controller $name, // name of the method that returns this form on the controller
FieldList $fields, // list of FormField instances FieldList $fields, // list of FormField instances
FieldList $actions, // list of FormAction instances FieldList $actions, // list of FormAction instances
$required // optional use of RequiredFields object $required // optional use of RequiredFields object
); );
In practice, this looks like: In practice, this looks like:
**mysite/code/Page.php** **mysite/code/Page.php**
:::php ```php
<?php <?php
class Page_Controller extends ContentController { use SilverStripe\CMS\Controllers\ContentController;
use SilverStripe\Forms\FieldList;
private static $allowed_actions = array( use SilverStripe\Forms\Form;
'HelloForm' use SilverStripe\Forms\FormAction;
); use SilverStripe\Forms\RequiredFields;
use SilverStripe\Forms\TextField;
public function HelloForm() {
$fields = new FieldList(
TextField::create('Name', 'Your Name')
);
$actions = new FieldList( class PageController extends ContentController
FormAction::create("doSayHello")->setTitle("Say hello") {
); private static $allowed_actions = array(
'HelloForm'
);
public function HelloForm()
{
$fields = new FieldList(
TextField::create('Name', 'Your Name')
);
$required = new RequiredFields('Name'); $actions = new FieldList(
FormAction::create('doSayHello')->setTitle('Say hello')
);
$form = new Form($this, 'HelloForm', $fields, $actions, $required); $required = new RequiredFields('Name');
return $form;
}
public function doSayHello($data, Form $form) {
$form->sessionMessage('Hello '. $data['Name'], 'success');
return $this->redirectBack(); $form = new Form($this, 'HelloForm', $fields, $actions, $required);
}
} return $form;
}
public function doSayHello($data, Form $form)
{
$form->sessionMessage('Hello ' . $data['Name'], 'success');
return $this->redirectBack();
}
}
```
**mysite/templates/Page.ss** **mysite/templates/Page.ss**
:::ss ```ss
$HelloForm $HelloForm
```
<div class="info" markdown="1"> <div class="info" markdown="1">
The examples above use `FormField::create()` instead of the `new` operator (`new FormField()`). These are functionally The examples above use `FormField::create()` instead of the `new` operator (`new FormField()`). These are functionally
@ -80,10 +90,11 @@ the [api:FormActions]. The URL is known as the `$controller` instance will know
Because the `HelloForm()` method will be the location the user is taken to, it needs to be handled like any other Because the `HelloForm()` method will be the location the user is taken to, it needs to be handled like any other
controller action. To grant it access through URLs, we add it to the `$allowed_actions` array. controller action. To grant it access through URLs, we add it to the `$allowed_actions` array.
:::php ```php
private static $allowed_actions = array( private static $allowed_actions = array(
'HelloForm' 'HelloForm'
); );
```
<div class="notice" markdown="1"> <div class="notice" markdown="1">
Form actions (`doSayHello`), on the other hand, should _not_ be included in `$allowed_actions`; these are handled Form actions (`doSayHello`), on the other hand, should _not_ be included in `$allowed_actions`; these are handled
@ -96,8 +107,9 @@ separately through [api:Form::httpSubmission()].
Fields in a [api:Form] are represented as a single [api:FieldList] instance containing subclasses of [api:FormField]. Fields in a [api:Form] are represented as a single [api:FieldList] instance containing subclasses of [api:FormField].
Some common examples are [api:TextField] or [api:DropdownField]. Some common examples are [api:TextField] or [api:DropdownField].
:::php ```php
TextField::create($name, $title, $value); TextField::create($name, $title, $value);
```
<div class="info" markdown='1'> <div class="info" markdown='1'>
A list of the common FormField subclasses is available on the [Common Subclasses](field_types/common_subclasses/) page. A list of the common FormField subclasses is available on the [Common Subclasses](field_types/common_subclasses/) page.
@ -106,48 +118,52 @@ A list of the common FormField subclasses is available on the [Common Subclasses
The fields are added to the [api:FieldList] `fields` property on the `Form` and can be modified at up to the point the The fields are added to the [api:FieldList] `fields` property on the `Form` and can be modified at up to the point the
`Form` is rendered. `Form` is rendered.
:::php ```php
$fields = new FieldList( $fields = new FieldList(
TextField::create('Name'), TextField::create('Name'),
EmailField::create('Email') EmailField::create('Email')
); );
$form = new Form($controller, 'MethodName', $fields, ...); $form = new Form($controller, 'MethodName', $fields, ...);
// or use `setFields` // or use `setFields`
$form->setFields($fields); $form->setFields($fields);
// to fetch the current fields.. // to fetch the current fields..
$fields = $form->getFields(); $fields = $form->getFields();
```
A field can be appended to the [api:FieldList]. A field can be appended to the [api:FieldList].
:::php ```php
$fields = $form->Fields(); $fields = $form->Fields();
// add a field // add a field
$fields->push(TextField::create(..)); $fields->push(TextField::create(/* ... */));
// insert a field before another one // insert a field before another one
$fields->insertBefore(TextField::create(..), 'Email'); $fields->insertBefore(TextField::create(/* ... */), 'Email');
// insert a field after another one // insert a field after another one
$fields->insertAfter(TextField::create(..), 'Name'); $fields->insertAfter(TextField::create(/* ... */), 'Name');
// insert a tab before the main content tab (used to position tabs in the CMS) // insert a tab before the main content tab (used to position tabs in the CMS)
$fields->insertBefore(Tab::create(...), 'Main'); $fields->insertBefore(Tab::create(/* ... */), 'Main');
// Note: you need to create and position the new tab prior to adding fields via addFieldToTab() // Note: you need to create and position the new tab prior to adding fields via addFieldToTab()
```
Fields can be fetched after they have been added in. Fields can be fetched after they have been added in.
:::php ```php
$email = $form->Fields()->dataFieldByName('Email'); $email = $form->Fields()->dataFieldByName('Email');
$email->setTitle('Your Email Address'); $email->setTitle('Your Email Address');
```
Fields can be removed from the form. Fields can be removed from the form.
:::php ```php
$form->getFields()->removeByName('Email'); $form->getFields()->removeByName('Email');
```
<div class="alert" markdown="1"> <div class="alert" markdown="1">
Forms can be tabbed (such as the CMS interface). In these cases, there are additional functions such as `addFieldToTab` Forms can be tabbed (such as the CMS interface). In these cases, there are additional functions such as `addFieldToTab`
@ -164,13 +180,14 @@ default `FormField` object has several methods for doing common operations.
Most of the `set` operations will return the object back so methods can be chained. Most of the `set` operations will return the object back so methods can be chained.
</div> </div>
:::php ```php
$field = new TextField(..); $field = new TextField(..);
$field $field
->setMaxLength(100) ->setMaxLength(100)
->setAttribute('placeholder', 'Enter a value..') ->setAttribute('placeholder', 'Enter a value..')
->setTitle(''); ->setTitle('');
```
### Custom Templates ### Custom Templates
@ -178,61 +195,67 @@ The [api:Form] HTML markup and each of the [api:FormField] instances are rendere
templates by using the `setTemplate` method on either the `Form` or `FormField`. For more details on providing custom templates by using the `setTemplate` method on either the `Form` or `FormField`. For more details on providing custom
templates see [Form Templates](form_templates) templates see [Form Templates](form_templates)
:::php ```php
$form = new Form(..); $form = new Form(..);
$form->setTemplate('CustomForm'); $form->setTemplate('CustomForm');
// or, for a FormField // or, for a FormField
$field = new TextField(..); $field = new TextField(..);
$field->setTemplate('CustomTextField'); $field->setTemplate('CustomTextField');
$field->setFieldHolderTemplate('CustomTextField_Holder'); $field->setFieldHolderTemplate('CustomTextField_Holder');
```
## Adding FormActions ## Adding FormActions
[api:FormAction] objects are displayed at the bottom of the `Form` in the form of a `button` or `input` tag. When a [api:FormAction] objects are displayed at the bottom of the `Form` in the form of a `button` or `input` tag. When a
user presses the button, the form is submitted to the corresponding method. user presses the button, the form is submitted to the corresponding method.
:::php ```php
FormAction::create($action, $title); FormAction::create($action, $title);
```
As with [api:FormField], the actions for a `Form` are stored within a [api:FieldList] instance in the `actions` property As with [api:FormField], the actions for a `Form` are stored within a [api:FieldList] instance in the `actions` property
on the form. on the form.
:::php ```php
public function MyForm() { public function MyForm()
$fields = new FieldList(..); {
$fields = new FieldList(/* .. */);
$actions = new FieldList( $actions = new FieldList(
FormAction::create('doSubmitForm', 'Submit') FormAction::create('doSubmitForm', 'Submit')
); );
$form = new Form($controller, 'MyForm', $fields, $actions); $form = new Form($controller, 'MyForm', $fields, $actions);
// Get the actions // Get the actions
$actions = $form->Actions(); $actions = $form->Actions();
// As actions is a FieldList, push, insertBefore, removeByName and other // As actions is a FieldList, push, insertBefore, removeByName and other
// methods described for `Fields` also work for actions. // methods described for `Fields` also work for actions.
$actions->push( $actions->push(
FormAction::create('doSecondaryFormAction', 'Another Button') FormAction::create('doSecondaryFormAction', 'Another Button')
); );
$actions->removeByName('doSubmitForm'); $actions->removeByName('doSubmitForm');
$form->setActions($actions); $form->setActions($actions);
return $form return $form
} }
public function doSubmitForm($data, $form) { public function doSubmitForm($data, $form)
// {
} //
}
public function doSecondaryFormAction($data, $form) { public function doSecondaryFormAction($data, $form)
// {
} //
}
```
The first `$action` argument for creating a `FormAction` is the name of the method to invoke when submitting the form The first `$action` argument for creating a `FormAction` is the name of the method to invoke when submitting the form
with the particular button. In the previous example, clicking the 'Another Button' would invoke the with the particular button. In the previous example, clicking the 'Another Button' would invoke the
@ -252,45 +275,55 @@ The `$action` method takes two arguments:
* `$data` an array containing the values of the form mapped from `$name => $value` * `$data` an array containing the values of the form mapped from `$name => $value`
* `$form` the submitted [api:Form] instance. * `$form` the submitted [api:Form] instance.
:::php ```php
<?php <?php
class Page_Controller extends ContentController { use SilverStripe\CMS\Controllers\ContentController;
use SilverStripe\Forms\EmailField;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\Form;
use SilverStripe\Forms\FormAction;
use SilverStripe\Forms\TextField;
private static $allowed_actions = array( class PageController extends ContentController
'MyForm' {
); private static $allowed_actions = array(
'MyForm'
);
public function MyForm() { public function MyForm()
$fields = new FieldList( {
TextField::create('Name'), $fields = new FieldList(
EmailField::create('Email') TextField::create('Name'),
); EmailField::create('Email')
);
$actions = new FieldList( $actions = new FieldList(
FormAction::create('doSubmitForm', 'Submit') FormAction::create('doSubmitForm', 'Submit')
); );
$form = new Form($controller, 'MyForm', $fields, $actions); $form = new Form($controller, 'MyForm', $fields, $actions);
return $form return $form
} }
public function doSubmitForm($data, $form) { public function doSubmitForm($data, $form)
// Submitted data is available as a map. {
echo $data['Name']; // Submitted data is available as a map.
echo $data['Email']; echo $data['Name'];
echo $data['Email'];
// You can also fetch the value from the field. // You can also fetch the value from the field.
echo $form->Fields()->dataFieldByName('Email')->Value(); echo $form->Fields()->dataFieldByName('Email')->Value();
// Using the Form instance you can get / set status such as error messages. // Using the Form instance you can get / set status such as error messages.
$form->sessionMessage("Successful!", 'good'); $form->sessionMessage('Successful!', 'good');
// After dealing with the data you can redirect the user back. // After dealing with the data you can redirect the user back.
return $this->redirectBack(); return $this->redirectBack();
} }
} }
```
## Validation ## Validation
@ -300,12 +333,14 @@ validating its' own data value.
For more information, see the [Form Validation](validation) documentation. For more information, see the [Form Validation](validation) documentation.
:::php ```php
$validator = new RequiredFields(array( $validator = new RequiredFields(array(
'Name', 'Email' 'Name',
)); 'Email'
));
$form = new Form($this, 'MyForm', $fields, $actions, $validator); $form = new Form($this, 'MyForm', $fields, $actions, $validator);
```
## API Documentation ## API Documentation

View File

@ -7,43 +7,53 @@ SilverStripe provides server-side form validation out of the box through the [ap
[api:RequiredFields]. A single `Validator` instance is set on each `Form`. Validators are implemented as an argument to [api:RequiredFields]. A single `Validator` instance is set on each `Form`. Validators are implemented as an argument to
the [api:Form] constructor or through the function `setValidator`. the [api:Form] constructor or through the function `setValidator`.
:::php ```php
<?php <?php
class Page_Controller extends ContentController { use SilverStripe\CMS\Controllers\ContentController;
use SilverStripe\Forms\EmailField;
use SilverStripe\Forms\Form;
use SilverStripe\Forms\FormAction;
use SilverStripe\Forms\TextField;
use SilverStripe\Forms\RequiredFields;
private static $allowed_actions = array( class PageController extends ContentController
'MyForm' {
); private static $allowed_actions = array(
'MyForm'
);
public function MyForm() { public function MyForm()
$fields = new FieldList( {
TextField::create('Name'), $fields = new FieldList(
EmailField::create('Email') TextField::create('Name'),
); EmailField::create('Email')
);
$actions = new FieldList( $actions = new FieldList(
FormAction::create('doSubmitForm', 'Submit') FormAction::create('doSubmitForm', 'Submit')
); );
// the fields 'Name' and 'Email' are required. // the fields 'Name' and 'Email' are required.
$required = new RequiredFields(array( $required = new RequiredFields(array(
'Name', 'Email' 'Name', 'Email'
)); ));
// $required can be set as an argument // $required can be set as an argument
$form = new Form($controller, 'MyForm', $fields, $actions, $required); $form = new Form($controller, 'MyForm', $fields, $actions, $required);
// Or, through a setter. // Or, through a setter.
$form->setValidator($required); $form->setValidator($required);
return $form; return $form;
} }
public function doSubmitForm($data, $form) { public function doSubmitForm($data, $form)
//.. {
} //..
} }
}
```
In this example we will be required to input a value for `Name` and a valid email address for `Email` before the In this example we will be required to input a value for `Name` and a valid email address for `Email` before the
`doSubmitForm` method is called. `doSubmitForm` method is called.
@ -63,15 +73,17 @@ The data value of the `FormField` submitted is not passed into validate. It is s
the `setValue` method. the `setValue` method.
</div> </div>
:::php ```php
public function validate($validator) { public function validate($validator)
if($this->Value() == 10) { {
$validator->validationError($this->Name(), 'This value cannot be 10'); if ((int) $this->Value() === 10) {
return false; $validator->validationError($this->Name(), 'This value cannot be 10');
} return false;
}
return true; return true;
} }
```
The `validate` method should return `true` if the value passes any validation and `false` if SilverStripe should trigger The `validate` method should return `true` if the value passes any validation and `false` if SilverStripe should trigger
a validation error on the page. In addition a useful error message must be set on the given validator. a validation error on the page. In addition a useful error message must be set on the given validator.
@ -86,80 +98,94 @@ two ways to go about this:
A custom `FormField` which handles the validation. This means the `FormField` can be reused throughout the site and have A custom `FormField` which handles the validation. This means the `FormField` can be reused throughout the site and have
the same validation logic applied to it throughout. the same validation logic applied to it throughout.
**mysite/code/formfields/CustomNumberField.php** **mysite/code/CustomNumberField.php**
:::php ```php
<?php <?php
class CustomNumberField extends TextField { use SilverStripe\Forms\TextField;
public function validate($validator) { class CustomNumberField extends TextField
if(!is_numeric($this->value)) { {
$validator->validationError( public function validate($validator)
$this->name, "Not a number. This must be between 2 and 5", "validation", false {
); if (!is_numeric($this->value)) {
$validator->validationError(
return false; $this->name, 'Not a number. This must be between 2 and 5', 'validation', false
} );
else if($this->value > 5 || $this->value < 2) {
$validator->validationError( return false;
$this->name, "Your number must be between 2 and 5", "validation", false } elseif ($this->value > 5 || $this->value < 2) {
); $validator->validationError(
$this->name, 'Your number must be between 2 and 5', 'validation', false
);
return false; return false;
} }
return true; return true;
} }
} }
```
Or, an alternative approach to the custom class is to define the behavior inside the Form's action method. This is less Or, an alternative approach to the custom class is to define the behavior inside the Form's action method. This is less
reusable and would not be possible within the `CMS` or other automated `UI` but does not rely on creating custom reusable and would not be possible within the `CMS` or other automated `UI` but does not rely on creating custom
`FormField` classes. `FormField` classes.
:::php ```php
<?php <?php
class Page_Controller extends ContentController { use SilverStripe\CMS\Controllers\ContentController;
use SilverStripe\Forms\EmailField;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\Form;
use SilverStripe\Forms\FormAction;
use SilverStripe\Forms\TextField;
use SilverStripe\Security\Member;
private static $allowed_actions = array( class Page_Controller extends ContentController
'MyForm' {
); private static $allowed_actions = array(
'MyForm'
);
public function MyForm() { public function MyForm()
$fields = new FieldList( {
TextField::create('Name'), $fields = new FieldList(
EmailField::create('Email') TextField::create('Name'),
); EmailField::create('Email')
);
$actions = new FieldList( $actions = new FieldList(
FormAction::create('doSubmitForm', 'Submit') FormAction::create('doSubmitForm', 'Submit')
); );
$form = new Form($controller, 'MyForm', $fields, $actions); $form = new Form($controller, 'MyForm', $fields, $actions);
return $form; return $form;
} }
public function doSubmitForm($data, $form) { public function doSubmitForm($data, $form)
// At this point, RequiredFields->isValid() will have been called already, {
// so we can assume that the values exist. Say we want to make sure that email hasn't already been used. // At this point, RequiredFields->isValid() will have been called already,
// so we can assume that the values exist. Say we want to make sure that email hasn't already been used.
$check = Member::get()->filter('Email', $data['Email'])->first();
$check = Member::get()->filter('Email', $data['Email'])->first();
if($check) { if ($check) {
$form->addErrorMessage('Email', 'This email already exists', 'bad'); $form->addErrorMessage('Email', 'This email already exists', 'bad');
return $this->redirectBack(); return $this->redirectBack();
} }
$form->sessionMessage("You have been added to our mailing list", 'good'); $form->sessionMessage('You have been added to our mailing list', 'good');
return $this->redirectBack(); return $this->redirectBack();
} }
} }
```
## Exempt validation actions ## Exempt validation actions
In some cases you might need to disable validation for specific actions. E.g. actions which discard submitted In some cases you might need to disable validation for specific actions. E.g. actions which discard submitted
@ -167,32 +193,32 @@ data may not need to check the validity of the posted content.
You can disable validation on individual using one of two methods: You can disable validation on individual using one of two methods:
```php
$actions = new FieldList(
$action = FormAction::create('doSubmitForm', 'Submit')
);
$form = new Form($controller, 'MyForm', $fields, $actions);
:::php // Disable actions on the form action themselves
$actions = new FieldList( $action->setValidationExempt(true);
$action = FormAction::create('doSubmitForm', 'Submit')
);
$form = new Form($controller, 'MyForm', $fields, $actions);
// Disable actions on the form action themselves
$action->setValidationExempt(true);
// Alternatively, you can whitelist individual actions on the form object by name
$form->setValidationExemptActions(['doSubmitForm']);
// Alternatively, you can whitelist individual actions on the form object by name
$form->setValidationExemptActions(['doSubmitForm']);
```
## Server-side validation messages ## Server-side validation messages
If a `FormField` fails to pass `validate()` the default error message is returned. If a `FormField` fails to pass `validate()` the default error message is returned.
:::php ```
'$Name' is required '$Name' is required
```
Use `setCustomValidationMessage` to provide a custom message. Use `setCustomValidationMessage` to provide a custom message.
:::php ```php
$field = new TextField(..); $field = new TextField(/* .. */);
$field->setCustomValidationMessage('Whoops, looks like you have missed me!'); $field->setCustomValidationMessage('Whoops, looks like you have missed me!');
## JavaScript validation ## JavaScript validation
@ -201,15 +227,15 @@ to provide the information required in order to plug in custom libraries like [P
[jQuery.Validate](http://jqueryvalidation.org/). Most of these libraries work on HTML `data-` attributes or special [jQuery.Validate](http://jqueryvalidation.org/). Most of these libraries work on HTML `data-` attributes or special
classes added to each input. For Parsley we can structure the form like. classes added to each input. For Parsley we can structure the form like.
:::php ```php
$form = new Form(..); $form = new Form(/* .. */);
$form->setAttribute('data-parsley-validate', true); $form->setAttribute('data-parsley-validate', true);
$field = $fields->dataFieldByName('Name'); $field = $fields->dataFieldByName('Name');
$field->setAttribute('required', true);
$field->setAttribute('data-parsley-mincheck', '2');
$field->setAttribute('required', true);
$field->setAttribute('data-parsley-mincheck', '2');
```
## Model Validation ## Model Validation
@ -228,23 +254,26 @@ error message, or a [api:ValidationResult] object containing the list of errors
E.g. E.g.
```php
use SilverStripe\Control\Controller;
use SilverStripe\ORM\ValidationException;
:::php class MyController extends Controller
class MyController extends Controller {
public function doSave($data, $form)
{ {
public function doSave($data, $form) { $success = $this->sendEmail($data);
$success = $this->sendEmail($data);
// Example error handling
// Example error handling if (!$success) {
if (!$success) { throw new ValidationException('Sorry, we could not email to that address');
throw new ValidationException('Sorry, we could not email to that address');
}
// If success
return $this->redirect($this->Link('success'));
} }
// If success
return $this->redirect($this->Link('success'));
} }
}
```
### Validation in the CMS ### Validation in the CMS
@ -257,28 +286,36 @@ respect the provided `Validator` and handle displaying error and success respons
Again, custom error messages can be provided through the `FormField` Again, custom error messages can be provided through the `FormField`
</div> </div>
:::php ```php
<?php <?php
class Page extends SiteTree { use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Forms\TextField;
use SilverStripe\Forms\RequiredFields;
private static $db = array( class Page extends SiteTree
'MyRequiredField' => 'Text' {
); private static $db = array(
'MyRequiredField' => 'Text'
);
public function getCMSFields() { public function getCMSFields()
$fields = parent::getCMSFields(); {
$fields = parent::getCMSFields();
$fields->addFieldToTab('Root.Main', $fields->addFieldToTab('Root.Main',
TextField::create('MyRequiredField')->setCustomValidationMessage('You missed me.') TextField::create('MyRequiredField')->setCustomValidationMessage('You missed me.')
); );
} }
public function getCMSValidator() { public function getCMSValidator()
return new RequiredFields(array( {
'MyRequiredField' return new RequiredFields(array(
)); 'MyRequiredField'
} ));
}
}
```
## API Documentation ## API Documentation

View File

@ -431,7 +431,7 @@ code could be used:
:::php :::php
class GalleryPage extends Page {} class GalleryPage extends Page {}
class GalleryPage_Controller extends Page_Controller { class GalleryPageController extends PageController {
private static $allowed_actions = array('Form'); private static $allowed_actions = array('Form');
public function Form() { public function Form() {
$fields = new FieldList( $fields = new FieldList(

View File

@ -12,7 +12,7 @@ code for a `Form` is to create it as a subclass to `Form`. Let's look at a examp
:::php :::php
<?php <?php
class Page_Controller extends ContentController { class PageController extends ContentController {
public function SearchForm() { public function SearchForm() {
$fields = new FieldList( $fields = new FieldList(
@ -128,7 +128,7 @@ Our controller will now just have to create a new instance of this form object.
:::php :::php
<?php <?php
class Page_Controller extends ContentController { class PageController extends ContentController {
private static $allowed_actions = array( private static $allowed_actions = array(
'SearchForm', 'SearchForm',

View File

@ -8,7 +8,7 @@ Let's start by defining a new `ContactPage` page type:
<?php <?php
class ContactPage extends Page { class ContactPage extends Page {
} }
class ContactPage_Controller extends Page_Controller { class ContactPageController extends PageController {
private static $allowed_actions = array('Form'); private static $allowed_actions = array('Form');
public function Form() { public function Form() {
$fields = new FieldList( $fields = new FieldList(
@ -61,7 +61,7 @@ If you now create a ContactPage in the CMS (making sure you have rebuilt the dat
Now that we have a contact form, we need some way of collecting the data submitted. We do this by creating a function on the controller with the same name as the form action. In this case, we create the function 'submit' on the ContactPage_Controller class. Now that we have a contact form, we need some way of collecting the data submitted. We do this by creating a function on the controller with the same name as the form action. In this case, we create the function 'submit' on the ContactPage_Controller class.
:::php :::php
class ContactPage_Controller extends Page_Controller { class ContactPageController extends PageController {
private static $allowed_actions = array('Form'); private static $allowed_actions = array('Form');
public function Form() { public function Form() {
// ... // ...

View File

@ -83,7 +83,7 @@ If your caching logic is complex or re-usable, you can define a method on your c
fragment. fragment.
For example, a block that shows a collection of rotating slides needs to update whenever the relationship For example, a block that shows a collection of rotating slides needs to update whenever the relationship
`Page::$many_many = array('Slides' => 'Slide')` changes. In Page_Controller: `Page::$many_many = array('Slides' => 'Slide')` changes. In `PageController`:
:::php :::php
@ -151,7 +151,7 @@ heavy load:
<% cached 'blogstatistics', $Blog.ID if $HighLoad %> <% cached 'blogstatistics', $Blog.ID if $HighLoad %>
By adding a `HighLoad` function to your `Page_Controller`, you could enable or disable caching dynamically. By adding a `HighLoad` function to your `PageController`, you could enable or disable caching dynamically.
To cache the contents of a page for all anonymous users, but dynamically calculate the contents for logged in members, To cache the contents of a page for all anonymous users, but dynamically calculate the contents for logged in members,
use something like: use something like:

View File

@ -14,27 +14,35 @@ The simple usage, Permission::check("PERM_CODE") will detect if the currently lo
**Group ACLs** **Group ACLs**
* Call **Permission::check("MY_PERMISSION_CODE")** to see if the current user has MY_PERMISSION_CODE. * Call **Permission::check('MY_PERMISSION_CODE')** to see if the current user has MY_PERMISSION_CODE.
* MY_PERMISSION_CODE can be loaded into the Security admin on the appropriate group, using the "Permissions" tab. * `MY_PERMISSION_CODE` can be loaded into the Security admin on the appropriate group, using the "Permissions" tab.
## PermissionProvider ## PermissionProvider
[api:PermissionProvider] is an interface which lets you define a method *providePermissions()*. [api:PermissionProvider] is an interface which lets you define a method *providePermissions()*.
This method should return a map of permission code names with a human readable explanation of its purpose. This method should return a map of permission code names with a human readable explanation of its purpose.
:::php ```php
class Page_Controller implements PermissionProvider { use SilverStripe\Security\PermissionProvider;
public function init() {
parent::init();
if(!Permission::check("VIEW_SITE")) Security::permissionFailure();
}
public function providePermissions() { class PageController implements PermissionProvider
return array( {
"VIEW_SITE" => "Access the site", public function init()
); {
} parent::init();
} if (!Permission::check('VIEW_SITE')) {
Security::permissionFailure();
}
}
public function providePermissions()
{
return array(
'VIEW_SITE' => 'Access the site'
);
}
}
```
This can then be used to add a dropdown for permission codes to the security panel. Permission::get_all_codes() will be This can then be used to add a dropdown for permission codes to the security panel. Permission::get_all_codes() will be
@ -89,10 +97,11 @@ This works much like ADMIN permissions (see above)
You can check if a user has access to the CMS by simply performing a check against `CMS_ACCESS`. You can check if a user has access to the CMS by simply performing a check against `CMS_ACCESS`.
:::php ```php
if (Permission::checkMember($member, 'CMS_ACCESS')) { if (Permission::checkMember($member, 'CMS_ACCESS')) {
//user can access the CMS //user can access the CMS
} }
```
Internally, this checks that the user has any of the defined `CMS_ACCESS_*` permissions. Internally, this checks that the user has any of the defined `CMS_ACCESS_*` permissions.

View File

@ -57,7 +57,7 @@ You can use [api:RSSFeed] to easily create a feed showing your latest Page updat
.. ..
class Page_Controller extends ContentController { class PageController extends ContentController {
private static $allowed_actions = array( private static $allowed_actions = array(
'rss' 'rss'
@ -118,7 +118,7 @@ Then in our controller, we add a new action which returns a the XML list of `Pla
:::php :::php
<?php <?php
class Page_Controller extends ContentController { class PageController extends ContentController {
private static $allowed_actions = array( private static $allowed_actions = array(
'players' 'players'

View File

@ -79,9 +79,9 @@ the `$fields` constructor parameter.
:::php :::php
<?php <?php
.. // ..
class Page_Controller extends ContentController { class PageController extends ContentController {
public function SearchForm() { public function SearchForm() {
$context = singleton('MyDataObject')->getCustomSearchContext(); $context = singleton('MyDataObject')->getCustomSearchContext();

View File

@ -59,7 +59,7 @@ authorised users, the following should be considered:
:::php :::php
class Page_Controller extends ContentController { class PageController extends ContentController {
public function init() { public function init() {
parent::init(); parent::init();
// Whitelist any protected files on this page for the current user // Whitelist any protected files on this page for the current user
@ -88,7 +88,7 @@ authorised users, the following should be considered:
:::php :::php
class Page_Controller extends ContentController { class PageController extends ContentController {
public function init() { public function init() {
parent::init(); parent::init();
// Whitelist any protected files on this page for the current user // Whitelist any protected files on this page for the current user

View File

@ -23,7 +23,7 @@ use SilverStripe\Core\Object;
* If you want to implement a FileField into a form element, you need to pass it an array of source data. * If you want to implement a FileField into a form element, you need to pass it an array of source data.
* *
* <code> * <code>
* class ExampleForm_Controller extends Page_Controller { * class ExampleFormController extends PageController {
* *
* function Form() { * function Form() {
* $fields = new FieldList( * $fields = new FieldList(

View File

@ -518,7 +518,7 @@ class Security extends Controller implements TemplateGlobalProvider
$tmpPage = new SiteTree(); $tmpPage = new SiteTree();
$tmpPage->Title = $title; $tmpPage->Title = $title;
/** @skipUpgrade */ /** @skipUpgrade */
$tmpPage->URLSegment = "Security"; $tmpPage->URLSegment = 'Security';
// Disable ID-based caching of the log-in page by making it a random number // Disable ID-based caching of the log-in page by making it a random number
$tmpPage->ID = -1 * rand(1, 10000000); $tmpPage->ID = -1 * rand(1, 10000000);

View File

@ -253,9 +253,9 @@ class SSViewer implements Flushable
$templates[] = $template; $templates[] = $template;
$templates[] = ['type' => 'Includes', $template]; $templates[] = ['type' => 'Includes', $template];
// If the class is "Page_Controller", look for Page.ss // If the class is "PageController" (PSR-2 compatibility) or "Page_Controller" (legacy), look for Page.ss
if (stripos($class, '_controller') !== false) { if (preg_match('/^(?<name>.+[^\\\\])_?Controller$/iU', $class, $matches)) {
$templates[] = str_ireplace('_controller', '', $class) . $suffix; $templates[] = $matches['name'] . $suffix;
} }
if ($baseClass && $class == $baseClass) { if ($baseClass && $class == $baseClass) {

View File

@ -23,7 +23,7 @@ use SilverStripe\View\Requirements_Backend;
use SilverStripe\View\SSViewer; use SilverStripe\View\SSViewer;
use SilverStripe\View\Requirements; use SilverStripe\View\Requirements;
use SilverStripe\View\Tests\SSViewerTest\SSViewerTestModel; use SilverStripe\View\Tests\SSViewerTest\SSViewerTestModel;
use SilverStripe\View\Tests\SSViewerTest\SSViewerTestModel_Controller; use SilverStripe\View\Tests\SSViewerTest\SSViewerTestModelController;
use SilverStripe\View\ViewableData; use SilverStripe\View\ViewableData;
use SilverStripe\View\SSViewer_FromString; use SilverStripe\View\SSViewer_FromString;
use SilverStripe\View\SSTemplateParser; use SilverStripe\View\SSTemplateParser;
@ -1526,17 +1526,15 @@ after'
public function testLayout() public function testLayout()
{ {
$self = $this;
$this->useTestTheme( $this->useTestTheme(
__DIR__.'/SSViewerTest', __DIR__.'/SSViewerTest',
'layouttest', 'layouttest',
function () use ($self) { function () {
$template = new SSViewer(array('Page')); $template = new SSViewer(array('Page'));
$self->assertEquals("Foo\n\n", $template->process(new ArrayData(array()))); $this->assertEquals("Foo\n\n", $template->process(new ArrayData(array())));
$template = new SSViewer(array('Shortcodes', 'Page')); $template = new SSViewer(array('Shortcodes', 'Page'));
$self->assertEquals("[file_link]\n\n", $template->process(new ArrayData(array()))); $this->assertEquals("[file_link]\n\n", $template->process(new ArrayData(array())));
} }
); );
} }
@ -1546,48 +1544,47 @@ after'
*/ */
public function testGetTemplatesByClass() public function testGetTemplatesByClass()
{ {
$self = $this;
$this->useTestTheme( $this->useTestTheme(
__DIR__.'/SSViewerTest', __DIR__ . '/SSViewerTest',
'layouttest', 'layouttest',
function () use ($self) { function () {
// Test passing a string // Test passing a string
$templates = SSViewer::get_templates_by_class( $templates = SSViewer::get_templates_by_class(
SSViewerTestModel_Controller::class, SSViewerTestModelController::class,
'', '',
Controller::class Controller::class
); );
$self->assertEquals( $this->assertEquals(
[ [
SSViewerTestModel_Controller::class, SSViewerTestModelController::class,
[ [
'type' => 'Includes', 'type' => 'Includes',
SSViewerTestModel_Controller::class, SSViewerTestModelController::class,
], ],
SSViewerTestModel::class, SSViewerTestModel::class,
Controller::class, Controller::class,
[ [
'type' => 'Includes', 'type' => 'Includes',
Controller::class, Controller::class,
], ],
], ],
$templates $templates
); );
// Test to ensure we're stopping at the base class. // Test to ensure we're stopping at the base class.
$templates = SSViewer::get_templates_by_class( $templates = SSViewer::get_templates_by_class(
SSViewerTestModel_Controller::class, SSViewerTestModelController::class,
'', '',
SSViewerTestModel_Controller::class SSViewerTestModelController::class
); );
$self->assertEquals( $this->assertEquals(
[ [
SSViewerTestModel_Controller::class, SSViewerTestModelController::class,
[ [
'type' => 'Includes', 'type' => 'Includes',
SSViewerTestModel_Controller::class, SSViewerTestModelController::class,
], ],
SSViewerTestModel::class, SSViewerTestModel::class,
], ],
$templates $templates
); );
@ -1595,27 +1592,27 @@ after'
// Make sure we can search templates by suffix. // Make sure we can search templates by suffix.
$templates = SSViewer::get_templates_by_class( $templates = SSViewer::get_templates_by_class(
SSViewerTestModel::class, SSViewerTestModel::class,
'_Controller', 'Controller',
DataObject::class DataObject::class
); );
$self->assertEquals( $this->assertEquals(
[ [
SSViewerTestModel_Controller::class, SSViewerTestModelController::class,
[ [
'type' => 'Includes', 'type' => 'Includes',
SSViewerTestModel_Controller::class, SSViewerTestModelController::class,
], ],
DataObject::class.'_Controller', DataObject::class . 'Controller',
[ [
'type' => 'Includes', 'type' => 'Includes',
DataObject::class.'_Controller', DataObject::class . 'Controller',
], ],
], ],
$templates $templates
); );
// Let's throw something random in there. // Let's throw something random in there.
$self->setExpectedException('InvalidArgumentException'); $this->setExpectedException('InvalidArgumentException');
SSViewer::get_templates_by_class(array()); SSViewer::get_templates_by_class(array());
} }
); );

View File

@ -5,7 +5,7 @@ namespace SilverStripe\View\Tests\SSViewerTest;
use SilverStripe\Dev\TestOnly; use SilverStripe\Dev\TestOnly;
use SilverStripe\Control\Controller; use SilverStripe\Control\Controller;
class SSViewerTestModel_Controller extends Controller implements TestOnly class SSViewerTestModelController extends Controller implements TestOnly
{ {
} }