Webylon 3.1 API Docs
  • Package
  • Class
  • Tree
  • Deprecated
  • Download
Version: current
  • 3.2
  • 3.1

Packages

  • auth
  • Booking
  • cart
    • shipping
    • steppedcheckout
  • Catalog
  • cms
    • assets
    • batchaction
    • batchactions
    • bulkloading
    • comments
    • content
    • core
    • export
    • newsletter
    • publishers
    • reports
    • security
    • tasks
  • Dashboard
  • DataObjectManager
  • event
  • faq
  • forms
    • actions
    • core
    • fields-basic
    • fields-dataless
    • fields-datetime
    • fields-files
    • fields-formatted
    • fields-formattedinput
    • fields-relational
    • fields-structural
    • transformations
    • validators
  • googlesitemaps
  • guestbook
  • installer
  • newsletter
  • None
  • photo
    • gallery
  • PHP
  • polls
  • recaptcha
  • sapphire
    • api
    • bulkloading
    • control
    • core
    • cron
    • dev
    • email
    • fields-formattedinput
    • filesystem
    • formatters
    • forms
    • i18n
    • integration
    • misc
    • model
    • parsers
    • search
    • security
    • tasks
    • testing
    • tools
    • validation
    • view
    • widgets
  • seo
    • open
      • graph
  • sfDateTimePlugin
  • spamprotection
  • stealth
    • captha
  • subsites
  • userform
    • pagetypes
  • userforms
  • webylon
  • widgets

Classes

  • Announcement_Controller
  • AnnouncementHolder_Controller
  • BookingAdminPage_Controller
  • BookingPage_Controller
  • Cart_Controller
  • CartPage_Controller
  • Catalog_Controller
  • CheckoutPage_Controller
  • ChequePayment_Handler
  • ContactsPage_Controller
  • ContentController
  • ContentNegotiator
  • Controller
  • DataObjectManager_Controller
  • DatePickerField_Controller
  • Director
  • DocPage_Controller
  • DocumentsPage_Controller
  • Event_Controller
  • EventHolder_Controller
  • FileDataObjectManager_Controller
  • FindCyrillic_Controller
  • HomePage_Controller
  • LastDoc_Controller
  • LiveCalendarWidget_Controller
  • MapObject_Controller
  • MapObjectGroup_Controller
  • MapPage_Controller
  • MediawebPage_Controller
  • ModelAsController
  • MultiUploadControls
  • NewsArchive
  • Orders1CExchange_Controller
  • Page_Controller
  • Payment_Handler
  • PhotoAlbumManager_Controller
  • Product_Controller
  • ProductSearchPage_Controller
  • ProfilePage_Controller
  • PublHolder_Controller
  • Publication_Controller
  • RatingExtension_Controller
  • RegistrationPage_Controller
  • RemoveOrphanedPagesTask
  • RequestHandler
  • Room_Controller
  • RoomCatalog_Controller
  • RootURLController
  • SapphireInfo
  • Search_Controller
  • Session
  • SimpleOrderPage_Controller
  • SiteMap_Controller
  • SpecialCatalog_Controller
  • SS_HTTPRequest
  • SS_HTTPResponse
  • StartCatalog_Controller
  • SubsitesSelectorPage_Controller
  • VideoBankPage_Controller

Interfaces

  • NestedController

Exceptions

  • SS_HTTPResponse_Exception
  1 <?php
  2 
  3 /**
  4  * This class is the base class of any Sapphire object that can be used to handle HTTP requests.
  5  * 
  6  * Any RequestHandler object can be made responsible for handling its own segment of the URL namespace.
  7  * The {@link Director} begins the URL parsing process; it will parse the beginning of the URL to identify which
  8  * controller is being used.  It will then call {@link handleRequest()} on that Controller, passing it the parameters that it
  9  * parsed from the URL, and the {@link SS_HTTPRequest} that contains the remainder of the URL to be parsed.
 10  *
 11  * You can use ?debug_request=1 to view information about the different components and rule matches for a specific URL.
 12  *
 13  * In Sapphire, URL parsing is distributed throughout the object graph.  For example, suppose that we have a search form
 14  * that contains a {@link TreeMultiSelectField} named "Groups".  We want to use ajax to load segments of this tree as they are needed
 15  * rather than downloading the tree right at the beginning.  We could use this URL to get the tree segment that appears underneath
 16  * Group #36: "admin/crm/SearchForm/field/Groups/treesegment/36"
 17  *  - Director will determine that admin/crm is controlled by a new ModelAdmin object, and pass control to that.
 18  *    Matching Director Rule: "admin/crm" => "ModelAdmin" (defined in mysite/_config.php)
 19  *  - ModelAdmin will determine that SearchForm is controlled by a Form object returned by $this->SearchForm(), and pass control to that.
 20  *    Matching $url_handlers: "$Action" => "$Action" (defined in RequestHandler class)
 21  *  - Form will determine that field/Groups is controlled by the Groups field, a TreeMultiselectField, and pass control to that.
 22  *    Matching $url_handlers: 'field/$FieldName!' => 'handleField' (defined in Form class)
 23  *  - TreeMultiselectField will determine that treesegment/36 is handled by its treesegment() method.  This method will return an HTML fragment that is output to the screen.
 24  *    Matching $url_handlers: "$Action/$ID" => "handleItem" (defined in TreeMultiSelectField class)
 25  *
 26  * {@link RequestHandler::handleRequest()} is where this behaviour is implemented.
 27  * 
 28  * @package sapphire
 29  * @subpackage control
 30  */
 31 class RequestHandler extends ViewableData {
 32     protected $request = null;
 33     
 34     /**
 35      * This variable records whether RequestHandler::__construct()
 36      * was called or not. Useful for checking if subclasses have
 37      * called parent::__construct()
 38      *
 39      * @var boolean
 40      */
 41     protected $brokenOnConstruct = true;
 42     
 43     /**
 44      * The default URL handling rules.  This specifies that the next component of the URL corresponds to a method to
 45      * be called on this RequestHandlingData object.
 46      *
 47      * The keys of this array are parse rules.  See {@link SS_HTTPRequest::match()} for a description of the rules available.
 48      * 
 49      * The values of the array are the method to be called if the rule matches.  If this value starts with a '$', then the
 50      * named parameter of the parsed URL wil be used to determine the method name.
 51      */
 52     static $url_handlers = array(
 53         '$Action' => '$Action',
 54     );
 55 
 56     
 57     /**
 58      * Define a list of action handling methods that are allowed to be called directly by URLs.
 59      * The variable should be an array of action names. This sample shows the different values that it can contain:
 60      *
 61      * <code>
 62      * array(
 63      *      'someaction', // someaction can be accessed by anyone, any time
 64      *      'otheraction' => true, // So can otheraction
 65      *      'restrictedaction' => 'ADMIN', // restrictedaction can only be people with ADMIN privilege
 66      *      'complexaction' '->canComplexAction' // complexaction can only be accessed if $this->canComplexAction() returns true
 67      *  );
 68      * </code>
 69      */
 70     static $allowed_actions = null;
 71     
 72     public function __construct() {
 73         $this->brokenOnConstruct = false;
 74         parent::__construct();
 75     }
 76     
 77     /**
 78      * Handles URL requests.
 79      *
 80      *  - ViewableData::handleRequest() iterates through each rule in {@link self::$url_handlers}.
 81      *  - If the rule matches, the named method will be called.
 82      *  - If there is still more URL to be processed, then handleRequest() 
 83      *    is called on the object that that method returns.
 84      *
 85      * Once all of the URL has been processed, the final result is returned.  
 86      * However, if the final result is an array, this
 87      * array is interpreted as being additional template data to customise the 
 88      * 2nd to last result with, rather than an object
 89      * in its own right.  This is most frequently used when a Controller's 
 90      * action will return an array of data with which to
 91      * customise the controller.
 92      * 
 93      * @param $request The {@link SS_HTTPRequest} object that is reponsible for distributing URL parsing
 94      * @uses SS_HTTPRequest
 95      * @uses SS_HTTPRequest->match()
 96      * @return SS_HTTPResponse|RequestHandler|string|array
 97      */
 98     function handleRequest(SS_HTTPRequest $request) {
 99         // $handlerClass is used to step up the class hierarchy to implement url_handlers inheritance
100         $handlerClass = ($this->class) ? $this->class : get_class($this);
101     
102         if($this->brokenOnConstruct) {
103             user_error("parent::__construct() needs to be called on {$handlerClass}::__construct()", E_USER_WARNING);
104         }
105     
106         $this->request = $request;
107         
108         // We stop after RequestHandler; in other words, at ViewableData
109         while($handlerClass && $handlerClass != 'ViewableData') {
110             $urlHandlers = Object::get_static($handlerClass, 'url_handlers');
111             
112             if($urlHandlers) foreach($urlHandlers as $rule => $action) {
113                 if(isset($_REQUEST['debug_request'])) Debug::message("Testing '$rule' with '" . $request->remaining() . "' on $this->class");
114                 if($params = $request->match($rule, true)) {
115                     // FIXME: This unnecessary coupling was added to fix a bug in Image_Uploader.
116                     if($this instanceof Controller) $this->urlParams = $request->allParams();
117                 
118                     if(isset($_REQUEST['debug_request'])) {
119                         Debug::message("Rule '$rule' matched to action '$action' on $this->class.  Latest request params: " . var_export($request->latestParams(), true));
120                     }
121                 
122                     // Actions can reference URL parameters, eg, '$Action/$ID/$OtherID' => '$Action',
123                     if($action[0] == '$') $action = $params[substr($action,1)];
124                 
125                     if($this->checkAccessAction($action)) {
126                         if(!$action) {
127                             if(isset($_REQUEST['debug_request'])) Debug::message("Action not set; using default action method name 'index'");
128                             $action = "index";
129                         } else if(!is_string($action)) {
130                             user_error("Non-string method name: " . var_export($action, true), E_USER_ERROR);
131                         }
132                         
133                         try {
134                             $result = $this->$action($request);
135                         } catch(SS_HTTPResponse_Exception $responseException) {
136                             $result = $responseException->getResponse();
137                         }
138                     } else {
139                         return $this->httpError(403, "Action '$action' isn't allowed on class $this->class");
140                     }
141                 
142                     if($result instanceof SS_HTTPResponse && $result->isError()) {
143                         if(isset($_REQUEST['debug_request'])) Debug::message("Rule resulted in HTTP error; breaking");
144                         return $result;
145                     }
146                 
147                     // If we return a RequestHandler, call handleRequest() on that, even if there is no more URL to parse.
148                     // It might have its own handler. However, we only do this if we haven't just parsed an empty rule ourselves,
149                     // to prevent infinite loops. Also prevent further handling of controller actions which return themselves
150                     // to avoid infinite loops.
151                     if($this !== $result && !$request->isEmptyPattern($rule) && is_object($result) && $result instanceof RequestHandler) {
152                         $returnValue = $result->handleRequest($request);
153 
154                         // Array results can be used to handle 
155                         if(is_array($returnValue)) $returnValue = $this->customise($returnValue);
156                     
157                         return $returnValue;
158                         
159                     // If we return some other data, and all the URL is parsed, then return that
160                     } else if($request->allParsed()) {
161                         return $result;
162                     
163                     // But if we have more content on the URL and we don't know what to do with it, return an error.
164                     } else {
165                         return $this->httpError(404, "I can't handle sub-URLs of a $this->class object.");
166                     }
167                 
168                     return $this;
169                 }
170             }
171             
172             $handlerClass = get_parent_class($handlerClass);
173         }
174         
175         // If nothing matches, return this object
176         return $this;
177     }
178     
179     /**
180      * Get a unified array of allowed actions on this controller (if such data is available) from both the controller
181      * ancestry and any extensions.
182      *
183      * @return array|null
184      */
185     public function allowedActions() {
186         $actions = Object::combined_static(get_class($this), 'allowed_actions', 'RequestHandler');
187         
188         foreach($this->extension_instances as $extension) {
189             if($extensionActions = Object::get_static(get_class($extension), 'allowed_actions')) {
190                 $actions = array_merge($actions, $extensionActions);
191             }
192         }
193         
194         if($actions) {
195             // convert all keys and values to lowercase to 
196             // allow for easier comparison, unless it is a permission code
197             $actions = array_change_key_case($actions, CASE_LOWER);
198             
199             foreach($actions as $key => $value) {
200                 if(is_numeric($key)) $actions[$key] = strtolower($value);
201             }
202             
203             return $actions;
204         }
205     }
206     
207     /**
208      * Checks if this request handler has a specific action (even if the current user cannot access it).
209      *
210      * @param string $action
211      * @return bool
212      */
213     public function hasAction($action) {
214         if($action == 'index') return true;
215         
216         $action  = strtolower($action);
217         $actions = $this->allowedActions();
218         
219         // Check if the action is defined in the allowed actions as either a
220         // key or value. Note that if the action is numeric, then keys are not
221         // searched for actions to prevent actual array keys being recognised
222         // as actions.
223         if(is_array($actions)) {
224             $isKey   = !is_numeric($action) && array_key_exists($action, $actions);
225             $isValue = in_array($action, $actions);
226 
227             if($isKey || $isValue) return true;
228         }
229         
230         if(!is_array($actions) || !$this->uninherited('allowed_actions')) {
231             if($action != 'init' && $action != 'run' && method_exists($this, $action)) return true;
232         }
233         
234         return false;
235     }
236     
237     /**
238      * Check that the given action is allowed to be called from a URL.
239      * It will interrogate {@link self::$allowed_actions} to determine this.
240      */
241     function checkAccessAction($action) {
242         $actionOrigCasing = $action;
243         $action            = strtolower($action);
244         $allowedActions    = $this->allowedActions();
245 
246         if($allowedActions)  {
247             // check for specific action rules first, and fall back to global rules defined by asterisk
248             foreach(array($action,'*') as $actionOrAll) {
249                 // check if specific action is set
250                 if(isset($allowedActions[$actionOrAll])) {
251                     $test = $allowedActions[$actionOrAll];
252                     if($test === true || $test === 1 || $test === '1') {
253                         // Case 1: TRUE should always allow access
254                         return true;
255                     } elseif(substr($test, 0, 2) == '->') {
256                         // Case 2: Determined by custom method with "->" prefix
257                         return $this->{substr($test, 2)}();
258                     } else {
259                         // Case 3: Value is a permission code to check the current member against
260                         return Permission::check($test);
261                     }
262                     
263                 } elseif((($key = array_search($actionOrAll, $allowedActions)) !== false) && is_numeric($key)) {
264                     // Case 4: Allow numeric array notation (search for array value as action instead of key)
265                     return true;
266                 }
267             }
268         }
269         
270         // If we get here an the action is 'index', then it hasn't been specified, which means that
271         // it should be allowed.
272         if($action == 'index' || empty($action)) return true;
273         
274         if($allowedActions === null || !$this->uninherited('allowed_actions')) {
275             // If no allowed_actions are provided, then we should only let through actions that aren't handled by magic methods
276             // we test this by calling the unmagic method_exists. 
277             if(method_exists($this, $action)) {
278                 // Disallow any methods which aren't defined on RequestHandler or subclasses
279                 // (e.g. ViewableData->getSecurityID())
280                 $r = new ReflectionClass(get_class($this));
281                 if($r->hasMethod($actionOrigCasing)) {
282                     $m = $r->getMethod($actionOrigCasing);
283                     return ($m && is_subclass_of($m->getDeclaringClass()->getName(), 'RequestHandler'));
284                 } else {
285                     throw new Exception("method_exists() true but ReflectionClass can't find method - PHP is b0kred");
286                 }
287             } else if(!$this->hasMethod($action)){
288                 // Return true so that a template can handle this action
289                 return true;
290             }
291         }
292         
293         return false;
294     }
295     
296     /**
297      * Throws a HTTP error response encased in a {@link SS_HTTPResponse_Exception}, which is later caught in
298      * {@link RequestHandler::handleAction()} and returned to the user.
299      *
300      * @param int $errorCode
301      * @param string $errorMessage
302      * @uses SS_HTTPResponse_Exception
303      */
304     public function httpError($errorCode, $errorMessage = null) {
305         throw new SS_HTTPResponse_Exception($errorMessage, $errorCode);
306     }
307     
308     /**
309      * Returns the SS_HTTPRequest object that this controller is using.
310      *
311      * @return SS_HTTPRequest
312      */
313     function getRequest() {
314         return $this->request;
315     }
316 }
317 
[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -
Webylon 3.1 API Docs API documentation generated by ApiGen 2.8.0