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 .
*/
class Controller extends ViewableData {
protected $urlParams ;
protected $requestParams ;
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
/**
* The HTTPResponse object that the controller returns
*/
protected $response ;
2007-07-19 12:40:28 +02:00
function setURLParams ( $urlParams ) {
$this -> urlParams = $urlParams ;
}
2007-08-21 01:22:30 +02:00
/**
* @ return
*/
2007-07-19 12:40:28 +02:00
function getURLParams () {
return $this -> urlParams ;
}
2007-09-14 20:02:03 +02:00
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 $urlParams named parameters extracted from the URL , including Action .
* @ 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
*/
protected $baseInitCalled = false ;
function run ( $requestParams ) {
if ( isset ( $_GET [ 'debug_profile' ])) Profiler :: mark ( " Controller " , " run " );
2007-08-17 07:45:15 +02:00
$this -> pushCurrent ();
2007-08-17 05:09:46 +02:00
$this -> response = new HTTPResponse ();
2007-07-19 12:40:28 +02:00
$this -> requestParams = $requestParams ;
2007-08-17 05:09:46 +02:00
2007-07-19 12:40:28 +02:00
$this -> action = isset ( $this -> urlParams [ 'Action' ]) ? str_replace ( " - " , " _ " , $this -> urlParams [ 'Action' ]) : " index " ;
// 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 );
2007-08-17 05:09:46 +02:00
// If we had a redirection or something, halt processing.
2007-08-17 07:45:15 +02:00
if ( $this -> response -> isFinished ()) {
$this -> popCurrent ();
return $this -> response ;
}
2007-07-19 12:40:28 +02:00
// Look at the action variables for forms
2007-09-14 20:02:03 +02:00
$funcName = null ;
2007-07-19 12:40:28 +02:00
foreach ( $this -> requestParams as $paramName => $paramVal ) {
if ( substr ( $paramName , 0 , 7 ) == 'action_' ) {
// Cleanup action_, _x and _y from image fields
$funcName = preg_replace ( array ( '/^action_/' , '/_x$|_y$/' ), '' , $paramName );
break ;
}
}
// Form handler
if ( isset ( $this -> requestParams [ 'executeForm' ]) && is_string ( $this -> requestParams [ 'executeForm' ])) {
if ( isset ( $funcName )) {
Form :: set_current_action ( $funcName );
}
// 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 ();
}
2007-09-14 20:02:03 +02:00
2007-07-19 12:40:28 +02:00
} else {
$formController = $this ;
}
// Create the form object
$form = $formController ;
$formObjParts = explode ( '.' , $this -> requestParams [ 'executeForm' ]);
foreach ( $formObjParts as $formMethod ){
if ( isset ( $_GET [ 'debug_profile' ])) Profiler :: mark ( " Calling $formMethod " , " on $form->class " );
$form = $form -> $formMethod ();
if ( isset ( $_GET [ 'debug_profile' ])) Profiler :: unmark ( " Calling $formMethod " , " on $form->class " );
if ( ! $form ) break ; //user_error("Form method '" . $this->requestParams['executeForm'] . "' returns null in controller class '$this->class' ($_SERVER[REQUEST_URI])", E_USER_ERROR);
}
2007-09-14 20:02:03 +02:00
2007-07-19 12:40:28 +02:00
// Populate the form
2007-09-14 20:02:03 +02:00
if ( isset ( $_GET [ 'debug_profile' ])) Profiler :: mark ( " Controller " , " populate form " );
2007-07-19 12:40:28 +02:00
if ( $form ){
$form -> loadDataFrom ( $this -> requestParams , true );
// disregard validation if a single field is called
2007-08-23 07:47:54 +02:00
2007-07-19 12:40:28 +02:00
if ( ! isset ( $_REQUEST [ 'action_callfieldmethod' ])) {
$valid = $form -> beforeProcessing ();
2007-08-17 07:45:15 +02:00
if ( ! $valid ) {
$this -> popCurrent ();
return $this -> response ;
}
2007-08-23 07:47:54 +02:00
} else {
$fieldcaller = $form -> dataFieldByName ( $requestParams [ 'fieldName' ]);
if ( is_a ( $fieldcaller , " TableListField " )){
if ( $fieldcaller -> hasMethod ( 'php' )){
$valid = $fieldcaller -> php ( $requestParams );
if ( ! $valid ) exit ();
}
}
2007-07-19 12:40:28 +02:00
}
// If the action wasnt' set, choose the default on the form.
if ( ! isset ( $funcName ) && $defaultAction = $form -> defaultAction ()){
$funcName = $defaultAction -> actionName ();
}
if ( isset ( $funcName )) {
$form -> setButtonClicked ( $funcName );
}
} else {
user_error ( " No form ( " . Session :: get ( 'CMSMain.currentPage' ) . " ) returned by $formController->class -> $_REQUEST[executeForm] " , E_USER_WARNING );
}
if ( isset ( $_GET [ 'debug_profile' ])) Profiler :: unmark ( " Controller " , " populate form " );
if ( ! isset ( $funcName )) {
user_error ( " No action button has been clicked in this form executon, and no default has been allowed " , E_USER_ERROR );
}
2007-10-26 04:23:55 +02:00
// Protection against CSRF attacks
2007-10-28 22:44:38 +01:00
if ( $form -> securityTokenEnabled ()) {
2007-10-26 04:23:55 +02:00
$securityID = Session :: get ( 'SecurityID' );
if ( ! $securityID || ! isset ( $this -> requestParams [ 'SecurityID' ]) || $securityID != $this -> requestParams [ 'SecurityID' ]) {
trigger_error ( " Security ID doesn't match, possible CRSF attack. " , E_USER_ERROR );
}
}
2007-07-19 12:40:28 +02:00
2007-09-14 20:02:03 +02:00
2007-07-19 12:40:28 +02:00
// First, try a handler method on the controller
if ( $this -> hasMethod ( $funcName ) || ! $form ) {
if ( isset ( $_GET [ 'debug_controller' ])){
Debug :: show ( " Found function $funcName on the controller " );
}
2007-09-14 20:02:03 +02:00
if ( isset ( $_GET [ 'debug_profile' ])) Profiler :: mark ( " $this->class :: $funcName (controller action) " );
2007-07-19 12:40:28 +02:00
$result = $this -> $funcName ( $this -> requestParams , $form );
2007-09-14 20:02:03 +02:00
if ( isset ( $_GET [ 'debug_profile' ])) Profiler :: unmark ( " $this->class :: $funcName (controller action) " );
2007-07-19 12:40:28 +02:00
// Otherwise, try a handler method on the form object
} else {
if ( isset ( $_GET [ 'debug_controller' ])) {
Debug :: show ( " Found function $funcName on the form object " );
}
2007-09-14 20:02:03 +02:00
if ( isset ( $_GET [ 'debug_profile' ])) Profiler :: mark ( " $form->class :: $funcName (form action) " );
2007-07-19 12:40:28 +02:00
$result = $form -> $funcName ( $this -> requestParams , $form );
2007-09-14 20:02:03 +02:00
if ( isset ( $_GET [ 'debug_profile' ])) Profiler :: unmark ( " $form->class :: $funcName (form action) " );
2007-07-19 12:40:28 +02:00
}
// Normal action
} else {
if ( ! isset ( $funcName )) $funcName = $this -> action ;
2007-08-16 08:27:55 +02:00
if ( $this -> hasMethod ( $funcName )) {
2007-07-19 12:40:28 +02:00
if ( isset ( $_GET [ 'debug_controller' ])) Debug :: show ( " Found function $funcName on the $this->class controller " );
if ( isset ( $_GET [ 'debug_profile' ])) Profiler :: mark ( " $this->class :: $funcName (controller action) " );
2007-08-17 05:09:46 +02:00
2007-07-19 12:40:28 +02:00
$result = $this -> $funcName ( $this -> urlParams );
if ( isset ( $_GET [ 'debug_profile' ])) Profiler :: unmark ( " $this->class :: $funcName (controller action) " );
2007-09-14 20:02:03 +02:00
2007-07-19 12:40:28 +02:00
} else {
if ( isset ( $_GET [ 'debug_controller' ])) Debug :: show ( " Running default action for $funcName on the $this->class controller " );
if ( isset ( $_GET [ 'debug_profile' ])) Profiler :: mark ( " Controller::defaultAction( $funcName ) " );
$result = $this -> defaultAction ( $funcName , $this -> urlParams );
2007-09-14 20:02:03 +02:00
if ( isset ( $_GET [ 'debug_profile' ])) Profiler :: unmark ( " Controller::defaultAction( $funcName ) " );
2007-07-19 12:40:28 +02:00
}
}
2007-09-14 20:02:03 +02:00
2007-07-19 12:40:28 +02:00
// If your controller function returns an array, then add that data to the
// default template
2007-09-14 20:02:03 +02:00
2007-07-19 12:40:28 +02:00
if ( is_array ( $result )) {
$extended = $this -> customise ( $result );
$viewer = $this -> getViewer ( $funcName );
2007-09-14 20:02:03 +02:00
2007-07-19 12:40:28 +02:00
$result = $viewer -> process ( $extended );
}
2007-08-17 05:09:46 +02:00
$this -> response -> setBody ( $result );
2007-07-19 12:40:28 +02:00
2007-08-17 05:09:46 +02:00
if ( $result ) ContentNegotiator :: process ( $this -> response );
2007-07-19 12:40:28 +02:00
// Set up HTTP cache headers
2007-08-17 05:09:46 +02:00
HTTP :: add_cache_headers ( $this -> response );
2007-07-19 12:40:28 +02:00
if ( isset ( $_GET [ 'debug_profile' ])) Profiler :: unmark ( " Controller " , " run " );
2007-08-17 05:09:46 +02:00
2007-08-17 07:45:15 +02:00
$this -> popCurrent ();
2007-08-17 05:09:46 +02:00
return $this -> response ;
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
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 {
$parentClass = $this -> class ;
while ( $parentClass != " Controller " ) {
$templateName = $parentClass ;
if (( $pos = strpos ( $templateName , '_' )) !== false ) $templateName = substr ( $templateName , 0 , $pos );
if ( $action && $action != " index " ) $templates [] = $templateName . '_' . $action ;
$templates [] = $templateName ;
2007-09-14 20:02:03 +02:00
2007-07-19 12:40:28 +02:00
$parentClass = get_parent_class ( $parentClass );
}
$templates = array_unique ( $templates );
}
if ( isset ( $_GET [ 'showtemplate' ])) Debug :: show ( $templates );
return new SSViewer ( $templates );
}
/**
* Call this to disable basic authentication on test sites
* must be called in the init () method
*/
function disableBasicAuth () {
$this -> basicAuthEnabled = false ;
}
/**
* Initialisation function that is run before any action on the controller is called .
*/
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
/**
* Deprecated - use Controller :: curr () instead
* @ returns Controller
*/
2007-07-19 12:40:28 +02:00
public static function currentController () {
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
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
*/
function LinkTo ( $a , $b ) {
return Director :: baseURL () . $a . '/' . $b ;
}
2007-09-14 20:02:03 +02:00
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
/**
* Handle redirection
*/
function redirect ( $url ) {
// 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 ;
}
$this -> response -> redirect ( $url );
}
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 " )
);
}
2007-07-19 12:40:28 +02:00
}
?>