--- /dev/null
+<?php
+/**
+ * Copyright 2007 Maintainable Software, LLC
+ * Copyright 2008 The Horde Project (http://www.horde.org/)
+ *
+ * @author Mike Naberezny <mike@maintainable.com>
+ * @author Derek DeVries <derek@maintainable.com>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php
+ * @category Horde
+ * @package Horde_Controller
+ */
+
+/**
+ * @author Mike Naberezny <mike@maintainable.com>
+ * @author Derek DeVries <derek@maintainable.com>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php
+ * @category Horde
+ * @package Horde_Controller
+ */
+abstract class Horde_Controller_Base
+{
+ /**
+ * 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 = '';
+
+ /**
+ * New controller instance
+ */
+ public function __construct($options)
+ {
+ foreach ($options as $key => $val) {
+ $this->{'_' . $key} = $val;
+ }
+ }
+
+ /**
+ * Lazy loading of resources: view, ...
+ *
+ * @param string $name Property to load
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case '_view':
+ $this->_view = new Horde_View;
+ return $this->_view;
+ }
+ }
+
+ /**
+ * 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.
+ *
+ * <code>
+ * <?php
+ * ...
+ * $request = new Horde_Controller_Request_Base();
+ * $response = new Horde_Controller_Response_Base();
+ *
+ * $response = $controller->process($request, $response);
+ * ...
+ * ?>
+ * </code>
+ *
+ * @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
+ }
+
+ return $this->_response;
+ }
+
+ /**
+ * Render the response to the user. Actions are automatically rendered if no other
+ * action is specified.
+ *
+ * <code>
+ * <?php
+ * ...
+ * $this->render(array('text' => 'some text to render'));
+ * $this->render(array('action' => 'actionName'));
+ * $this->render(array('nothing' => 1));
+ * ...
+ * ?>
+ * </code>
+ *
+ * @see renderText()
+ * @see renderAction()
+ * @see renderNothing()
+ *
+ * @param array $options
+ *
+ * @throws Horde_Controller_Exception
+ */
+ protected function render($options = array())
+ {
+ // 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 ($status = $options['status']) {
+ $header = $this->interpretStatus($status);
+ $this->_response->setStatus($header);
+ }
+
+ // set response location
+ if ($location = $options['location']) {
+ $url = $this->urlFor($location);
+ $this->_response->setHeader("Location: $url", $replace=true);
+ }
+
+ // render text
+ if ($text = $options['text']) {
+ $this->renderText($text);
+
+ // render xml
+ } elseif ($xml = $options['xml']) {
+ $this->_response->setContentType('application/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);
+ }
+ }
+
+ /**
+ * Render text directly to the screen without using a template
+ *
+ * <code>
+ * <?php
+ * ...
+ * $this->renderText('some text to render to the screen');
+ * ...
+ * ?>
+ * </code>
+ *
+ * @param string $text
+ */
+ protected function renderText($text)
+ {
+ $this->_response->setBody($text);
+ $this->_performedRender = true;
+ }
+
+ /**
+ * The name of the action method will render by default.
+ *
+ * render 'listDocuments' template file
+ * <code>
+ * <?php
+ * ...
+ * $this->renderAction('listDocuments');
+ * ...
+ * ?>
+ * </code>
+ *
+ * @param string $name
+ */
+ protected function renderAction($name)
+ {
+ // 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->_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.
+ *
+ * <code>
+ * <?php
+ * ...
+ * $this->renderNothing();
+ * ...
+ * ?>
+ * </code>
+ */
+ protected function renderNothing()
+ {
+ $this->renderText('');
+ }
+
+ /**
+ * 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
+ * availabie in controllers using the $params array.
+ *
+ * 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)
+ *
+ * We can specify a directory to look in instead of relying on the default order
+ * by doing $this->renderAction('shared/foo').
+ */
+ protected function _initViewPaths()
+ {
+ $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);
+ }
+ }
+
+ /**
+ * This gets called before action is performed in a controller.
+ * Override method in subclass to setup filters/helpers
+ */
+ protected function _initializeApplication(){
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Copyright 2007 Maintainable Software, LLC
+ * Copyright 2008 The Horde Project (http://www.horde.org/)
+ *
+ * @author Mike Naberezny <mike@maintainable.com>
+ * @author Derek DeVries <derek@maintainable.com>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @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 <mike@maintainable.com>
+ * @author Derek DeVries <derek@maintainable.com>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php
+ * @category Horde
+ * @package Horde_Controller
+ */
+class Horde_Controller_Dispatcher
+{
+ /** @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 = '';
+
+ /**
+ * Class constructor.
+ */
+ public function __construct($context)
+ {
+ if (!isset($context['mapper']) || ! $context['mapper'] instanceof Horde_Routes_Mapper) {
+ throw new Horde_Controller_Exception('Mapper object missing from Dispatcher constructor');
+ }
+
+ 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 .= '/';
+ }
+
+ // 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 . $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');
+ }
+
+ $ret[$key] = $val;
+ }
+ 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->getAllParams());
+
+ $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 . '}';
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Copyright 2007 Maintainable Software, LLC
+ * Copyright 2008 The Horde Project (http://www.horde.org/)
+ *
+ * @author Mike Naberezny <mike@maintainable.com>
+ * @author Derek DeVries <derek@maintainable.com>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php
+ * @category Horde
+ * @package Horde_Controller
+ */
+
+/**
+ * @author Mike Naberezny <mike@maintainable.com>
+ * @author Derek DeVries <derek@maintainable.com>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php
+ * @category Horde
+ * @package Horde_Controller
+ */
+class Horde_Controller_Exception extends Exception
+{
+}
--- /dev/null
+<?php
+/**
+ * Copyright 2007 Maintainable Software, LLC
+ * Copyright 2008 The Horde Project (http://www.horde.org/)
+ *
+ * @author Mike Naberezny <mike@maintainable.com>
+ * @author Derek DeVries <derek@maintainable.com>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @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
+ */
+
+/**
+ * @author Mike Naberezny <mike@maintainable.com>
+ * @author Derek DeVries <derek@maintainable.com>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php
+ * @category Horde
+ * @package Horde_Controller
+ * @subpackage Request
+ */
+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 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
+ *
+ * @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());
+ }
+
+ /**
+ * Get rid of register_globals variables.
+ *
+ * @author Richard Heyes
+ * @author Stefan Esser
+ * @url http://www.phpguru.org/article.php?ne_id=60
+ */
+ public function reverseRegisterGlobals()
+ {
+ if (ini_get('register_globals')) {
+ // Variables that shouldn't be unset
+ $noUnset = array(
+ 'GLOBALS',
+ '_GET',
+ '_POST',
+ '_COOKIE',
+ '_REQUEST',
+ '_SERVER',
+ '_ENV',
+ '_FILES',
+ );
+
+ $input = array_merge(
+ $_GET,
+ $_POST,
+ $_COOKIE,
+ $_SERVER,
+ $_ENV,
+ $_FILES,
+ isset($_SESSION) ? $_SESSION : array()
+ );
+
+ foreach ($input as $k => $v) {
+ if (!in_array($k, $noUnset) && isset($GLOBALS[$k])) {
+ unset($GLOBALS[$k]);
+ }
+ }
+ }
+ }
+
+ /**
+ * @author Ilia Alshanetsky <ilia@php.net>
+ */
+ public function reverseMagicQuotes()
+ {
+ set_magic_quotes_runtime(0);
+ if (get_magic_quotes_gpc()) {
+ $input = array(&$_GET, &$_POST, &$_REQUEST, &$_COOKIE, &$_ENV, &$_SERVER);
+
+ while (list($k, $v) = each($input)) {
+ foreach ($v as $key => $val) {
+ if (!is_array($val)) {
+ $key = stripslashes($key);
+ $input[$k][$key] = stripslashes($val);
+ continue;
+ }
+ $input[] =& $input[$k][$key];
+ }
+ }
+
+ unset($input);
+ }
+ }
+
+ /**
+ * Turn this request into a URL-encoded query string.
+ */
+ public function __toString()
+ {
+ return http_build_query($this);
+ }
+
+ 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;
+ }
+
+ /**
+ * The default locale (eg. en-us) the application uses.
+ *
+ * @var string
+ * @access private
+ */
+ var $_defaultLocale = 'en-us';
+
+ /**
+ * The locales (eg. en-us, fi_fi, se_se etc) the application
+ * supports.
+ *
+ * @var array
+ * @access private
+ */
+ var $_supportedLocales = NULL;
+
+ /**
+ * Gets the used character encoding.
+ *
+ * Returns the name of the character encoding used in the body of
+ * this request.
+ *
+ * @todo implement this method
+ * @return string the used character encoding
+ * @access public
+ */
+ function getCharacterEncoding()
+ {
+ // XXX: what to do with this?
+ }
+
+ /**
+ * Gets the default locale for the application.
+ *
+ * @return string the default locale
+ * @access public
+ */
+ function getDefaultLocale()
+ {
+ return $this->_defaultLocale;
+ }
+
+ /**
+ * Gets the supported locales for the application.
+ *
+ * @return array the supported locales
+ * @access public
+ */
+ function getSupportedLocales()
+ {
+ return $this->_supportedLocales;
+ }
+
+ /**
+ * Deduces the clients preferred locale.
+ *
+ * You might want to override this method if you want to do more
+ * sophisticated decisions. It gets the supported locales and the
+ * default locale from the class attributes file and tries to find a
+ * match. If no match is found it uses the default locale. The
+ * locale is always changed into lowercase.
+ *
+ * @return string the locale
+ * @access public
+ */
+ function getLocale()
+ {
+ require_once('HTTP.php');
+
+ if ($this->_supportedLocales == NULL) {
+ return $this->_defaultLocale;
+ } else {
+ return strtolower(HTTP::negotiateLanguage( $this->_supportedLocales,
+ $this->_defaultLocale ));
+ }
+ }
+
+ /**
+ * Sets the default locale for the application.
+ *
+ * Create an instance of <code>Ismo_Core_Request</code> manually and
+ * set the default locale with this method. Then add it as the
+ * application's request class with
+ * <code>Ismo_Core_Application::setRequest()</code>.
+ *
+ * @param string $locale the default locale
+ * @access public
+ */
+ function setDefaultLocale($locale)
+ {
+ $this->_defaultLocale = str_replace('_', '-', $locale);
+ }
+
+ /**
+ * Sets the locales supported by the application.
+ *
+ * Create an instance of <code>Ismo_Core_Request</code> manually and
+ * set the supported locales with this method. Then add it as the
+ * application's request class with
+ * <code>Ismo_Core_Application::setRequest()</code>.
+ *
+ * @param array $locales the locales
+ * @access public
+ */
+ function setSupportedLocales($locales)
+ {
+ if (is_array($locales)) {
+ foreach ($locales as $n => $locale) {
+ $this->_supportedLocales[ str_replace('_', '-', $locale) ] = true;
+ }
+ }
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Copyright 2007 Maintainable Software, LLC
+ * Copyright 2008 The Horde Project (http://www.horde.org/)
+ *
+ * @author Mike Naberezny <mike@maintainable.com>
+ * @author Derek DeVries <derek@maintainable.com>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php
+ * @category Horde
+ * @package Horde_Controller
+ * @subpackage Request
+ */
+
+/**
+ * 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 <mike@maintainable.com>
+ * @author Derek DeVries <derek@maintainable.com>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @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
+{
+ /**
+ * 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())
+ {
+ $this->_initSessionData();
+
+ // 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->_isAjax = $this->getServer('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest';
+ }
+
+
+ /*##########################################################################
+ # 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()
+ {
+ return $this->_domain;
+ }
+
+ /**
+ * 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;
+ }
+
+
+ /**
+ * 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()
+ {
+ return $this->_remoteIp;
+ }
+
+ /**
+ * Get cookie value from specified $name OR get All when $name isn't passed in
+ *
+ * @param string $name
+ * @param string $default
+ * @return string
+ */
+ public function getCookie($name=null, $default=null)
+ {
+ if (isset($name)) {
+ return isset($this->_cookie[$name]) ? $this->_cookie[$name] : $default;
+ } else {
+ return $this->_cookie;
+ }
+ }
+
+ /**
+ * 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)
+ {
+ if (isset($name)) {
+ return isset($this->_session[$name]) ? $this->_session[$name] : $default;
+ } else {
+ return $this->_session;
+ }
+ }
+
+ /**
+ * 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, */
+ $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;
+ }
+
+ /**
+ * Get entire list of $_GET parameters
+ * @return array
+ */
+ public function getGetParams()
+ {
+ return $this->_get;
+ }
+
+ /**
+ * Get entire list of $_POST parameters
+ *
+ * @return array
+ */
+ public function getPostParams()
+ {
+ return $this->_post;
+ }
+
+ /**
+ * 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 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;
+ }
+
+ /**
+ * Gets the value of header.
+ *
+ * Returns the value of the specified request header.
+ *
+ * @param string $name the name of the header
+ * @return string the value of the specified header
+ * @access public
+ */
+ function getHeader($name)
+ {
+ if ($this->_headers == NULL) {
+ $this->_headers = $this->_getAllHeaders();
+ }
+
+ if (isset($this->_headers[$name])) {
+ return $this->_headers[$name];
+ }
+ return NULL;
+ }
+
+ /**
+ * Gets all the header names.
+ *
+ * Returns an array of all the header names this request
+ * contains.
+ *
+ * @return array all the available headers as strings
+ * @access public
+ */
+ function getHeaderNames()
+ {
+ if ($this->_headers == NULL) {
+ $this->_headers = $this->_getAllHeaders();
+ }
+ return array_keys($this->_headers);
+ }
+
+ /**
+ * Gets all the headers.
+ *
+ * Returns an associative array of all the header names and values of this
+ * request.
+ *
+ * @return array containing all the headers
+ * @access public
+ */
+ function getHeaders()
+ {
+ if ($this->_headers == NULL) {
+ $this->_headers = $this->_getAllHeaders();
+ }
+ return $this->_headers;
+ }
+
+ /**
+ * Returns all HTTP_* headers.
+ *
+ * Returns all the HTTP_* headers. Works both if PHP is an apache
+ * module and if it's running as a CGI.
+ *
+ * @return array the headers' names and values
+ * @access private
+ */
+ function _getAllHeaders()
+ {
+ if (function_exists('getallheaders')) {
+ return getallheaders();
+ }
+
+ reset($_SERVER);
+ $result = array();
+ array_walk($_SERVER, array($this, '_getAllHeadersHelper'), $result);
+
+ // map so that the variables gotten from the environment when
+ // running as CGI have the same names as when PHP is an apache
+ // module
+ $map = array (
+ 'HTTP_ACCEPT' => 'Accept',
+ 'HTTP_ACCEPT_CHARSET' => 'Accept-Charset',
+ 'HTTP_ACCEPT_ENCODING' => 'Accept-Encoding',
+ 'HTTP_ACCEPT_LANGUAGE' => 'Accept-Language',
+ 'HTTP_CONNECTION' => 'Connection',
+ 'HTTP_HOST' => 'Host',
+ 'HTTP_KEEP_ALIVE' => 'Keep-Alive',
+ 'HTTP_USER_AGENT' => 'User-Agent' );
+
+ $mapped_result = array();
+ foreach ($result as $k => $v) {
+ $mapped_result[$map[$k]] = $v;
+ }
+
+ return $mapped_result;
+ }
+
+ /**
+ * Helper function for _getallheaders.
+ *
+ * For use with array_walk.
+ */
+ protected function _getAllHeadersHelper($value, $key, &$result)
+ {
+ $header_name = substr($key, 0, 5);
+ if ($header_name == 'HTTP_') {
+ $result[$key] = $value;
+ }
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Copyright 2007 Maintainable Software, LLC
+ * Copyright 2008 The Horde Project (http://www.horde.org/)
+ *
+ * @author Mike Naberezny <mike@maintainable.com>
+ * @author Derek DeVries <derek@maintainable.com>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php
+ * @category Horde
+ * @package Horde_Controller
+ * @subpackage Request
+ */
+
+/**
+ * @author Mike Naberezny <mike@maintainable.com>
+ * @author Derek DeVries <derek@maintainable.com>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @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
+{
+}
--- /dev/null
+<?php
+/**
+ * Copyright 2007 Maintainable Software, LLC
+ * Copyright 2008 The Horde Project (http://www.horde.org/)
+ *
+ * @author Mike Naberezny <mike@maintainable.com>
+ * @author Derek DeVries <derek@maintainable.com>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php
+ * @category Horde
+ * @package Horde_Controller
+ * @subpackage Response
+ */
+
+/**
+ * @author Mike Naberezny <mike@maintainable.com>
+ * @author Derek DeVries <derek@maintainable.com>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php
+ * @category Horde
+ * @package Horde_Controller
+ * @subpackage Response
+ */
+class Horde_Controller_Response_Base
+{
+ /**
+ */
+ public function send()
+ {
+ echo $this->_body;
+ }
+
+ public function setBody($body)
+ {
+ $this->_body = $body;
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Copyright 2007 Maintainable Software, LLC
+ * Copyright 2008 The Horde Project (http://www.horde.org/)
+ *
+ * @author Mike Naberezny <mike@maintainable.com>
+ * @author Derek DeVries <derek@maintainable.com>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php
+ * @category Horde
+ * @package Horde_Controller
+ * @subpackage Response
+ */
+
+/**
+ * @author Mike Naberezny <mike@maintainable.com>
+ * @author Derek DeVries <derek@maintainable.com>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @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
+{
+ /**
+ * Adds the specified cookie to the response.
+ *
+ * This method can be called multiple times to set more than one cookie or
+ * to modify an already set one. Returns true if the adding was successful,
+ * false otherwise.
+ *
+ * @param Ismo_Core_Cookie $cookie the cookie object to add
+ * @return boolean true if the adding was successful,
+ * false otherwise
+ * @access public
+ */
+ function addCookie($cookie)
+ {
+ if (get_class($cookie) == 'ismo_core_cookie' ||
+ get_parent_class($cookie) == 'ismo_core_cookie') {
+ $secure = 0;
+ if ($cookie->isSecure()) {
+ $secure = 1;
+ }
+
+ setcookie( $cookie->getName(),
+ $cookie->getValue(),
+ $cookie->getExpire(),
+ $cookie->getPath(),
+ $cookie->getDomain(),
+ $secure );
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Deletes the specified cookie from the response.
+ *
+ * @param IsmoCookie $cookie the cookie object to delete
+ * @access public
+ */
+ function deleteCookie($cookie)
+ {
+ if (get_class($cookie) == 'ismo_core_cookie' ||
+ get_parent_class($cookie) == 'ismo_core_cookie') {
+ $secure = 0;
+ if ($cookie->isSecure()) {
+ $secure = 1;
+ }
+
+ // set the expiration date to one hour ago
+ $cookie->setExpire(time() - 3600);
+
+ setcookie( $cookie->getName(),
+ $cookie->getValue(),
+ $cookie->getExpire(),
+ $cookie->getPath(),
+ $cookie->getDomain(),
+ $secure );
+ }
+ }
+
+ /**
+ * Adds a response header with the given name and value.
+ *
+ * This method allows response headers to have multiple values. Returns true
+ * if the header could be added, false otherwise. False will be returned
+ * f.g. when the headers have already been sent. The replace parameter
+ * indicates if an already existing header with the same name should be
+ * replaced or not.
+ *
+ * @param string $name the name of the header
+ * @param string $value the value of the header
+ * @param boolean $replace should the header be replaced or not
+ * @return boolean true if the header could be set, false
+ * otherwise
+ * @access public
+ */
+ function addHeader($name, $value, $replace)
+ {
+ if (headers_sent()) {
+ return false;
+ }
+
+ header("$name: $value", $replace);
+ return true;
+ }
+
+ /**
+ * Sends an error response to the client using the specified status code.
+ *
+ * Sends an error response to the client using the specified status code.
+ * This will create a page that looks like an HTML-formatted server error
+ * page containing the specifed message (if any), setting the content type
+ * to "text/html", leaving cookies and other headers unmodified.
+ *
+ * If the headers have already been sent this method returns <b>false</b>
+ * otherwise <b>true</b>. After this method the response should be
+ * considered commited, i.e. both headers and data have been sent to the
+ * client.
+ *
+ * @todo decide what the error page should look like
+ * @param string $code the status code to use
+ * @param string $msg the message to show
+ * @return boolean <b>true</b> if the error response could be
+ * send, <b>false</b> otherwise (if the headers
+ * have already been sent)
+ * @access public
+ */
+ function sendError($code, $msg = NULL)
+ {
+ if (headers_sent()) {
+ return false;
+ }
+
+ header('HTTP/1.0 '.$code);
+
+ // @todo what kind of HTML page should it be?
+ ?>
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
+<html><head>
+<title><?= $code ?></title>
+</head><body>
+<h1><?= $code ?></h1>
+<?php
+ if ($msg != NULL)
+ {
+ echo $msg;
+ }
+?>
+<hr>
+</body></html>
+<?php
+
+ return true;
+ }
+
+ /**
+ * Sends a temporary redirect respones to the client using the specifed
+ * redirect URL.
+ *
+ * If the headers have already been sent this method returns <b>false</b>
+ * otherwise <b>true</b>. After this method the response should be
+ * considered commited.
+ *
+ * Examples:
+ * <code>
+ * $u = new Ismo_Core_Url("http://a.b.c");
+ * $response->sendRedirect($u);
+ * </code>
+ * Redirects the browser to http://a.b.c using an Ismo_Core_Url instance.
+ *
+ * <code>
+ * $response->sendRedirect("http://d.e.f");
+ * </code>
+ * Redirects the browser to http://d.e.f using a string.
+ *
+ * @param mixed $location url to redirect to, this can either be an
+ * Ismo_Core_Url instance or a string
+ * @return boolean <b>false</b> if the headers have already
+ * been sent, <b>true</b> otherwise
+ * @access public
+ */
+ function sendRedirect($location)
+ {
+ if (headers_sent()) {
+ return false;
+ }
+
+ if (get_class($location) == 'ismo_core_url') {
+ $location = $location->toString(false);
+ }
+
+ /* so that it works correctly for IE */
+ header('HTTP/1.1 301 Moved Permanently');
+ header('Location: ' . $location);
+ header('Connection: close');
+
+ return true;
+ }
+
+ /**
+ * Sets the status code for this request.
+ *
+ * Sets the status code for this response. This method is used to set the
+ * return status code when there is no error (for example, for the status
+ * codes SC_OK or SC_MOVED_TEMPORARILY). If there is an error, and the
+ * caller wishes to provide a message for the response, the sendError()
+ * method should be used instead.
+ *
+ * @param string $code the status code to set
+ * @access public
+ * @see sendError()
+ */
+ function setStatus($code)
+ {
+ header('HTTP/1.0 ' . $code);
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Copyright 2007 Maintainable Software, LLC
+ * Copyright 2008 The Horde Project (http://www.horde.org/)
+ *
+ * @author Mike Naberezny <mike@maintainable.com>
+ * @author Derek DeVries <derek@maintainable.com>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php
+ * @category Horde
+ * @package Horde_Controller
+ * @subpackage Response
+ */
+
+/**
+ * @author Mike Naberezny <mike@maintainable.com>
+ * @author Derek DeVries <derek@maintainable.com>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @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
+{
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<package packagerversion="1.4.9" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
+http://pear.php.net/dtd/tasks-1.0.xsd
+http://pear.php.net/dtd/package-2.0
+http://pear.php.net/dtd/package-2.0.xsd">
+ <name>Controller</name>
+ <channel>pear.horde.org</channel>
+ <summary>Horde Controller libraries</summary>
+ <description>This package provides the controller part of an MVC system for Horde.
+ </description>
+ <lead>
+ <name>Mike Naberezny</name>
+ <user>mnaberez</user>
+ <email>mike@naberezny.com</email>
+ <active>yes</active>
+ </lead>
+ <lead>
+ <name>Chuck Hagenbuch</name>
+ <user>chuck</user>
+ <email>chuck@horde.org</email>
+ <active>yes</active>
+ </lead>
+ <date>2008-09-24</date>
+ <version>
+ <release>0.1.0</release>
+ <api>0.1.0</api>
+ </version>
+ <stability>
+ <release>beta</release>
+ <api>beta</api>
+ </stability>
+ <license uri="http://opensource.org/licenses/bsd-license.php">BSD</license>
+ <notes>* Initial release.</notes>
+ <contents>
+ <dir name="/">
+ <dir name="lib">
+ <dir name="Horde">
+ <dir name="Controller">
+ <dir name="Request">
+ <file name="Base.php" role="php" />
+ <file name="Http.php" role="php" />
+ <file name="Mock.php" role="php" />
+ </dir> <!-- /lib/Horde/Controller/Requeset -->
+ <dir name="Response">
+ <file name="Base.php" role="php" />
+ <file name="Http.php" role="php" />
+ <file name="Mock.php" role="php" />
+ </dir> <!-- /lib/Horde/Controller/Response -->
+ <file name="Base.php" role="php" />
+ <file name="Dispatcher.php" role="php" />
+ <file name="Exception.php" role="php" />
+ </dir> <!-- /lib/Horde/Controller -->
+ </dir> <!-- /lib/Horde -->
+ </dir> <!-- /lib -->
+ </dir> <!-- / -->
+ </contents>
+ <dependencies>
+ <required>
+ <php>
+ <min>5.2.0</min>
+ </php>
+ <pearinstaller>
+ <min>1.5.0</min>
+ </pearinstaller>
+ </required>
+ </dependencies>
+ <phprelease>
+ <filelist>
+ <install name="lib/Horde/Controller/Base.php" as="Horde/Controller/Base.php" />
+ <install name="lib/Horde/Controller/Dispatcher.php" as="Horde/Controller/Dispatcher.php" />
+ <install name="lib/Horde/Controller/Exception.php" as="Horde/Controller/Exception.php" />
+ <install name="lib/Horde/Controller/Request/Base.php" as="Horde/Controller/Request/Base.php" />
+ <install name="lib/Horde/Controller/Request/Http.php" as="Horde/Controller/Request/Http.php" />
+ <install name="lib/Horde/Controller/Request/Mock.php" as="Horde/Controller/Request/Mock.php" />
+ <install name="lib/Horde/Controller/Response/Base.php" as="Horde/Controller/Response/Base.php" />
+ <install name="lib/Horde/Controller/Response/Http.php" as="Horde/Controller/Response/Http.php" />
+ <install name="lib/Horde/Controller/Response/Mock.php" as="Horde/Controller/Response/Mock.php" />
+ </filelist>
+ </phprelease>
+</package>
--- /dev/null
+<?php
+/**
+ * Copyright 2007 Maintainable Software, LLC
+ * Copyright 2008 The Horde Project (http://www.horde.org/)
+ *
+ * @author Mike Naberezny <mike@maintainable.com>
+ * @author Derek DeVries <derek@maintainable.com>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php
+ * @category Horde
+ * @package Horde_Controller
+ * @subpackage UnitTests
+ */
+
+if (!defined('PHPUnit_MAIN_METHOD')) {
+ define('PHPUnit_MAIN_METHOD', 'Horde_Controller_AllTests::main');
+}
+
+require_once 'PHPUnit/Framework/TestSuite.php';
+require_once 'PHPUnit/TextUI/TestRunner.php';
+
+/**
+ * @author Mike Naberezny <mike@maintainable.com>
+ * @author Derek DeVries <derek@maintainable.com>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php
+ * @category Horde
+ * @package Horde_Controller
+ * @subpackage UnitTests
+ */
+class Horde_Controller_AllTests {
+
+ public static function main()
+ {
+ PHPUnit_TextUI_TestRunner::run(self::suite());
+ }
+
+ public static function suite()
+ {
+ set_include_path(dirname(__FILE__) . '/../../../lib' . PATH_SEPARATOR . get_include_path());
+ if (!spl_autoload_functions()) {
+ spl_autoload_register(create_function('$class', '$filename = str_replace(array(\'::\', \'_\'), \'/\', $class); include "$filename.php";'));
+ }
+
+ $suite = new PHPUnit_Framework_TestSuite('Horde Framework - Horde_Controller');
+
+ $basedir = dirname(__FILE__);
+ $baseregexp = preg_quote($basedir . DIRECTORY_SEPARATOR, '/');
+
+ foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($basedir)) as $file) {
+ if ($file->isFile() && preg_match('/Test.php$/', $file->getFilename())) {
+ $pathname = $file->getPathname();
+ require $pathname;
+
+ $class = str_replace(DIRECTORY_SEPARATOR, '_',
+ preg_replace("/^$baseregexp(.*)\.php/", '\\1', $pathname));
+ $suite->addTestSuite('Horde_Controller_' . $class);
+ }
+ }
+
+ return $suite;
+ }
+
+}
+
+if (PHPUnit_MAIN_METHOD == 'Horde_Controller_AllTests::main') {
+ Horde_Controller_AllTests::main();
+}