diff --git a/.upgrade.yml b/.upgrade.yml index 63d2cf9f7..6b02a842a 100644 --- a/.upgrade.yml +++ b/.upgrade.yml @@ -1087,6 +1087,7 @@ mappings: ConfigTest_TestNest: SilverStripe\Core\Tests\Config\ConfigTest\TestNest ConfigTest: SilverStripe\Core\Tests\Config\ConfigTest ConfigTest_Config_MemCache: SilverStripe\Core\Tests\Config\ConfigTest\ConfigTestMemCache + Page_Controller: PageController skipConfigs: - db - casting diff --git a/docs/en/01_Tutorials/01_Building_A_Basic_Site.md b/docs/en/01_Tutorials/01_Building_A_Basic_Site.md index d65f0a71e..37c37d4ab 100644 --- a/docs/en/01_Tutorials/01_Building_A_Basic_Site.md +++ b/docs/en/01_Tutorials/01_Building_A_Basic_Site.md @@ -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: - :::php - 'Firefox', - 'Chrome' => 'Chrome', - 'Internet Explorer' => 'Internet Explorer', - 'Safari' => 'Safari', - 'Opera' => 'Opera', - 'Lynx' => 'Lynx' - )) - ); + // ... - // Create actions - $actions = new FieldList( - new FormAction('doBrowserPoll', 'Submit') - ); + 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' + )) + ); - 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. ```php - // 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 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' + )) +); ``` 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 array mapping the values to the options listed in the dropdown. - ```php - $actions = new FieldList( - new FormAction('doBrowserPoll', 'Submit'); - ); +```php +$actions = new FieldList( + new FormAction('doBrowserPoll', 'Submit'); +); ``` 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. ```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. @@ -107,14 +115,14 @@ Add the following code to the top of your home page template, just before `
-

Browser Poll

- $BrowserPollForm -
-
- ... +```ss +... +
+

Browser Poll

+ $BrowserPollForm +
+
+... ``` 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** - ```css - /* BROWSER POLL */ - #BrowserPoll { - float: right; - margin: 20px 10px 0 0; - width: 20%; - } - form FieldList { - border:0; - } +```css +/* BROWSER POLL */ +#BrowserPoll { + float: right; + margin: 20px 10px 0 0; + width: 20%; +} +form FieldList { + border: 0; +} - #BrowserPoll .message { - float:left; +#BrowserPoll .message { + float: left; display: block; - color:red; - background:#efefef; - border:1px solid #ccc; - padding:5px; - margin:5px; - } + color: red; + background: #efefef; + border: 1px solid #ccc; + padding: 5px; + margin: 5px; +} - #BrowserPoll h2 { - font-size: 1.5em; - line-height:2em; - color: #0083C8; - } +#BrowserPoll h2 { + font-size: 1.5em; + line-height:2em; + color: #0083C8; +} - #BrowserPoll .field { - padding:3px 0; - } +#BrowserPoll .field { + padding:3px 0; +} - #BrowserPoll input.text { - padding: 0; - font-size:1em; - } +#BrowserPoll input.text { + padding: 0; + font-size:1em; +} - #BrowserPoll .btn-toolbar { - padding:5px 0; - } +#BrowserPoll .btn-toolbar { + padding: 5px 0; +} - #BrowserPoll .bar { - background-color: #015581; - } +#BrowserPoll .bar { + 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** - ```php - 'Text', - 'Browser' => 'Text' - ); - } +```php + '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 - class HomePage_Controller extends Page_Controller { - // ... - public function doBrowserPoll($data, $form) { - $submission = new BrowserPollSubmission(); - $form->saveInto($submission); - $submission->write(); - return $this->redirectBack(); - } - } +```php +use BrowserPollSubmission; +use PageController; + +class HomePageController extends PageController +{ + // ... + 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. @@ -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. -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** - ```php - public function BrowserPollForm() { - // ... - $validator = new RequiredFields('Name', 'Browser'); - return new Form($this, 'BrowserPollForm', $fields, $actions, $validator); - } +```php +public function BrowserPollForm() +{ + // ... + $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. @@ -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. -**mysite/code/HomePage.php** +**mysite/code/HomePageController.php** - ```php - // ... - class HomePage_Controller extends Page_Controller { - // ... - public function doBrowserPoll($data, $form) { - $submission = new BrowserPollSubmission(); - $form->saveInto($submission); - $submission->write(); - Session::set('BrowserPollVoted', true); - return $this->redirectBack(); - } - } +```php +// ... +class HomePageController extends PageController +{ + // ... + public function doBrowserPoll($data, $form) + { + $submission = new BrowserPollSubmission(); + $form->saveInto($submission); + $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 it is. - ```php - // ... - class HomePage_Controller extends Page_Controller { - // ... - public function BrowserPollForm() { - if(Session::get('BrowserPollVoted')) return false; - // ... - } - } +```php +// ... +class HomePageController extends PageController +{ + // ... + 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, @@ -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. -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 - public function BrowserPollResults() { - $submissions = new GroupedList(BrowserPollSubmission::get()); - $total = $submissions->Count(); +```php +public function BrowserPollResults() +{ + $submissions = new GroupedList(BrowserPollSubmission::get()); + $total = $submissions->Count(); - $list = new ArrayList(); - foreach($submissions->groupBy('Browser') as $browserName => $browserSubmissions) { - $percentage = (int) ($browserSubmissions->Count() / $total * 100); - $list->push(new ArrayData(array( - 'Browser' => $browserName, - 'Percentage' => $percentage - ))); - } - return $list; - } + $list = new ArrayList(); + foreach($submissions->groupBy('Browser') as $browserName => $browserSubmissions) { + $percentage = (int) ($browserSubmissions->Count() / $total * 100); + $list->push(new ArrayData(array( + 'Browser' => $browserName, + 'Percentage' => $percentage + ))); + } + return $list; +} ``` This code introduces a few new concepts, so let's step through it. - ```php - $submissions = new GroupedList(BrowserPollSubmission::get()); +```php +$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). ```php - $total = $submissions->Count(); +$total = $submissions->Count(); ``` We get the total number of submissions, which is needed to calculate the percentages. - ```php - $list = new ArrayList(); - foreach($submissions->groupBy('Browser') as $browserName => $browserSubmissions) { - $percentage = (int) ($browserSubmissions->Count() / $total * 100); - $list->push(new ArrayData(array( - 'Browser' => $browserName, - 'Percentage' => $percentage - ))); - } +```php +$list = new ArrayList(); +foreach ($submissions->groupBy('Browser') as $browserName => $browserSubmissions) { + $percentage = (int) ($browserSubmissions->Count() / $total * 100); + $list->push(new ArrayData(array( + 'Browser' => $browserName, + '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. @@ -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** ```ss -
-

Browser Poll

- <% if $BrowserPollForm %> - $BrowserPollForm - <% else %> -
    - <% loop $BrowserPollResults %> -
  • -
    $Browser: $Percentage%
    -
     
    -
  • - <% end_loop %> -
- <% end_if %> -
+
+

Browser Poll

+ <% if $BrowserPollForm %> + $BrowserPollForm + <% else %> +
    + <% loop $BrowserPollResults %> +
  • +
    $Browser: $Percentage%
    +
     
    +
  • + <% end_loop %> +
+ <% end_if %> +
``` Here we first check if the *BrowserPollForm* is returned, and if it is display it. Otherwise the user has already voted, diff --git a/docs/en/01_Tutorials/05_Dataobject_Relationship_Management.md b/docs/en/01_Tutorials/05_Dataobject_Relationship_Management.md index 918978f4f..020e31927 100644 --- a/docs/en/01_Tutorials/05_Dataobject_Relationship_Management.md +++ b/docs/en/01_Tutorials/05_Dataobject_Relationship_Management.md @@ -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: - | Project | Student | Mentor | - | ------- | ------- | ------- | - | Developer Toolbar | Jakob,Ofir | Mark,Sean | + | Project | Student | Mentor | + | ------- | ------- | ------- | + | Developer Toolbar | Jakob,Ofir | Mark,Sean | | Behaviour Testing | Michal,Wojtek | Ingo, Sean | - | Content Personalization | Yuki | Philipp | + | Content Personalization | Yuki | Philipp | | Module Management | Andrew | Marcus,Sam | ### 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** - :::php - 'Varchar', - 'University' => 'Varchar', - ); - private static $has_one = array( - 'Project' => 'Project' - ); - } +```php + 'Varchar', + 'University' => 'Varchar', + ); + private static $has_one = array( + 'Project' => 'Project' + ); +} +``` **mysite/code/Project.php** - :::php - 'Student' - ); - } - class Project_Controller extends Page_Controller { - } +```php + 'Student' + ); +} +``` + +**mysite/code/ProjectController.php** + +```php +getComponentByType('GridFieldDataColumns')->setDisplayFields(array( - 'Name' => 'Name', - 'Project.Title'=> 'Project' // Retrieve from a has-one relationship - )); - // 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; - } - } +```php +getComponentByType('SilverStripe\\Forms\\GridField\\GridFieldDataColumns') + ->setDisplayFields(array( + 'Name' => 'Name', + 'Project.Title'=> 'Project' // Retrieve from a has-one relationship + )); + // 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. 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.
- 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 - with more sophisticated search and management logic, consider - using the [ModelAdmin](/developer_guides/customising_the_admin_interface/modeladmin) - interface instead. + 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 + with more sophisticated search and management logic, consider + using the [ModelAdmin](/developer_guides/customising_the_admin_interface/modeladmin) + interface instead.
![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** - :::php - 'Varchar', - ); - private static $belongs_many_many = array( - 'Projects' => 'Project' - ); - } +```php + 'Varchar', + ); + private static $belongs_many_many = array( + 'Projects' => 'Project' + ); +} +``` **mysite/code/Project.php** - :::php - class Project extends Page { - // ... - private static $many_many = array( - 'Mentors' => 'Mentor' - ); - } +```php + '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" (after you've performed a `dev/build` command, of course). @@ -231,22 +287,31 @@ to configure it a bit differently. **mysite/code/Project.php** - :::php - class Project extends Page { - // ... - public function getCMSFields() { - // ... - // Same setup, but for mentors - $mentorsField = new GridField( - 'Mentors', - 'Mentors', - $this->Mentors(), - GridFieldConfig_RelationEditor::create() - ); - $fields->addFieldToTab('Root.Mentors', $mentorsField); - return $fields; - } - } +```php +Mentors(), + GridFieldConfig_RelationEditor::create() + ); + $fields->addFieldToTab('Root.Mentors', $mentorsField); + return $fields; + } +} +``` The important difference to our student management UI is the usage 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** - :::ss - <% include SideBar %> -
-
-

$Title

-
- $Content - - - - - - - - - - <% loop $Children %> - - - - - - <% end_loop %> - -
ProjectStudentsMentors
- $Title - - <% loop $Students %> - $Name ($University)<% if $Last !=1 %>,<% end_if %> - <% end_loop %> - - <% loop $Mentors %> - $Name<% if $Last !=1 %>,<% end_if %> - <% end_loop %> -
-
-
-
+```ss +<% include SideBar %> +
+
+

$Title

+
+ $Content + + + + + + + + + + <% loop $Children %> + + + + + + <% end_loop %> + +
ProjectStudentsMentors
+ $Title + + <% loop $Students %> + $Name ($University)<% if not $Last %>, <% end_if %> + <% end_loop %> + + <% loop $Mentors %> + $Name<% if not $Last %>, <% end_if %> + <% end_loop %> +
+
+
+
+``` 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. @@ -344,36 +410,37 @@ we can access the "Students" and "Mentors" relationships directly in the templat **themes/simple/templates/Layout/Project.ss** - :::ss - <% include SideBar %> -
-
-

$Title

-
- $Content -

Students

- <% if $Students %> -
    - <% loop $Students %> -
  • $Name ($University)
  • - <% end_loop %> -
- <% else %> -

No students found

- <% end_if %> -

Mentors

- <% if $Mentors %> -
    - <% loop $Mentors %> -
  • $Name
  • - <% end_loop %> -
- <% else %> -

No mentors found

- <% end_if %> -
-
-
+```ss +<% include SideBar %> +
+
+

$Title

+
+ $Content +

Students

+ <% if $Students %> +
    + <% loop $Students %> +
  • $Name ($University)
  • + <% end_loop %> +
+ <% else %> +

No students found

+ <% end_if %> +

Mentors

+ <% if $Mentors %> +
    + <% loop $Mentors %> +
  • $Name
  • + <% end_loop %> +
+ <% else %> +

No mentors found

+ <% end_if %> +
+
+
+``` Follow the link to a project detail from from your holder page, 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** - :::ss - $Name ($University) +```ss +$Name ($University) +``` To use this template, we need to add a new method to our student class: - :::php - class Student extends DataObject { - function getInfo() { - return $this->renderWith('StudentInfo'); - } - } +```php +use SilverStripe\ORM\DataObject; + +class Student extends DataObject +{ + public function getInfo() + { + return $this->renderWith('StudentInfo'); + } +} +``` Replace the student template code in both `Project.ss` and `ProjectHolder.ss` templates with the new placeholder, `$Info`. diff --git a/docs/en/02_Developer_Guides/01_Templates/01_Syntax.md b/docs/en/02_Developer_Guides/01_Templates/01_Syntax.md index c70c42842..8a7f83bee 100644 --- a/docs/en/02_Developer_Guides/01_Templates/01_Syntax.md +++ b/docs/en/02_Developer_Guides/01_Templates/01_Syntax.md @@ -98,7 +98,8 @@ Variables can come from your database fields, or custom methods you define on yo **mysite/code/Page.php** :::php - public function UsersIpAddress() { + public function UsersIpAddress() + { return $this->getRequest()->getIP(); } @@ -112,8 +113,8 @@ Variables can come from your database fields, or custom methods you define on yo
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] -class. This object gives you access to all the database fields on [api:Page_Controller], its corresponding [api:Page] +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:PageController], its corresponding [api:Page] record and any subclasses of those two. **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 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 - $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 classes of the current scope object, and any [api:Extension] instances you have. diff --git a/docs/en/02_Developer_Guides/01_Templates/02_Common_Variables.md b/docs/en/02_Developer_Guides/01_Templates/02_Common_Variables.md index dc99fe2fc..4375aa88f 100644 --- a/docs/en/02_Developer_Guides/01_Templates/02_Common_Variables.md +++ b/docs/en/02_Developer_Guides/01_Templates/02_Common_Variables.md @@ -15,7 +15,7 @@ scope, and you can specify additional static methods to be available globally in
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 -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.
diff --git a/docs/en/02_Developer_Guides/01_Templates/04_Rendering_Templates.md b/docs/en/02_Developer_Guides/01_Templates/04_Rendering_Templates.md index d61b1d9b8..c7c499d6d 100644 --- a/docs/en/02_Developer_Guides/01_Templates/04_Rendering_Templates.md +++ b/docs/en/02_Developer_Guides/01_Templates/04_Rendering_Templates.md @@ -11,79 +11,88 @@ subclasses). The following will render the given data into a template. Given the template: **mysite/templates/Coach_Message.ss** - - :::ss - $Name is the $Role on our team. + +```ss +$Name is the $Role on our team. +``` 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. **mysite/code/Page.php** - :::php - $arrayData = new ArrayData(array( - 'Name' => 'John', - 'Role' => 'Head Coach' - )); +```php +$arrayData = new ArrayData(array( + 'Name' => 'John', + 'Role' => 'Head Coach' +)); - echo $arrayData->renderWith('Coach_Message'); +echo $arrayData->renderWith('Coach_Message'); - // returns "John is the Head Coach on our team." +// returns "John is the Head Coach on our team." +```
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.
- :::php - $controller->renderWith(array("MyController", "MyBaseController")); +```php +$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 template. - :::php - renderWith("AjaxTemplate"); - } else { - return $this->httpError(404); - } - } - } + public function iwantmyajax() + { + if (Director::is_ajax()) { + return $this->renderWith('AjaxTemplate'); + } 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 does, such as `ArrayData` or `ArrayList`. - :::php - push(new ArrayData(array( + 'Title' => 'First Job' + ))); - public function iwantmyajax() { - if(Director::is_ajax()) { - $experience = new ArrayList(); - $experience->push(new ArrayData(array( - 'Title' => 'First Job' - ))); - - return $this->customise(new ArrayData(array( - 'Name' => 'John', - 'Role' => 'Head Coach', - 'Experience' => $experience - )))->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); + } + } +} +``` diff --git a/docs/en/02_Developer_Guides/02_Controllers/01_Introduction.md b/docs/en/02_Developer_Guides/02_Controllers/01_Introduction.md index 53285d2a5..80fb81946 100644 --- a/docs/en/02_Developer_Guides/02_Controllers/01_Introduction.md +++ b/docs/en/02_Developer_Guides/02_Controllers/01_Introduction.md @@ -34,7 +34,7 @@ at http://yoursite.com/teams/ and the `players` custom action is at http://yours
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.
diff --git a/docs/en/02_Developer_Guides/02_Controllers/02_Routing.md b/docs/en/02_Developer_Guides/02_Controllers/02_Routing.md index caeb0ab21..7d6dc3836 100644 --- a/docs/en/02_Developer_Guides/02_Controllers/02_Routing.md +++ b/docs/en/02_Developer_Guides/02_Controllers/02_Routing.md @@ -4,11 +4,11 @@ summary: A more in depth look at how to map requests to particular controllers a # Routing 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`
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.
@@ -17,16 +17,17 @@ These routes by standard, go into a `routes.yml` file in your applications `_con **mysite/_config/routes.yml** - :::yml - --- - Name: mysiteroutes - After: framework/routes#coreroutes - --- - Director: - rules: - 'teams//$Action/$ID/$Name': 'TeamController' - 'player/': 'PlayerController' - '': 'HomeController' +```yml +--- +Name: mysiteroutes +After: framework/routes#coreroutes +--- +Director: + rules: + 'teams//$Action/$ID/$Name': 'TeamController' + 'player/': 'PlayerController' + '': 'HomeController' +```
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 - :::yml - 'teams//$Action/$ID/$Name': 'TeamController' +```yml +'teams//$Action/$ID/$Name': 'TeamController' +``` 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 - :::php - // GET /teams/ +```php +// GET /teams/ - print_r($this->getRequest()->params()); +print_r($this->getRequest()->params()); - // Array - // ( - // [Action] => null - // [ID] => null - // [Name] => null - // ) +// Array +// ( +// [Action] => null +// [ID] => null +// [Name] => null +// ) - // GET /teams/players/ +// GET /teams/players/ - print_r($this->getRequest()->params()); +print_r($this->getRequest()->params()); - // Array - // ( - // [Action] => 'players' - // [ID] => null - // [Name] => null - // ) +// Array +// ( +// [Action] => 'players' +// [ID] => null +// [Name] => null +// ) - // GET /teams/players/1 +// GET /teams/players/1 - print_r($this->getRequest()->params()); +print_r($this->getRequest()->params()); - // Array - // ( - // [Action] => 'players' - // [ID] => 1 - // [Name] => null - // ) +// Array +// ( +// [Action] => 'players' +// [ID] => 1 +// [Name] => null +// ) +``` You can also fetch one parameter at a time. - :::php - - // GET /teams/players/1/ - - echo $this->getRequest()->param('ID'); - // returns '1' +```php +// GET /teams/players/1/ +echo $this->getRequest()->param('ID'); +// returns '1' +``` ## 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 | | `//` | **Shift Point** - Declares that only variables denoted with a $ are parsed into the $params AFTER this point in the regex | - :::yml - 'teams/$Action/$ID/$OtherID': 'TeamController' +```yml +'teams/$Action/$ID/$OtherID': 'TeamController' - # /teams/ - # /teams/players/ - # /teams/ +# /teams/ +# /teams/players/ +# /teams/ +``` 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 decision making. - :::yml - 'teams/$Action!/$ID!/': 'TeamController' +```yml +'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 `$Action` and `$ID` are required. Any requests to `team/` will result in a `404` error rather than being handed off to the `TeamController`. - :::yml - `admin/help//$Action/$ID`: 'AdminHelp' +```yml +'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 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** - :::php - 'payroll', - 'coach/$ID/$Name' => 'payroll' - ); + private static $url_handlers = array( + 'staff/$ID/$Name' => 'payroll', + 'coach/$ID/$Name' => 'payroll' + ); +``` 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. - :::php - class FeedController extends ContentController { +```php +use SilverStripe\CMS\Controllers\ContentController; - private static $allowed_actions = array('go'); - private static $url_handlers = array( - 'go/$UserName/$AuthToken/$Timestamp/$OutputType/$DeleteMode' => 'go' - ); - public function go() { - $this->validateUser( - $this->getRequest()->param('UserName'), - $this->getRequest()->param('AuthToken') - ); - /* more processing goes here */ - } +class FeedController extends ContentController +{ + private static $allowed_actions = array('go'); + private static $url_handlers = array( + 'go/$UserName/$AuthToken/$Timestamp/$OutputType/$DeleteMode' => 'go' + ); + + public function go() + { + $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 information for the framework to choose the desired controller. - :::yaml - Director: - rules: - 'feed': 'FeedController' +```yml +Director: + rules: + 'feed': 'FeedController' +``` ## Links diff --git a/docs/en/02_Developer_Guides/03_Forms/00_Introduction.md b/docs/en/02_Developer_Guides/03_Forms/00_Introduction.md index 0172fae70..5272e5797 100644 --- a/docs/en/02_Developer_Guides/03_Forms/00_Introduction.md +++ b/docs/en/02_Developer_Guides/03_Forms/00_Introduction.md @@ -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. - :::php - $form = new Form( - $controller, // the Controller to render this form on - $name, // name of the method that returns this form on the controller - FieldList $fields, // list of FormField instances - FieldList $actions, // list of FormAction instances - $required // optional use of RequiredFields object - ); + :::php + $form = new Form( + $controller, // the Controller to render this form on + $name, // name of the method that returns this form on the controller + FieldList $fields, // list of FormField instances + FieldList $actions, // list of FormAction instances + $required // optional use of RequiredFields object + ); In practice, this looks like: **mysite/code/Page.php** - :::php - setTitle("Say hello") - ); +class PageController extends ContentController +{ + 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); - - return $form; - } - - public function doSayHello($data, Form $form) { - $form->sessionMessage('Hello '. $data['Name'], 'success'); + $required = new RequiredFields('Name'); - 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** - :::ss - $HelloForm - +```ss +$HelloForm +```
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 controller action. To grant it access through URLs, we add it to the `$allowed_actions` array. - :::php - private static $allowed_actions = array( - 'HelloForm' - ); +```php +private static $allowed_actions = array( + 'HelloForm' +); +```
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]. Some common examples are [api:TextField] or [api:DropdownField]. - :::php - TextField::create($name, $title, $value); +```php +TextField::create($name, $title, $value); +```
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 `Form` is rendered. - :::php - $fields = new FieldList( - TextField::create('Name'), - EmailField::create('Email') - ); +```php +$fields = new FieldList( + TextField::create('Name'), + EmailField::create('Email') +); - $form = new Form($controller, 'MethodName', $fields, ...); +$form = new Form($controller, 'MethodName', $fields, ...); - // or use `setFields` - $form->setFields($fields); +// or use `setFields` +$form->setFields($fields); - // to fetch the current fields.. - $fields = $form->getFields(); +// to fetch the current fields.. +$fields = $form->getFields(); +``` A field can be appended to the [api:FieldList]. - :::php - $fields = $form->Fields(); +```php +$fields = $form->Fields(); - // add a field - $fields->push(TextField::create(..)); +// add a field +$fields->push(TextField::create(/* ... */)); - // insert a field before another one - $fields->insertBefore(TextField::create(..), 'Email'); +// insert a field before another one +$fields->insertBefore(TextField::create(/* ... */), 'Email'); - // insert a field after another one - $fields->insertAfter(TextField::create(..), 'Name'); - - // insert a tab before the main content tab (used to position tabs in the CMS) - $fields->insertBefore(Tab::create(...), 'Main'); - // Note: you need to create and position the new tab prior to adding fields via addFieldToTab() +// insert a field after another one +$fields->insertAfter(TextField::create(/* ... */), 'Name'); + +// insert a tab before the main content tab (used to position tabs in the CMS) +$fields->insertBefore(Tab::create(/* ... */), 'Main'); +// 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. - - :::php - $email = $form->Fields()->dataFieldByName('Email'); - $email->setTitle('Your Email Address'); + +```php +$email = $form->Fields()->dataFieldByName('Email'); +$email->setTitle('Your Email Address'); +``` Fields can be removed from the form. - - :::php - $form->getFields()->removeByName('Email'); + +```php +$form->getFields()->removeByName('Email'); +```
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.
- :::php - $field = new TextField(..); +```php +$field = new TextField(..); - $field - ->setMaxLength(100) - ->setAttribute('placeholder', 'Enter a value..') - ->setTitle(''); +$field + ->setMaxLength(100) + ->setAttribute('placeholder', 'Enter a value..') + ->setTitle(''); +``` ### 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 see [Form Templates](form_templates) - :::php - $form = new Form(..); +```php +$form = new Form(..); - $form->setTemplate('CustomForm'); +$form->setTemplate('CustomForm'); - // or, for a FormField - $field = new TextField(..); +// or, for a FormField +$field = new TextField(..); - $field->setTemplate('CustomTextField'); - $field->setFieldHolderTemplate('CustomTextField_Holder'); +$field->setTemplate('CustomTextField'); +$field->setFieldHolderTemplate('CustomTextField_Holder'); +``` ## Adding FormActions [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. - :::php - FormAction::create($action, $title); +```php +FormAction::create($action, $title); +``` As with [api:FormField], the actions for a `Form` are stored within a [api:FieldList] instance in the `actions` property on the form. - - :::php - public function MyForm() { - $fields = new FieldList(..); + +```php +public function MyForm() +{ + $fields = new FieldList(/* .. */); - $actions = new FieldList( - FormAction::create('doSubmitForm', 'Submit') - ); + $actions = new FieldList( + FormAction::create('doSubmitForm', 'Submit') + ); - $form = new Form($controller, 'MyForm', $fields, $actions); + $form = new Form($controller, 'MyForm', $fields, $actions); - // Get the actions - $actions = $form->Actions(); + // Get the actions + $actions = $form->Actions(); - // As actions is a FieldList, push, insertBefore, removeByName and other - // methods described for `Fields` also work for actions. + // As actions is a FieldList, push, insertBefore, removeByName and other + // methods described for `Fields` also work for actions. - $actions->push( - FormAction::create('doSecondaryFormAction', 'Another Button') - ); + $actions->push( + FormAction::create('doSecondaryFormAction', 'Another Button') + ); - $actions->removeByName('doSubmitForm'); - $form->setActions($actions); + $actions->removeByName('doSubmitForm'); + $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 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` * `$form` the submitted [api:Form] instance. - :::php - Fields()->dataFieldByName('Email')->Value(); + // You can also fetch the value from the field. + echo $form->Fields()->dataFieldByName('Email')->Value(); - // Using the Form instance you can get / set status such as error messages. - $form->sessionMessage("Successful!", 'good'); + // Using the Form instance you can get / set status such as error messages. + $form->sessionMessage('Successful!', 'good'); - // After dealing with the data you can redirect the user back. - return $this->redirectBack(); - } - } + // After dealing with the data you can redirect the user back. + return $this->redirectBack(); + } +} +``` ## Validation @@ -300,12 +333,14 @@ validating its' own data value. For more information, see the [Form Validation](validation) documentation. - :::php - $validator = new RequiredFields(array( - 'Name', 'Email' - )); +```php +$validator = new RequiredFields(array( + 'Name', + 'Email' +)); - $form = new Form($this, 'MyForm', $fields, $actions, $validator); +$form = new Form($this, 'MyForm', $fields, $actions, $validator); +``` ## API Documentation diff --git a/docs/en/02_Developer_Guides/03_Forms/01_Validation.md b/docs/en/02_Developer_Guides/03_Forms/01_Validation.md index cb939f3f9..61422cd3d 100644 --- a/docs/en/02_Developer_Guides/03_Forms/01_Validation.md +++ b/docs/en/02_Developer_Guides/03_Forms/01_Validation.md @@ -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 the [api:Form] constructor or through the function `setValidator`. - :::php - setValidator($required); + // Or, through a setter. + $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 `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.
- :::php - public function validate($validator) { - if($this->Value() == 10) { - $validator->validationError($this->Name(), 'This value cannot be 10'); - return false; - } +```php +public function validate($validator) +{ + if ((int) $this->Value() === 10) { + $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 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 the same validation logic applied to it throughout. -**mysite/code/formfields/CustomNumberField.php** +**mysite/code/CustomNumberField.php** - :::php - value)) { - $validator->validationError( - $this->name, "Not a number. This must be between 2 and 5", "validation", false - ); - - return false; - } - else if($this->value > 5 || $this->value < 2) { - $validator->validationError( - $this->name, "Your number must be between 2 and 5", "validation", false - ); +class CustomNumberField extends TextField +{ + public function validate($validator) + { + if (!is_numeric($this->value)) { + $validator->validationError( + $this->name, 'Not a number. This must be between 2 and 5', 'validation', false + ); + + return 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 reusable and would not be possible within the `CMS` or other automated `UI` but does not rely on creating custom `FormField` classes. - - :::php - 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(); + 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. + + $check = Member::get()->filter('Email', $data['Email'])->first(); - if($check) { - $form->addErrorMessage('Email', 'This email already exists', 'bad'); + if ($check) { + $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'); - - return $this->redirectBack(); - } - } - + $form->sessionMessage('You have been added to our mailing list', 'good'); + + return $this->redirectBack(); + } +} +``` + ## Exempt validation actions 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: +```php +$actions = new FieldList( + $action = FormAction::create('doSubmitForm', 'Submit') +); +$form = new Form($controller, 'MyForm', $fields, $actions); - :::php - $actions = new FieldList( - $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']); +// 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']); +``` ## Server-side validation messages 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. - :::php - $field = new TextField(..); - $field->setCustomValidationMessage('Whoops, looks like you have missed me!'); +```php +$field = new TextField(/* .. */); +$field->setCustomValidationMessage('Whoops, looks like you have missed me!'); ## 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 classes added to each input. For Parsley we can structure the form like. - :::php - $form = new Form(..); - $form->setAttribute('data-parsley-validate', true); +```php +$form = new Form(/* .. */); +$form->setAttribute('data-parsley-validate', true); - $field = $fields->dataFieldByName('Name'); - - $field->setAttribute('required', true); - $field->setAttribute('data-parsley-mincheck', '2'); +$field = $fields->dataFieldByName('Name'); +$field->setAttribute('required', true); +$field->setAttribute('data-parsley-mincheck', '2'); +``` ## Model Validation @@ -228,23 +254,26 @@ error message, or a [api:ValidationResult] object containing the list of errors 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); - - // Example error handling - if (!$success) { - throw new ValidationException('Sorry, we could not email to that address'); - } - - // If success - return $this->redirect($this->Link('success')); + $success = $this->sendEmail($data); + + // Example error handling + if (!$success) { + throw new ValidationException('Sorry, we could not email to that address'); } + + // If success + return $this->redirect($this->Link('success')); } - +} +``` ### 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`
- :::php - 'Text' - ); +class Page extends SiteTree +{ + private static $db = array( + 'MyRequiredField' => 'Text' + ); - public function getCMSFields() { - $fields = parent::getCMSFields(); + public function getCMSFields() + { + $fields = parent::getCMSFields(); - $fields->addFieldToTab('Root.Main', - TextField::create('MyRequiredField')->setCustomValidationMessage('You missed me.') - ); - } - - public function getCMSValidator() { - return new RequiredFields(array( - 'MyRequiredField' - )); - } + $fields->addFieldToTab('Root.Main', + TextField::create('MyRequiredField')->setCustomValidationMessage('You missed me.') + ); + } + + public function getCMSValidator() + { + return new RequiredFields(array( + 'MyRequiredField' + )); + } +} +``` ## API Documentation diff --git a/docs/en/02_Developer_Guides/03_Forms/Field_types/05_UploadField.md b/docs/en/02_Developer_Guides/03_Forms/Field_types/05_UploadField.md index 0c7214f68..05bff307c 100644 --- a/docs/en/02_Developer_Guides/03_Forms/Field_types/05_UploadField.md +++ b/docs/en/02_Developer_Guides/03_Forms/Field_types/05_UploadField.md @@ -431,7 +431,7 @@ code could be used: :::php class GalleryPage extends Page {} - class GalleryPage_Controller extends Page_Controller { + class GalleryPageController extends PageController { private static $allowed_actions = array('Form'); public function Form() { $fields = new FieldList( diff --git a/docs/en/02_Developer_Guides/03_Forms/How_Tos/01_Encapsulate_Forms.md b/docs/en/02_Developer_Guides/03_Forms/How_Tos/01_Encapsulate_Forms.md index ac1c3b67a..a330b5056 100644 --- a/docs/en/02_Developer_Guides/03_Forms/How_Tos/01_Encapsulate_Forms.md +++ b/docs/en/02_Developer_Guides/03_Forms/How_Tos/01_Encapsulate_Forms.md @@ -12,7 +12,7 @@ code for a `Form` is to create it as a subclass to `Form`. Let's look at a examp :::php 'Slide')` changes. In Page_Controller: +`Page::$many_many = array('Slides' => 'Slide')` changes. In `PageController`: :::php @@ -151,7 +151,7 @@ heavy load: <% 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, use something like: diff --git a/docs/en/02_Developer_Guides/09_Security/02_Permissions.md b/docs/en/02_Developer_Guides/09_Security/02_Permissions.md index cf5f70470..66d5c2619 100644 --- a/docs/en/02_Developer_Guides/09_Security/02_Permissions.md +++ b/docs/en/02_Developer_Guides/09_Security/02_Permissions.md @@ -14,27 +14,35 @@ The simple usage, Permission::check("PERM_CODE") will detect if the currently lo **Group ACLs** -* 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. +* 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. ## PermissionProvider [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. - :::php - class Page_Controller implements PermissionProvider { - public function init() { - parent::init(); - if(!Permission::check("VIEW_SITE")) Security::permissionFailure(); - } +```php +use SilverStripe\Security\PermissionProvider; - public function providePermissions() { - return array( - "VIEW_SITE" => "Access the site", - ); - } - } +class PageController implements PermissionProvider +{ + 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 @@ -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`. - :::php - if (Permission::checkMember($member, 'CMS_ACCESS')) { - //user can access the CMS - } +```php +if (Permission::checkMember($member, 'CMS_ACCESS')) { + //user can access the CMS +} +``` Internally, this checks that the user has any of the defined `CMS_ACCESS_*` permissions. diff --git a/docs/en/02_Developer_Guides/11_Integration/02_RSSFeed.md b/docs/en/02_Developer_Guides/11_Integration/02_RSSFeed.md index e27da7790..3e692fb1c 100644 --- a/docs/en/02_Developer_Guides/11_Integration/02_RSSFeed.md +++ b/docs/en/02_Developer_Guides/11_Integration/02_RSSFeed.md @@ -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( 'rss' @@ -118,7 +118,7 @@ Then in our controller, we add a new action which returns a the XML list of `Pla :::php getCustomSearchContext(); diff --git a/docs/en/02_Developer_Guides/14_Files/03_File_Security.md b/docs/en/02_Developer_Guides/14_Files/03_File_Security.md index 2da883bf2..8f92cf7f9 100644 --- a/docs/en/02_Developer_Guides/14_Files/03_File_Security.md +++ b/docs/en/02_Developer_Guides/14_Files/03_File_Security.md @@ -59,7 +59,7 @@ authorised users, the following should be considered: :::php - class Page_Controller extends ContentController { + class PageController extends ContentController { public function init() { parent::init(); // Whitelist any protected files on this page for the current user @@ -88,7 +88,7 @@ authorised users, the following should be considered: :::php - class Page_Controller extends ContentController { + class PageController extends ContentController { public function init() { parent::init(); // Whitelist any protected files on this page for the current user diff --git a/src/Forms/FileField.php b/src/Forms/FileField.php index cf84354ca..dac58754d 100644 --- a/src/Forms/FileField.php +++ b/src/Forms/FileField.php @@ -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. * * - * class ExampleForm_Controller extends Page_Controller { + * class ExampleFormController extends PageController { * * function Form() { * $fields = new FieldList( diff --git a/src/Security/Security.php b/src/Security/Security.php index 7d6910ebe..982aa8c74 100644 --- a/src/Security/Security.php +++ b/src/Security/Security.php @@ -518,7 +518,7 @@ class Security extends Controller implements TemplateGlobalProvider $tmpPage = new SiteTree(); $tmpPage->Title = $title; /** @skipUpgrade */ - $tmpPage->URLSegment = "Security"; + $tmpPage->URLSegment = 'Security'; // Disable ID-based caching of the log-in page by making it a random number $tmpPage->ID = -1 * rand(1, 10000000); diff --git a/src/View/SSViewer.php b/src/View/SSViewer.php index 52de5b838..151b5452f 100644 --- a/src/View/SSViewer.php +++ b/src/View/SSViewer.php @@ -253,9 +253,9 @@ class SSViewer implements Flushable $templates[] = $template; $templates[] = ['type' => 'Includes', $template]; - // If the class is "Page_Controller", look for Page.ss - if (stripos($class, '_controller') !== false) { - $templates[] = str_ireplace('_controller', '', $class) . $suffix; + // If the class is "PageController" (PSR-2 compatibility) or "Page_Controller" (legacy), look for Page.ss + if (preg_match('/^(?.+[^\\\\])_?Controller$/iU', $class, $matches)) { + $templates[] = $matches['name'] . $suffix; } if ($baseClass && $class == $baseClass) { diff --git a/tests/php/View/SSViewerTest.php b/tests/php/View/SSViewerTest.php index 27206d00e..d69f970b0 100644 --- a/tests/php/View/SSViewerTest.php +++ b/tests/php/View/SSViewerTest.php @@ -23,7 +23,7 @@ use SilverStripe\View\Requirements_Backend; use SilverStripe\View\SSViewer; use SilverStripe\View\Requirements; 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\SSViewer_FromString; use SilverStripe\View\SSTemplateParser; @@ -1526,17 +1526,15 @@ after' public function testLayout() { - $self = $this; - $this->useTestTheme( __DIR__.'/SSViewerTest', 'layouttest', - function () use ($self) { + function () { $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')); - $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() { - $self = $this; $this->useTestTheme( - __DIR__.'/SSViewerTest', + __DIR__ . '/SSViewerTest', 'layouttest', - function () use ($self) { + function () { // Test passing a string $templates = SSViewer::get_templates_by_class( - SSViewerTestModel_Controller::class, + SSViewerTestModelController::class, '', Controller::class ); - $self->assertEquals( + $this->assertEquals( [ - SSViewerTestModel_Controller::class, - [ - 'type' => 'Includes', - SSViewerTestModel_Controller::class, - ], - SSViewerTestModel::class, - Controller::class, - [ - 'type' => 'Includes', - Controller::class, - ], + SSViewerTestModelController::class, + [ + 'type' => 'Includes', + SSViewerTestModelController::class, + ], + SSViewerTestModel::class, + Controller::class, + [ + 'type' => 'Includes', + Controller::class, + ], ], $templates ); // Test to ensure we're stopping at the base class. $templates = SSViewer::get_templates_by_class( - SSViewerTestModel_Controller::class, + SSViewerTestModelController::class, '', - SSViewerTestModel_Controller::class + SSViewerTestModelController::class ); - $self->assertEquals( + $this->assertEquals( [ - SSViewerTestModel_Controller::class, - [ - 'type' => 'Includes', - SSViewerTestModel_Controller::class, - ], - SSViewerTestModel::class, + SSViewerTestModelController::class, + [ + 'type' => 'Includes', + SSViewerTestModelController::class, + ], + SSViewerTestModel::class, ], $templates ); @@ -1595,27 +1592,27 @@ after' // Make sure we can search templates by suffix. $templates = SSViewer::get_templates_by_class( SSViewerTestModel::class, - '_Controller', + 'Controller', DataObject::class ); - $self->assertEquals( + $this->assertEquals( [ - SSViewerTestModel_Controller::class, - [ - 'type' => 'Includes', - SSViewerTestModel_Controller::class, - ], - DataObject::class.'_Controller', - [ - 'type' => 'Includes', - DataObject::class.'_Controller', - ], + SSViewerTestModelController::class, + [ + 'type' => 'Includes', + SSViewerTestModelController::class, + ], + DataObject::class . 'Controller', + [ + 'type' => 'Includes', + DataObject::class . 'Controller', + ], ], $templates ); // Let's throw something random in there. - $self->setExpectedException('InvalidArgumentException'); + $this->setExpectedException('InvalidArgumentException'); SSViewer::get_templates_by_class(array()); } ); diff --git a/tests/php/View/SSViewerTest/SSViewerTestModel_Controller.php b/tests/php/View/SSViewerTest/SSViewerTestModelController.php similarity index 63% rename from tests/php/View/SSViewerTest/SSViewerTestModel_Controller.php rename to tests/php/View/SSViewerTest/SSViewerTestModelController.php index 43c32feb5..d9283d84d 100644 --- a/tests/php/View/SSViewerTest/SSViewerTestModel_Controller.php +++ b/tests/php/View/SSViewerTest/SSViewerTestModelController.php @@ -5,7 +5,7 @@ namespace SilverStripe\View\Tests\SSViewerTest; use SilverStripe\Dev\TestOnly; use SilverStripe\Control\Controller; -class SSViewerTestModel_Controller extends Controller implements TestOnly +class SSViewerTestModelController extends Controller implements TestOnly { }