Files
yii/framework/web/CWebApplication.php
2009-02-25 21:45:42 +00:00

662 lines
20 KiB
PHP

<?php
/**
* CWebApplication class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2009 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* CWebApplication extends CApplication by providing functionalities specific to Web requests.
*
* CWebApplication manages the controllers in MVC pattern, and provides the following additional
* core application components:
* <ul>
* <li>{@link urlManager}: provides URL parsing and constructing functionality;</li>
* <li>{@link request}: encapsulates the Web request information;</li>
* <li>{@link session}: provides the session-related functionalities;</li>
* <li>{@link assetManager}: manages the publishing of private asset files.</li>
* <li>{@link user}: represents the user session information.</li>
* <li>{@link themeManager}: manages themes.</li>
* <li>{@link authManager}: manages role-based access control (RBAC).</li>
* <li>{@link clientScript}: manages client scripts (javascripts and CSS).</li>
* </ul>
*
* User requests are resolved as controller-action pairs and additional parameters.
* CWebApplication creates the requested controller instance and let it to handle
* the actual user request. If the user does not specify controller ID, it will
* assume {@link defaultController} is requested (which defaults to 'site').
*
* Controller class files must reside under the directory {@link getControllerPath controllerPath}
* (defaults to 'protected/controllers'). The file name is the same as the controller
* name and the class name is the controller ID appended with 'Controller'.
* For example, the controller 'article' is defined by the class 'ArticleController'
* which is in the file 'protected/controller/article.php'.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id$
* @package system.web
* @since 1.0
*/
class CWebApplication extends CApplication
{
/**
* @return string the ID of the default controller. Defaults to 'site'.
*/
public $defaultController='site';
/**
* @var mixed the application-wide layout. Defaults to 'main' (relative to {@link getLayoutPath layoutPath}).
* If this is false, then no layout will be used.
*/
public $layout='main';
/**
* @var array mapping from controller ID to controller configurations.
* Each name-value pair specifies the configuration for a single controller.
* A controller configuration can be either a string or an array.
* If the former, the string should be the class name or
* {@link YiiBase::getPathOfAlias class path alias} of the controller.
* If the latter, the array must contain a 'class' element which specifies
* the controller's class name or {@link YiiBase::getPathOfAlias class path alias}.
* The rest name-value pairs in the array are used to initialize
* the corresponding controller properties. For example,
* <pre>
* array(
* 'post'=>array(
* 'class'=>'path.to.PostController',
* 'pageTitle'=>'something new',
* ),
* 'user'=>'path.to.UserController',,
* )
* </pre>
*
* Note, when processing an incoming request, the controller map will first be
* checked to see if the request can be handled by one of the controllers in the map.
* If not, a controller will be searched for under the {@link getControllerPath default controller path}.
*/
public $controllerMap=array();
/**
* @var array the configuration specifying a controller which should handle
* all user requests. This is mainly used when the application is in maintenance mode
* and we should use a controller to handle all incoming requests.
* The configuration specifies the controller route (the first element)
* and GET parameters (the rest name-value pairs). For example,
* <pre>
* array(
* 'offline/notice',
* 'param1'=>'value1',
* 'param2'=>'value2',
* )
* </pre>
* Defaults to null, meaning catch-all is not effective.
*/
public $catchAllRequest;
private $_controllerPath;
private $_viewPath;
private $_systemViewPath;
private $_layoutPath;
private $_controller;
private $_homeUrl;
private $_theme;
private $_modulePath;
private $_moduleConfig=array();
private $_modules=array();
/**
* Processes the current request.
* It first resolves the request into controller and action,
* and then creates the controller to perform the action.
*/
public function processRequest()
{
if(is_array($this->catchAllRequest) && isset($this->catchAllRequest[0]))
{
$route=$this->catchAllRequest[0];
foreach(array_splice($this->catchAllRequest,1) as $name=>$value)
$_GET[$name]=$value;
}
else
$route=$this->getUrlManager()->parseUrl($this->getRequest());
$this->runController($route);
}
/**
* Registers the core application components.
* This method overrides the parent implementation by registering additional core components.
* @see setComponents
*/
protected function registerCoreComponents()
{
parent::registerCoreComponents();
$components=array(
'urlManager'=>array(
'class'=>'CUrlManager',
),
'request'=>array(
'class'=>'CHttpRequest',
),
'session'=>array(
'class'=>'CHttpSession',
),
'assetManager'=>array(
'class'=>'CAssetManager',
),
'user'=>array(
'class'=>'CWebUser',
),
'themeManager'=>array(
'class'=>'CThemeManager',
),
'authManager'=>array(
'class'=>'CPhpAuthManager',
),
'clientScript'=>array(
'class'=>'CClientScript',
),
);
$this->setComponents($components);
}
/**
* @return CHttpRequest the request component
*/
public function getRequest()
{
return $this->getComponent('request');
}
/**
* @return CUrlManager the URL manager component
*/
public function getUrlManager()
{
return $this->getComponent('urlManager');
}
/**
* @return IAuthManager the authorization manager component
*/
public function getAuthManager()
{
return $this->getComponent('authManager');
}
/**
* @return CAssetManager the asset manager component
*/
public function getAssetManager()
{
return $this->getComponent('assetManager');
}
/**
* @return CHttpSession the session component
*/
public function getSession()
{
return $this->getComponent('session');
}
/**
* @return CWebUser the user session information
*/
public function getUser()
{
return $this->getComponent('user');
}
/**
* Returns the view renderer.
* If this component is registered and enabled, the default
* view rendering logic defined in {@link CBaseController} will
* be replaced by this renderer.
* @return IViewRenderer the view renderer.
*/
public function getViewRenderer()
{
return $this->getComponent('viewRenderer');
}
/**
* Returns the client script manager.
* @return CClientScript the client script manager
*/
public function getClientScript()
{
return $this->getComponent('clientScript');
}
/**
* @return CThemeManager the theme manager.
*/
public function getThemeManager()
{
return $this->getComponent('themeManager');
}
/**
* @return CTheme the theme used currently. Null if no theme is being used.
*/
public function getTheme()
{
if(is_string($this->_theme))
$this->_theme=$this->getThemeManager()->getTheme($this->_theme);
return $this->_theme;
}
/**
* @param string the theme name
*/
public function setTheme($value)
{
$this->_theme=$value;
}
/**
* Creates a relative URL based on the given controller and action information.
* @param string the URL route. This should be in the format of 'ControllerID/ActionID'.
* @param array additional GET parameters (name=>value). Both the name and value will be URL-encoded.
* @param string the token separating name-value pairs in the URL.
* @return string the constructed URL
*/
public function createUrl($route,$params=array(),$ampersand='&')
{
return $this->getUrlManager()->createUrl($route,$params,$ampersand);
}
/**
* Creates an absolute URL based on the given controller and action information.
* @param string the URL route. This should be in the format of 'ControllerID/ActionID'.
* @param array additional GET parameters (name=>value). Both the name and value will be URL-encoded.
* @param string schema to use (e.g. http, https). If empty, the schema used for the current request will be used.
* @param string the token separating name-value pairs in the URL.
* @return string the constructed URL
*/
public function createAbsoluteUrl($route,$params=array(),$schema='',$ampersand='&')
{
return $this->getRequest()->getHostInfo($schema).$this->createUrl($route,$params,$ampersand);
}
/**
* Returns the relative URL for the application.
* This is a shortcut method to {@link CHttpRequest::getBaseUrl()}.
* @param boolean whether to return an absolute URL. Defaults to false, meaning returning a relative one.
* This parameter has been available since 1.0.2.
* @return string the relative URL for the application
* @see CHttpRequest::getBaseUrl()
*/
public function getBaseUrl($absolute=false)
{
return $this->getRequest()->getBaseUrl($absolute);
}
/**
* @return string the homepage URL
*/
public function getHomeUrl()
{
if($this->_homeUrl===null)
{
if($this->getUrlManager()->showScriptName)
return $this->getRequest()->getScriptUrl();
else
return $this->getRequest()->getBaseUrl().'/';
}
else
return $this->_homeUrl;
}
/**
* @param string the homepage URL
*/
public function setHomeUrl($value)
{
$this->_homeUrl=$value;
}
/**
* Creates the controller and performs the specified action.
* @param string the route of the current request. See {@link createController} for more details.
* @throws CHttpException if the controller could not be created.
*/
public function runController($route)
{
if(($ca=$this->createController($route))!==null)
{
list($controller,$actionID)=$ca;
$oldController=$this->_controller;
$this->_controller=$controller;
$controller->init();
$controller->run($actionID);
$this->_controller=$oldController;
}
else
throw new CHttpException(404,Yii::t('yii','Unable to resolve the request "{route}".',
array('{route}'=>$route===''?$this->defaultController:$route)));
}
/**
* Creates a controller instance based on a route.
* The route should contain the controller ID and the action ID.
* It may also contain additional GET variables. All these must be concatenated together with slashes.
*
* This method will attempt to create a controller in the following order:
* <ol>
* <li>If the first segment is found in {@link controllerMap}, the corresponding
* controller configuration will be used to create the controller;</li>
* <li>If the first segment is found to be a module ID, the corresponding module
* will be used to create the controller;</li>
* <li>Otherwise, it will search under the {@link controllerPath} to create
* the corresponding controller. For example, if the route is "admin/user/create",
* then the controller will be created using the class file "protected/controllers/admin/UserController.php".</li>
* </ol>
* @param string the route of the request.
* @param CWebModule the module that the new controller will belong to. Defaults to null, meaning the application
* instance is the owner.
* @return array the controller instance and the action ID. Null if the controller class does not exist or the route is invalid.
*/
public function createController($route,$owner=null)
{
if($owner===null)
$owner=$this;
if(($route=trim($route,'/'))==='')
$route=$owner->defaultController;
$caseSensitive=$this->getUrlManager()->caseSensitive;
$route.='/';
while(($pos=strpos($route,'/'))!==false)
{
$id=substr($route,0,$pos);
if(!preg_match('/^\w+$/',$id))
return null;
if(!$caseSensitive)
$id=strtolower($id);
$route=(string)substr($route,$pos+1);
if(!isset($basePath)) // first segment
{
if(isset($owner->controllerMap[$id]))
{
return array(
Yii::createComponent($owner->controllerMap[$id],$id),
$this->parseActionParams($route),
);
}
if(($module=$owner->getModule($id))!==null)
return $this->createController($route,$module);
$basePath=$owner->getControllerPath();
$controllerID=$id;
}
else
$controllerID.='/'.$id;
$className=ucfirst($id).'Controller';
$classFile=$basePath.DIRECTORY_SEPARATOR.$className.'.php';
if(is_file($classFile))
{
if(!class_exists($className,false))
require($classFile);
if(class_exists($className,false) && is_subclass_of($className,'CController'))
{
return array(
new $className($controllerID,$owner===$this?null:$owner),
$this->parseActionParams($route),
);
}
return null;
}
$basePath.=DIRECTORY_SEPARATOR.$id;
}
}
/**
* Parses a path info into an action ID and GET variables.
* @param string path info
* @return string action ID
* @since 1.0.3
*/
protected function parseActionParams($pathInfo)
{
if(($pos=strpos($pathInfo,'/'))!==false)
{
CUrlManager::parsePathInfo((string)substr($pathInfo,$pos+1));
$actionID=substr($pathInfo,0,$pos);
return $this->getUrlManager()->caseSensitive ? $actionID : strtolower($actionID);
}
else
return $pathInfo;
}
/**
* @return CController the currently active controller
*/
public function getController()
{
return $this->_controller;
}
/**
* @return string the directory that contains the controller classes. Defaults to 'protected/controllers'.
*/
public function getControllerPath()
{
if($this->_controllerPath!==null)
return $this->_controllerPath;
else
return $this->_controllerPath=$this->getBasePath().DIRECTORY_SEPARATOR.'controllers';
}
/**
* @param string the directory that contains the controller classes.
* @throws CException if the directory is invalid
*/
public function setControllerPath($value)
{
if(($this->_controllerPath=realpath($value))===false || !is_dir($this->_controllerPath))
throw new CException(Yii::t('yii','The controller path "{path}" is not a valid directory.',
array('{path}'=>$value)));
}
/**
* @return string the directory that contains the application modules. Defaults to 'protected/modules'.
* @since 1.0.3
*/
public function getModulePath()
{
if($this->_modulePath!==null)
return $this->_modulePath;
else
return $this->_modulePath=$this->getBasePath().DIRECTORY_SEPARATOR.'modules';
}
/**
* @param string the directory that contains the application modules.
* @throws CException if the directory is invalid
* @since 1.0.3
*/
public function setModulePath($value)
{
if(($this->_modulePath=realpath($value))===false || !is_dir($this->_modulePath))
throw new CException(Yii::t('yii','The module path "{path}" is not a valid directory.',
array('{path}'=>$value)));
}
/**
* @return string the root directory of view files. Defaults to 'protected/views'.
*/
public function getViewPath()
{
if($this->_viewPath!==null)
return $this->_viewPath;
else
return $this->_viewPath=$this->getBasePath().DIRECTORY_SEPARATOR.'views';
}
/**
* @param string the root directory of view files.
* @throws CException if the directory does not exist.
*/
public function setViewPath($path)
{
if(($this->_viewPath=realpath($path))===false || !is_dir($this->_viewPath))
throw new CException(Yii::t('yii','The view path "{path}" is not a valid directory.',
array('{path}'=>$path)));
}
/**
* @return string the root directory of system view files. Defaults to 'protected/views/system'.
*/
public function getSystemViewPath()
{
if($this->_systemViewPath!==null)
return $this->_systemViewPath;
else
return $this->_systemViewPath=$this->getViewPath().DIRECTORY_SEPARATOR.'system';
}
/**
* @param string the root directory of system view files.
* @throws CException if the directory does not exist.
*/
public function setSystemViewPath($path)
{
if(($this->_systemViewPath=realpath($path))===false || !is_dir($this->_systemViewPath))
throw new CException(Yii::t('yii','The system view path "{path}" is not a valid directory.',
array('{path}'=>$path)));
}
/**
* @return string the root directory of layout files. Defaults to 'protected/views/layouts'.
*/
public function getLayoutPath()
{
if($this->_layoutPath!==null)
return $this->_layoutPath;
else
return $this->_layoutPath=$this->getViewPath().DIRECTORY_SEPARATOR.'layouts';
}
/**
* @param string the root directory of layout files.
* @throws CException if the directory does not exist.
*/
public function setLayoutPath($path)
{
if(($this->_layoutPath=realpath($path))===false || !is_dir($this->_layoutPath))
throw new CException(Yii::t('yii','The layout path "{path}" is not a valid directory.',
array('{path}'=>$path)));
}
/**
* Retrieves the named application module.
* @param string application module ID (case-sensitive)
* @return CWebModule the application module instance, null if the application module is disabled or does not exist.
* @since 1.0.3
*/
public function getModule($id)
{
if(array_key_exists($id,$this->_modules))
return $this->_modules[$id];
else if(isset($this->_moduleConfig[$id]))
{
$config=$this->_moduleConfig[$id];
return $this->_modules[$id]=$this->createModule($id,$config);
}
}
/**
* @return array the configurations of the currently installed modules (id=>configuration)
* @since 1.0.3
*/
public function getModules()
{
return $this->_moduleConfig;
}
/**
* Configures the modules of this application.
*
* Call this method to declare modules and configure them with their initial property values.
* The parameter should be an array of module configurations. Each array element represents a single module,
* which can be either a string representing the module ID or an ID-config pair representing
* a module with the specified ID and the initial property values.
*
* For example, the following array declares two modules:
* <pre>
* array(
* 'admin',
* 'payment'=>array(
* 'server'=>'paymentserver.com',
* ),
* )
* </pre>
*
* By default, the module class is determined using the expression <code>ucfirst($moduleID).'Module'</code>.
* And the class file is located under <code>modules/$moduleID</code>.
* You may override this default by explicitly specifying the 'class' option in the configuration.
*
* You may also enable or disable a module by specifying the 'enabled' option in the configuration.
*
* @param array application module configuration.
* @since 1.0.3
*/
public function setModules($modules)
{
foreach($modules as $id=>$module)
{
if(is_int($id))
{
$id=$module;
$module=array();
}
if(!isset($module['class']))
$module['classFile']=$this->getModulePath().DIRECTORY_SEPARATOR.$id.DIRECTORY_SEPARATOR.ucfirst($id).'Module.php';
if(isset($this->_moduleConfig[$id]))
$this->_moduleConfig[$id]=CMap::mergeArray($this->_moduleConfig[$id],$module);
else
$this->_moduleConfig[$id]=$module;
}
}
/**
* Creates an application module based on the given configuration.
* The module created will be initialized with the given configuration.
* And its {@link CWebModule::init()} method will also be invoked.
* @param string the module ID
* @param array module configuration
* @param CWebModule the parent module. Null if no parent.
* @return CWebModule the create module instance. Null if the module is not enabled.
* @since 1.0.3
*/
public function createModule($id,$config,$owner=null)
{
if(!isset($config['enabled']) || $config['enabled'])
{
Yii::trace("Loading \"$id\" application module",'system.web.CWebApplication');
if(isset($config['classFile']))
{
$className=substr(basename($config['classFile']),0,-4);
if(!class_exists($className,false))
require($config['classFile']);
unset($config['classFile']);
}
else
$className=Yii::import($config['class'],true);
unset($config['enabled'],$config['classFile'],$config['class']);
if($owner===null)
$module=new $className($id);
else
$module=new $className($owner->getId().'/'.$id,$owner);
Yii::setPathOfAlias($className,$module->getBasePath());
$module->init($config);
return $module;
}
}
}