2007-07-19 12:40:28 +02:00
< ? php
/**
* Base controller class .
* Controllers are the cornerstone of all site functionality in Sapphire . The { @ link Director }
* selects a controller to pass control to , and then calls { @ link run ()} . This method will execute
* the appropriate action - either by calling the action method , or displaying the action ' s template .
2007-09-14 20:02:03 +02:00
*
2007-07-19 12:40:28 +02:00
* See { @ link getTemplate ()} for information on how the template is chosen .
2008-02-25 03:10:37 +01:00
* @ package sapphire
* @ subpackage control
2007-07-19 12:40:28 +02:00
*/
2008-08-09 05:19:54 +02:00
class Controller extends RequestHandlingData {
2008-10-06 00:35:14 +02:00
2008-02-25 03:10:37 +01:00
/**
2008-10-06 00:35:14 +02:00
* @ var array $urlParams An array of arguments extracted from the URL
2008-08-09 05:19:54 +02:00
*/
2007-07-19 12:40:28 +02:00
protected $urlParams ;
2008-10-06 00:35:14 +02:00
/**
* @ var array $requestParams Contains all GET and POST parameters
* passed to the current { @ link HTTPRequest } .
* @ uses HTTPRequest -> requestVars ()
*/
2007-07-19 12:40:28 +02:00
protected $requestParams ;
2008-10-06 00:35:14 +02:00
/**
* @ var string $action The URL part matched on the current controller as
* determined by the " $Action " part of the { @ link $url_handlers } definition .
* Should correlate to a public method on this controller .
* Used in { @ link render ()} and { @ link getViewer ()} to determine
* action - specific templates .
*/
2007-07-19 12:40:28 +02:00
protected $action ;
2007-08-17 07:45:15 +02:00
/**
* The { @ link Session } object for this controller
*/
protected $session ;
2007-07-19 12:40:28 +02:00
2007-08-17 07:45:15 +02:00
/**
* Stack of current controllers .
* Controller :: $controller_stack [ 0 ] is the current controller .
*/
protected static $controller_stack = array ();
2007-07-19 12:40:28 +02:00
protected $basicAuthEnabled = true ;
2007-08-17 05:09:46 +02:00
/**
2008-10-05 21:45:38 +02:00
* @ var HTTPResponse $response The response object that the controller returns .
* Set in { @ link handleRequest ()} .
2007-08-17 05:09:46 +02:00
*/
protected $response ;
2007-07-19 12:40:28 +02:00
2008-10-05 21:45:38 +02:00
/**
* @ var HTTPRequest $request The request object that the controller was called with .
* Set in { @ link handleRequest ()} . Useful to generate the {}
*/
protected $request ;
2008-08-09 05:19:54 +02:00
/**
* Default URL handlers - ( Action ) / ( ID ) / ( OtherID )
*/
static $url_handlers = array (
2008-08-09 05:29:30 +02:00
'$Action//$ID/$OtherID' => 'handleAction' ,
2008-08-09 05:19:54 +02:00
);
static $allowed_actions = array (
'handleAction' ,
'handleIndex' ,
);
/**
* Handles HTTP requests .
2008-10-04 06:45:43 +02:00
*
* If you are going to overload handleRequest , make sure that you start the method with $this -> pushCurrent ()
* and end the method with $this -> popCurrent () . Failure to do this will create weird session errors .
*
2008-08-09 05:19:54 +02:00
* @ param $request The { @ link HTTPRequest } object that is responsible for distributing request parsing .
*/
2008-08-11 06:48:45 +02:00
function handleRequest ( HTTPRequest $request ) {
if ( ! $request ) user_error ( " Controller::handleRequest() not passed a request! " , E_USER_ERROR );
2008-08-09 05:19:54 +02:00
$this -> pushCurrent ();
$this -> urlParams = $request -> allParams ();
2008-10-05 21:45:38 +02:00
$this -> request = $request ;
2008-08-09 05:19:54 +02:00
$this -> response = new HTTPResponse ();
// Init
$this -> baseInitCalled = false ;
$this -> init ();
if ( ! $this -> baseInitCalled ) user_error ( " init() method on class ' $this->class ' doesn't call Controller::init(). Make sure that you have parent::init() included. " , E_USER_WARNING );
// If we had a redirection or something, halt processing.
if ( $this -> response -> isFinished ()) {
$this -> popCurrent ();
return $this -> response ;
}
$body = parent :: handleRequest ( $request );
if ( $body instanceof HTTPResponse ) {
2008-08-11 04:57:59 +02:00
if ( isset ( $_REQUEST [ 'debug_request' ])) Debug :: message ( " Request handler returned HTTPResponse object to $this->class controller; returning it without modification. " );
2008-08-09 05:19:54 +02:00
$this -> response = $body ;
} else {
2008-08-11 04:57:59 +02:00
if ( is_object ( $body )) {
if ( isset ( $_REQUEST [ 'debug_request' ])) Debug :: message ( " Request handler $body->class object to $this->class controller;, rendering with template returned by $body->class ::getViewer() " );
$body = $body -> getViewer ( $request -> latestParam ( 'Action' )) -> process ( $body );
}
2008-08-09 05:19:54 +02:00
$this -> response -> setBody ( $body );
}
ContentNegotiator :: process ( $this -> response );
HTTP :: add_cache_headers ( $this -> response );
$this -> popCurrent ();
return $this -> response ;
}
/**
* Controller ' s default action handler . It will call the method named in $Action , if that method exists .
* If $Action isn ' t given , it will use " index " as a default .
*/
function handleAction ( $request ) {
// urlParams, requestParams, and action are set for backward compatability
2008-08-11 00:49:59 +02:00
foreach ( $request -> latestParams () as $k => $v ) {
if ( $v || ! isset ( $this -> urlParams [ $k ])) $this -> urlParams [ $k ] = $v ;
}
2008-08-09 05:19:54 +02:00
$this -> action = str_replace ( " - " , " _ " , $request -> param ( 'Action' ));
$this -> requestParams = $request -> requestVars ();
if ( ! $this -> action ) $this -> action = 'index' ;
$methodName = $this -> action ;
2008-08-14 05:35:13 +02:00
2008-08-09 05:19:54 +02:00
// run & init are manually disabled, because they create infinite loops and other dodgy situations
2008-08-09 07:21:54 +02:00
if ( $this -> checkAccessAction ( $this -> action ) && ! in_array ( strtolower ( $this -> action ), array ( 'run' , 'init' ))) {
2008-08-09 05:19:54 +02:00
if ( $this -> hasMethod ( $methodName )) {
$result = $this -> $methodName ( $request );
// Method returns an array, that is used to customise the object before rendering with a template
if ( is_array ( $result )) {
return $this -> getViewer ( $this -> action ) -> process ( $this -> customise ( $result ));
// Method returns a string / object, in which case we just return that
} else {
return $result ;
}
// There is no method, in which case we just render this object using a (possibly alternate) template
} else {
return $this -> getViewer ( $this -> action ) -> process ( $this );
}
} else {
return $this -> httpError ( 403 , " Action ' $this->action ' isn't allowed on class $this->class " );
}
}
2007-07-19 12:40:28 +02:00
function setURLParams ( $urlParams ) {
$this -> urlParams = $urlParams ;
}
2007-08-21 01:22:30 +02:00
/**
2008-02-25 03:10:37 +01:00
* @ return array The parameters extracted from the URL by the { @ link Director } .
2007-08-21 01:22:30 +02:00
*/
2007-07-19 12:40:28 +02:00
function getURLParams () {
return $this -> urlParams ;
}
2007-12-02 22:29:31 +01:00
/**
* Returns the HTTPResponse object that this controller is building up .
* Can be used to set the status code and headers
*/
function getResponse () {
return $this -> response ;
}
2008-10-05 21:45:38 +02:00
/**
* Get the request with which this controller was called ( if any ) .
* Usually set in { @ link handleRequest ()} .
*
* @ return HTTPRequest
*/
function getRequest () {
return $this -> request ;
}
2007-09-14 20:02:03 +02:00
2008-03-03 00:24:10 +01:00
protected $baseInitCalled = false ;
2007-07-19 12:40:28 +02:00
/**
2008-02-25 03:10:37 +01:00
* Executes this controller , and return an { @ link HTTPResponse } object with the result .
*
* This method first does a few set - up activities :
* - Push this controller ont to the controller stack - see { @ link Controller :: curr ()} for information about this .
* - Call { @ link init ()}
*
* Then it looks for the action method . The action is taken from $this -> urlParams [ 'Action' ] - for this reason , it ' s important
* to have $Action included in your Director rule
*
* If $requestParams [ 'executeForm' ] is set , then the Controller assumes that we ' re processing a form . This is usually
* set by adding ? executeForm = XXX to the form ' s action URL . Form processing differs in the following ways :
* - The action name will be the name of the button clicked . If no button - click can be detected , the first button in the
* list will be assumed .
* - If the given action method doesn ' t exist on the controller , Controller will look for that method on the Form object .
* this lets developers package both a form and its action handlers in a single subclass of Form .
*
* NOTE : You should rarely need to overload run () - this kind of change is only really appropriate for things like nested
* controllers - { @ link ModelAsController } and { @ link RootURLController } are two examples here . If you want to make more
* orthodox functionality , it ' s better to overload { @ link init ()} or { @ link index ()} .
*
2007-07-19 12:40:28 +02:00
* Execute the appropriate action handler . If none is given , use defaultAction to display
* a template . The default action will be appropriate in most cases where displaying data
* is the core goal ; the Viewer can call methods on the controller to get the data it needs .
2007-08-21 01:22:30 +02:00
*
2007-07-19 12:40:28 +02:00
* @ param array $requestParams GET and POST variables .
2007-08-21 01:22:30 +02:00
* @ return HTTPResponse The response that this controller produces , including HTTP headers such as redirection info
2007-07-19 12:40:28 +02:00
*/
2008-03-03 09:48:52 +01:00
/**
* Return the object that is going to own a form that ' s being processed , and handle its execution .
* Note that the result needn ' t be an actual controller object .
*/
function getFormOwner () {
// Get the appropraite ocntroller: sometimes we want to get a form from another controller
if ( isset ( $this -> requestParams [ 'formController' ])) {
$formController = Director :: getControllerForURL ( $this -> requestParams [ 'formController' ]);
while ( is_a ( $formController , 'NestedController' )) {
$formController = $formController -> getNestedController ();
}
return $formController ;
} else {
return $this ;
}
}
2007-07-19 12:40:28 +02:00
2008-03-03 00:24:10 +01:00
/**
* This is the default action handler used if a method doesn ' t exist .
* It will process the controller object with the template returned by { @ link getViewer ()}
*/
2007-07-19 12:40:28 +02:00
function defaultAction ( $action ) {
return $this -> getViewer ( $action ) -> process ( $this );
}
2007-09-14 20:02:03 +02:00
2008-03-03 00:24:10 +01:00
/**
* Returns the action that is being executed on this controller .
*/
2007-07-19 12:40:28 +02:00
function getAction () {
return $this -> action ;
}
2007-09-14 20:02:03 +02:00
2007-07-19 12:40:28 +02:00
/**
* Return an SSViewer object to process the data
2007-08-21 01:22:30 +02:00
* @ return SSViewer The viewer identified being the default handler for this Controller / Action combination
2007-07-19 12:40:28 +02:00
*/
function getViewer ( $action ) {
// Hard-coded templates
if ( $this -> templates [ $action ]) {
$templates = $this -> templates [ $action ];
} else if ( $this -> templates [ 'index' ]) {
$templates = $this -> templates [ 'index' ];
} else if ( $this -> template ) {
$templates = $this -> template ;
} else {
2008-10-06 21:25:45 +02:00
// Add action-specific templates for inheritance chain
2008-08-14 05:35:13 +02:00
$parentClass = $this -> class ;
if ( $action && $action != 'index' ) {
$parentClass = $this -> class ;
while ( $parentClass != " Controller " ) {
$templates [] = strtok ( $parentClass , '_' ) . '_' . $action ;
$parentClass = get_parent_class ( $parentClass );
}
}
2008-10-06 21:25:45 +02:00
// Add controller templates for inheritance chain
2007-07-19 12:40:28 +02:00
$parentClass = $this -> class ;
while ( $parentClass != " Controller " ) {
2008-08-14 05:35:13 +02:00
$templates [] = strtok ( $parentClass , '_' );
2007-07-19 12:40:28 +02:00
$parentClass = get_parent_class ( $parentClass );
}
2008-08-14 05:35:13 +02:00
2008-10-06 21:25:45 +02:00
// remove duplicates
2007-07-19 12:40:28 +02:00
$templates = array_unique ( $templates );
}
return new SSViewer ( $templates );
}
2008-10-05 21:27:21 +02:00
/**
* Render the current controller with the templates determined
* by { @ link getViewer ()} .
*
* @ param array $params Key - value array for custom template variables ( Optional )
* @ return string Parsed template content
*/
function render ( $params = null ) {
$template = $this -> getViewer ( $this -> getAction ());
// if the object is already customised (e.g. through Controller->run()), use it
$obj = ( $this -> customisedObj ) ? $this -> customisedObj : $this ;
if ( $params ) $obj = $this -> customise ( $params );
return $template -> process ( $obj );
}
2007-07-19 12:40:28 +02:00
/**
2008-03-03 00:24:10 +01:00
* Call this to disable basic authentication on test sites .
2007-07-19 12:40:28 +02:00
* must be called in the init () method
2008-03-03 00:24:10 +01:00
* @ deprecated Use BasicAuth :: disable () instead ? This is used in CliController - it should be updated .
2007-07-19 12:40:28 +02:00
*/
function disableBasicAuth () {
$this -> basicAuthEnabled = false ;
}
/**
* Initialisation function that is run before any action on the controller is called .
2008-04-09 13:17:39 +02:00
*
* @ uses BasicAuth :: requireLogin ()
2007-07-19 12:40:28 +02:00
*/
function init () {
// Test and development sites should be secured, via basic-auth
2007-10-02 06:56:43 +02:00
if ( ClassInfo :: hasTable ( " Group " ) && ClassInfo :: hasTable ( " Member " ) && Director :: isTest () && $this -> basicAuthEnabled ) {
2007-07-19 12:40:28 +02:00
BasicAuth :: requireLogin ( " SilverStripe test website. Use your CMS login " , " ADMIN " );
}
//
Cookie :: set ( " PastVisitor " , true );
2007-10-02 06:56:43 +02:00
// ClassInfo::hasTable() called to ensure that we're not in a very-first-setup stage
if ( ClassInfo :: hasTable ( " Group " ) && ClassInfo :: hasTable ( " Member " ) && ( $member = Member :: currentUser ())) {
2007-07-19 12:40:28 +02:00
Cookie :: set ( " PastMember " , true );
DB :: query ( " UPDATE Member SET LastVisited = NOW() WHERE ID = $member->ID " , null );
}
// This is used to test that subordinate controllers are actually calling parent::init() - a common bug
$this -> baseInitCalled = true ;
}
2007-08-21 01:22:30 +02:00
/**
2008-02-25 03:10:37 +01:00
* @ deprecated use Controller :: curr () instead
2007-08-21 01:22:30 +02:00
* @ returns Controller
*/
2007-07-19 12:40:28 +02:00
public static function currentController () {
2008-02-25 03:10:37 +01:00
user_error ( 'Controller::currentController() is deprecated. Use Controller::curr() instead.' , E_USER_NOTICE );
2007-08-17 07:45:15 +02:00
return self :: curr ();
2007-07-19 12:40:28 +02:00
}
2007-08-17 05:09:46 +02:00
/**
* Returns the current controller
2007-08-21 01:22:30 +02:00
* @ returns Controller
2007-08-17 05:09:46 +02:00
*/
public static function curr () {
2007-08-17 07:45:15 +02:00
if ( Controller :: $controller_stack ) {
return Controller :: $controller_stack [ 0 ];
} else {
user_error ( " No current controller available " , E_USER_WARNING );
}
2007-08-17 05:09:46 +02:00
}
2007-08-21 00:39:44 +02:00
/**
* Tests whether we have a currently active controller or not
2007-08-21 01:22:30 +02:00
* @ return boolean True if there is at least 1 controller in the stack .
2007-08-21 00:39:44 +02:00
*/
public static function has_curr () {
return Controller :: $controller_stack ? true : false ;
}
2007-07-19 12:40:28 +02:00
/**
* Returns true if the member is allowed to do the given action .
* @ param perm The permission to be checked , such as 'View' .
* @ param member The member whose permissions need checking . Defaults to the currently logged
* in user .
2007-08-21 01:22:30 +02:00
* @ return boolean
2008-03-03 00:24:10 +01:00
* @ deprecated I don ' t believe that the system has widespread use / support of this .
2007-07-19 12:40:28 +02:00
*/
function can ( $perm , $member = null ) {
if ( ! $member ) $member = Member :: currentUser ();
if ( $this -> hasMethod ( $methodName = 'can' . $perm )) {
return $this -> $methodName ( $member );
} else {
return true ;
}
}
2007-09-14 20:02:03 +02:00
2007-07-19 12:40:28 +02:00
//-----------------------------------------------------------------------------------
2007-09-14 20:02:03 +02:00
2007-07-19 12:40:28 +02:00
/**
* returns a date object for use within a template
* Usage : $Now . Year - Returns 2006
2007-08-21 01:22:30 +02:00
* @ return Date The current date
2007-07-19 12:40:28 +02:00
*/
function Now () {
$d = new Date ( null );
$d -> setVal ( date ( " Y-m-d h:i:s " ));
return $d ;
}
2007-09-14 20:02:03 +02:00
2007-07-19 12:40:28 +02:00
/**
* Returns a link to any other page
2008-03-03 00:24:10 +01:00
* @ deprecated It ' s unclear what value this has ; construct a link manually or use your own custom link - gen functions .
2007-07-19 12:40:28 +02:00
*/
function LinkTo ( $a , $b ) {
return Director :: baseURL () . $a . '/' . $b ;
}
2007-09-14 20:02:03 +02:00
2008-03-03 00:24:10 +01:00
/**
* Returns an absolute link to this controller
*/
2007-07-19 12:40:28 +02:00
function AbsoluteLink () {
return Director :: absoluteURL ( $this -> Link ());
}
2007-09-14 20:02:03 +02:00
2007-07-19 12:40:28 +02:00
/**
* Returns the currently logged in user
*/
function CurrentMember () {
return Member :: currentUser ();
}
2007-09-14 20:02:03 +02:00
2007-07-19 12:40:28 +02:00
/**
* Returns true if the visitor has been here before
2007-08-21 01:22:30 +02:00
* @ return boolean
2007-07-19 12:40:28 +02:00
*/
function PastVisitor () {
return Cookie :: get ( " PastVisitor " ) ? true : false ;
}
2007-09-14 20:02:03 +02:00
2007-07-19 12:40:28 +02:00
/**
* Return true if the visitor has signed up for a login account before
2007-08-21 01:22:30 +02:00
* @ return boolean
2007-07-19 12:40:28 +02:00
*/
function PastMember () {
return Cookie :: get ( " PastMember " ) ? true : false ;
}
2007-08-17 07:45:15 +02:00
/**
* Pushes this controller onto the stack of current controllers .
* This means that any redirection , session setting , or other things that rely on Controller :: curr () will now write to this
* controller object .
*/
function pushCurrent () {
array_unshift ( self :: $controller_stack , $this );
// Create a new session object
2007-08-18 01:11:43 +02:00
if ( ! $this -> session ) {
2007-10-02 06:33:18 +02:00
if ( isset ( self :: $controller_stack [ 1 ])) {
$this -> session = self :: $controller_stack [ 1 ] -> getSession ();
} else {
$this -> session = new Session ( null );
}
2007-08-18 01:11:43 +02:00
}
2007-08-17 07:45:15 +02:00
}
/**
* Pop this controller off the top of the stack .
*/
function popCurrent () {
2007-08-18 01:31:51 +02:00
if ( $this === self :: $controller_stack [ 0 ]) {
2007-08-17 07:45:15 +02:00
array_shift ( self :: $controller_stack );
} else {
2007-08-18 01:14:30 +02:00
user_error ( " popCurrent called on $this->class controller, but it wasn't at the top of the stack " , E_USER_WARNING );
2007-08-17 07:45:15 +02:00
}
}
2007-08-17 05:09:46 +02:00
/**
2008-03-03 00:24:10 +01:00
* Redirct to the given URL .
* It is generally recommended to call Director :: redirect () rather than calling this function directly .
2007-08-17 05:09:46 +02:00
*/
2008-04-22 03:45:55 +02:00
function redirect ( $url , $code = 302 ) {
2008-02-25 03:10:37 +01:00
if ( $this -> response -> getHeader ( 'Location' )) {
2008-04-26 08:53:13 +02:00
user_error ( " Already directed to " . $this -> response -> getHeader ( 'Location' ) . " ; now trying to direct to $url " , E_USER_WARNING );
return ;
2008-02-25 03:10:37 +01:00
}
2007-08-17 05:09:46 +02:00
// Attach site-root to relative links, if they have a slash in them
2007-08-28 04:49:31 +02:00
if ( $url == " " || $url [ 0 ] == '?' || ( substr ( $url , 0 , 4 ) != " http " && $url [ 0 ] != " / " && strpos ( $url , '/' ) !== false )){
2007-08-17 05:09:46 +02:00
$url = Director :: baseURL () . $url ;
}
2008-04-22 03:45:55 +02:00
$this -> response -> redirect ( $url , $code );
2007-08-17 05:09:46 +02:00
}
2007-08-17 07:45:15 +02:00
2007-08-31 02:26:41 +02:00
/**
* Tests whether a redirection has been requested .
* @ return string If redirect () has been called , it will return the URL redirected to . Otherwise , it will return null ;
*/
function redirectedTo () {
return $this -> response -> getHeader ( 'Location' );
}
2007-08-17 07:45:15 +02:00
/**
* Get the Session object representing this Controller ' s session
2007-08-21 01:22:30 +02:00
* @ return Session
2007-08-17 07:45:15 +02:00
*/
function getSession () {
return $this -> session ;
}
/**
* Set the Session object .
*/
function setSession ( Session $session ) {
$this -> session = $session ;
}
2007-08-21 00:39:44 +02:00
/**
* Returns true if this controller is processing an ajax request
2007-08-21 01:22:30 +02:00
* @ return boolean True if this controller is processing an ajax request
2007-08-21 00:39:44 +02:00
*/
function isAjax () {
return (
isset ( $this -> requestParams [ 'ajax' ]) ||
( isset ( $_SERVER [ 'HTTP_X_REQUESTED_WITH' ]) && $_SERVER [ 'HTTP_X_REQUESTED_WITH' ] == " XMLHttpRequest " )
);
}
2008-08-09 05:54:55 +02:00
/**
* Joins two link segments together , putting a slash between them if necessary .
* Use this for building the results of Link () methods .
2008-08-19 12:06:43 +02:00
*
* If either of the links have query strings , then they will be combined and put at the end of the resulting url .
2008-08-09 05:54:55 +02:00
*/
static function join_links () {
$args = func_get_args ();
2008-08-19 12:06:43 +02:00
$result = " " ;
$querystrings = array ();
2008-08-09 05:54:55 +02:00
foreach ( $args as $arg ) {
2008-08-19 12:06:43 +02:00
if ( strpos ( $arg , '?' ) !== false ) {
list ( $arg , $suffix ) = explode ( '?' , $arg , 2 );
$querystrings [] = $suffix ;
}
2008-08-28 06:25:13 +02:00
if ( $arg ) {
if ( $result && substr ( $result , - 1 ) != '/' && $arg [ 0 ] != '/' ) $result .= " / $arg " ;
else $result .= $arg ;
}
2008-08-09 05:54:55 +02:00
}
2008-08-19 12:06:43 +02:00
if ( $querystrings ) $result .= '?' . implode ( '&' , $querystrings );
2008-08-09 05:54:55 +02:00
return $result ;
}
2007-07-19 12:40:28 +02:00
}
?>