diff --git a/core/Session.php b/core/Session.php index 16b99dbbc..b0278ce86 100644 --- a/core/Session.php +++ b/core/Session.php @@ -2,7 +2,15 @@ /** * Handles all manipulation of the session. - * + * + * The static methods are used to manipulate the currently active controller's session. + * The instance methods are used to manipulate a particular session. There can be more than one of these created. + * + * In order to support things like testing, the session is associated with a particular Controller. In normal usage, this is loaded from + * and saved to the regular PHP session, but for things like static-page-generation and unit-testing, you can create multiple Controllers, + * each with their own session. + * + * The instance object is basically just a way of manipulating a set of nested maps, and isn't specific to session data. * This class is currently really basic and could do with a more well-thought-out implementation * * $session->myVar = 'XYZ' would be fine, as would Session::data->myVar. What about the equivalent @@ -15,10 +23,41 @@ */ class Session { public static function set($name, $val) { + return Controller::curr()->getSession()->inst_set($name, $val); + } + public static function addToArray($name, $val) { + return Controller::curr()->getSession()->inst_addToArray($name, $val); + } + public static function get($name) { + return Controller::curr()->getSession()->inst_get($name); + } + public static function clear($name) { + return Controller::curr()->getSession()->inst_clear($name); + } + public static function getAll() { + return Controller::curr()->getSession()->inst_getAll($name); + } + + /** + * Session data + */ + protected $data = array(); + + /** + * Create a new session object, with the given starting data + * @param $data Can be an array of data (such as $_SESSION) or another Session object to clone. + */ + function __construct($data) { + if($data instanceof Session) $data = $data->inst_getAll(); + + $this->data = $data; + } + + public function inst_set($name, $val) { $names = explode('.', $name); // We still want to do this even if we have strict path checking for legacy code - $var = &$_SESSION; + $var = &$this->data; foreach($names as $n) { $var = &$var[$n]; @@ -27,11 +66,11 @@ class Session { $var = $val; } - public static function addToArray($name, $val) { + public function inst_addToArray($name, $val) { $names = explode('.', $name); // We still want to do this even if we have strict path checking for legacy code - $var = &$_SESSION; + $var = &$this->data; foreach($names as $n) { $var = &$var[$n]; @@ -40,14 +79,14 @@ class Session { $var[] = $val; } - public static function get($name) { + public function inst_get($name) { $names = explode('.', $name); - if(!isset($_SESSION)) { + if(!isset($this->data)) { return null; } - $var = $_SESSION; + $var = $this->data; foreach($names as $n) { if(!isset($var[$n])) { @@ -59,11 +98,11 @@ class Session { return $var; } - public static function clear($name) { + public function inst_clear($name) { $names = explode('.', $name); // We still want to do this even if we have strict path checking for legacy code - $var = &$_SESSION; + $var = &$this->data; foreach($names as $n) { $var = &$var[$n]; @@ -72,8 +111,8 @@ class Session { $var = null; } - public static function getAll() { - return $_SESSION; + public function inst_getAll() { + return $this->data; } /** diff --git a/core/control/Controller.php b/core/control/Controller.php index 2deee1db1..8c679d167 100644 --- a/core/control/Controller.php +++ b/core/control/Controller.php @@ -15,8 +15,17 @@ class Controller extends ViewableData { protected $requestParams; protected $action; + + /** + * The {@link Session} object for this controller + */ + protected $session; - protected static $currentController; + /** + * Stack of current controllers. + * Controller::$controller_stack[0] is the current controller. + */ + protected static $controller_stack = array(); protected $basicAuthEnabled = true; @@ -43,7 +52,7 @@ class Controller extends ViewableData { protected $baseInitCalled = false; function run($requestParams) { if(isset($_GET['debug_profile'])) Profiler::mark("Controller", "run"); - Controller::$currentController = $this; + $this->pushCurrent(); $this->response = new HTTPResponse(); $this->requestParams = $requestParams; @@ -56,7 +65,10 @@ class Controller extends ViewableData { 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()) return $this->response; + if($this->response->isFinished()) { + $this->popCurrent(); + return $this->response; + } // Look at the action variables for forms foreach($this->requestParams as $paramName => $paramVal) { @@ -104,7 +116,10 @@ class Controller extends ViewableData { // disregard validation if a single field is called if(!isset($_REQUEST['action_callfieldmethod'])) { $valid = $form->beforeProcessing(); - if(!$valid) return $this->response; + if(!$valid) { + $this->popCurrent(); + return $this->response; + } } // If the action wasnt' set, choose the default on the form. @@ -185,6 +200,7 @@ class Controller extends ViewableData { if(isset($_GET['debug_profile'])) Profiler::unmark("Controller", "run"); + $this->popCurrent(); return $this->response; } @@ -255,14 +271,18 @@ class Controller extends ViewableData { } public static function currentController() { - return Controller::$currentController; + return self::curr(); } /** * Returns the current controller */ public static function curr() { - return Controller::$currentController; + if(Controller::$controller_stack) { + return Controller::$controller_stack[0]; + } else { + user_error("No current controller available", E_USER_WARNING); + } } /** @@ -323,11 +343,32 @@ class Controller extends ViewableData { 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) $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->Controller controller, but it wasn't at the top of the stack", E_USER_WARNING); + } + } /** * Handle redirection */ - function redirect($url) { // Attach site-root to relative links, if they have a slash in them if(substr($url,0,4) != "http" && $url[0] != "/" && strpos($url,'/') !== false){ @@ -336,6 +377,21 @@ class Controller extends ViewableData { $this->response->redirect($url); } + + /** + * Get the Session object representing this Controller's session + */ + function getSession() { + return $this->session; + } + + /** + * Set the Session object. + */ + function setSession(Session $session) { + $this->session = $session; + } + } ?> diff --git a/core/control/Director.php b/core/control/Director.php index b5a7c8f77..a277aba11 100644 --- a/core/control/Director.php +++ b/core/control/Director.php @@ -42,16 +42,20 @@ class Director { function direct($url) { if(isset($_GET['debug_profile'])) Profiler::mark("Director","direct"); $controllerObj = Director::getControllerForURL($url); + + // Load the session into the controller + $controllerObj->setSession(new Session($_SESSION)); if(is_string($controllerObj) && substr($controllerObj,0,9) == 'redirect:') { Director::redirect(substr($controllerObj, 9)); } else if($controllerObj) { $response = $controllerObj->run(array_merge((array)$_GET, (array)$_POST, (array)$_FILES)); + + // Save the updated session back + $_SESSION = $controllerObj->getSession()->inst_getAll(); - if(isset($_GET['debug_profile'])) Profiler::mark("Outputting to browser"); $response->output(); - if(isset($_GET['debug_profile'])) Profiler::unmark("Outputting to browser"); } if(isset($_GET['debug_profile'])) Profiler::unmark("Director","direct");