mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 12:05:37 +00:00
Improved docs on $allowed_actions
Added section to "Controllers" and "Form" topics, added $allowed_actions definitions to all controller examples
This commit is contained in:
parent
50995fbecb
commit
3e27d27f7a
@ -68,6 +68,9 @@ You can have more customized logic and interface feedback through a custom contr
|
||||
:::php
|
||||
<?php
|
||||
class MyController extends Controller {
|
||||
|
||||
static $allowed_actions = array('Form');
|
||||
|
||||
protected $template = "BlankPage";
|
||||
|
||||
function Link($action = null) {
|
||||
|
@ -83,11 +83,12 @@ You can access the following controller-method with /team/signup
|
||||
class Team extends DataObject {}
|
||||
|
||||
class Team_Controller extends Controller {
|
||||
function signup($id, $otherId) {
|
||||
static $allowed_actions = array('signup');
|
||||
public function signup($id, $otherId) {
|
||||
return $this->renderWith('MyTemplate');
|
||||
}
|
||||
}
|
||||
|
||||
## SSViewer template rendering
|
||||
|
||||
See [templates](/topics/templates) for information on the SSViewer template system.
|
||||
See [templates](/topics/templates) for information on the SSViewer template system.
|
||||
|
@ -98,6 +98,7 @@ This code provides a good template:
|
||||
|
||||
:::php
|
||||
class MyProcess extends Controller {
|
||||
public static $allowed_actions = array('index');
|
||||
function index() {
|
||||
set_time_limit(0);
|
||||
while(memory_get_usage() < 32*1024*1024) {
|
||||
|
@ -3,8 +3,7 @@
|
||||
Base controller class. You will extend this to take granular control over the actions and url handling of aspects of
|
||||
your SilverStripe site.
|
||||
|
||||
|
||||
## Example
|
||||
## Usage
|
||||
|
||||
`mysite/code/Controllers/FastFood.php`
|
||||
|
||||
@ -12,11 +11,12 @@ your SilverStripe site.
|
||||
<?php
|
||||
|
||||
class FastFood_Controller extends Controller {
|
||||
function order($arguments) {
|
||||
public static $allowed_actions = array('order');
|
||||
public function order(SS_HTTPRequest $request) {
|
||||
print_r($arguments);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
|
||||
|
||||
@ -37,6 +37,96 @@ Request for `/fastfood/order/24/cheesefries` would result in the following to th
|
||||
[Name] => cheesefries
|
||||
)
|
||||
|
||||
<div class="warning" markdown='1'>
|
||||
SilverStripe automatically adds a URL routing entry based on the controller's class name,
|
||||
so a `MyController` class is accessible through `http://yourdomain.com/MyController`.
|
||||
</div>
|
||||
|
||||
## Access Control
|
||||
|
||||
### Through $allowed_actions
|
||||
|
||||
All public methods on a controller are accessible by their name through the `$Action`
|
||||
part of the URL routing, so a `MyController->mymethod()` is accessible at
|
||||
`http://yourdomain.com/MyController/mymethod`. This is not always desireable,
|
||||
since methods can return internal information, or change state in a way
|
||||
that's not intended to be used through a URL endpoint.
|
||||
|
||||
SilverStripe strongly recommends securing your controllers
|
||||
through defining a `$allowed_actions` array on the class,
|
||||
which allows whitelisting of methods, as well as a concise
|
||||
way to perform checks against permission codes or custom logic.
|
||||
|
||||
:::php
|
||||
class MyController extends Controller {
|
||||
public static $allowed_actions = array(
|
||||
// someaction can be accessed by anyone, any time
|
||||
'someaction',
|
||||
// So can otheraction
|
||||
'otheraction' => true,
|
||||
// restrictedaction can only be people with ADMIN privilege
|
||||
'restrictedaction' => 'ADMIN',
|
||||
// complexaction can only be accessed if $this->canComplexAction() returns true
|
||||
'complexaction' '->canComplexAction'
|
||||
);
|
||||
}
|
||||
|
||||
There's a couple of rules guiding these checks:
|
||||
|
||||
* Each controller is only responsible for access control on the methods it defines
|
||||
* If a method on a parent class is overwritten, access control for it has to be redefined as well
|
||||
* An action named "index" is whitelisted by default
|
||||
* A wildcard (`*`) can be used to define access control for all methods (incl. methods on parent classes)
|
||||
* Specific method entries in `$allowed_actions` overrule any `*` settings
|
||||
* Methods returning forms also count as actions which need to be defined
|
||||
* Form action methods (targets of `FormAction`) should NOT be included in `$allowed_actions`,
|
||||
they're handled separately through the form routing (see the ["forms" topic](/topics/forms))
|
||||
* `$allowed_actions` can be defined on `Extension` classes applying to the controller.
|
||||
|
||||
|
||||
If the permission check fails, SilverStripe will return a "403 Forbidden" HTTP status.
|
||||
|
||||
### Through the action
|
||||
|
||||
Each method responding to a URL can also implement custom permission checks,
|
||||
e.g. to handle responses conditionally on the passed request data.
|
||||
|
||||
:::php
|
||||
class MyController extends Controller {
|
||||
public static $allowed_actions = array('myaction');
|
||||
public function myaction($request) {
|
||||
if(!$request->getVar('apikey')) {
|
||||
return $this->httpError(403, 'No API key provided');
|
||||
}
|
||||
|
||||
return 'valid';
|
||||
}
|
||||
}
|
||||
|
||||
Unless you transform the response later in the request processing,
|
||||
it'll look pretty ugly to the user. Alternatively, you can use
|
||||
`ErrorPage::response_for(<status-code>)` to return a more specialized layout.
|
||||
|
||||
Note: This is recommended as an addition for `$allowed_actions`, in order to handle
|
||||
more complex checks, rather than a replacement.
|
||||
|
||||
### Through the init() method
|
||||
|
||||
After checking for allowed_actions, each controller invokes its `init()` method,
|
||||
which is typically used to set up common state in the controller, and
|
||||
include JavaScript and CSS files in the output which are used for any action.
|
||||
If an `init()` method returns a `SS_HTTPResponse` with either a 3xx or 4xx HTTP
|
||||
status code, it'll abort execution. This behaviour can be used to implement
|
||||
permission checks.
|
||||
|
||||
:::php
|
||||
class MyController extends Controller {
|
||||
public static $allowed_actions = array();
|
||||
public function init() {
|
||||
parent::init();
|
||||
if(!Permission::check('ADMIN')) return $this->httpError(403);
|
||||
}
|
||||
}
|
||||
|
||||
## URL Handling
|
||||
|
||||
@ -51,9 +141,10 @@ the case below we also want any orders coming through `/fastfood/drivethrough/`
|
||||
|
||||
:::php
|
||||
class FastFood_Controller extends Controller {
|
||||
static $allowed_actions = array('drivethrough');
|
||||
public static $url_handlers = array(
|
||||
'drivethrough/$Action/$ID/$Name' => 'order'
|
||||
);
|
||||
'drivethrough/$Action/$ID/$Name' => 'order'
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
@ -45,16 +45,16 @@ TODO Describe behaviour.js solution easily, how to disable it
|
||||
Setting fieldEl.requiredErrorMsg or formEl.requiredErrorMsg will override the default error message. Both can include
|
||||
the string '$FieldLabel', which will be replaced with the field's label. Otherwise, the message is "Please fill out
|
||||
"$FieldLabel", it is required".
|
||||
|
||||
|
||||
You can use Behaviour to load in the appropriate value:
|
||||
|
||||
|
||||
:::js
|
||||
Behaviour.register({
|
||||
'#Form_Form' : {
|
||||
requiredErrorMsg: "Please complete this question before moving on.",
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
### Other validation libraries
|
||||
|
||||
By default, SilverStripe forms with an attached Validator instance use the custom Validator.js clientside logic. It is
|
||||
@ -65,12 +65,12 @@ Disable for all forms (in `mysite/_config.php`):
|
||||
|
||||
:::php
|
||||
Validator::set_javascript_validation_handler('none');
|
||||
|
||||
|
||||
Disable for a specific form:
|
||||
|
||||
:::php
|
||||
$myForm->getValidator()->setJavascriptValidationHandler('none');
|
||||
|
||||
|
||||
|
||||
## Related
|
||||
|
||||
|
@ -7,7 +7,7 @@ instantiating the Form class itself, or by subclassing it.
|
||||
|
||||
## Instantiating a form
|
||||
|
||||
Creating a form is a matter of defining a method to represent that form. This method should return a form object. The
|
||||
Creating a form is a matter of defining a method to represent that form. This method should return a form object. The
|
||||
constructor takes the following arguments:
|
||||
|
||||
* `$controller`: This must be the controller that contains the form.
|
||||
@ -59,9 +59,9 @@ The real difference, however, is that you can then define your controller method
|
||||
|
||||
|
||||
## Form Field Types
|
||||
|
||||
|
||||
There are many classes extending `[api:FormField]`. Some examples:
|
||||
|
||||
|
||||
* `[api:TextField]`
|
||||
* `[api:EmailField]`
|
||||
* `[api:NumericField]`
|
||||
@ -73,12 +73,12 @@ There are many classes extending `[api:FormField]`. Some examples:
|
||||
|
||||
Full overview at [form-field-types](/reference/form-field-types)
|
||||
|
||||
|
||||
|
||||
### Using Form Fields
|
||||
|
||||
|
||||
To get these fields automatically rendered into a form element, all you need to do is create a new instance of the
|
||||
class, and add it to the fieldset of the form.
|
||||
|
||||
|
||||
:::php
|
||||
$form = new Form(
|
||||
$controller = $this,
|
||||
@ -114,7 +114,7 @@ Implementing the more complex fields requires extra arguments.
|
||||
new TextField(
|
||||
$name = "FirstName",
|
||||
$title = "First name"
|
||||
),
|
||||
),
|
||||
new TextField("Surname"),
|
||||
new EmailField("Email", "Email address")
|
||||
new DropdownField(
|
||||
@ -122,7 +122,7 @@ Implementing the more complex fields requires extra arguments.
|
||||
$title = "Country (if outside nz)",
|
||||
$source = Geoip::getCountryDropDown(),
|
||||
$value = Geoip::visitor_country()
|
||||
)
|
||||
)
|
||||
), new FieldSet(
|
||||
// List the action buttons here
|
||||
new FormAction("signup", "Sign up")
|
||||
@ -173,28 +173,28 @@ First of all, you need to create your form on it's own class, that way you can d
|
||||
$fields = new FieldSet(
|
||||
new TextField('FirstName', 'First name'),
|
||||
new EmailField('Email', 'Email address')
|
||||
);
|
||||
);
|
||||
|
||||
$actions = new FieldSet(
|
||||
new FormAction('submit', 'Submit')
|
||||
);
|
||||
|
||||
parent::__construct($controller, $name, $fields, $actions);
|
||||
}
|
||||
|
||||
parent::__construct($controller, $name, $fields, $actions);
|
||||
}
|
||||
|
||||
function forTemplate() {
|
||||
return $this->renderWith(array(
|
||||
$this->class,
|
||||
'Form'
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function submit($data, $form) {
|
||||
// do stuff here
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
`forTemplate()` tells the `[api:Form]` class to render with a template of return value of `$this->class`, which in this case
|
||||
is *MyForm*, the name of the class. If the template doesn't exist, then it falls back to using Form.ss.
|
||||
|
||||
@ -203,36 +203,36 @@ basic customisation:
|
||||
|
||||
:::ss
|
||||
<form $FormAttributes>
|
||||
<% if Message %>
|
||||
<p id="{$FormName}_error" class="message $MessageType">$Message</p>
|
||||
<% else %>
|
||||
<p id="{$FormName}_error" class="message $MessageType" style="display: none"></p>
|
||||
<% end_if %>
|
||||
|
||||
<fieldset>
|
||||
<% if Message %>
|
||||
<p id="{$FormName}_error" class="message $MessageType">$Message</p>
|
||||
<% else %>
|
||||
<p id="{$FormName}_error" class="message $MessageType" style="display: none"></p>
|
||||
<% end_if %>
|
||||
|
||||
<fieldset>
|
||||
<div id="FirstName" class="field text">
|
||||
<label class="left" for="$FormName_FirstName">First name</label>
|
||||
$dataFieldByName(FirstName)
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="Email" class="field email">
|
||||
<label class="left" for="$FormName_Email">Email</label>
|
||||
$dataFieldByName(Email)
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
$dataFieldByName(SecurityID)
|
||||
</fieldset>
|
||||
|
||||
<% if Actions %>
|
||||
<div class="Actions">
|
||||
</fieldset>
|
||||
|
||||
<% if Actions %>
|
||||
<div class="Actions">
|
||||
<% control Actions %>$Field<% end_control %>
|
||||
</div>
|
||||
<% end_if %>
|
||||
</div>
|
||||
<% end_if %>
|
||||
</form>
|
||||
|
||||
`$dataFieldByName(FirstName)` will return the form control contents of `Field()` for the particular field object, in
|
||||
this case `TextField->Field()` or `EmailField->Field()` which returns an `<input>` element with specific markup
|
||||
for the type of field. Pass in the name of the field as the first parameter, as done above, to render it into the
|
||||
for the type of field. Pass in the name of the field as the first parameter, as done above, to render it into the
|
||||
template.
|
||||
|
||||
To find more methods, have a look at the `[api:Form]` class, as there is a lot of different methods of customising the form
|
||||
|
@ -67,7 +67,8 @@ Example:
|
||||
|
||||
:::php
|
||||
class MyController extends Controller {
|
||||
function myurlaction($RAW_urlParams) {
|
||||
static $allowed_actions = array('myurlaction');
|
||||
public function myurlaction($RAW_urlParams) {
|
||||
$SQL_urlParams = Convert::raw2sql($RAW_urlParams); // works recursively on an array
|
||||
$objs = DataObject::get('Player', "Name = '{$SQL_data[OtherID]}'");
|
||||
// ...
|
||||
@ -81,7 +82,6 @@ This means if you've got a chain of functions passing data through, escaping sho
|
||||
:::php
|
||||
class MyController extends Controller {
|
||||
/**
|
||||
|
||||
* @param array $RAW_data All names in an indexed array (not SQL-safe)
|
||||
*/
|
||||
function saveAllNames($RAW_data) {
|
||||
@ -210,7 +210,8 @@ PHP:
|
||||
|
||||
:::php
|
||||
class MyController extends Controller {
|
||||
function search($request) {
|
||||
static $allowed_actions = array('search');
|
||||
public function search($request) {
|
||||
$htmlTitle = '<p>Your results for:' . Convert::raw2xml($request->getVar('Query')) . '</p>';
|
||||
return $this->customise(array(
|
||||
'Query' => DBField::create('Text', $request->getVar('Query')),
|
||||
@ -239,7 +240,8 @@ PHP:
|
||||
|
||||
:::php
|
||||
class MyController extends Controller {
|
||||
function search($request) {
|
||||
static $allowed_actions = array('search');
|
||||
public function search($request) {
|
||||
$rssRelativeLink = "/rss?Query=" . urlencode($_REQUEST['query']) . "&sortOrder=asc";
|
||||
$rssLink = Controller::join_links($this->Link(), $rssRelativeLink);
|
||||
return $this->customise(array(
|
||||
|
@ -29,6 +29,8 @@ the form in a method on *HomePage_Controller*.
|
||||
|
||||
:::php
|
||||
class HomePage_Controller extends Page_Controller {
|
||||
static $allowed_actions = array('BrowserPollForm');
|
||||
|
||||
// ...
|
||||
|
||||
function BrowserPollForm() {
|
||||
@ -415,4 +417,4 @@ In this tutorial we have explored forms, and seen the different approaches to cr
|
||||
decide to use the [userforms module](http://silverstripe.org/user-forms-module) or create a form in PHP depends on the situation and flexibility
|
||||
required.
|
||||
|
||||
[Next Tutorial >>](4-site-search)
|
||||
[Next Tutorial >>](4-site-search)
|
||||
|
Loading…
x
Reference in New Issue
Block a user