'handleAction', ); static $allowed_actions = array( 'handleAction', 'handleIndex', ); /** * Handles HTTP requests. * * 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. * * @param $request The {@link HTTPRequest} object that is responsible for distributing request parsing. */ function handleRequest(HTTPRequest $request) { if(!$request) user_error("Controller::handleRequest() not passed a request!", E_USER_ERROR); $this->pushCurrent(); $this->urlParams = $request->allParams(); $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) { if(isset($_REQUEST['debug_request'])) Debug::message("Request handler returned HTTPResponse object to $this->class controller; returning it without modification."); $this->response = $body; } else { 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); } $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 foreach($request->latestParams() as $k => $v) { if($v || !isset($this->urlParams[$k])) $this->urlParams[$k] = $v; } $this->action = str_replace("-","_",$request->param('Action')); $this->requestParams = $request->requestVars(); if(!$this->action) $this->action = 'index'; $methodName = $this->action; // run & init are manually disabled, because they create infinite loops and other dodgy situations if($this->checkAccessAction($this->action) && !in_array(strtolower($this->action), array('run', 'init'))) { 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"); } } 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 */ /** * 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; if($action && $action != 'index') { $parentClass = $this->class; while($parentClass != "Controller") { $templates[] = strtok($parentClass,'_') . '_' . $action; $parentClass = get_parent_class($parentClass); } } $parentClass = $this->class; while($parentClass != "Controller") { $templates[] = strtok($parentClass,'_'); $parentClass = get_parent_class($parentClass); } $templates = array_unique($templates); } return new SSViewer($templates); } /** * 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); } /** * 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") ); } /** * Joins two link segments together, putting a slash between them if necessary. * Use this for building the results of Link() methods. * * If either of the links have query strings, then they will be combined and put at the end of the resulting url. */ static function join_links() { $args = func_get_args(); $result = ""; $querystrings = array(); foreach($args as $arg) { if(strpos($arg,'?') !== false) { list($arg, $suffix) = explode('?',$arg,2); $querystrings[] = $suffix; } if($arg) { if($result && substr($result,-1) != '/' && $arg[0] != '/') $result .= "/$arg"; else $result .= $arg; } } if($querystrings) $result .= '?' . implode('&', $querystrings); return $result; } } ?>