From: Chuck Hagenbuch Date: Sun, 24 Jan 2010 01:58:33 +0000 (-0500) Subject: Rewrite of Horde's MVC system to use controllers built around the single responsibili... X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=4b2dca59ca1ccedb43bd00c12f88c87a55d6d1ec;p=horde.git Rewrite of Horde's MVC system to use controllers built around the single responsibility principle and dependency injection. --- diff --git a/framework/Controller/lib/Horde/Controller.php b/framework/Controller/lib/Horde/Controller.php new file mode 100644 index 000000000..877b4d5be --- /dev/null +++ b/framework/Controller/lib/Horde/Controller.php @@ -0,0 +1,13 @@ + + * @license http://opensource.org/licenses/bsd-license.php BSD + */ +interface Horde_Controller +{ + public function processRequest(Horde_Controller_Request $request, Horde_Controller_Response $response); +} diff --git a/framework/Controller/lib/Horde/Controller/Base.php b/framework/Controller/lib/Horde/Controller/Base.php index fc4e430ae..8c034d424 100644 --- a/framework/Controller/lib/Horde/Controller/Base.php +++ b/framework/Controller/lib/Horde/Controller/Base.php @@ -1,502 +1,119 @@ - * @author Derek DeVries - * @author Chuck Hagenbuch - * @license http://opensource.org/licenses/bsd-license.php - * @category Horde - * @package Horde_Controller - */ - -/** - * @author Mike Naberezny - * @author Derek DeVries - * @author Chuck Hagenbuch - * @license http://opensource.org/licenses/bsd-license.php - * @category Horde - * @package Horde_Controller + * This class is for convenience, if you decide you wish to use only logging or + * the injector or views, or neither, you do not have to use it. As long as + * your controllers implement Horde_Controller, they are runnable. + * + * @category Horde + * @package Horde_Controller + * @author James Pepin + * @license http://opensource.org/licenses/bsd-license.php BSD */ -abstract class Horde_Controller_Base +abstract class Horde_Controller_Base implements Horde_Controller { /** - * Params is the list of variables set through routes. - * @var Horde_Support_Array - */ - protected $params; - - /** - * Have we performed a render on this controller - * @var boolean - */ - protected $_performedRender = false; - - /** - * Have we performed a redirect on this controller - * @var boolean - */ - protected $_performedRedirect = false; - - /** - * The request object we are processing - * @var Horde_Controller_Request_Base - * @todo Assign default value. - */ - protected $_request; - - /** - * The response object we are returning - * @var Horde_Controller_Response - * @todo Assign default value. - */ - protected $_response; - - /** - * The current action being performed - * @var string - * @todo Assign default value. - */ - protected $_action; - - /** - * Normal methods available as action requests. - * @var array - */ - protected $_actionMethods = array(); - - /** - * @var string - */ - protected $_viewsDir = ''; - - /** - * @var - */ - protected $_urlWriter; - - /** - * New controller instance - */ - public function __construct($options = array()) - { - foreach ($options as $key => $val) { - $this->{'_' . $key} = $val; - } - } - - /** - * Access to resources (request) and lazy loading of some (view). + * This is marked private on purpose, so that you have to use the + * getInjector() method to access it in derived classes. This is done so + * that you don't assume its always set, since its set via setter-injection + * to save on having to define a constructor param for it * - * @param string $name Property to access + * @var Horde_Injector */ - public function __get($name) - { - switch ($name) { - case 'request': - return $this->_request; - - case '_view': - $this->_view = new Horde_View; - return $this->_view; - } - } + private $_injector; /** - * Process the {@link Horde_Controller_Request_Base} and return - * the {@link Horde_Controller_Response}. This is the method that is called - * for every request to be processed. It then determines which action to call - * based on the parameters set within the {@link Horde_Controller_Request_Base} - * object. - * - * - * process($request, $response); - * ... - * ?> - * + * Private on purpose so you have to use getLogger(). * - * @param Horde_Controller_Request_Base $request - * @param Horde_Controller_Response_Base $response - * @return Horde_Controller_Response_Base - */ - public function process(Horde_Controller_Request_Base $request, Horde_Controller_Response_Base $response) - { - $this->_request = $request; - $this->_response = $response; - - $this->_initParams(); - - $this->_shortName = str_replace('Controller', '', $this->params[':controller']); - - try { - // templates - $this->_initActionMethods(); - $this->_initViewPaths(); - $this->_initViewHelpers(); - - // Initialize application logic used through all actions - $this->_initializeApplication(); - if ($this->_performed()) { - return $this->_response; - } - - // Initialize sub-controller logic used through all actions - if (is_callable(array($this, '_initialize'))) { - $this->_initialize(); - } - - // pre filters - - // execute action & save any changes to sessionData - $this->{$this->_action}(); - - // post filters - - // render default if we haven't performed an action yet - if (!$this->_performed()) { - $this->render(); - } - } catch (Exception $e) { - // error handling - throw $e; - } - - return $this->_response; - } - - /** + * @var Horde_Log_Logger */ - protected function interpretStatus($status) - { - return Horde_Controller_StatusCodes::interpret($status); - } + private $_logger; /** - * Generate a URL - * @see Horde_Controller_UrlWriter + * Private on purpose so you have to use getView(). * - * @param string|array $first named route, string, or options array - * @param array $second options array (if not in $first) - * @return string generated url + * @var Horde_View */ - protected function urlFor($first = array(), $second = array()) - { - return $this->getUrlWriter()->urlFor($first, $second); - } + private $_view; /** - * Get an instance of UrlWriter for this controller. + * Set the injector for this controller * - * @return Horde_Controller_UrlWriter - */ - public function getUrlWriter() - { - // instantiate UrlWriter that will generate URLs for this controller - if (!$this->_urlWriter) { - $defaults = array('controller' => $this->getControllerName()); - $this->_urlWriter = new Horde_Controller_UrlWriter($defaults); - } - return $this->_urlWriter; - } - - /** - * Get the current controller's name. + * @inject * - * @return string + * @param Horde_Injector The injector that this controller should use to create objects */ - protected function getControllerName() + public function setInjector(Horde_Injector $injector) { - if (empty($this->params)) { - $this->_initParams(); - } - - return $this->params[':controller']; + $this->_injector = $injector; } /** - * Render the response to the user. Actions are automatically rendered if no other - * action is specified. - * - * - * render(array('text' => 'some text to render')); - * $this->render(array('action' => 'actionName')); - * $this->render(array('nothing' => 1)); - * ... - * ?> - * - * - * @see renderText() - * @see renderAction() - * @see renderNothing() + * Get the injector for this controller * - * @param array $options - * - * @throws Horde_Controller_Exception + * @return Horde_Injector The injector previously set for this controller, + * or a new Horde_Injector_TopLevel */ - protected function render($options = array()) + public function getInjector() { - // should not render/redirect more than once. - if ($this->_performed()) { - throw new Horde_Controller_Exception("Double render error: \"$this->_action\""); - } - - // validate options - - // set response status - if (!empty($options['status'])) { - $header = $this->interpretStatus($options['status']); - $this->_response->setStatus($header); - } - - // set response location - if (!empty($options['location'])) { - $url = $this->urlFor($options['location']); - $this->_response->setHeader("Location: $url", $replace = true); - } - - // render text - if (!empty($options['text'])) { - $this->renderText($options['text']); - - // render xml - } elseif (!empty($options['xml'])) { - $this->_response->setContentType('application/xml'); - - $xml = $options['xml']; - if (is_object($xml) && method_exists($xml, 'toXml')) { - $xml = $xml->toXml(); - } - - $this->renderText($xml); - - // render template file - } elseif (!empty($options['action'])) { - $this->renderAction($options['action']); - - // render empty body - } elseif (!empty($options['nothing'])) { - $this->renderText(''); - - // render default template - } else { - $this->renderAction($this->_action); + if ($this->_injector) { + return $this->_injector; } + return new Horde_Injector_TopLevel(); } /** - * Render text directly to the screen without using a template + * Set the Logger for this controller * - * - * renderText('some text to render to the screen'); - * ... - * ?> - * + * @inject * - * @param string $text + * @param Horde_Log_Logger The logger to use for this controller */ - protected function renderText($text) + public function setLogger(Horde_Log_Logger $logger) { - $this->_response->setBody($text); - $this->_performedRender = true; + $this->_logger = $logger; } /** - * The name of the action method will render by default. - * - * render 'listDocuments' template file - * - * renderAction('listDocuments'); - * ... - * ?> - * + * Get the logger assigned to this controller * - * @param string $name + * @return Horde_Log_Logger The logger for this controller */ - protected function renderAction($name) + public function getLogger() { - // current url - $this->_view->currentUrl = '/' . $this->_request->getUri(); - - // copy instance variables - foreach (get_object_vars($this) as $key => $value) { - $this->_view->$key = $value; - } - - // add suffix - if ($this->_actionConflict) { - $name = str_replace('Action', '', $name); - } - if (strpos($name, '.') === false) { - $name .= '.html.php'; - } - - // prepend this controller's "short name" only if the action was - // specified without a controller "short name". - // e.g. index -> Shortname/index - // Shortname/index -> Shortname/index - if (strpos($name, '/') === false) { - // $name = $this->_shortName . '/' . $name; + if ($this->_logger) { + return $this->_logger; } - - if ($this->_useLayout) { - $this->_view->contentForLayout = $this->_view->render($name); - $text = $this->_view->render($this->_layoutName); - } else { - $text = $this->_view->render($name); - } - $this->renderText($text); - } - - /** - * Render blank content. This can be used anytime you want to send a 200 OK - * response back to the user, but don't need to actually render any content. - * This is mostly useful for ajax requests. - * - * - * renderNothing(); - * ... - * ?> - * - */ - protected function renderNothing() - { - $this->renderText(''); + return new Horde_Log_Logger(new Horde_Log_Handler_Null()); } /** - * Set the layout template for the controller. Specify the name of the file in - * the /app/views/layouts directory without the .html extension + * Set the Horde_View object to be used for this controller * - * - * setLayout('application'); - * } - * ... - * ?> - * + * @inject * - * @param string $layoutName + * @param Horde_View_Base The view object */ - protected function setLayout($layoutName) + public function setView(Horde_View_Base $view) { - $this->_useLayout = true; - $this->_layoutName = $layoutName; + $this->_view = $view; } /** - * Check if a render or redirect has been performed - * @return boolean - */ - protected function _performed() - { - return $this->_performedRender || $this->_performedRedirect; - } - - /** - * Each variable set through routing {@link Horde_Routes_Mapper} is - * available in controllers using the $params array. + * Gets the current view for this controller * - * The controller also has access to GET/POST arrays using $params - * - * The action method to be performed is stored in $this->params[':action'] key - */ - protected function _initParams() - { - $this->params = new Horde_Support_Array($this->_request->getParameters()); - $this->_action = $this->params->get(':action'); - } - - /** - * Set the list of public actions that are available for this Controller. - * Subclasses can remove methods from being publicly called by calling - * {@link hideAction()}. - * - * @throws Horde_Controller_Exception - */ - protected function _initActionMethods() - { - // Perform reflection to get the list of public methods - $reflect = new ReflectionClass($this); - $methods = $reflect->getMethods(); - foreach ($methods as $m) { - if ($m->isPublic() && !$m->isConstructor() && !$m->isDestructor() && - $m->getName() != 'process' && substr($m->getName(), 0, 1) != '_') { - $this->_actionMethods[$m->getName()] = 1; - } - } - - // try action suffix. - if (!isset($this->_actionMethods[$this->_action]) && - isset($this->_actionMethods[$this->_action.'Action'])) { - $this->_actionConflict = true; - $this->_action = $this->_action.'Action'; - } - // action isn't set, but there is a methodMissing() catchall method - if (!isset($this->_actionMethods[$this->_action]) && - isset($this->_actionMethods['methodMissing'])) { - $this->_action = 'methodMissing'; - - // make sure we have an action set, and that there is no methodMissing() method - } elseif (!isset($this->_actionMethods[$this->_action]) && - !isset($this->_actionMethods['methodMissing'])) { - $msg = 'Missing action: '.get_class($this)."::".$this->_action; - throw new Horde_Controller_Exception($msg); - } - } - - /** - * Initialize the view paths where the templates reside for this controller. - * These are added in FIFO order, so if we do $this->renderAction('foo'), - * in the BarController, the order it will search these directories will be: - * 1. /views/Bar/foo.html - * 2. /views/shared/foo.html - * 3. /views/layouts/foo.html - * 4. /views/foo.html (the default) + * @note This method will create an empty Horde_View if none has been set. * - * We can specify a directory to look in instead of relying on the default order - * by doing $this->renderAction('shared/foo'). + * @return Horde_View_Base The view for this controller, or a new empty + * Horde_View if none is set */ - protected function _initViewPaths() + public function getView() { - $this->_view->addTemplatePath($this->_viewsDir . 'layouts'); - $this->_view->addTemplatePath($this->_viewsDir . 'shared'); - $this->_view->addTemplatePath($this->_viewsDir . $this->_shortName); - } - - /** - * Initialize the default helpers for use in the views - */ - protected function _initViewHelpers() - { - $controllerHelper = $this->_shortName . 'Helper'; - if (class_exists($controllerHelper)) { - new $controllerHelper($this->_view); + if ($this->_view) { + return $this->_view; } + return new Horde_View(); } - - /** - * This gets called before action is performed in a controller. - * Override method in subclass to setup filters/helpers - */ - protected function _initializeApplication() - { - } - } diff --git a/framework/Controller/lib/Horde/Controller/Dispatcher.php b/framework/Controller/lib/Horde/Controller/Dispatcher.php deleted file mode 100644 index fa3e25ac8..000000000 --- a/framework/Controller/lib/Horde/Controller/Dispatcher.php +++ /dev/null @@ -1,266 +0,0 @@ - - * @author Derek DeVries - * @author Chuck Hagenbuch - * @license http://opensource.org/licenses/bsd-license.php - * @category Horde - * @package Horde_Controller - */ - -/** - * Dispatch a request to the appropriate controller and execute the response. - * - * @author Mike Naberezny - * @author Derek DeVries - * @author Chuck Hagenbuch - * @license http://opensource.org/licenses/bsd-license.php - * @category Horde - * @package Horde_Controller - */ -class Horde_Controller_Dispatcher -{ - /** @var Horde_Controller_Dispatcher */ - private static $_instance; - - /** @var Horde_Routes_Mapper */ - protected $_mapper; - - /** @var Horde_Log_Logger */ - protected $_logger; - - /** @var Horde_Support_Inflector */ - protected $_inflector; - - /** @var string */ - protected $_controllerDir = ''; - - /** @var string */ - protected $_viewsDir = ''; - - /** - * Singleton method. This should be the only way of instantiating a - * Horde_Controller_Dispatcher object. - * - * @return Horde_Controller_Dispatcher - */ - public static function singleton($context = array()) - { - if (self::$_instance === null) { - self::$_instance = new self($context); - } - return self::$_instance; - } - - /** - * Class constructor. Client code should use the singleton method to - * instantiate objects. - */ - protected function __construct($context) - { - if (!isset($context['mapper']) || ! $context['mapper'] instanceof Horde_Routes_Mapper) { - $context['mapper'] = new Horde_Routes_Mapper(); - $context['mapper']->connect('/:controller/:action/:id'); - } - - foreach ($context as $key => $val) { - $this->{'_' . $key} = $val; - } - - // Make sure controller directory, if set, ends in a /. - if ($this->_controllerDir && substr($this->_controllerDir, -1) != '/') { - $this->_controllerDir .= '/'; - } - - // Set the mapper's controller directory and controllerScan - if ($this->_controllerDir && !$this->_mapper->directory) { - $this->_mapper->directory = $this->_controllerDir; - } - $scanner = new Horde_Controller_Scanner($this->_mapper); - $this->_mapper->controllerScan = $scanner->getCallback(); - - // Make sure views directory, if set, ends in a /. - if ($this->_viewsDir && substr($this->_viewsDir, -1) != '/') { - $this->_viewsDir .= '/'; - } - - // Make sure we have an inflector - if (!$this->_inflector) { - $this->_inflector = new Horde_Support_Inflector(); - } - } - - /** - * Get the route utilities for this dispatcher and its mapper. - * - * @return Horde_Routes_Utils - */ - public function getRouteUtils() - { - return $this->_mapper->utils; - } - - /** - * Dispatch the request to the correct controller. - * - * @param Horde_Controller_Request_Base $request - */ - public function dispatch(Horde_Controller_Request_Base $request, $response = null) - { - $t = new Horde_Support_Timer(); - $t->push(); - - if (! $response instanceof Horde_Controller_Response_Base) { - $response = new Horde_Controller_Response_Http(); - // $response = new Horde_Controller_Response_Base(); - } - - // Recognize routes and process request - $controller = $this->recognize($request); - $response = $controller->process($request, $response); - - // Send response and log request - $time = $t->pop(); - $this->_logRequest($request, $time); - $response->send(); - } - - /** - * Check if request path matches any Routes to get the controller - * - * @return Horde_Controller_Base - * @throws Horde_Controller_Exception - */ - public function recognize($request) - { - $path = $request->getPath(); - if (substr($path, 0, 1) != '/') { - $path = '/' . $path; - } - - $matchdata = $this->_mapper->match($path); - if ($matchdata) { - $hash = $this->formatMatchdata($matchdata); - } - - if (empty($hash) || !isset($hash[':controller'])) { - $msg = 'No routes match the path: "' . $request->getPath() . '"'; - throw new Horde_Controller_Exception($msg); - } - - $request->setPathParams($hash); - - // try to load the class - $controllerName = $hash[':controller']; - if (!class_exists($controllerName, false)) { - $path = $this->_controllerDir . str_replace('_', '/', $controllerName) . '.php'; - if (file_exists($path)) { - require $path; - } else { - $msg = "The Controller \"$controllerName\" does not exist at " . $path; - throw new Horde_Controller_Exception($msg); - } - } - - $options = array( - 'viewsDir' => $this->_viewsDir, - ); - return new $controllerName($options); - } - - /** - * Take the $matchdata returned by a Horde_Routes_Mapper match and add - * in :controller and :action that are used by the rest of the framework. - * - * Format controller names: my_stuff => MyStuffController - * Format action names: action_name => actionName - * - * @param array $matchdata - * @return mixed false | array - */ - public function formatMatchdata($matchdata) - { - $ret = array(); - foreach ($matchdata as $key => $val) { - if ($key == 'controller') { - $ret[':controller'] = $this->_inflector->camelize($val) . 'Controller'; - } elseif ($key == 'action') { - $ret[':action'] = $this->_inflector->camelize($val, 'lower'); - } elseif ($key == 'module') { - $ret[':module'] = $this->_inflector->camelize($val); - } - - $ret[$key] = $val; - } - - if (!empty($ret[':module'])) { - $ret[':controller'] = $ret[':module'] . '_' . $ret[':controller']; - } - - return !empty($ret) && isset($ret['controller']) ? $ret : false; - } - - /** - * Log the http request - * - * @todo - get total query times - * - * @param Horde_Controller_Request_Base $request - * @param int $totalTime - */ - protected function _logRequest(Horde_Controller_Request_Base $request, $totalTime) - { - if (!is_callable(array($this->_logger, 'info'))) { - return; - } - - $queryTime = 0; // total time to execute queries - $queryCount = 0; // total queries performed - $phpTime = $totalTime - $queryTime; - - // embed user info in log - $uri = $request->getUri(); - $method = $request->getMethod(); - - $paramStr = 'PARAMS=' . $this->_formatLogParams($request->getParameters()); - - $msg = "$method $uri $totalTime ms (DB=$queryTime [$queryCount] PHP=$phpTime) $paramStr"; - $msg = wordwrap($msg, 80, "\n\t ", 1); - - $this->_logger->info($msg); - } - - /** - * Formats the request parameters as a "key => value, key => value, ..." string - * for the log file. - * - * @param array $params - * @return string - */ - protected function _formatLogParams($params) - { - $paramStr = '{'; - $count = 0; - foreach ($params as $key => $value) { - if ($key != 'controller' && $key != 'action' && - $key != ':controller' && $key != ':action') { - if ($count++ > 0) { $paramStr .= ', '; } - - $paramStr .= $key.' => '; - - if (is_array($value)) { - $paramStr .= $this->_formatLogParams($value); - } elseif (is_object($value)) { - $paramStr .= get_class($value); - } else { - $paramStr .= $value; - } - } - } - return $paramStr . '}'; - } - -} diff --git a/framework/Controller/lib/Horde/Controller/FileUpload.php b/framework/Controller/lib/Horde/Controller/FileUpload.php deleted file mode 100644 index 06695b8ef..000000000 --- a/framework/Controller/lib/Horde/Controller/FileUpload.php +++ /dev/null @@ -1,39 +0,0 @@ - - * @author Derek DeVries - * @author Chuck Hagenbuch - * @license http://opensource.org/licenses/bsd-license.php - * @category Horde - * @package Horde_Controller - */ - -/** - * A file upload from multipart form - * - * @author Mike Naberezny - * @author Derek DeVries - * @author Chuck Hagenbuch - * @license http://opensource.org/licenses/bsd-license.php - * @category Horde - * @package Horde_Controller - */ -class Horde_Controller_FileUpload -{ - public $originalFilename = null; - public $length = null; - public $contentType = null; - public $path = null; - - public function __construct($options) - { - $this->originalFilename = isset($options['name']) ? $options['name'] : null; - $this->length = isset($options['size']) ? $options['size'] : null; - $this->contentType = isset($options['type']) ? $options['type'] : null; - $this->path = isset($options['tmp_name']) ? $options['tmp_name'] : null; - } - -} diff --git a/framework/Controller/lib/Horde/Controller/Filter/Gzip.php b/framework/Controller/lib/Horde/Controller/Filter/Gzip.php new file mode 100644 index 000000000..9994cc4f8 --- /dev/null +++ b/framework/Controller/lib/Horde/Controller/Filter/Gzip.php @@ -0,0 +1,37 @@ + + * @license http://opensource.org/licenses/bsd-license.php BSD + */ +class Horde_Controller_Filter_Gzip implements Horde_Controller_PostFilter +{ + public function processResponse(Horde_Controller_Request $request, Horde_Controller_Response $response, Horde_Controller $controller) + { + $body = $response->getBody(); + $body = gzencode($body); + + $response->setHeader('Content-Encoding', 'gzip'); + $response->setHeader('Content-Length', $this->_byteCount($body)); + $response->setBody($body); + + return $response; + } + + /** + * If mbstring is set to overload str* function then we could be counting + * multi-byte chars as single bytes so we need to treat the string like its + * 8-bit encoded to get an accurate byte count. + */ + protected function _byteCount($string) + { + if (ini_get('mbstring.func_overload') > 0) { + return mb_strlen($string, '8bit'); + } + + return strlen($string); + } +} diff --git a/framework/Controller/lib/Horde/Controller/FilterCollection.php b/framework/Controller/lib/Horde/Controller/FilterCollection.php new file mode 100644 index 000000000..829c39a8b --- /dev/null +++ b/framework/Controller/lib/Horde/Controller/FilterCollection.php @@ -0,0 +1,20 @@ + + * @author Bob McKee + * @license http://opensource.org/licenses/bsd-license.php BSD + */ +interface Horde_Controller_FilterCollection +{ + /** + */ + public function addPreFilter(Horde_Controller_PreFilter $filter); + + /** + */ + public function addPostFilter(Horde_Controller_PostFilter $filter); +} diff --git a/framework/Controller/lib/Horde/Controller/FilterRunner.php b/framework/Controller/lib/Horde/Controller/FilterRunner.php new file mode 100644 index 000000000..75cbd00d6 --- /dev/null +++ b/framework/Controller/lib/Horde/Controller/FilterRunner.php @@ -0,0 +1,97 @@ + + * @author James Pepin + * @license http://opensource.org/licenses/bsd-license.php BSD + */ +class Horde_Controller_FilterRunner implements Horde_Controller_FilterCollection +{ + /** + * @var Horde_Controller + */ + protected $_controller; + + /** + * @var array + */ + protected $_preFilters = array(); + + /** + * @var array + */ + protected $_postFilters = array(); + + /** + */ + public function __construct(Horde_Controller $controller) + { + $this->_controller = $controller; + } + + /** + * Append filter to prefilters array + * + * @param Horde_Controller_PreFilter $filter + */ + public function addPreFilter(Horde_Controller_PreFilter $filter) + { + array_push($this->_preFilters, $filter); + } + + /** + * Prepend fitler to postfilters array + * + * @param Horde_Controller_PostFilter $filter + */ + public function addPostFilter(Horde_Controller_PostFilter $filter) + { + array_unshift($this->_postFilters, $filter); + } + + /** + * Executes filters and controller method. Execution happens in the following order: + * + * - Run processRequest() on prefilters in first-in-first-out order + * - Run processRequest() on controller + * - Run processResponse() on postfilters in first-in-last-out order + * + * @param Horde_Controller_Request $request + * @param Horde_Controller_Response $response + * + * @return Horde_Controller_Response + */ + public function processRequest(Horde_Controller_Request $request, Horde_Controller_Response $response) + { + if ($this->_applyPreFilters($request, $response) !== Horde_Controller_PreFilter::REQUEST_HANDLED) { + $this->_controller->processRequest($request, $response); + $this->_applyPostFilters($request, $response); + } + return $response; + } + + /** + */ + protected function _applyPreFilters(Horde_Controller_Request $request, Horde_Controller_Response $response) + { + foreach ($this->_preFilters as $filter) { + if ($filter->processRequest($request, $response, $this->_controller) === Horde_Controller_PreFilter::REQUEST_HANDLED) { + return Horde_Controller_PreFilter::REQUEST_HANDLED; + } + } + + return Horde_Controller_PreFilter::REQUEST_CONTINUE; + } + + /** + */ + protected function _applyPostFilters(Horde_Controller_Request $request, Horde_Controller_Response $response) + { + foreach ($this->_postFilters as $filter) { + $filter->processResponse($request, $response, $this->_controller); + } + } +} diff --git a/framework/Controller/lib/Horde/Controller/Mime/Type.php b/framework/Controller/lib/Horde/Controller/Mime/Type.php deleted file mode 100644 index a5df481ee..000000000 --- a/framework/Controller/lib/Horde/Controller/Mime/Type.php +++ /dev/null @@ -1,136 +0,0 @@ - - * @author Derek DeVries - * @author Chuck Hagenbuch - * @license http://opensource.org/licenses/bsd-license.php - * @category Horde - * @package Horde_Controller - * @subpackage Response - */ - -/** - * Handles managing of what types of responses the client can handle and which - * one was requested. - * - * @author Mike Naberezny - * @author Derek DeVries - * @author Chuck Hagenbuch - * @license http://opensource.org/licenses/bsd-license.php - * @category Horde - * @package Horde_Controller - * @subpackage Response - */ -class Horde_Controller_Mime_Type -{ - public $symbol; - public $synonyms; - public $string; - - public static $set = array(); - public static $lookup = array(); - public static $extensionLookup = array(); - public static $registered = false; - - public function __construct($string, $symbol = null, $synonyms = array()) - { - $this->string = $string; - $this->symbol = $symbol; - $this->synonyms = $synonyms; - } - - public function __toString() - { - return $this->symbol; - } - - public static function lookup($string) - { - if (!empty(self::$lookup[$string])) { - return self::$lookup[$string]; - } else { - return null; - } - } - - public static function lookupByExtension($ext) - { - if (!empty(self::$extensionLookup[$ext])) { - return self::$extensionLookup[$ext]; - } else { - return null; - } - } - - public static function register($string, $symbol, $synonyms = array(), $extSynonyms = array()) - { - $type = new Horde_Controller_Mime_Type($string, $symbol, $synonyms); - self::$set[] = $type; - - // add lookup strings - foreach (array_merge((array)$string, $synonyms) as $string) { - self::$lookup[$string] = $type; - } - - // add extesnsion lookups - foreach (array_merge((array)$symbol, $extSynonyms) as $ext) { - self::$extensionLookup[$ext] = $type; - } - } - - /** - * @todo - actually parse the header. This is simply mocked out - * with common types for now - */ - public static function parse($acceptHeader) - { - $types = array(); - - if (strstr($acceptHeader, 'text/javascript')) { - if (isset(self::$extensionLookup['js'])) { - $types[] = self::$extensionLookup['js']; - } - - } elseif (strstr($acceptHeader, 'text/html')) { - if (isset(self::$extensionLookup['html'])) { - $types[] = self::$extensionLookup['html']; - } - - } elseif (strstr($acceptHeader, 'text/xml')) { - if (isset(self::$extensionLookup['xml'])) { - $types[] = self::$extensionLookup['xml']; - } - - // default to html - } else { - if (isset(self::$extensionLookup['html'])) { - $types[] = self::$extensionLookup['html']; - } - } - return $types; - } - - /** - * Register mime types - * @todo - move this elsewhere? - */ - public static function registerTypes() - { - if (!self::$registered) { - Horde_Controller_Mime_Type::register('*/*', 'all'); - Horde_Controller_Mime_Type::register('text/plain', 'text', array(), array('txt')); - Horde_Controller_Mime_Type::register('text/html', 'html', array('application/xhtml+xml'), array('xhtml')); - Horde_Controller_Mime_Type::register('text/javascript', 'js', array('application/javascript', 'application/x-javascript'), array('xhtml')); - Horde_Controller_Mime_Type::register('application/json', 'json'); - Horde_Controller_Mime_Type::register('text/xml', 'rss'); - Horde_Controller_Mime_Type::register('application/atom+xml', 'atom'); - Horde_Controller_Mime_Type::register('text/csv', 'csv'); - Horde_Controller_Mime_Type::register('application/xml', 'xml', array('text/xml', 'application/x-xml')); - self::$registered = true; - } - } - -} diff --git a/framework/Controller/lib/Horde/Controller/Null.php b/framework/Controller/lib/Horde/Controller/Null.php new file mode 100644 index 000000000..a8bbf011f --- /dev/null +++ b/framework/Controller/lib/Horde/Controller/Null.php @@ -0,0 +1,16 @@ + + * @license http://opensource.org/licenses/bsd-license.php BSD + */ +class Horde_Controller_Null implements Horde_Controller +{ + public function processRequest(Horde_Controller_Request $request, Horde_Controller_Response $response) + { + } +} diff --git a/framework/Controller/lib/Horde/Controller/PostFilter.php b/framework/Controller/lib/Horde/Controller/PostFilter.php new file mode 100644 index 000000000..7dd88b200 --- /dev/null +++ b/framework/Controller/lib/Horde/Controller/PostFilter.php @@ -0,0 +1,14 @@ + + * @author Bob McKee + * @license http://opensource.org/licenses/bsd-license.php BSD + */ +interface Horde_Controller_PostFilter +{ + public function processResponse(Horde_Controller_Request $request, Horde_Controller_Response $response, Horde_Controller $controller); +} diff --git a/framework/Controller/lib/Horde/Controller/PreFilter.php b/framework/Controller/lib/Horde/Controller/PreFilter.php new file mode 100644 index 000000000..1bdb88629 --- /dev/null +++ b/framework/Controller/lib/Horde/Controller/PreFilter.php @@ -0,0 +1,17 @@ + + * @author Bob McKee + * @license http://opensource.org/licenses/bsd-license.php BSD + */ +interface Horde_Controller_PreFilter +{ + const REQUEST_HANDLED = true; + const REQUEST_CONTINUE = false; + + public function processRequest(Horde_Controller_Request $request, Horde_Controller_Response $response, Horde_Controller $controller); +} diff --git a/framework/Controller/lib/Horde/Controller/Request.php b/framework/Controller/lib/Horde/Controller/Request.php new file mode 100644 index 000000000..a5f0307b8 --- /dev/null +++ b/framework/Controller/lib/Horde/Controller/Request.php @@ -0,0 +1,47 @@ + + * @license http://opensource.org/licenses/bsd-license.php BSD + */ +interface Horde_Controller_Request +{ + /** + */ + public function getPath(); + + /** + */ + public function getMethod(); + + /** + */ + public function getGetVars(); + + /** + */ + public function getFileVars(); + + /** + */ + public function getServerVars(); + + /** + */ + public function getPostVars(); + + /** + */ + public function getCookieVars(); + + /** + */ + public function getRequestVars(); + + /** + */ + public function getSessionId(); +} diff --git a/framework/Controller/lib/Horde/Controller/Request/Base.php b/framework/Controller/lib/Horde/Controller/Request/Base.php deleted file mode 100644 index 01dc7668d..000000000 --- a/framework/Controller/lib/Horde/Controller/Request/Base.php +++ /dev/null @@ -1,212 +0,0 @@ - - * @author Derek DeVries - * @author Chuck Hagenbuch - * @license http://opensource.org/licenses/bsd-license.php - * @category Horde - * @package Horde_Controller - * @subpackage Request - * - * http://pythonpaste.org/webob/ - * http://usrportage.de/archives/875-Dojo-and-the-Zend-Framework.html - * http://framework.zend.com/manual/en/zend.filter.input.html - * http://www.lornajane.net/posts/2009/Adding-PUT-variables-to-Request-Object-in-Zend-Framework - */ - -/** - * @author Mike Naberezny - * @author Derek DeVries - * @author Chuck Hagenbuch - * @license http://opensource.org/licenses/bsd-license.php - * @category Horde - * @package Horde_Controller - * @subpackage Request - */ -abstract class Horde_Controller_Request_Base -{ - /** - * Request timestamp - * - * @var integer - */ - protected $_timestamp; - - /** - * The SAPI - * - * @var string - */ - protected $_sapi; - - /** - * Unique id per request. - * @var string - */ - protected $_requestId; - - protected $_server; - protected $_env; - - protected $_body; - - /** - */ - public function __construct($options = array()) - { - $this->_initRequestId(); - - $this->_server = isset($options['server']) ? $options['server'] : $_SERVER; - $this->_env = isset($options['env']) ? $options['env'] : $_ENV; - - if (isset($_SERVER['REQUEST_TIME'])) { - $this->_timestamp = $_SERVER['REQUEST_TIME']; - } else { - $this->_timestamp = time(); - } - - $this->_sapi = php_sapi_name(); - } - - /** - * Get request timestamp - * - * @return integer - */ - public function getTimestamp() - { - return $this->_timestamp; - } - - /** - * Get server variable with the specified $name - * - * @param string $name - * @return string - */ - public function getServer($name) - { - if ($name == 'SCRIPT_NAME' && strncmp($this->_sapi, 'cgi', 3) === 0) { - $name = 'SCRIPT_URL'; - } - return isset($this->_server[$name]) ? $this->_server[$name] : null; - } - - /** - * Get environment variable with the specified $name - * - * @param string $name - * @return string - */ - public function getEnv($name) - { - return isset($this->_env[$name]) ? $this->_env[$name] : null; - } - - /** - * Get a combination of all parameters. We have to do - * some wacky loops to make sure that nested values in one - * param list don't overwrite other nested values - * - * @return array - */ - public function getParameters() - { - $allParams = array(); - $paramArrays = array($this->_pathParams, $this->_formattedRequestParams); - - foreach ($paramArrays as $params) { - foreach ((array)$params as $key => $value) { - if (!is_array($value) || !isset($allParams[$key])) { - $allParams[$key] = $value; - } else { - $allParams[$key] = array_merge($allParams[$key], $value); - } - } - } - return $allParams; - } - - /** - * Get entire list of parameters set by {@link Horde_Controller_Route_Path} for - * the current request - * - * @return array - */ - public function getPathParams() - { - return $this->_pathParams; - } - - /** - * When the {@link Horde_Controller_Dispatcher} determines the - * correct {@link Horde_Controller_Route_Path} to match the url, it uses the - * Routing object data to set appropriate variables so that they can be passed - * to the Controller object. - * - * @param array $params - */ - public function setPathParams($params) - { - $this->_pathParams = !empty($params) ? $params : array(); - } - - /** - * Get the unique ID generated for this request - * @see _initRequestId() - * @return string - */ - public function getRequestId() - { - return $this->_requestId; - } - - /** - * The request body - * - * @TODO Allow overriding php://input, and make the default interface to - * return an SplFileObject, or a (doesn't currently exist) Horde_File - * object, instead of the raw data. - * - * @return string - */ - public function getBody() - { - if (!isset($this->_body)) { - $this->_body = file_get_contents('php://input'); - } - return $this->_body; - } - - /** - * Return the request content length - * - * @return int - */ - public function getContentLength() - { - return strlen($this->getBody()); - } - - /** - * Turn this request into a URL-encoded query string. - */ - public function __toString() - { - return http_build_query($this); - } - - abstract public function getPath(); - - /** - * Uniquely identify each request from others. This aids in threading - * related log requests during troubleshooting on a busy server - */ - private function _initRequestId() - { - $this->_requestId = (string)new Horde_Support_Uuid; - } -} diff --git a/framework/Controller/lib/Horde/Controller/Request/Cli.php b/framework/Controller/lib/Horde/Controller/Request/Cli.php deleted file mode 100644 index 349308c60..000000000 --- a/framework/Controller/lib/Horde/Controller/Request/Cli.php +++ /dev/null @@ -1,121 +0,0 @@ - - * @author Derek DeVries - * @author Chuck Hagenbuch - * @license http://opensource.org/licenses/bsd-license.php - * @category Horde - * @package Horde_Controller - * @subpackage Request - */ - -/** - * Represents a command line request invocation. - * - * @author Mike Naberezny - * @author Derek DeVries - * @author Chuck Hagenbuch - * @license http://opensource.org/licenses/bsd-license.php - * @category Horde - * @package Horde_Controller - * @subpackage Request - */ -class Horde_Controller_Request_Cli extends Horde_Controller_Request_Base -{ - /** - * Command line arguments - */ - protected $_argv; - - /** - * Constructor - * - * In addition to the $options from Horde_Controller_Request_Base, you can - * pass a 'path' argument as a string, or a Horde_Argv_Parser object as - * 'argvParser' to override the path of the cli request. - */ - public function __construct($options = array()) - { - parent::__construct($options); - - $pathArgs = null; - if (isset($options['path'])) { - $pathArgs = $options['path']; - } elseif (isset($options['argvParser'])) { - $pathArgs = $options['argvParser']; - } - - $this->setPath($pathArgs); - } - - public function getUri() - { - return $this->getPath(); - } - - public function getMethod() - { - return 'CLI'; - } - - public function setArgv($args) - { - $this->_argv = $args; - } - - public function getPath() - { - return $this->_path; - } - - public function setPath($args = null) - { - if (is_string($args)) { - $this->_path = $args; - } else { - if ($args instanceof Horde_Argv_Parser) { - $parser = $args; - } else { - $parser = new Horde_Argv_Parser(array( - 'allowUnknownArgs' => true, - 'addHelpOption' => false, - )); - } - - list($this->_argv, $args) = $parser->parseArgs(); - if (!count($args)) { - throw new Horde_Controller_Exception('unknown command: ' . implode(' ', $args)); - } - $this->_path = $args[0]; - $this->_path = str_replace('-', '_', $this->_path); - } - } - - /** - * Get all command line parameters. - * some wacky loops to make sure that nested values in one - * param list don't overwrite other nested values - * - * @return array - */ - public function getParameters() - { - $allParams = array(); - $paramArrays = array($this->_pathParams, $this->_argv); - - foreach ($paramArrays as $params) { - foreach ((array)$params as $key => $value) { - if (!is_array($value) || !isset($allParams[$key])) { - $allParams[$key] = $value; - } else { - $allParams[$key] = array_merge($allParams[$key], $value); - } - } - } - return $allParams; - } - -} diff --git a/framework/Controller/lib/Horde/Controller/Request/Http.php b/framework/Controller/lib/Horde/Controller/Request/Http.php index ef411bba4..24f1ca05b 100644 --- a/framework/Controller/lib/Horde/Controller/Request/Http.php +++ b/framework/Controller/lib/Horde/Controller/Request/Http.php @@ -1,517 +1,89 @@ - * @author Derek DeVries - * @author Chuck Hagenbuch - * @license http://opensource.org/licenses/bsd-license.php - * @category Horde - * @package Horde_Controller - * @subpackage Request + * @category Horde + * @package Horde_Controller + * @author James Pepin + * @license http://opensource.org/licenses/bsd-license.php BSD */ - -/** - * Represents an HTTP request to the server. This class handles all - * headers/cookies/session data so that it all has one point of entry for being - * written/retrieved. - * - * @author Mike Naberezny - * @author Derek DeVries - * @author Chuck Hagenbuch - * @license http://opensource.org/licenses/bsd-license.php - * @category Horde - * @package Horde_Controller - * @subpackage Request - */ -class Horde_Controller_Request_Http extends Horde_Controller_Request_Base +class Horde_Controller_Request_Http implements Horde_Controller_Request { + protected $_path; + /** * All the headers. * @var array */ protected $_headers = null; - /** - * PHPSESSID - * @var string - */ - protected $_sessionId; - - // superglobal arrays - protected $_get; - protected $_post; - protected $_files; - protected $_request; - - // cookie/session info - protected $_cookie; - protected $_session; - - protected $_contentType; - protected $_accepts; - protected $_format; - protected $_method; - protected $_remoteIp; - protected $_port; - protected $_https; - protected $_isAjax; - - protected $_domain; - protected $_uri; - protected $_pathParams; - /*########################################################################## # Construct/Destruct ##########################################################################*/ - /** - * Request is populated with all the superglobals from page request if - * data is not passed in. - * - * @param array $options Associative array with all superglobals - */ - public function __construct($options = array()) - { - try { - if (!empty($options['session_control']) && $options['session_control'] != 'none') { - $this->_initSessionData(); - } - - // register default mime types - Horde_Controller_Mime_Type::registerTypes(); - - // superglobal data if not passed in thru constructor - $this->_get = isset($options['get']) ? $options['get'] : $_GET; - $this->_post = isset($options['post']) ? $options['post'] : $_POST; - $this->_cookie = isset($options['cookie']) ? $options['cookie'] : $_COOKIE; - $this->_request = isset($options['request']) ? $options['request'] : $_REQUEST; - - parent::__construct($options); - - $this->_pathParams = array(); - //$this->_formattedRequestParams = $this->_parseFormattedRequestParameters(); - - // use FileUpload object to store files - $this->_setFilesSuperglobals(); - - // disable all superglobal data to force us to use correct way - //@TODO - //$_GET = $_POST = $_FILES = $_COOKIE = $_REQUEST = $_SERVER = array(); - - $this->_domain = $this->getServer('SERVER_NAME'); - $this->_uri = trim($this->getServer('REQUEST_URI'), '/'); - $this->_method = $this->getServer('REQUEST_METHOD'); - // @TODO look at HTTP_X_FORWARDED_FOR, handling multiple addresses: http://weblogs.asp.net/james_crowley/archive/2007/06/19/gotcha-http-x-forwarded-for-returns-multiple-ip-addresses.aspx - $this->_remoteIp = $this->getServer('REMOTE_ADDR'); - $this->_port = $this->getServer('SERVER_PORT'); - $this->_https = $this->getServer('HTTPS') || $this->getServer('SSL_PROTOCOL') || $this->getServer('HTTP_X_CLUSTER_SSL'); - $this->_isAjax = $this->getServer('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'; - } catch (Exception $e) { - $this->_malformed = true; - $this->_exception = $e; - } - } - - - /*########################################################################## - # Public Methods - ##########################################################################*/ - - /** - * Get the http request method: - * eg. GET, POST, PUT, DELETE - * - * @return string - */ - public function getMethod() - { - $methods = array('GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'OPTIONS'); - - if ($this->_method == 'POST') { - $params = $this->getParameters(); - if (isset($params['_method'])) { - $faked = strtoupper($params['_method']); - if (in_array($faked, $methods)) return $faked; - } - } - - return $this->_method; - } - - /** - * Get list of all superglobals to pass into a different request - * - * @return array - */ - public function getGlobals() - { - return array('get' => $this->_get, - 'post' => $this->_post, - 'cookie' => $this->_cookie, - 'session' => $this->_session, - 'files' => $this->_files, - 'request' => $this->_request, - 'server' => $this->_server, - 'env' => $this->_env); - } - - /** - * Get the domain for the current request - * eg. https://www.maintainable.com/articles/show/123 - * $domain is -> www.maintainable.com - * - * @return string - */ - public function getDomain() + public function __construct($path) { - return $this->_domain; + $this->_path = $path; } - /** - * Get the host for the current request - * eg. http://www.maintainable.com:3000/articles/show/123 - * $host is -> http://www.maintainablesoftware.com:3000 - * - * @param boolean $usePort - * @return string - */ - public function getHost($usePort = false) - { - $scheme = 'http' . ($this->_https == 'on' ? 's' : null); - $port = $usePort && !empty($this->_port) && $this->_port != '80' ? ':' . $this->_port : null; - return "{$scheme}://{$this->_domain}$port"; - } - /** - * @todo add ssl support - * @return string - */ - public function getProtocol() - { - // return $this->getServer('SERVER_PROTOCOL'); - return 'http://'; - } - - /** - * Get the uri for the current request - * eg. https://www.maintainable.com/articles/show/123?page=1 - * $uri is -> articles/show/123?page=1 - * - * @return string - */ - public function getUri() - { - return $this->_uri; - } - - /** - * Get the path from the URI. (strip get params) - * eg. https://www.maintainable.com/articles/show/123?page=1 - * $path is -> articles/show/123 - * - * @return string - */ public function getPath() { - $path = $this->_uri; - if (strstr($path, '?')) { - $path = trim(substr($path, 0, strpos($path, '?')), '/'); - } - return $path; - } - - public function getContentType() - { - if (!isset($this->_contentType)) { - $type = $this->getServer('CONTENT_TYPE'); - // strip parameters from content-type like "; charset=UTF-8" - if (is_string($type)) { - if (preg_match('/^([^,\;]*)/', $type, $matches)) { - $type = $matches[1]; - } - } - - $this->_contentType = Horde_Controller_Mime_Type::lookup($type); - } - return $this->_contentType; - } - - /** - * @return array - */ - public function getAccepts() - { - if (!isset($this->_accepts)) { - $accept = $this->getServer('HTTP_ACCEPT'); - if (empty($accept)) { - $types = array(); - $contentType = $this->getContentType(); - if ($contentType) { $types[] = $contentType; } - $types[] = Horde_Controller_Mime_Type::lookupByExtension('all'); - $accepts = $types; - } else { - $accepts = Horde_Controller_Mime_Type::parse($accept); - } - $this->_accepts = $accepts; - } - return $this->_accepts; + return $this->_path; } - - /** - * Returns the Mime type for the format used in the request. If there is no - * format available, the first of the - * - * @return string - */ - public function getFormat() - { - if (!isset($this->_format)) { - $params = $this->getParameters(); - if (isset($params['format'])) { - $this->_format = Horde_Controller_Mime_Type::lookupByExtension($params['format']); - } else { - $this->_format = current($this->getAccepts()); - } - } - return $this->_format; - } - - /** - * Get the remote Ip address as a dotted decimal string. - * - * @return string - */ - public function getRemoteIp() + public function getMethod() { - return $this->_remoteIp; + $serverVars = $this->getServerVars(); + return $serverVars['REQUEST_METHOD']; } /** - * Get cookie value from specified $name OR get All when $name isn't passed in + * Gets the request variables GET, POST, COOKIE, SERVER, REQUEST etc. * - * @param string $name - * @param string $default - * @return string + * @param string $name The name of the superglobal whose vars to return */ - public function getCookie($name=null, $default=null) + protected function getVars($name) { - if (isset($name)) { - return isset($this->_cookie[$name]) ? $this->_cookie[$name] : $default; - } else { - return $this->_cookie; - } + return $GLOBALS['_' . $name]; } - /** - * Get session value from session data by $name or ALL when $name isn't passed in - * - * @param string $name - * @param string $default - * @return mixed - */ - public function getSession($name=null, $default=null) + public function getGetVars() { - if (isset($name)) { - return isset($this->_session[$name]) ? $this->_session[$name] : $default; - } else { - return $this->_session; - } + return $this->getVars('GET'); } - /** - * Get entire list of $_COOKIE parameters - * - * @return array - */ - public function getCookieParams() + public function getFileVars() { - return $this->_cookie; + return $this->getVars('FILES'); } - /** - * Get entire list of $_SERVER parameters - * - * @return array - */ - public function getServerParams() + public function getServerVars() { - return $this->_server; + return $this->getVars('SERVER'); } - /** - * Get a combination of all parameters. We have to do - * some wacky loops to make sure that nested values in one - * param list don't overwrite other nested values - * - * @return array - */ - public function getParameters() + public function getPostVars() { - $allParams = array(); - $paramArrays = array($this->_pathParams, /*$this->_formattedRequestParams, */ - $this->_get, $this->_post, $this->_files); - - foreach ($paramArrays as $params) { - foreach ((array)$params as $key => $value) { - if (!is_array($value) || !isset($allParams[$key])) { - $allParams[$key] = $value; - } else { - $allParams[$key] = array_merge($allParams[$key], $value); - } - } - } - return $allParams; + return $this->getVars('POST'); } - /** - * Get entire list of $_GET parameters - * @return array - */ - public function getGetParams() + public function getCookieVars() { - return $this->_get; + return $this->getVars('COOKIE'); } - /** - * Get entire list of $_POST parameters - * - * @return array - */ - public function getPostParams() + public function getRequestVars() { - return $this->_post; + return $this->getVars('REQUEST'); } - /** - * Get entire list of $_FILES parameters - * - * @return array - */ - public function getFilesParams() - { - return $this->_files; - } - - /** - * Get the session ID of this request (PHPSESSID) - * @see _initSession() - * @return string - */ public function getSessionId() { - return $this->_sessionId; - } - - - /*########################################################################## - # Modifiers - ##########################################################################*/ - - /** - * Set the uri and parse it for useful info - * - * @param string $uri - */ - public function setUri($uri) - { - $this->_uri = trim($uri, '/'); - } - - /** - * Set the session array. - * - * @param string $name - * @param mixed $value - */ - public function setSession($name, $value=null) - { - if (is_array($name)) { - $this->_session = $name; - } else { - $this->_session[$name] = $value; - } - } - - - /*########################################################################## - # Private Methods - ##########################################################################*/ - - /** - * Start up default session storage, and get stored data. - * - * @todo further investigate session_cache_limiter() on ie6 (see below) - * @todo implement active record session store - */ - protected function _initSessionData() - { - $this->_sessionId = session_id(); - - if (! strlen($this->_sessionId)) { - // internet explorer 6 will ignore the filename/content-type during - // sendfile over ssl unless session_cache_limiter('public') is set - // http://joseph.randomnetworks.com/archives/2004/10/01/making-ie-accept-file-downloads/ - $agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : ''; - if (strpos($agent, 'MSIE') !== false) { - session_cache_limiter("public"); - } - - session_start(); - $this->_sessionId = session_id(); - } - - // Important: Setting "$this->_session = $_SESSION" does NOT work. - $this->_session = array(); - if (is_array($_SESSION)) { - foreach ($_SESSION as $key => $value) { - $this->_session[$key] = $value; - } - } - } - - /** - * Initialize the File upload information - */ - protected function _setFilesSuperglobals() - { - if (empty($_FILES)) { - $this->_files = array(); - return; - } - $_FILES = array_map(array($this, '_fixNestedFiles'), $_FILES); - - // create FileUpload object of the file options - foreach ((array)$_FILES as $name => $options) { - if (isset($options['tmp_name'])) { - $this->_files[$name] = new Horde_Controller_FileUpload($options); - } else { - foreach ($options as $attr => $data) { - $this->_files[$name][$attr] = new Horde_Controller_FileUpload($data); - } - } - } - } - - /** - * fix $_FILES superglobal array. (PHP mungles data when we use brackets) - * - * @link http://www.shauninman.com/archive/2006/11/30/fixing_the_files_superglobal - * @param array $group - */ - protected function _fixNestedFiles($group) - { - // only rearrange nested files - if (!is_array($group['tmp_name'])) { return $group; } - - foreach ($group as $property => $arr) { - foreach ($arr as $item => $value) { - $result[$item][$property] = $value; - } - } - return $result; + //TODO: how do we get session ID? + //should probably be passing it in the constructor, or via setSession + //we should definitely lazy-load sessions though cause we don't always care about it + //perhaps a preFilter to start the session if the controller requests it, and then call setSession on the request + //object + return 0; } /** @@ -596,5 +168,4 @@ class Horde_Controller_Request_Http extends Horde_Controller_Request_Base return $result; } - } diff --git a/framework/Controller/lib/Horde/Controller/Request/Mock.php b/framework/Controller/lib/Horde/Controller/Request/Mock.php deleted file mode 100644 index 381ebaf54..000000000 --- a/framework/Controller/lib/Horde/Controller/Request/Mock.php +++ /dev/null @@ -1,29 +0,0 @@ - - * @author Derek DeVries - * @author Chuck Hagenbuch - * @license http://opensource.org/licenses/bsd-license.php - * @category Horde - * @package Horde_Controller - * @subpackage Request - */ - -/** - * @author Mike Naberezny - * @author Derek DeVries - * @author Chuck Hagenbuch - * @license http://opensource.org/licenses/bsd-license.php - * @category Horde - * @package Horde_Controller - * @subpackage Request - */ -class Horde_Controller_Request_Mock extends Horde_Controller_Request_Base -{ - public function getPath() - { - } -} diff --git a/framework/Controller/lib/Horde/Controller/Request/Null.php b/framework/Controller/lib/Horde/Controller/Request/Null.php new file mode 100644 index 000000000..7803705c2 --- /dev/null +++ b/framework/Controller/lib/Horde/Controller/Request/Null.php @@ -0,0 +1,73 @@ + + * @license http://opensource.org/licenses/bsd-license.php BSD + */ +class Horde_Controller_Request_Null implements Horde_Controller_Request +{ + /** + */ + public function getMethod() + { + } + + /** + */ + public function getPath() + { + } + + /** + */ + public function getParameters() + { + } + + /** + */ + public function getGetVars() + { + } + + /** + */ + public function getFileVars() + { + } + + /** + */ + public function getServerVars() + { + } + + /** + */ + public function getPostVars() + { + } + + /** + */ + public function getCookieVars() + { + } + + /** + */ + public function getRequestVars() + { + } + + /** + */ + public function getSessionId() + { + } +} diff --git a/framework/Controller/lib/Horde/Controller/RequestConfiguration.php b/framework/Controller/lib/Horde/Controller/RequestConfiguration.php new file mode 100644 index 000000000..19e40f240 --- /dev/null +++ b/framework/Controller/lib/Horde/Controller/RequestConfiguration.php @@ -0,0 +1,18 @@ + + * @author James Pepin + * @license http://opensource.org/licenses/bsd-license.php BSD + */ +interface Horde_Controller_RequestConfiguration +{ + public function getControllerName(); + + public function setControllerName($controllerName); + + public function getSettingsExporterName(); + + public function setSettingsExporterName($settingsName); +} diff --git a/framework/Controller/lib/Horde/Controller/Response.php b/framework/Controller/lib/Horde/Controller/Response.php new file mode 100644 index 000000000..39581d8c2 --- /dev/null +++ b/framework/Controller/lib/Horde/Controller/Response.php @@ -0,0 +1,62 @@ + + * @license http://opensource.org/licenses/bsd-license.php BSD + */ +class Horde_Controller_Response +{ + protected $_headers = array(); + protected $_body; + protected $_requestConfiguration; + + public function __construct() + { + } + + public function setHeaders(array $headers) + { + $this->_headers = array_merge($this->_headers, $headers); + } + + public function setHeader($name, $value) + { + $this->_headers[$name] = $value; + } + + public function setBody($body) + { + $this->_body = $body; + } + + public function getHeaders() + { + return $this->_headers; + } + + public function getBody() + { + return $this->_body; + } + + public function internalRedirect() + { + return $this->_requestConfiguration != null; + } + + public function setRedirectUrl($url) + { + $this->_headers['Location'] = $url; + } + + public function getRedirectConfiguration() + { + return $this->_requestConfiguration; + } + + public function setRedirectConfiguration(Horde_Controller_RequestConfiguration $config) + { + $this->_requestConfiguration = $config; + } +} diff --git a/framework/Controller/lib/Horde/Controller/Response/Base.php b/framework/Controller/lib/Horde/Controller/Response/Base.php deleted file mode 100644 index 11bc9469d..000000000 --- a/framework/Controller/lib/Horde/Controller/Response/Base.php +++ /dev/null @@ -1,39 +0,0 @@ - - * @author Derek DeVries - * @author Chuck Hagenbuch - * @license http://opensource.org/licenses/bsd-license.php - * @category Horde - * @package Horde_Controller - * @subpackage Response - */ - -/** - * @author Mike Naberezny - * @author Derek DeVries - * @author Chuck Hagenbuch - * @license http://opensource.org/licenses/bsd-license.php - * @category Horde - * @package Horde_Controller - * @subpackage Response - */ -class Horde_Controller_Response_Base -{ - protected $_body = ''; - - /** - */ - public function send() - { - echo $this->_body; - } - - public function setBody($body) - { - $this->_body = $body; - } -} diff --git a/framework/Controller/lib/Horde/Controller/Response/Cli.php b/framework/Controller/lib/Horde/Controller/Response/Cli.php deleted file mode 100644 index 8b1d77875..000000000 --- a/framework/Controller/lib/Horde/Controller/Response/Cli.php +++ /dev/null @@ -1,78 +0,0 @@ - - * @author Derek DeVries - * @author Chuck Hagenbuch - * @license http://opensource.org/licenses/bsd-license.php - * @category Horde - * @package Horde_Controller - * @subpackage Response - */ - -/** - * @TODO Allow specifying the stream where output is going instead of assuming - * STDOUT. - * - * @author Mike Naberezny - * @author Derek DeVries - * @author Chuck Hagenbuch - * @license http://opensource.org/licenses/bsd-license.php - * @category Horde - * @package Horde_Controller - * @subpackage Response - */ -class Horde_Controller_Response_Cli extends Horde_Controller_Response_Base -{ - /** - * @var stream - */ - protected $_stream; - - public function construct() - { - $this->_stream = fopen('php://stdout'); - } - - /** - * Writes a string to the Response stream - * - * Can be called with an array of parameters or with a variable number of - * parameters like printf. - * - * @param string $string The string to write to the reponse stream - * @param array|params $params The parameters to replace in the string (think printf) - */ - public function write($string, $params = null) - { - if (!is_array($params)) { - $params = func_get_args(); - array_shift($params); - } - fwrite($this->_stream, vsprintf($string, $params)); - } - - /** - * Writes a newline-terminated string to the Response stream - * - * Can be called with an array of parameters or with a variable number of - * parameters like printf. - * - * @param string $string The string to write to the reponse stream - * @param array|params $params The parameters to replace in the string (think printf) - */ - public function writeLn($string, $params = array()) - { - if (!is_array($params)) { - $params = func_get_args(); - array_shift($params); - } - $line = vsprintf($string, $params); - if (substr($line, -1) != "\n") { - $line .= "\n"; - } - fwrite($this->_stream, $line); - } -} diff --git a/framework/Controller/lib/Horde/Controller/Response/Http.php b/framework/Controller/lib/Horde/Controller/Response/Http.php deleted file mode 100644 index 0675e4cd6..000000000 --- a/framework/Controller/lib/Horde/Controller/Response/Http.php +++ /dev/null @@ -1,305 +0,0 @@ - - * @author Derek DeVries - * @author Chuck Hagenbuch - * @license http://opensource.org/licenses/bsd-license.php - * @category Horde - * @package Horde_Controller - * @subpackage Response - */ - -/** - * @author Mike Naberezny - * @author Derek DeVries - * @author Chuck Hagenbuch - * @license http://opensource.org/licenses/bsd-license.php - * @category Horde - * @package Horde_Controller - * @subpackage Response - */ -class Horde_Controller_Response_Http extends Horde_Controller_Response_Base -{ - /** - * Cookies sent with response - * @var array - */ - protected $_cookie = array(); - - /** - * Stored session data - * @var array - */ - protected $_session = array(); - - /** - * The url to redirect the request to - * @var string - */ - protected $_redirectUrl = null; - - /** - * The http status code. Default to OK - * @var string - */ - protected $_status = '200 OK'; - - /** - * HTTP headers to send - * @var array - */ - protected $_headers = array(); - - /** - * prevent cached content (ie) - * @var array - */ - protected $_preventCache = true; - - /** - * Body of the rendered page - * @var string - */ - protected $_body = null; - - - /*########################################################################## - # Construct - ##########################################################################*/ - - /** - * Construct response - */ - public function __construct(){} - - - /*########################################################################## - # Instance methods - ##########################################################################*/ - - /** - * Set the url for redirection - * - * @param string $toUrl - */ - public function redirect($toUrl) - { - /* Alternate 301 branch - should allow choosing status: - $this->_status = '301 Moved Permanently'; - $this->_redirectUrl = $toUrl; - $this->_headers["Location: $toUrl"] = true; - $this->_headers["Connection: close"] = true; - */ - - $this->_status = '302 Found'; - $this->_redirectUrl = $toUrl; - $this->_headers["Location: $toUrl"] = true; - } - - /** - * Page was not found - */ - public function pageNotFound() - { - $this->_status = '404 Page Not Found'; - } - - /** - * Send content to the browser. - * - * After the body content has been sent, terminates the execution of the - * PHP script. - */ - public function send() - { - // send all headers - foreach ($this->getHeaders() as $header => $replace) { - header($header, $replace); - } - - // set cookies - foreach ($this->_cookie as $name => $options) { - setcookie($name, $options['value'], $options['expiration'], $options['path']); - } - - // set session data - foreach ($this->_session as $name => $value) { - $_SESSION[$name] = $value; - } - - // send body - print $this->getBody(); - } - - - /*########################################################################## - # Accessors - ##########################################################################*/ - - /** - * Set a cookie - * - * @param string $name - * @param string $value - * @param int $expiration - * @param string $path - */ - public function setCookie($name, $value, $expiration=0, $path=null) - { - // only set cookies for this matter by default - $this->_cookie[$name] = array('value' => $value, - 'expiration' => $expiration, - 'path' => isset($path) ? $path : '/'); - } - - /** - * Set a session variable OR all session variables (by array). - * - * - * // set single session var - * $this->setSession('NAME', 'my session'); - * - * // set all session vars (overwrites previous single sessions set) - * $this->setSession(array('NAME 1' => 'my session 1', - * 'NAME 2' => 'my session 2')); - * - * - * @param mixed $name - * @param mixed $value - */ - public function setSession($name, $value=null) - { - // Set by name or all at once - if (is_string($name)) { - $this->_session[$name] = $value; - } elseif (is_array($name)) { - $this->_session = $name; - } - } - - /** - * Add header information - * - * @param string $header - * @param boolean $replace - */ - public function setHeader($header, $replace=true) - { - $this->_headers[$header] = $replace; - } - - /** - * Set the body of the response - * - * @param string $body - */ - public function setBody($body) - { - $this->_body = $body; - } - - /** - * Set the HTTP status code - * - * @param string $status - */ - public function setStatus($status) - { - $this->_status = $status; - } - - /** - * Get the headers of the response - * - * @return array - */ - public function getHeaders() - { - $headers["HTTP/1.1 $this->_status"] = true; - - if ($this->_status == '200 OK') { - $headers["Connection: close"] = true; - $headers["Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"] = true; - - // Try to keep browser from caching any screen to ensure current data. - if ($this->_preventCache && !isset($this->_headers["Expires: 0"])) { - $headers["Expires: Mon, 26 Jul 1997 05:00:00 GMT"] = true; - $headers["Cache-Control: no-store, no-cache, must-revalidate"] = true; - $headers["Pragma: no-cache"] = true; - } - } - return array_merge($this->_headers, $headers); - } - - /** - * Get the body of the response - * - * @return string - */ - public function getBody() - { - return $this->_body; - } - - /** - * Get the HTTP status of the response - * - * @return string - */ - public function getStatus() - { - return $this->_status; - } - - /** - * Get 3 digit http code from the status - * - * @return int - */ - public function getStatusCode() - { - preg_match("/(\d\d\d)/", $this->_status, $matches); - return isset($matches[1]) ? (int) $matches[1] : 0; - } - - /** - * @todo charset - */ - public function setContentType($mimeType) - { - $this->setHeader("Content-Type: $mimeType", $replace=true); - } - - /** - * Get if the response is a 200 OK - * - * @return boolean - */ - public function isOk() - { - return substr($this->_status, 0, 1) == '2'; - } - - /** - * Get if the response is a redirection - * - * @return boolean - */ - public function isRedirect() - { - return substr($this->_status, 0, 1) == '3'; - } - - /** - * Get where the page is redirecting to - * - * @return string - */ - public function getRedirectUrl() - { - return $this->_redirectUrl; - } -} diff --git a/framework/Controller/lib/Horde/Controller/Response/Mock.php b/framework/Controller/lib/Horde/Controller/Response/Mock.php deleted file mode 100644 index eabd257d6..000000000 --- a/framework/Controller/lib/Horde/Controller/Response/Mock.php +++ /dev/null @@ -1,26 +0,0 @@ - - * @author Derek DeVries - * @author Chuck Hagenbuch - * @license http://opensource.org/licenses/bsd-license.php - * @category Horde - * @package Horde_Controller - * @subpackage Response - */ - -/** - * @author Mike Naberezny - * @author Derek DeVries - * @author Chuck Hagenbuch - * @license http://opensource.org/licenses/bsd-license.php - * @category Horde - * @package Horde_Controller - * @subpackage Response - */ -class Horde_Controller_Response_Mock extends Horde_Controller_Response_Base -{ -} diff --git a/framework/Controller/lib/Horde/Controller/ResponseWriter.php b/framework/Controller/lib/Horde/Controller/ResponseWriter.php new file mode 100644 index 000000000..9bc4234f0 --- /dev/null +++ b/framework/Controller/lib/Horde/Controller/ResponseWriter.php @@ -0,0 +1,11 @@ + + * @license http://opensource.org/licenses/bsd-license.php BSD + */ +interface Horde_Controller_ResponseWriter +{ + public function writeResponse(Horde_Controller_Response $response); +} diff --git a/framework/Controller/lib/Horde/Controller/ResponseWriter/Web.php b/framework/Controller/lib/Horde/Controller/ResponseWriter/Web.php new file mode 100644 index 000000000..864a6cc02 --- /dev/null +++ b/framework/Controller/lib/Horde/Controller/ResponseWriter/Web.php @@ -0,0 +1,19 @@ + + * @license http://opensource.org/licenses/bsd-license.php BSD + */ +class Horde_Controller_ResponseWriter_Web implements Horde_Controller_ResponseWriter +{ + /** + */ + public function writeResponse(Horde_Controller_Response $response) + { + foreach ($response->getHeaders() as $key => $value) { + header("$key: $value"); + } + echo $response->getBody(); + } +} diff --git a/framework/Controller/lib/Horde/Controller/ResponseWriter/WebDebug.php b/framework/Controller/lib/Horde/Controller/ResponseWriter/WebDebug.php new file mode 100644 index 000000000..562434c24 --- /dev/null +++ b/framework/Controller/lib/Horde/Controller/ResponseWriter/WebDebug.php @@ -0,0 +1,30 @@ + + * @license http://opensource.org/licenses/bsd-license.php BSD + */ +class Horde_Controller_ResponseWriter_WebDebug implements Horde_Controller_ResponseWriter +{ + public function writeResponse(Horde_Controller_Response $response) + { + $headerHtml = '
Headers:
';
+        $headers = $response->getHeaders();
+        foreach ($headers as $key => $value) {
+            $headerHtml .= "$key: $value\n";
+        }
+        echo htmlspecialchars($headerHtml) . '
'; + + if ($headers['Location']) { + echo '

Redirect To: ' . htmlspecialchars($headers['Location']) . '

'; + } + + if (isset($headers['Content-Encoding']) && $headers['Content-Encoding'] == 'gzip') { + // Strip off the header and inflate it + echo gzinflate(substr($response->getBody(), 10)); + } else { + echo $response->getBody(); + } + } +} diff --git a/framework/Controller/lib/Horde/Controller/Runner.php b/framework/Controller/lib/Horde/Controller/Runner.php new file mode 100644 index 000000000..82d188a7e --- /dev/null +++ b/framework/Controller/lib/Horde/Controller/Runner.php @@ -0,0 +1,46 @@ + + * @license http://opensource.org/licenses/bsd-license.php BSD + */ +class Horde_Controller_Runner +{ + protected $_logger; + + public function __construct(Horde_Log_Logger $logger) + { + $this->_logger = $logger; + } + + public function execute(Horde_Injector $injector, Horde_Controller_Request $request, Horde_Controller_RequestConfiguration $config) + { + $this->_logger->debug('RequestConfiguration in Horde_Controller_Runner: ' . print_r($config, true)); + + $exporter = $injector->getInstance($config->getSettingsExporterName()); + $exporter->exportBindings($injector); + + $controller = $config->getControllerName(); + if (!$controller) { + throw new Horde_Controller_Exception('No controller defined'); + } + + $implementationBinder = new Horde_Injector_Binder_Implementation($controller); + $injector->addBinder('Horde_Controller', new Horde_Injector_Binder_AnnotatedSetters($implementationBinder)); + + $filterRunner = $injector->createInstance('Horde_Controller_FilterRunner'); + $exporter->exportFilters($filterRunner, $injector); + + $response = $filterRunner->processRequest($request, $injector->createInstance('Horde_Controller_Response')); + if ($response->internalRedirect()) { + $this->_logger->debug('Internal redirect'); + return $this->execute($injector->createChildInjector(), $request, $response->getRedirectConfiguration()); + } + + $this->_logger->debug('Returning Horde_Controller_Response'); + return $response; + } +} diff --git a/framework/Controller/lib/Horde/Controller/Scanner.php b/framework/Controller/lib/Horde/Controller/Scanner.php deleted file mode 100644 index 16f92922f..000000000 --- a/framework/Controller/lib/Horde/Controller/Scanner.php +++ /dev/null @@ -1,170 +0,0 @@ - - * @author Derek DeVries - * @author Chuck Hagenbuch - * @license http://opensource.org/licenses/bsd-license.php - * @category Horde - * @package Horde_Controller - */ - -/** - * Horde_Routes_Mapper requires a list of all possible controller names - * in order to build the regular expressions it uses for matching routes. - * It uses a callback, controllerScan, to get this list. - * - * Depending on the routes connected to the mapper, it may be possible to - * determine all of the controller names from the routes themselves. If - * not, the filesystem must be scanned to determine the controller names. - * - * This class contains two controllerScan strategies, one that scans the - * filesystem and one that doesn't, and can determine the most efficient - * strategy to use for a given mapper. - * - * @author Mike Naberezny - * @author Derek DeVries - * @author Chuck Hagenbuch - * @license http://opensource.org/licenses/bsd-license.php - * @category Horde - * @package Horde_Controller - */ -class Horde_Controller_Scanner -{ - /** - * @var Horde_Routes_Mapper - */ - protected $_mapper; - - /** - * controllerScan strategy selected for this mapper. - * @var callback - */ - protected $_callback; - - /** - * Array of controller names collected from route hardcodes - * @var array - */ - protected $_controllers; - - - /** - * Constructor. Analyze the routes connected to this mapper to - * select a controllerScan strategy. - * - * @param Horde_Routes_Mapper - */ - public function __construct($mapper) - { - $this->_mapper = $mapper; - $this->analyze(); - } - - /** - * Analyze the routes connected to the mapper. If all of the possible - * controller names can be determined from the routes themselves, select - * the scanHardcodes() strategy that returns them collected from the - * routes. If the possible controller names cannot be determined this - * way, select the scanFilesystem() strategy. - */ - public function analyze() - { - $needScan = false; - $controllers = array(); - foreach ($this->_mapper->matchList as $route) { - if (in_array('controller', $route->hardCoded)) { - $controllers[ $route->defaults['controller'] ] = true; - } else { - $needScan = true; - break; - } - } - $this->_controllers = array_keys($controllers); - - if ($needScan || empty($this->_controllers)) { - $this->_callback = array($this, 'scanFilesystem'); - } else { - $this->_callback = array($this, 'scanHardcodes'); - } - } - - /** - * Get the controllerScan callback stategy selected for this mapper. - * - * @return callback - */ - public function getCallback() - { - return $this->_callback; - } - - /** - * Scan a directory and return an array of the controllers it contains. - * The array is used by Horde_Routes to build its matching regexps. - * - * @param string $dirname Controller directory - * @param string $prefix Prefix controllers found with string - * @return array Controller names - */ - public function scanFilesystem($dirname = null, $prefix = '') - { - $controllers = array(); - - if ($dirname === null) { - return $controllers; - } - - $baseregexp = preg_quote($dirname, '/'); - - foreach (new RecursiveIteratorIterator( - new RecursiveDirectoryIterator($dirname)) as $entry) { - - if ($entry->isFile()) { - // match .php files that don't start with an underscore - if (preg_match('/^[^_]{1,1}.*\.php$/', basename($entry->getFilename())) != 0) { - // strip off base path: dirname/admin/users.php -> admin/users.php - $controller = preg_replace("/^$baseregexp(.*)\.php/", '\\1', $entry->getPathname()); - - // PrepareController -> prepare_controller -> prepare - $controller = strtolower(preg_replace('/([a-z])([A-Z])/', "\${1}_\${2}", $controller)); - $controller = substr($controller, 0, -(strlen('_controller'))); - - // add to controller list - $controllers[] = $prefix . $controller; - } - } - } - - $callback = array('Horde_Routes_Utils', 'longestFirst'); - usort($controllers, $callback); - - return $controllers; - } - - /** - * Return an array of controller names that were collected from the - * hardcodes of the routes connected to this mapper. - * - * @param string $dirname For method signature compatibility only - * @param string $prefix Prefix controllers found with string - * @return array Controller names - */ - public function scanHardcodes($dirname = null, $prefix = null) - { - if ($prefix === null) { - $controllers = $this->_controllers; - } else { - $controllers = array(); - foreach ($this->_controllers as $controller) { - $controllers[] = $prefix . $controller; - } - } - - usort($controllers, 'Horde_Routes_Utils::longestFirst'); - return $controllers; - } - -} \ No newline at end of file diff --git a/framework/Controller/lib/Horde/Controller/SettingsExporter.php b/framework/Controller/lib/Horde/Controller/SettingsExporter.php new file mode 100644 index 000000000..42da2f30f --- /dev/null +++ b/framework/Controller/lib/Horde/Controller/SettingsExporter.php @@ -0,0 +1,20 @@ + + * @author James Pepin + * @license http://opensource.org/licenses/bsd-license.php BSD + */ +interface Horde_Controller_SettingsExporter +{ + /** + */ + public function exportBindings(Horde_Injector $injector); + + /** + */ + public function exportFilters(Horde_Controller_FilterCollection $filters, Horde_Injector $injector); +} diff --git a/framework/Controller/lib/Horde/Controller/SettingsExporter/Default.php b/framework/Controller/lib/Horde/Controller/SettingsExporter/Default.php new file mode 100644 index 000000000..5e9326301 --- /dev/null +++ b/framework/Controller/lib/Horde/Controller/SettingsExporter/Default.php @@ -0,0 +1,24 @@ + + * @author James Pepin + * @license http://opensource.org/licenses/bsd-license.php BSD + */ +class Horde_Controller_SettingsExporter_Default implements Horde_Controller_SettingsExporter +{ + /** + */ + public function exportBindings(Horde_Injector $injector) + { + } + + /** + */ + public function exportFilters(Horde_Controller_FilterCollection $filters, Horde_Injector $injector) + { + } +} diff --git a/framework/Controller/lib/Horde/Controller/StatusCodes.php b/framework/Controller/lib/Horde/Controller/StatusCodes.php deleted file mode 100644 index 64fdb180b..000000000 --- a/framework/Controller/lib/Horde/Controller/StatusCodes.php +++ /dev/null @@ -1,139 +0,0 @@ - - * @license http://opensource.org/licenses/bsd-license.php BSD - * @category Horde - * @package Horde_Controller - */ -class Horde_Controller_StatusCodes -{ - /** - * All known status codes and their messages - * @var array - */ - public static $statusCodes = array( - 100 => "Continue", - 101 => "Switching Protocols", - 102 => "Processing", - - 200 => "OK", - 201 => "Created", - 202 => "Accepted", - 203 => "Non-Authoritative Information", - 204 => "No Content", - 205 => "Reset Content", - 206 => "Partial Content", - 207 => "Multi-Status", - 226 => "IM Used", - - 300 => "Multiple Choices", - 301 => "Moved Permanently", - 302 => "Found", - 303 => "See Other", - 304 => "Not Modified", - 305 => "Use Proxy", - 307 => "Temporary Redirect", - - 400 => "Bad Request", - 401 => "Unauthorized", - 402 => "Payment Required", - 403 => "Forbidden", - 404 => "Not Found", - 405 => "Method Not Allowed", - 406 => "Not Acceptable", - 407 => "Proxy Authentication Required", - 408 => "Request Timeout", - 409 => "Conflict", - 410 => "Gone", - 411 => "Length Required", - 412 => "Precondition Failed", - 413 => "Request Entity Too Large", - 414 => "Request-URI Too Long", - 415 => "Unsupported Media Type", - 416 => "Requested Range Not Satisfiable", - 417 => "Expectation Failed", - 422 => "Unprocessable Entity", - 423 => "Locked", - 424 => "Failed Dependency", - 426 => "Upgrade Required", - - 500 => "Internal Server Error", - 501 => "Not Implemented", - 502 => "Bad Gateway", - 503 => "Service Unavailable", - 504 => "Gateway Timeout", - 505 => "HTTP Version Not Supported", - 507 => "Insufficient Storage", - 510 => "Not Extended" - ); - - /** - * Given a status parameter, determine whether it needs to be converted - * to a string. If it is an integer, use the $statusCodes hash to lookup - * the default message. If it is a string, build $symbolToStatusCode - * and convert it. - * - * interpret(404) => "404 Not Found" - * interpret("notFound") => "404 Not Found" - * - * Differences from Rails: - * - $status is camelized, not underscored. - * - an unknown status raises an exception - * - * @param string|integer Status code or "symbol" - * @return string Header - */ - public static function interpret($status) - { - // Status from integer or numeric string - if (is_numeric($status)) { - if (isset(self::$statusCodes[$status])) { - return $status . ' ' . self::$statusCodes[$status]; - } else { - $msg = 'Unknown status code: ' . $status; - throw new InvalidArgumentException($msg); - } - - // Status from string - } elseif (is_string($status)) { - // Build a string-to-integer lookup for converting a symbol (like - // 'created' or 'notImplemented') into its corresponding HTTP status - // code (like 200 or 501). - static $symbolToStatusCode = array(); - $inflector = new Horde_Support_Inflector(); - if (empty($symbolToStatusCode)) { - foreach (self::$statusCodes as $code => $message) { - $symbol = $inflector->camelize($message, $first='lower'); - $symbolToStatusCode[$symbol] = $code; - } - } - - // Convert status symbol to integer code, return header - if (isset($symbolToStatusCode[$status])) { - return self::interpret($symbolToStatusCode[$status]); - } - - // Error: Status symbol could not be converted to an integer code - // Try to help if the developer mixed up underscore/camel - $msg = 'Unknown status: \'' . $status . '\''; - if (strpos($status, '_')) { - $status = $inflector->camelize($status, $first='lower'); - if (isset($symbolToStatusCode[$status])) { - $msg .= ' (underscore), did you mean \'' . $status . '\' (camel)?'; - } - } - throw new InvalidArgumentException($msg); - - // Status is an unknown type - } else { - $msg = '$status must be numeric or string, got ' - . gettype($status); - throw new InvalidArgumentException($msg); - } - - } - -} diff --git a/framework/Controller/lib/Horde/Controller/UrlWriter.php b/framework/Controller/lib/Horde/Controller/UrlWriter.php deleted file mode 100644 index 330e3009b..000000000 --- a/framework/Controller/lib/Horde/Controller/UrlWriter.php +++ /dev/null @@ -1,84 +0,0 @@ - - * @license http://opensource.org/licenses/bsd-license.php BSD - * @category Horde - * @package Horde_Controller - */ -class Horde_Controller_UrlWriter -{ - /** - * Defaults to merge into route parameters when not using named routes. - * @var array - */ - protected $_defaults; - - /** - * @var Horde_Routes_Util - */ - protected $_utils; - - /** - * Class constructor - * - * @param array $defaults Defaults to merge for urlFor() - * @param null|Horde_Route_Utils $utils Route utilities - */ - public function __construct($defaults = array(), $utils = null) - { - $this->_defaults = $defaults; - if ($utils === null) { - $utils = Horde_Controller_Dispatcher::singleton()->getRouteUtils(); - } - $this->_utils = $utils; - } - - /** - * Generate a URL. Same signature as Horde_Routes_Utils->urlFor(). - * - * @param $first mixed - * @param $second mixed - * @return string - */ - public function urlFor($first, $second = array()) - { - // anonymous route: serialize to params & merge defaults - // urlFor(array('controller' => 'books')) - if (is_array($first)) { - $first = array_merge($this->_defaults, - $this->_serializeToParams($first)); - } - - // named route: serialize to params only (no merge) - // urlFor('notes', array('action' => 'show', 'id' => 1)) - if (is_array($second)) { - $second = $this->_serializeToParams($second); - } - - // url generation "route memory" is not useful here - $this->_utils->mapperDict = array(); - - // generate url - return $this->_utils->urlFor($first, $second); - } - - /** - * Serialize any objects in the collection supporting toParam() before - * passing the collection to Horde_Routes. - * - * @param array $collection - * @param array - */ - protected function _serializeToParams($collection) - { - foreach ($collection as &$value) { - if (is_object($value) && method_exists($value, 'toParam')) { - $value = $value->toParam(); - } - } - return $collection; - } -} \ No newline at end of file diff --git a/framework/Controller/package.xml b/framework/Controller/package.xml index 1294e57b8..c06886095 100644 --- a/framework/Controller/package.xml +++ b/framework/Controller/package.xml @@ -36,29 +36,35 @@ http://pear.php.net/dtd/package-2.0.xsd"> - - - + + + - - - + - - - - - - + + + + + + + - - - - - + + + + + + + + + + + + @@ -80,21 +86,25 @@ http://pear.php.net/dtd/package-2.0.xsd"> - - - - - - - - + + + + + + - - - - - + + + + + + + + + + + diff --git a/framework/Controller/test/Horde/Controller/FilterRunnerTest.php b/framework/Controller/test/Horde/Controller/FilterRunnerTest.php new file mode 100644 index 000000000..81bee642c --- /dev/null +++ b/framework/Controller/test/Horde/Controller/FilterRunnerTest.php @@ -0,0 +1,73 @@ +getMock('Horde_Controller_PreFilter', array('processRequest')); + $filter->expects($this->once()) + ->method('processRequest') + ->will($this->returnValue(Horde_Controller_PreFilter::REQUEST_HANDLED)); + + $runner = new Horde_Controller_FilterRunner($this->_getControllerMockNeverCalled()); + $runner->addPreFilter($filter); + $runner->processRequest($this->getMock('Horde_Controller_Request'), new Horde_Controller_Response()); + } + + public function testShouldUsePreFiltersInFirstInFirstOutOrder() + { + // The second filter should never be called because first filter returns + // REQUEST_HANDLED, meaning it can handle the request. + $preFilter1 = $this->getMock('Horde_Controller_PreFilter', array('processRequest')); + $preFilter1->expects($this->once()) + ->method('processRequest') + ->will($this->returnValue(Horde_Controller_PreFilter::REQUEST_HANDLED)); + + $preFilter2 = $this->getMock('Horde_Controller_PreFilter', array('processRequest')); + $preFilter2->expects($this->never()) + ->method('processRequest'); + + $runner = new Horde_Controller_FilterRunner($this->_getControllerMockNeverCalled()); + $runner->addPreFilter($preFilter1); + $runner->addPreFilter($preFilter2); + $this->_runFilterRunner($runner); + } + + public function testShouldUsePostFiltersInFirstInLastOutOrder() + { + // Both filters should be called because the first filter returns + // REQUEST_HANDLED, meaning it can handle the request + $postFilter1 = $this->getMock('Horde_Controller_PostFilter', array('processResponse')); + $postFilter1->expects($this->once()) + ->method('processResponse') + ->will($this->returnValue(Horde_Controller_PreFilter::REQUEST_HANDLED)); + + $postFilter2 = $this->getMock('Horde_Controller_PostFilter', array('processResponse')); + $postFilter2->expects($this->once()) + ->method('processResponse'); + + + $controller = $this->getMock('Horde_Controller', array('processRequest')); + $controller->expects($this->once()) + ->method('processRequest'); + + $runner = new Horde_Controller_FilterRunner($controller); + $runner->addPostFilter($postFilter1); + $runner->addPostFilter($postFilter2); + $this->_runFilterRunner($runner); + } + + private function _getControllerMockNeverCalled() + { + $controller = $this->getMock('Horde_Controller', array('processRequest')); + $controller->expects($this->never()) + ->method('processRequest'); + return $controller; + } + + private function _runFilterRunner(Horde_Controller_FilterRunner $runner) + { + $response = $this->getMock('Horde_Controller_Response', array('processRequest')); + $response->expects($this->never())->method('processRequest'); + $runner->processRequest(new Horde_Controller_Request_Null(), $response); + } +} diff --git a/framework/Core/lib/Horde/Core/Binder/Mapper.php b/framework/Core/lib/Horde/Core/Binder/Mapper.php new file mode 100644 index 000000000..1cae6fb18 --- /dev/null +++ b/framework/Core/lib/Horde/Core/Binder/Mapper.php @@ -0,0 +1,16 @@ +setHeader('HTTP/1.0 404 ', 'Not Found'); + $response->setBody('

404 File Not Found

'); + } +} diff --git a/framework/Core/lib/Horde/Core/Controller/RequestConfiguration.php b/framework/Core/lib/Horde/Core/Controller/RequestConfiguration.php new file mode 100644 index 000000000..96adfc014 --- /dev/null +++ b/framework/Core/lib/Horde/Core/Controller/RequestConfiguration.php @@ -0,0 +1,71 @@ +_classNames = array( + 'controller' => 'Horde_Core_Controller_NotFound', + 'settings' => 'Horde_Controller_SettingsExporter_Default', + ); + } + + /** + */ + public function setApplication($application) + { + $this->_application = $application; + } + + /** + */ + public function getApplication() + { + return $this->_application; + } + + /** + */ + public function setControllerName($controllerName) + { + $this->_classNames['controller'] = $controllerName; + } + + /** + */ + public function getControllerName() + { + return $this->_classNames['controller']; + } + + /** + */ + public function setSettingsExporterName($settingsName) + { + $this->_classNames['settings'] = $settingsName; + } + + /** + */ + public function getSettingsExporterName() + { + return $this->_classNames['settings']; + } +} diff --git a/framework/Core/lib/Horde/Core/Controller/RequestMapper.php b/framework/Core/lib/Horde/Core/Controller/RequestMapper.php new file mode 100644 index 000000000..ce93c7a15 --- /dev/null +++ b/framework/Core/lib/Horde/Core/Controller/RequestMapper.php @@ -0,0 +1,65 @@ +_mapper = $mapper; + } + + public function getRequestConfiguration(Horde_Injector $injector) + { + $request = $injector->getInstance('Horde_Controller_Request'); + $registry = $injector->getInstance('Horde_Registry'); + $settingsFinder = $injector->getInstance('Horde_Core_Controller_SettingsFinder'); + + $config = $injector->createInstance('Horde_Core_Controller_RequestConfiguration'); + + $uri = substr($request->getPath(), strlen($registry->get('webroot', 'horde'))); + $uri = trim($uri, '/'); + if (strpos($uri, '/') === false) { + $app = $uri; + } else { + list($app,) = explode('/', $uri, 2); + } + + // Check for route definitions. + $fileroot = $registry->get('fileroot', $app); + $routeFile = $fileroot . '/config/routes.php'; + if (!file_exists($routeFile)) { + throw new Horde_Routes_Exception('Not routable: ' . $uri . '. ' . $routeFile . ' does not exist.'); + } + + // Push $app onto the registry + $registry->pushApp($app); + $config->setApplication($app); + + // Application routes are relative only to the application. Let the + // mapper know where they start. + $this->_mapper->prefix = $registry->get('webroot', $app); + + // Set the application controller directory + $this->_mapper->directory = $registry->get('fileroot', $app) . '/app/controllers'; + + // Load application routes. + $mapper = $this->_mapper; + include $routeFile; + + // Match + // @TODO Cache routes + $match = $this->_mapper->match($request->getPath()); + + if (isset($match['controller'])) { + $config->setControllerName(ucfirst($app) . '_' . ucfirst($match['controller']) . '_Controller'); + $config->setSettingsExporterName($settingsFinder->getSettingsExporterName($config->getControllerName())); + } else { + $config->setControllerName('Horde_Core_Controller_NotFound'); + } + + return $config; + } +} diff --git a/framework/Core/lib/Horde/Core/Controller/SettingsFinder.php b/framework/Core/lib/Horde/Core/Controller/SettingsFinder.php new file mode 100644 index 000000000..3b46e9a4d --- /dev/null +++ b/framework/Core/lib/Horde/Core/Controller/SettingsFinder.php @@ -0,0 +1,31 @@ +_mapName($controllerName); + $current = $controllerName; + while (class_exists($current)) { + $settingsName = $this->_mapName($current); + if (class_exists($settingsName)) { + return $settingsName; + } + + $current = $this->_getParentName($current); + } + + return 'Horde_Controller_SettingsExporter_Default'; + } + + private function _mapName($controllerName) + { + return str_replace('_Controller', '_SettingsExporter', $controllerName); + } + + private function _getParentName($controllerName) + { + $klass = new ReflectionClass($controllerName); + $parent = $klass->getParentClass(); + return $parent->name; + } +} diff --git a/framework/Core/lib/Horde/Core/Factory/Request.php b/framework/Core/lib/Horde/Core/Factory/Request.php new file mode 100644 index 000000000..9856c14e6 --- /dev/null +++ b/framework/Core/lib/Horde/Core/Factory/Request.php @@ -0,0 +1,10 @@ + new Horde_Core_Binder_Tree(), 'Horde_Token' => new Horde_Core_Binder_Token(), 'Horde_Vfs' => new Horde_Core_Binder_Vfs(), - 'Net_DNS_Resolver' => new Horde_Core_Binder_Dns() + 'Net_DNS_Resolver' => new Horde_Core_Binder_Dns(), ); /* Define factories. */ $factories = array( + 'Horde_Controller_Request' => array( + 'Horde_Core_Factory_Request', + 'create', + ), + 'Horde_Controller_RequestConfiguration' => array( + 'Horde_Core_Controller_RequestMapper', + 'getRequestConfiguration', + ), 'Horde_Kolab_Server_Composite' => array( 'Horde_Core_Factory_KolabServer', - 'getComposite' + 'getComposite', ), 'Horde_Kolab_Session' => array( 'Horde_Core_Factory_KolabSession', - 'getSession' + 'getSession', ), 'Horde_Kolab_Storage' => array( 'Horde_Core_Factory_KolabStorage', - 'getStorage' + 'getStorage', ) ); + /* Define implementations. */ + $implementations = array( + 'Horde_Controller_ResponseWriter' => 'Horde_Controller_ResponseWriter_Web', + 'Horde_View_Base' => 'Horde_View', + ); + /* Setup injector. */ $GLOBALS['injector'] = $injector = new Horde_Injector(new Horde_Injector_TopLevel()); @@ -321,6 +335,9 @@ class Horde_Registry foreach ($factories as $key => $val) { $injector->bindFactory($key, $val[0], $val[1]); } + foreach ($implementations as $key => $val) { + $injector->bindImplementation($key, $val); + } $GLOBALS['registry'] = $this; $injector->setInstance('Horde_Registry', $this); @@ -1117,7 +1134,12 @@ class Horde_Registry * be done here because it is possible to try to load app-specific * libraries from other applications. */ $app_lib = $this->get('fileroot', $app) . '/lib'; - $GLOBALS['injector']->getInstance('Horde_Autoloader')->addClassPathMapper(new Horde_Autoloader_ClassPathMapper_Prefix('/^' . $app . '(?:$|_)/i', $app_lib)); + $autoloader = $GLOBALS['injector']->getInstance('Horde_Autoloader'); + $autoloader->addClassPathMapper(new Horde_Autoloader_ClassPathMapper_Prefix('/^' . $app . '(?:$|_)/i', $app_lib)); + $applicationMapper = new Horde_Autoloader_ClassPathMapper_Application($this->get('fileroot', $app) . '/app'); + $applicationMapper->addMapping('Controller', 'controllers'); + $applicationMapper->addMapping('SettingsExporter', 'settings'); + $autoloader->addClassPathMapper($applicationMapper); $checkPerms = !isset($options['check_perms']) || !empty($options['check_perms']); diff --git a/framework/Core/package.xml b/framework/Core/package.xml index 4d72958ad..5f8d0deca 100644 --- a/framework/Core/package.xml +++ b/framework/Core/package.xml @@ -125,6 +125,12 @@ Application Framework. + + + + + + @@ -141,6 +147,7 @@ Application Framework. + @@ -342,6 +349,7 @@ Application Framework. + @@ -377,6 +385,10 @@ Application Framework. + + + + @@ -392,6 +404,7 @@ Application Framework. + @@ -414,13 +427,6 @@ Application Framework. - - - - - - - diff --git a/horde/rampage.php b/horde/rampage.php index 4c90b5a5e..5ad2fe1e7 100644 --- a/horde/rampage.php +++ b/horde/rampage.php @@ -21,57 +21,39 @@ * Controllers/ * FooController.php -> class App_FooController * no nested components? + +request processing steps: + +- bootstrap +- injector bindings +* customization point +- create request +- create request mapper +- $config = $mapper->getRquestConfiguration($request) +- create runner +- execute runner + - get settings exporter + - export bindings + - get controller name + - create response + - create controller builder + - create filter runner + - export filters + - handle internal redirects + - return response +- write response + +add filtered requests/blue_filter port to Horde? */ require_once dirname(__FILE__) . '/lib/Application.php'; Horde_Registry::appInit('horde'); -// Set up our request and routing objects -$request = new Horde_Controller_Request_Http(); -$mapper = new Horde_Routes_Mapper(); - -$uri = substr($request->getUri(), strlen($registry->get('webroot', 'horde'))); -if (strpos($uri, '/') === false) { - $app = $uri; - $path = ''; -} else { - list($app, $path) = explode('/', $uri, 2); -} - -// Check for route definitions. -$fileroot = $registry->get('fileroot', $app); -$routeFile = $fileroot . '/config/routes.php'; -if (!file_exists($routeFile)) { - throw new Horde_Controller_Exception('Not routable: ' . $uri); -} - -// @TODO Use the registry to check app permissions, etc. -// $registry->pushApp($app); - -// Application routes are relative only to the application. Let the mapper know -// where they start. -$mapper->prefix = $registry->get('webroot', 'horde') . '/' . $app; - -// @TODO ? $mapper->createRegs(array('blogs', 'comments', 'posts')) would avoid -// the directory scan entirely. The argument is the name of every controller in -// the system. Should also cache the controller scan. - -// Load application routes. -include $routeFile; - -// Set up application class and controller loading -// @TODO separate $app from class names so that there can be multiple instances -// of an app in the registry? -$injector->getInstance('Horde_Autoloader')->addClassPathMapper(new Horde_Autoloader_ClassPathMapper_Prefix('/^' . $app . '(?:$|_)/i', $fileroot . '/lib/')); +$request = $injector->getInstance('Horde_Controller_Request'); -// Create our controller context. -$context = array( - 'mapper' => $mapper, - 'controllerDir' => $fileroot . '/app/controllers', - 'viewsDir' => $fileroot . '/app/views', - // 'logger' => '', -); +$runner = $injector->getInstance('Horde_Controller_Runner'); +$config = $injector->getInstance('Horde_Controller_RequestConfiguration'); +$response = $runner->execute($injector, $request, $config); -// Dispatch. -$dispatcher = Horde_Controller_Dispatcher::singleton($context); -$dispatcher->dispatch($request); +$responseWriter = $injector->getInstance('Horde_Controller_ResponseWriter'); +$responseWriter->writeResponse($response);