Location: PHPKode > projects > LightVC > awbush-lightvc-054201f/modules/lightvc.php
<?php
/**
 * LightVC - A lightweight view-controller framework.
 * http://lightvc.org/
 * 
 * You provide your own model/ORM. We recommend Cough <http://coughphp.com>.
 * 
 * The purpose of this framework is to provide just a "view-controller"
 * setup without all the other junk. Ideally, the classes from other frameworks
 * should be reusable but instead they are mostly coupled with their frameworks.
 * It's up to you to go get those classes if you need them, or provide your own.
 * 
 * Additionally, we've decoupled it from any sort of Model so that you can use
 * the one you already know and love. And if you don't know one, now is a great
 * time to check out CoughPHP. Other ORMs can be found at:
 * 
 * http://en.wikipedia.org/wiki/List_of_object-relational_mapping_software#PHP
 * 
 * By providing just the VC, we increase the reusability of not only the
 * framework itself, but non-framework components as well.
 * 
 * The framework is fast. Currently the speed of this framework is unmatched by
 * any other PHP framework available today.
 * 
 * You get to use the classes you've already been using without worrying about
 * naming conflicts or inefficiencies from loading both your classes and the
 * classes from some other framework.
 * 
 * LightVC aims to be easier to use, more configurable, and light in footprint.
 * 
 * @author Anthony Bush
 * @version 1.0.4 (2008-03-15)
 * @package lightvc
 * @see http://lightvc.org/
 **/

/**
 * Configuration class for the LVC suite of classes.
 *
 * @package lightvc
 * @author Anthony Bush
 * @since 2007-04-20
 **/
class Lvc_Config {
	protected static $controllerPaths = array();
	protected static $controllerSuffix = '.php'; // e.g. _controller.php
	protected static $controllerViewPaths = array();
	protected static $controllerViewSuffix = '.php'; // e.g. .tpl.php
	protected static $layoutViewPaths = array();
	protected static $layoutViewSuffix = '.php'; // e.g. .tpl.php
	protected static $elementViewPaths = array();
	protected static $elementViewSuffix = '.php'; // e.g. .tpl.php
	protected static $viewClassName = 'Lvc_View'; // e.g. AppView
	protected static $layoutContentVarName = 'layoutContent'; // e.g. content_for_layout
	
	/**
	 * Sets whether or not to send action params as an array or as arguments
	 * to the function.
	 * 
	 * true => action($params)
	 * false => action($param1, $param2, $param3, ...)
	 *
	 * @var boolean
	 **/
	protected static $sendActionParamsAsArray = false;
	
	// These may be moved into some sort of routing thing later. For now:
	
	/**
	 * The controller name to use if no controller name can be gathered from the request.
	 *
	 * @var string
	 **/
	protected static $defaultControllerName = 'page';
	/**
	 * The action name to call on the defaultControllerName if no controller name can be gathered from the request.
	 *
	 * @var string
	 **/
	protected static $defaultControllerActionName = 'view';
	/**
	 * The action params to use when calling defaultControllerActionName if no controller name can be gathered from the request.
	 *
	 * @var string
	 **/
	protected static $defaultControllerActionParams = array('page_name' => 'home');
	/**
	 * The default action name to call on a controller if the controller name
	 * was gathered from the request, but the action name couldn't be.
	 *
	 * @var string
	 **/
	protected static $defaultActionName = 'index';
	
	// Configuration Methods
	
	public static function addControllerPath($path) {
		self::$controllerPaths[] = $path;
	}
	public static function setControllerSuffix($suffix) {
		self::$controllerSuffix = $suffix;
	}
	public static function addControllerViewPath($path) {
		self::$controllerViewPaths[] = $path;
	}
	public static function setControllerViewSuffix($suffix) {
		self::$controllerViewSuffix = $suffix;
	}
	public static function addLayoutViewPath($path) {
		self::$layoutViewPaths[] = $path;
	}
	public static function setLayoutViewSuffix($suffix) {
		self::$layoutViewSuffix = $suffix;
	}
	public static function addElementViewPath($path) {
		self::$elementViewPaths[] = $path;
	}
	public static function setElementViewSuffix($suffix) {
		self::$elementViewSuffix = $suffix;
	}
	public static function setViewClassName($className) {
		self::$viewClassName = $className;
	}
	public static function setLayoutContentVarName($varName) {
		self::$layoutContentVarName = $varName;
	}
	public static function getLayoutContentVarName() {
		return self::$layoutContentVarName;
	}
	public static function setSendActionParamsAsArray($bool) {
		self::$sendActionParamsAsArray = $bool;
	}
	public static function getSendActionParamsAsArray() {
		return self::$sendActionParamsAsArray;
	}
	public static function setDefaultControllerName($defaultControllerName) {
		self::$defaultControllerName = $defaultControllerName;
	}
	public static function setDefaultControllerActionName($defaultControllerActionName) {
		self::$defaultControllerActionName = $defaultControllerActionName;
	}
	public static function setDefaultControllerActionParams($defaultControllerActionParams) {
		self::$defaultControllerActionParams = $defaultControllerActionParams;
	}
	public static function setDefaultActionName($defaultActionName) {
		self::$defaultActionName = $defaultActionName;
	}
	public static function getDefaultControllerName() {
		return self::$defaultControllerName;
	}
	public static function getDefaultControllerActionName() {
		return self::$defaultControllerActionName;
	}
	public static function getDefaultControllerActionParams() {
		return self::$defaultControllerActionParams;
	}
	public static function getDefaultActionName() {
		return self::$defaultActionName;
	}
	
	// Retrieval Methods
	
	public static function getController($controllerName) {
		foreach (self::$controllerPaths as $path) {
			$file = $path . $controllerName . self::$controllerSuffix;
			if (file_exists($file)) {
				include_once($file);
				$controllerClass = self::getControllerClassName($controllerName);
				$controller = new $controllerClass();
				$controller->setControllerName($controllerName);
				return $controller;
			}
		}
		return null;
	}
	
	public static function getControllerClassName($controllerName) {
		return str_replace(' ', '', ucwords(str_replace('_', ' ', $controllerName))) . 'Controller';
	}
	
	public static function getActionFunctionName($actionName) {
		return 'action' . str_replace(' ', '', ucwords(str_replace('_', ' ', $actionName)));
	}
	
	public static function getControllerView($viewName, &$data = array()) {
		return self::getView($viewName, $data, self::$controllerViewPaths, self::$controllerViewSuffix);
	}
	
	public static function getElementView($elementName, &$data = array()) {
		return self::getView($elementName, $data, self::$elementViewPaths, self::$elementViewSuffix);
	}
	
	public static function getLayoutView($layoutName, &$data = array()) {
		return self::getView($layoutName, $data, self::$layoutViewPaths, self::$layoutViewSuffix);
	}
	
	/**
	 * As an Lvc developer, you'll probably want to use `getControllerView`,
	 * `getElementView`, or `getLayoutView`.
	 * 
	 * Example usage:
	 * 
	 *     // Pass the whole file name and leave off the last parameters
	 *     getView('/full/path/to/file/file.php', $data);
	 * 
	 *     // Pass the view name and specify the paths to scan and the suffix to append.
	 *     getView('file', $data, array('/full/path/to/file/'), '.php');
	 *
	 * @var mixed Lvc_View object if one is found, otherwise null.
	 * @see getControllerView(), getElementView(), getLayoutView(), Lvc_Config::setViewClassName()
	 **/
	public static function getView($viewName, &$data = array(), &$paths = array(''), $suffix = '') {
		foreach ($paths as $path) {
			$file = $path . $viewName . $suffix;
			if (file_exists($file)) {
				return new self::$viewClassName($file, $data);
			}
		}
		return null;
	}
	
	public static function dump() {
		echo '<pre>';
		
		echo '<strong>Controller Paths:</strong>' . "\n";
		print_r(self::$controllerPaths);
		echo '<strong>Controller Suffix:</strong> ' . self::$controllerSuffix . "\n\n";
		
		echo '<strong>Layout View Paths:</strong>' . "\n";
		print_r(self::$layoutViewPaths);
		echo '<strong>Layout View Suffix:</strong> '     . self::$layoutViewSuffix . "\n\n";
		
		echo '<strong>Controller View Paths:</strong>' . "\n";
		print_r(self::$controllerViewPaths);
		echo '<strong>Controller View Suffix:</strong> '    . self::$controllerViewSuffix . "\n\n";
		
		echo '<strong>Element View Paths:</strong>' . "\n";
		print_r(self::$elementViewPaths);
		echo '<strong>Element View Suffix:</strong> '    . self::$elementViewSuffix . "\n\n";
		
		echo '</pre>';
	}
}

/**
 * Lvc classes throw this type of exception.
 *
 * @package lightvc
 * @author Anthony Bush
 * @since 2007-04-20
 **/
class Lvc_Exception extends Exception {
	
}

/**
 * A request provides information about what controller and action to run and
 * what parameters to run them with.
 * 
 * @package lightvc
 * @author Anthony Bush
 * @since 2007-04-20
 **/
class Lvc_Request {
	protected $controllerName = '';
	protected $controllerParams = array();
	protected $actionName = '';
	protected $actionParams = array();
	
	public function getControllerName() {
		return $this->controllerName;
	}
	public function &getControllerParams() {
		return $this->controllerParams;
	}
	public function getActionName() {
		return $this->actionName;
	}
	public function &getActionParams() {
		return $this->actionParams;
	}

	public function setControllerName($controllerName) {
		$this->controllerName = $controllerName;
	}
	public function setControllerParams(&$controllerParams) {
		$this->controllerParams = $controllerParams;
	}
	public function setActionName($actionName) {
		$this->actionName = $actionName;
	}
	public function setActionParams($actionParams) {
		$this->actionParams = $actionParams;
	}
	
	/**
	 * Override this in sub request objects to have custom error messages appended to
	 * LightVC messages.  For example, when HTTP Requests error, it might be useful
	 * to put the requested URL in the error log with the "Unable to load controller"
	 * message.
	 *
	 * @return string
	 * @since 2008-03-14
	 **/
	public function getAdditionalErrorInfo() {
		return '';
	}
}

/**
 * An HTTP request contains parameters from the GET, POST, PUT, and
 * DELETE arena.
 *
 * @package lightvc
 * @author Anthony Bush
 * @since 2007-04-20
 **/
class Lvc_HttpRequest extends Lvc_Request {
	protected $params = array();
	
	public function __construct() {
		
		$params = array();
		
		// Save GET data
		if (isset($_GET)) {
			$params['get'] =& $_GET;
		} else {
			$params['get'] = array();
		}
		
		// Ensure that we have some mode_rewritten url.
		if (!isset($params['get']['url'])) {
			$params['get']['url'] = '';
		}
		
		// Save POST data
		$params['post'] =& $_POST;
		
		// Save FILE data (consilidate it with _POST data)
		foreach ($_FILES as $name => $data) {
			if ($name != 'data') {
				$params['post'][$name] = $data;
			} else {
				// Convert _FILE[data][key][model][field] -> [data][model][field][key]
				// so that it matches up with _POST "data"
				foreach ($data as $key => $modelData) {
					foreach ($modelData as $model => $fields) {
						foreach ($fields as $field => $value) {
							$params['post']['data'][$model][$field][$key] = $value;
						}
					}
				}
			}
		}
		
		// Set params that will be used by routers.
		$this->setParams($params);
		// An HTTP request will default to passing all the parameters to the controller.
		$this->setControllerParams($params);
	}
	
	public function &getParams() {
		return $this->params;
	}
	public function setParams(&$params) {
		$this->params = $params;
	}
	
	/**
	 * Provides additional error information that might be useful when debugging
	 * errors.
	 *
	 * @return string
	 * @since 2008-03-14
	 **/
	public function getAdditionalErrorInfo() {
		if (isset($_SERVER['REQUEST_URI'])) {
			return 'Request URL was ' . $_SERVER['REQUEST_URI'];
		} else {
			return parent::getAdditionalErrorInfo();
		}
	}
}

/**
 * A router interface must at least provide a route() function that takes a
 * request object.
 * 
 * @package lightvc
 * @author Anthony Bush
 * @since 2007-04-22
 **/
interface Lvc_RouterInterface {
	/**
	 * Set the appropriate controller, action, and action parameters to use on
	 * the request object and return true. If no appropriate controller info
	 * can be found, return false.
	 * 
	 * @param mixed $request A request object to route.
	 * @return boolean
	 * @author Anthony Bush
	 * @since 2007-04-22
	 **/
	public function route($request);
}

/**
 * Routes a request using only GET data.
 * 
 * You can change the default keys for controller and action detection using
 * {@link setControllerKey()} and {@link setActionKey()} respectively.
 *
 * @package lightvc
 * @author Anthony Bush
 * @since 2007-04-22
 **/
class Lvc_GetRouter implements Lvc_RouterInterface {
	protected $controllerKey = 'controller';
	protected $actionKey = 'action';
	protected $actionParamsKey = null;
	protected $routes = array();
	
	public function setControllerKey($controllerKey) {
		$this->controllerKey = $controllerKey;
	}
	public function setActionKey($actionKey) {
		$this->actionKey = $actionKey;
	}
	public function setActionParamsKey($actionParamsKey) {
		$this->actionParamsKey = $actionParamsKey;
	}
	
	/**
	 * Add a param order for a controller / action.
	 * 
	 * For example:
	 * 
	 *     $router->addRoute('pages', 'show_page', array('page_name'));
	 * 
	 * will route:
	 * 
	 *     ?controller=pages&action=show_page&page_name=about
	 * 
	 * to:
	 * 
	 *     PagesController::actionShowPage('about');
	 * 
	 * whereas without the route the controller would be invoked with:
	 * 
	 *     PagesController::actionShowPage();
	 * 
	 * and you'd have to access the page_name via $this->get['page_name'].
	 *
	 * @return void
	 * @author Anthony Bush
	 * @since 2007-05-10
	 **/
	public function addRoute($controllerName, $actionName, $actionParamsOrder = array()) {
		$this->routes[$controllerName][$actionName] = $actionParamsOrder;
	}
	
	/**
	 * Set all routes at once. Useful if you want to specify routes in a
	 * config file and then pass them to this router all at once. See
	 * {@link addRoute()} for routing specifications.
	 * 
	 * @return void
	 * @author Anthony Bush
	 * @since 2007-05-10
	 **/
	public function setRoutes(&$routes) {
		$this->routes = $routes;
	}
	
	/**
	 * Construct the router and set all routes at once. See {@link setRoutes()}
	 * for more info.
	 *
	 * @return void
	 * @author Anthony Bush
	 * @see setRoutes()
	 * @since 2007-05-10
	 **/
	public function __construct(&$routes = null) {
		if ( ! is_null($routes)) {
			$this->setRoutes($routes);
		}
	}
	
	/**
	 * Attempts to routes a request using only the GET data.
	 * 
	 * @param Lvc_HttpRequest $request A request object to route.
	 * @return boolean
	 * @author Anthony Bush
	 * @since 2007-04-22
	 **/
	public function route($request) {
		$params = $request->getParams();
		
		// Use GET parameters to set controller, action, and action params
		if (isset($params['get'][$this->controllerKey])) {
			
			$request->setControllerName($params['get'][$this->controllerKey]);
			
			if (isset($params['get'][$this->actionKey])) {
				$request->setActionName($params['get'][$this->actionKey]);
			} else {
				$request->setActionName(Lvc_Config::getDefaultActionName());
			}
			
			// Using paramsKey method?
			if ( ! is_null($this->actionParamsKey) && isset($params['get'][$this->actionParamsKey])) {
				$request->setActionParams($params['get'][$this->actionParamsKey]);
			}
			// Using routes?
			else if ( ! empty($this->routes)) {
				if (isset($this->routes[$request->getControllerName()])
				 && isset($this->routes[$request->getControllerName()][$request->getActionName()])
				) {
					$actionParams = array();
					foreach ($this->routes[$request->getControllerName()][$request->getActionName()] as $paramName) {
						$actionParams[$paramName] = @$params['get'][$paramName];
					}
					$request->setActionParams($actionParams);
				}
			}
			
			return true;
		} else {
			return false;
		}
	}
}

/**
 * Attempts to route a request using the GET value for the 'url' key, which
 * should be set by the mod_rewrite rules. Any additional "directories" are
 * used as parameters for the action (using numeric indexes). Any extra GET
 * data is also amended to the action parameters.
 * 
 * If you need the numeric indexes to map to specific parameter names, use
 * the {@link Lvc_ParamOrderRewriteRouter} instead.
 * 
 * @package lightvc
 * @author Anthony Bush
 * @since 2007-04-22
 **/
class Lvc_RewriteRouter implements Lvc_RouterInterface {
	/**
	 * Attempts to route a request using the GET value for the 'url' key, which
	 * should be set by the mod_rewrite rules. Any additional "directories" are
	 * used as parameters for the action (using numeric indexes). Any extra GET
	 * data is also amended to the action parameters.
	 * 
	 * @param Lvc_HttpRequest $request A request object to route.
	 * @return boolean
	 * @author Anthony Bush
	 * @since 2007-04-22
	 **/
	public function route($request) {
		$params = $request->getParams();
		
		if (isset($params['get']['url'])) {
			
			// Use mod_rewrite's url
			$url = explode('/', $params['get']['url']);
			$count = count($url);
			
			// Set controller, action, and some action params from the segmented URL.
			if ($count > 0) {
				$request->setControllerName($url[0]);
				
				$actionParams = array();
				if ($count > 1) {
					$request->setActionName($url[1]);
					if ($count > 2) {
						for ($i = 2; $i < $count; $i++) {
							if ( ! empty($url[$i])) {
								$actionParams[] = $url[$i];
							}
						}
					}
				}
				
				$request->setActionParams($actionParams);
				return true;
			}
		}
		return false;
	}
}

/**
 * Routes a request using mod_rewrite data and regular expressions specified by
 * the LightVC user.
 * 
 * Specify routes using {@link addRoute()}.
 *
 * @package lightvc
 * @author Anthony Bush
 * @since 2007-05-08
 **/
class Lvc_RegexRewriteRouter implements Lvc_RouterInterface {
	protected $routes = array();
	
	/**
	 * Specify a regular expression and how it should be routed.
	 * 
	 * For example:
	 * 
	 *     $regexRouter->addRoute('|^wee/([^/]+)/?$|', array(
	 *         'controller' => 'hello_world',
	 *         'action' => 'index',
	 *         'action_params' => array(1, 'constant_value')
	 *     ));
	 * 
	 * would map "wee/anything" and "wee/anything/" to:
	 * 
	 *     HelloWorldController::actionIndex('anything', 'constant_value');
	 * 
	 * but would not map "wee/anything/anything_else".
	 * 
	 * The format of the $parsingInfo parameter is as follows:
	 * 
	 *     'controller' => a hard coded controller name or an integer specifying which match in the regex to use.
	 *     'action' => a hard coded action name or an integer specifying which match in the regex to use.
	 *     'action_params' => array(
	 *         a hard coded action value or an integer specifying which match in the regex to use,
	 *         repeat above line as needed,
	 *     ),
	 *     'additional_params' => a hard coded integer specifying which match in the regex to use for additional parameters. These will be exploded by "/" and added to the action params.
	 * 
	 * or
	 * 
	 *     'redirect' => a replacement string that will be used to redirect to.  You can have parts of the original url mapped into the new one (like IDs).  See http://www.php.net/manual/en/function.preg-replace.php's documentation for the replacement parameter.
	 * 
	 * You can specify as much or as little as you want in the $parsingInfo.
	 * That is, if you don't specify the controller name or action name, then
	 * the defaults will be used by the Lvc_FrontController.
	 * 
	 * @param $regex regular expression to match the rewritten part with.
	 * @param $parsingInfo an array containing any custom routing info.
	 * @return void
	 * @author Anthony Bush
	 * @since 2007-05-08
	 **/
	public function addRoute($regex, $parsingInfo = array()) {
		$this->routes[$regex] = $parsingInfo;
	}
	
	/**
	 * Set all routes at once. Useful if you want to specify routes in a
	 * config file and then pass them to this router all at once. See
	 * {@link addRoute()} for routing specifications.
	 * 
	 * @return void
	 * @author Anthony Bush
	 * @since 2007-05-08
	 **/
	public function setRoutes(&$routes) {
		$this->routes = $routes;
	}
	
	/**
	 * Construct the router and set all routes at once. See {@link setRoutes()}
	 * for more info.
	 *
	 * @return void
	 * @author Anthony Bush
	 * @see setRoutes()
	 * @since 2007-05-09
	 **/
	public function __construct(&$routes = null) {
		if ( ! is_null($routes)) {
			$this->setRoutes($routes);
		}
	}
	
	/**
	 * Routes like {@link Lvc_RewriteRouter} does, with the additional check to
	 * routes for specifying custom routes based on regular expressions.
	 * 
	 * @param Lvc_HttpRequest $request A request object to route.
	 * @return boolean
	 * @author Anthony Bush
	 * @since 2007-05-08
	 **/
	public function route($request) {
		$params = $request->getParams();
		
		if (isset($params['get']['url'])) {
			
			// Use mod_rewrite's url
			$url = $params['get']['url'];
			
			$matches = array();
			foreach ($this->routes as $regex => $parsingInfo) {
				if (preg_match($regex, $url, $matches)) {
					
					// Check for redirect action first
					if (isset($parsingInfo['redirect'])) {
						$redirectUrl = preg_replace($regex, $parsingInfo['redirect'], $url);
						header('Location: ' . $redirectUrl);
						exit();
					}
					
					// Get controller name if available
					if (isset($parsingInfo['controller'])) {
						if (is_int($parsingInfo['controller'])) {
							// Get the controller name from the regex matches
							$request->setControllerName(@$matches[$parsingInfo['controller']]);
						} else {
							// Use the constant value
							$request->setControllerName($parsingInfo['controller']);
						}
					}
					
					// Get action name if available
					if (isset($parsingInfo['action'])) {
						if (is_int($parsingInfo['action'])) {
							// Get the action from the regex matches
							$request->setActionName(@$matches[$parsingInfo['action']]);
						} else {
							// Use the constant value
							$request->setActionName($parsingInfo['action']);
						}
					}
					
					// Get action parameters
					$actionParams = array();
					if (isset($parsingInfo['action_params'])) {
						foreach ($parsingInfo['action_params'] as $key => $value) {
							if (is_int($value)) {
								// Get the value from the regex matches
								if (isset($matches[$value])) {
									$actionParams[$key] = $matches[$value];
								} else {
									$actionParams[$key] = null;
								}
							} else {
								// Use the constant value
								$actionParams[$key] = $value;
							}
						}
					}
					if (isset($parsingInfo['additional_params'])) {
						if (is_int($parsingInfo['additional_params'])) {
							// Get the value from the regex matches
							if (isset($matches[$parsingInfo['additional_params']])) {
								$actionParams = $actionParams + explode('/', $matches[$parsingInfo['additional_params']]);
							}
						}
					}
					
					
					$request->setActionParams($actionParams);
					return true;
				} // route matched
			} // loop through routes
		} // url _GET value set
		return false;
	}
}

/**
 * FrontController takes a Request object and invokes the appropriate controller
 * and action.
 * 
 * Example Usage:
 * 
 *     $fc = new Lvc_FrontController();
 *     $fc->addRouter(new Lvc_GetRouter());
 *     $fc->processRequest(new Lvc_HttpRequest());
 * 
 * @package lightvc
 * @author Anthony Bush
 * @since 2007-04-20
 **/
class Lvc_FrontController {
	protected $routers = array();
	
	/**
	 * Add a router to give it a chance to route the request.
	 * 
	 * The first router to return true to the {@link route()} call
	 * will be the last router called, so add them in the order you want them
	 * to run.
	 *
	 * @return void
	 * @author Anthony Bush
	 **/
	public function addRouter(Lvc_RouterInterface $router) {
		$this->routers[] = $router;
	}
	
	/**
	 * Processes the request data by instantiating the appropriate controller and
	 * running the appropriate action.
	 *
	 * @return void
	 * @throws Lvc_Exception
	 * @author Anthony Bush
	 **/
	public function processRequest(Lvc_Request $request) {
		try
		{
			// Give routers a chance to (re)-route the request.
			foreach ($this->routers as $router) {
				if ($router->route($request)) {
					break;
				}
			}

			// If controller name or action name are not set, set them to default.
			$controllerName = $request->getControllerName();
			if (empty($controllerName)) {
				$controllerName = Lvc_Config::getDefaultControllerName();
				$actionName     = Lvc_Config::getDefaultControllerActionName();
				$actionParams = $request->getActionParams() + Lvc_Config::getDefaultControllerActionParams();
				$request->setActionParams($actionParams);
			} else {
				$actionName = $request->getActionName();
				if (empty($actionName)) {
					$actionName   = Lvc_Config::getDefaultActionName();
				}
			}

			$controller = Lvc_Config::getController($controllerName);
			if (is_null($controller)) {
				throw new Lvc_Exception('Unable to load controller "' . $controllerName . '"');
			}
			$controller->setControllerParams($request->getControllerParams());
			$controller->runAction($actionName, $request->getActionParams());
		}
		catch (Lvc_Exception $e)
		{
			// Catch exceptions and append additional error info if the request object has anything to say.
			$moreInfo = $request->getAdditionalErrorInfo();
			if (!empty($moreInfo)) {
				throw new Lvc_Exception($e->getMessage() . '. ' . $moreInfo);
			} else {
				throw $e;
			}
		}
	}
}

/**
 * The base class that all other PageControllers should extend. Depending on the setup,
 * you might want an AppController to extend this one, and then have all your controllers
 * extend your AppController.
 * 
 * @package lightvc
 * @author Anthony Bush
 * @todo Finish up documentation in here...
 * @since 2007-04-20
 **/
class Lvc_PageController {
	/**
	 * Params is typically a combination of:
	 *     _GET (stored in params['get'])
	 *     _POST (stored in params['post'])
	 *     _FILE (also stored in params['post'])
	 *
	 * @var array
	 **/
	protected $params = array();
	
	/**
	 * A reference to $params['post']['data'], typically a combination of:
	 *     _POST['data'] (usually holds [Model][field])
	 *     _FILE['data'] (usually holds [key][Model][field], but the request object should remap it to [Model][field][key])
	 *
	 * @var array
	 **/
	protected $postData = array();
	
	/**
	 * Reference to post data (i.e. $this->params['post'])
	 * 
	 * @var array
	 **/
	protected $post = array();
	
	/**
	 * Reference to get data (i.e. $this->params['get'])
	 * 
	 * @var array
	 **/
	protected $get = array();
	
	/**
	 * Controller Name (e.g. controller_name, not ControllerNameController)
	 *
	 * @var string
	 **/
	protected $controllerName = null;
	
	/**
	 * Action Name (e.g. action_name, not actionActionName)
	 *
	 * @var string
	 **/
	protected $actionName = null;
	
	/**
	 * Variables we will pass to the view.
	 *
	 * @var array()
	 **/
	protected $viewVars = array();
	
	/**
	 * Have we loaded the view yet?
	 *
	 * @var boolean
	 **/
	protected $hasLoadedView = false;
	
	/**
	 * Specifies whether or not to load the default view for the action. If the
	 * action should not render any view, set it to false in the sub controller.
	 *
	 * @var boolean
	 **/
	protected $loadDefaultView = true;
	
	/**
	 * Don't set this yourself. It's used internally by parent controller /
	 * actions to determine whether or not to use the layout value in
	 * $layoutOverride rather than in $layout when requesting a sub action.
	 * 
	 * @var string
	 * @see setLayoutOverride(), $layoutOverride
	 **/
	protected $useLayoutOverride = false;
	
	/**
	 * Don't set this yourself. It's used internally by parent controller /
	 * actions to determine which layout to use when requesting a sub action.
	 * 
	 * @var string
	 * @see setLayoutOverride(), $useLayoutOverride
	 **/
	protected $layoutOverride = null;
	
	/**
	 * Set this in your controller to use a layout.
	 *
	 * @var string
	 **/
	protected $layout = null;
	
	/**
	 * An array of view variables specifically for the layout file.
	 *
	 * @var array
	 **/
	protected $layoutVars = array();
	
	/**
	 * Set the parameters of the controller.
	 * Actions will get their parameters through params['get'].
	 * Actions can access the post data as needed.
	 * 
	 * @param array $params an array of [paramName] => [paramValue] pairs
	 * @return void
	 * @author Anthony Bush
	 **/
	public function setControllerParams(&$params) {
		$this->params = $params;
		// Make a reference to the form data so we can get to it easier.
		if (isset($this->params['post']['data'])) {
			$this->postData =& $this->params['post']['data'];
		}
		if (isset($this->params['post'])) {
			$this->post =& $this->params['post'];
		}
		if (isset($this->params['get'])) {
			$this->get =& $this->params['get'];
		}
	}
	
	/**
	 * Don't call this yourself. It's used internally when creating new
	 * controllers so the controllers are aware of their name without
	 * needing any help from a user setting a member variable or from some
	 * reflector class.
	 *
	 * @return void
	 * @author Anthony Bush
	 **/
	public function setControllerName($controllerName) {
		$this->controllerName = $controllerName;
	}
	
	/**
	 * Set a variable for the view to use.
	 * 
	 * @param string $varName variable name to make available in the view
	 * @param $value value of the variable.
	 * @return void
	 * @author Anthony Bush
	 **/
	public function setVar($varName, $value) {
		$this->viewVars[$varName] = $value;
	}
	
	/**
	 * Set variables for the view in masse.
	 * 
	 * @param $varArray an array of [varName] => [value] pairs.
	 * @return void
	 * @author Anthony Bush
	 **/
	public function setVars(&$varArray) {
		$this->viewVars = $varArray + $this->viewVars;
	}
	
	/**
	 * Get the current value for a view variable.
	 * 
	 * @param string $varName
	 * @return mixed
	 * @author Anthony Bush
	 * @since 2007-11-13
	 **/
	public function getVar($varName) {
		if (isset($this->viewVars[$varName])) {
			return $this->viewVars[$varName];
		} else {
			return null;
		}
	}
	
	/**
	 * Set a variable for the layout view.
	 *
	 * @param $varName variable name to make available in the view
	 * @param $value value of the variable.
	 * @return void
	 * @author Anthony Bush
	 * @since 2007-05-17
	 **/
	public function setLayoutVar($varName, $value) {
		$this->layoutVars[$varName] = $value;
	}
	
	/**
	 * Get the current value for a layout variable.
	 * 
	 * @param string $varName
	 * @return mixed
	 * @author Anthony Bush
	 * @since 2007-11-13
	 **/
	public function getLayoutVar($varName) {
		if (isset($this->layoutVars[$varName])) {
			return $this->layoutVars[$varName];
		} else {
			return null;
		}
	}	
	
	/**
	 * Set the layout to use for the view.
	 * 
	 * @return void
	 * @author Anthony Bush
	 **/
	public function setLayout($layout) {
		$this->layout = $layout;
	}
	
	/**
	 * Don't call this yourself. It's used internally when requesting sub
	 * actions in order to avoid loading the layout multiple times.
	 *
	 * @return void
	 * @see $useLayoutOverride, $layoutOverride
	 * @author Anthony Bush
	 **/
	public function setLayoutOverride($layout) {
		$this->useLayoutOverride = true;
		$this->layoutOverride = $layout;
	}
	
	/**
	 * Returns the action name of this controller
	 *
	 * @return string
	 * @author lzhang
	 **/
	public function getActionName()
	{
		return $this->actionName;
	}
	
	/**
	 * Determine whether or not the the controller has the specified action.
	 * 
	 * @param string $actionName the action name to check for.
	 * @return boolean
	 * @author Anthony Bush
	 **/
	public function hasAction($actionName) {
		if (method_exists($this, Lvc_Config::getActionFunctionName($actionName))) {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Runs the requested action and returns the output from it.
	 * 
	 * Typically called by the FrontController.
	 * 
	 * @param string $actionName the action name to run.
	 * @param array $actionParams the parameters to pass to the action.
	 * @return string output from running the action.
	 * @author Anthony Bush
	 **/
	public function getActionOutput($actionName, &$actionParams = array()) {
		ob_start();
		$this->runAction($actionName, $actionParams);
		return ob_get_clean();
	}
	
	/**
	 * Runs the requested action and outputs its results.
	 * 
	 * Typically called by the FrontController.
	 * 
	 * @param string $actionName the action name to run.
	 * @param array $actionParams the parameters to pass to the action.
	 * @return void
	 * @throws Lvc_Exception
	 * @author Anthony Bush
	 **/
	public function runAction($actionName, &$actionParams = array()) {
		$this->actionName = $actionName;
		$func = Lvc_Config::getActionFunctionName($actionName);
		if (method_exists($this, $func)) {
			$this->beforeAction();
			
			// Call the action
			if (Lvc_Config::getSendActionParamsAsArray()) {
				$this->$func($actionParams);
			} else {
				call_user_func_array(array($this, $func), $actionParams);
			}
			
			// Load the view
			if ( ! $this->hasLoadedView && $this->loadDefaultView) {
				$this->loadView($this->controllerName . '/' . $actionName);
			}
			
			$this->afterAction();
			return true;
		} else {
			throw new Lvc_Exception('No action `' . $actionName . '`. Write the `' . $func . '` method');
		}
	}
	
	/**
	 * Load the requested controller view.
	 * 
	 * For example, you can load another view in your controller with:
	 * 
	 *     $this->loadView($this->getControllerName() . '/some_other_action');
	 * 
	 * Or some other controller with:
	 *
	 *     $this->loadView('some_other_controller/some_other_action');
	 * 
	 * Remember, the view for your action will be rendered automatically.
	 * 
	 * @param string $controllerViewName 'controller_name/action_name' format.
	 * @return void
	 * @throws Lvc_Exception
	 * @author Anthony Bush
	 **/
	protected function loadView($controllerViewName) {
		
		$view = Lvc_Config::getControllerView($controllerViewName, $this->viewVars);
		if (is_null($view)) {
			throw new Lvc_Exception('Unable to load controller view "' . $controllerViewName . '" for controller "' . $this->controllerName . '"');
		} else {
			$view->setController($this);
			$viewContents = $view->getOutput();
		}
		
		if ($this->useLayoutOverride) {
			$this->layout = $this->layoutOverride;
		}
		if ( ! empty($this->layout)) {
			// Use an explicit name for this data so we don't override some other variable...
			$this->layoutVars[Lvc_Config::getLayoutContentVarName()] = $viewContents;
			$layoutView = Lvc_Config::getLayoutView($this->layout, $this->layoutVars);
			if (is_null($layoutView)) {
				throw new Lvc_Exception('Unable to load layout view "' . $this->layout . '" for controller "' . $this->controllerName . '"');
			} else {
				$layoutView->setController($this);
				$layoutView->output();
			}
		} else {
			echo($viewContents);
		}
		$this->hasLoadedView = true;
	}
	
	/**
	 * Redirect to the specified url. NOTE that this function does not stop
	 * execution.
	 * 
	 * @param string $url URL to redirect to.
	 * @return void
	 * @author Anthony Bush
	 **/
	protected function redirect($url) {
		header('Location: ' . $url);
	}
	
	/**
	 * Execute code before every action.
	 * Override this in sub classes
	 *
	 * @return void
	 * @author Anthony Bush
	 **/
	protected function beforeAction() {
		
	}
	
	/**
	 * Execute code after every action.
	 * Override this in sub classes
	 *
	 * @return void
	 * @author Anthony Bush
	 **/
	protected function afterAction() {
		
	}
	
	/**
	 * Use this inside a controller action to get the output from another
	 * controller's action. By default, the layout functionality will be
	 * disabled for this "sub" action.
	 * 
	 * Example Usage:
	 * 
	 *     $enrollmentVerifyBox = $this->requestAction('enrollment_verify', array(), 'eligibility');
	 * 
	 * @param string $actionName name of action to invoke.
	 * @param array $actionParams parameters to invoke the action with.
	 * @param string $controllerName optional controller name. Current controller will be used if not specified.
	 * @param array $controllerParams optional controller params. Current controller params will be passed on if not specified.
	 * @param string $layout optional layout to force for the sub action.
	 * @return string output from requested controller's action.
	 * @throws Lvc_Exception
	 * @author Anthony Bush
	 **/
	protected function requestAction($actionName, $actionParams = array(), $controllerName = null, $controllerParams = null, $layout = null) {
		if (empty($controllerName)) {
			$controllerName = $this->controllerName;
		}
		if (is_null($controllerParams)) {
			$controllerParams = $this->params;
		}
		$controller = Lvc_Config::getController($controllerName);
		if (is_null($controller)) {
			throw new Lvc_Exception('Unable to load controller "' . $controllerName . '"');
		}
		$controller->setControllerParams($controllerParams);
		$controller->setLayoutOverride($layout);
		return $controller->getActionOutput($actionName, $actionParams);
	}
	
	/**
	 * Get the controller name. Mostly used internally...
	 *
	 * @return string controller name
	 * @author Anthony Bush
	 **/
	public function getControllerName() {
		return $this->controllerName;
	}
	
	/**
	 * Get the controller params. Mostly used internally...
	 *
	 * @return array controller params
	 * @author Anthony Bush
	 **/
	public function getControllerParams() {
		return $this->params;
	}
}

/**
 * A View can be outputted or have its output returned (i.e. it's renderable).
 * It can not be executed.
 * 
 * $inc = new Lvc_View('foo.php', array());
 * $inc->output();
 * $output = $inc->getOutput();
 * 
 * @package lightvc
 * @author Anthony Bush
 * @since 2007-04-20
 **/
class Lvc_View {
	/**
	 * Full path to file name to be included.
	 *
	 * @var string
	 **/
	protected $fileName;
	
	/**
	 * Data to be exposed to the view template file.
	 *
	 * @var array
	 **/
	protected $data;
	
	/**
	 * A reference to the parent controller
	 *
	 * @var Lvc_Controller
	 **/
	protected $controller;
	
	/**
	 * Construct a view to be rendered.
	 *
	 * @param string $fileName Full path to file name of the view template file.
	 * @param array $data an array of [varName] => [value] pairs. Each varName will be made available to the view.
	 * @return void
	 * @author Anthony Bush
	 **/
	public function __construct($fileName, &$data) {
		$this->fileName = $fileName;
		$this->data = $data;
	}
	
	/**
	 * Output the view (aka render).
	 *
	 * @return void
	 * @author Anthony Bush
	 **/
	public function output() {
		extract($this->data, EXTR_SKIP);
		include($this->fileName);
	}
	
	/**
	 * Return the output of the view.
	 *
	 * @return string output of view
	 * @author Anthony Bush
	 **/
	public function getOutput() {
		ob_start();
		$this->output();
		return ob_get_clean();
	}
	
	/**
	 * Render a sub element from within a view.
	 * 
	 * Views are not allowed to have business logic, but they can call upon
	 * other generic, shared, views, called elements here.
	 * 
	 * @param string $elementName name of element to render
	 * @param array $data optional data to pass to the element.
	 * @return void
	 * @throws Lvc_Exception
	 * @author Anthony Bush
	 **/
	protected function renderElement($elementName, $data = array()) {
		$view = Lvc_Config::getElementView($elementName, $data);
		if (!is_null($view)) {
			$view->setController($this->controller);
			$view->output();
		} else {
			error_log('Unable to render element "' . $elementName . '"');
			// throw new Lvc_Exception('Unable to render element "' . $elementName . '"');
		}
	}
	
	/**
	 * Set the controller when constructing a view if you want {@link setLayoutVar()}
	 * to be callable from a view.
	 *
	 * @return void
	 * @author Anthony Bush
	 * @since 2007-05-17
	 **/
	public function setController($controller) {
		$this->controller = $controller;
	}
	
	/**
	 * Set a variable for the layout file.  You can set the page title from a static
	 * page's view file this way.
	 * 
	 * @param $varName variable name to make available in the view
	 * @param $value value of the variable.
	 * @return void
	 * @author Anthony Bush
	 * @since 2007-05-17
	 **/
	public function setLayoutVar($varName, $value) {
		$this->controller->setLayoutVar($varName, $value);
	}
}

?>
Return current item: LightVC