* array( * 'someaction', // someaction can be accessed by anyone, any time * 'otheraction' => true, // So can otheraction * 'restrictedaction' => 'ADMIN', // restrictedaction can only be people with ADMIN privilege * 'complexaction' '->canComplexAction' // complexaction can only be accessed if $this->canComplexAction() returns true * ); * */ static $allowed_actions = null; protected $urlParams; protected $requestParams; protected $action; /** * The {@link Session} object for this controller */ protected $session; /** * Stack of current controllers. * Controller::$controller_stack[0] is the current controller. */ protected static $controller_stack = array(); protected $basicAuthEnabled = true; /** * The HTTPResponse object that the controller returns */ protected $response; function setURLParams($urlParams) { $this->urlParams = $urlParams; } /** * @return array The parameters extracted from the URL by the {@link Director}. */ function getURLParams() { return $this->urlParams; } /** * 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; } protected $baseInitCalled = false; /** * 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()}. * * 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. * * @param array $requestParams GET and POST variables. * @return HTTPResponse The response that this controller produces, including HTTP headers such as redirection info */ function run($requestParams) { if(isset($_GET['debug_profile'])) Profiler::mark("Controller", "run"); $this->pushCurrent(); $this->response = new HTTPResponse(); $this->requestParams = $requestParams; $this->action = isset($this->urlParams['Action']) ? str_replace("-","_",$this->urlParams['Action']) : ""; if(!$this->action) $this->action = 'index'; // Check security on the controller if(!$this->checkAccessAction($this->action)) { user_error("Disallowed action: '$this->action' on controller '$this->class'", E_USER_ERROR); } // 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; } // Look at the action variables for forms $funcName = null; 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); } $formOwner = $this->getFormOwner(); // Create the form object $form = $formOwner; $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); } // Populate the form if(isset($_GET['debug_profile'])) Profiler::mark("Controller", "populate form"); if($form){ $form->loadDataFrom($this->requestParams, true); // disregard validation if a single field is called if(!isset($_REQUEST['action_callfieldmethod'])) { $valid = $form->beforeProcessing(); if(!$valid) { $this->popCurrent(); return $this->response; } }else{ $fieldcaller = $form->dataFieldByName($requestParams['fieldName']); if(is_a($fieldcaller, "TableListField")){ if($fieldcaller->hasMethod('php')){ $valid = $fieldcaller->php($requestParams); if(!$valid) exit(); } } } // 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 $formOwner->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); } // Protection against CSRF attacks if($form->securityTokenEnabled()) { $securityID = Session::get('SecurityID'); if(!$securityID || !isset($this->requestParams['SecurityID']) || $securityID != $this->requestParams['SecurityID']) { // Don't show error on live sites, as spammers create a million of these if(!Director::isLive()) { trigger_error("Security ID doesn't match, possible CRSF attack.", E_USER_ERROR); } else { die(); } } } // 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"); } if(isset($_GET['debug_profile'])) Profiler::mark("$this->class::$funcName (controller action)"); $result = $this->$funcName($this->requestParams, $form); if(isset($_GET['debug_profile'])) Profiler::unmark("$this->class::$funcName (controller action)"); } else if(isset($formOwner) && $formOwner->hasMethod($funcName)) { $result = $formOwner->$funcName($this->requestParams, $form); // Otherwise, try a handler method on the form object } else { if(isset($_GET['debug_controller'])) { Debug::show("Found function $funcName on the form object"); } if(isset($_GET['debug_profile'])) Profiler::mark("$form->class::$funcName (form action)"); $result = $form->$funcName($this->requestParams, $form); if(isset($_GET['debug_profile'])) Profiler::unmark("$form->class::$funcName (form action)"); } // Normal action } else { if(!isset($funcName)) $funcName = $this->action; if($this->hasMethod($funcName)) { 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)"); $result = $this->$funcName($this->urlParams); if(isset($_GET['debug_profile'])) Profiler::unmark("$this->class::$funcName (controller action)"); } 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); if(isset($_GET['debug_profile'])) Profiler::unmark("Controller::defaultAction($funcName)"); } } // If your controller function returns an array, then add that data to the // default template if(is_array($result)) { $extended = $this->customise($result); $viewer = $this->getViewer($funcName); $result = $viewer->process($extended); } $this->response->setBody($result); if($result) ContentNegotiator::process($this->response); // Set up HTTP cache headers HTTP::add_cache_headers($this->response); if(isset($_GET['debug_profile'])) Profiler::unmark("Controller", "run"); $this->popCurrent(); return $this->response; } /** * 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; } } /** * 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()} */ function defaultAction($action) { return $this->getViewer($action)->process($this); } /** * Returns the action that is being executed on this controller. */ function getAction() { return $this->action; } /** * Return an SSViewer object to process the data * @return SSViewer The viewer identified being the default handler for this Controller/Action combination */ 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; $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 * @deprecated Use BasicAuth::disable() instead? This is used in CliController - it should be updated. */ function disableBasicAuth() { $this->basicAuthEnabled = false; } /** * Initialisation function that is run before any action on the controller is called. * * @uses BasicAuth::requireLogin() */ function init() { // Test and development sites should be secured, via basic-auth if(ClassInfo::hasTable("Group") && ClassInfo::hasTable("Member") && Director::isTest() && $this->basicAuthEnabled) { BasicAuth::requireLogin("SilverStripe test website. Use your CMS login", "ADMIN"); } // Cookie::set("PastVisitor", true); // 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())) { 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; } /** * @deprecated use Controller::curr() instead * @returns Controller */ public static function currentController() { user_error('Controller::currentController() is deprecated. Use Controller::curr() instead.', E_USER_NOTICE); return self::curr(); } /** * Returns the current controller * @returns Controller */ public static function curr() { if(Controller::$controller_stack) { return Controller::$controller_stack[0]; } else { user_error("No current controller available", E_USER_WARNING); } } /** * Tests whether we have a currently active controller or not * @return boolean True if there is at least 1 controller in the stack. */ public static function has_curr() { return Controller::$controller_stack ? true : false; } /** * 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. * @return boolean * @deprecated I don't believe that the system has widespread use/support of this. */ function can($perm, $member = null) { if(!$member) $member = Member::currentUser(); if($this->hasMethod($methodName = 'can' . $perm)) { return $this->$methodName($member); } else { return true; } } //----------------------------------------------------------------------------------- /** * returns a date object for use within a template * Usage: $Now.Year - Returns 2006 * @return Date The current date */ function Now() { $d = new Date(null); $d->setVal(date("Y-m-d h:i:s")); return $d; } /** * Returns a link to any other page * @deprecated It's unclear what value this has; construct a link manually or use your own custom link-gen functions. */ function LinkTo($a, $b) { return Director::baseURL() . $a . '/' . $b; } /** * Returns an absolute link to this controller */ function AbsoluteLink() { return Director::absoluteURL($this->Link()); } /** * Returns the currently logged in user */ function CurrentMember() { return Member::currentUser(); } /** * Returns true if the visitor has been here before * @return boolean */ function PastVisitor() { return Cookie::get("PastVisitor") ? true : false; } /** * Return true if the visitor has signed up for a login account before * @return boolean */ function PastMember() { return Cookie::get("PastMember") ? true : false; } /** * 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 if(!$this->session) { if(isset(self::$controller_stack[1])) { $this->session = self::$controller_stack[1]->getSession(); } else { $this->session = new Session(null); } } } /** * Pop this controller off the top of the stack. */ function popCurrent() { if($this === self::$controller_stack[0]) { array_shift(self::$controller_stack); } else { user_error("popCurrent called on $this->class controller, but it wasn't at the top of the stack", E_USER_WARNING); } } /** * Redirct to the given URL. * It is generally recommended to call Director::redirect() rather than calling this function directly. */ function redirect($url, $code=302) { if($this->response->getHeader('Location')) { user_error("Already directed to " . $this->response->getHeader('Location') . "; now trying to direct to $url", E_USER_WARNING); return; } // Attach site-root to relative links, if they have a slash in them if($url == "" || $url[0] == '?' || (substr($url,0,4) != "http" && $url[0] != "/" && strpos($url,'/') !== false)){ $url = Director::baseURL() . $url; } $this->response->redirect($url, $code); } /** * 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'); } /** * Get the Session object representing this Controller's session * @return Session */ function getSession() { return $this->session; } /** * Set the Session object. */ function setSession(Session $session) { $this->session = $session; } /** * Returns true if this controller is processing an ajax request * @return boolean True if this controller is processing an ajax request */ function isAjax() { return ( isset($this->requestParams['ajax']) || (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == "XMLHttpRequest") ); } /** * Check that the given action is allowed to be called on this controller. * This method is called by run() and makes use of {@link self::$allowed_actions}. */ function checkAccessAction($action) { $action = strtolower($action); // Collate self::$allowed_actions from this class and all parent classes $access = null; $className = $this->class; while($className != 'Controller') { // Merge any non-null parts onto $access. $accessPart = eval("return $className::\$allowed_actions;"); if($accessPart !== null) $access = array_merge((array)$access, $accessPart); // Build an array of parts for checking if part[0] == part[1], which means that this class doesn't directly define it. $accessParts[] = $accessPart; $className = get_parent_class($className); } // Add $allowed_actions from extensions if($this->extension_instances) { foreach($this->extension_instances as $inst) { $accessPart = $inst->stat('allowed_actions'); if($accessPart !== null) $access = array_merge((array)$access, $accessPart); } } if($access === null || (isset($accessParts[1]) && $accessParts[0] === $accessParts[1])) { // user_error("Deprecated: please define static \$allowed_actions on your Controllers for security purposes", E_USER_NOTICE); return true; } if($action == 'index') return true; if(isset($access[$action])) { $test = $access[$action]; if($test === true) return true; if(substr($test,0,2) == '->') { $funcName = substr($test,2); return $this->$funcName(); } if(Permission::check($test)) return true; } else if((($key = array_search($action, $access)) !== false) && is_numeric($key)) { return true; } return false; } } ?>