1<?php
2/**
3 * CWebApplication class file.
4 *
5 * @author Qiang Xue <qiang.xue@gmail.com>
6 * @link http://www.yiiframework.com/
7 * @copyright Copyright &copy; 2008-2010 Yii Software LLC
8 * @license http://www.yiiframework.com/license/
9 */
10
11/**
12 * CWebApplication extends CApplication by providing functionalities specific to Web requests.
13 *
14 * CWebApplication manages the controllers in MVC pattern, and provides the following additional
15 * core application components:
16 * <ul>
17 * <li>{@link urlManager}: provides URL parsing and constructing functionality;</li>
18 * <li>{@link request}: encapsulates the Web request information;</li>
19 * <li>{@link session}: provides the session-related functionalities;</li>
20 * <li>{@link assetManager}: manages the publishing of private asset files.</li>
21 * <li>{@link user}: represents the user session information.</li>
22 * <li>{@link themeManager}: manages themes.</li>
23 * <li>{@link authManager}: manages role-based access control (RBAC).</li>
24 * <li>{@link clientScript}: manages client scripts (javascripts and CSS).</li>
25 * <li>{@link widgetFactory}: creates widgets and supports widget skinning.</li>
26 * </ul>
27 *
28 * User requests are resolved as controller-action pairs and additional parameters.
29 * CWebApplication creates the requested controller instance and let it to handle
30 * the actual user request. If the user does not specify controller ID, it will
31 * assume {@link defaultController} is requested (which defaults to 'site').
32 *
33 * Controller class files must reside under the directory {@link getControllerPath controllerPath}
34 * (defaults to 'protected/controllers'). The file name and the class name must be
35 * the same as the controller ID with the first letter in upper case and appended with 'Controller'.
36 * For example, the controller 'article' is defined by the class 'ArticleController'
37 * which is in the file 'protected/controllers/ArticleController.php'.
38 *
39 * @author Qiang Xue <qiang.xue@gmail.com>
40 * @version $Id: CWebApplication.php 2172 2010-06-07 19:56:01Z qiang.xue $
41 * @package system.web
42 * @since 1.0
43 */
44class CWebApplication extends CApplication
45{
46	/**
47	 * @return string the ID of the default controller. Defaults to 'site'.
48	 */
49	public $defaultController='site';
50	/**
51	 * @var mixed the application-wide layout. Defaults to 'main' (relative to {@link getLayoutPath layoutPath}).
52	 * If this is false, then no layout will be used.
53	 */
54	public $layout='main';
55	/**
56	 * @var array mapping from controller ID to controller configurations.
57	 * Each name-value pair specifies the configuration for a single controller.
58	 * A controller configuration can be either a string or an array.
59	 * If the former, the string should be the class name or
60	 * {@link YiiBase::getPathOfAlias class path alias} of the controller.
61	 * If the latter, the array must contain a 'class' element which specifies
62	 * the controller's class name or {@link YiiBase::getPathOfAlias class path alias}.
63	 * The rest name-value pairs in the array are used to initialize
64	 * the corresponding controller properties. For example,
65	 * <pre>
66	 * array(
67	 *   'post'=>array(
68	 *      'class'=>'path.to.PostController',
69	 *      'pageTitle'=>'something new',
70	 *   ),
71	 *   'user'=>'path.to.UserController',,
72	 * )
73	 * </pre>
74	 *
75	 * Note, when processing an incoming request, the controller map will first be
76	 * checked to see if the request can be handled by one of the controllers in the map.
77	 * If not, a controller will be searched for under the {@link getControllerPath default controller path}.
78	 */
79	public $controllerMap=array();
80	/**
81	 * @var array the configuration specifying a controller which should handle
82	 * all user requests. This is mainly used when the application is in maintenance mode
83	 * and we should use a controller to handle all incoming requests.
84	 * The configuration specifies the controller route (the first element)
85	 * and GET parameters (the rest name-value pairs). For example,
86	 * <pre>
87	 * array(
88	 *     'offline/notice',
89	 *     'param1'=>'value1',
90	 *     'param2'=>'value2',
91	 * )
92	 * </pre>
93	 * Defaults to null, meaning catch-all is not effective.
94	 */
95	public $catchAllRequest;
96
97	private $_controllerPath;
98	private $_viewPath;
99	private $_systemViewPath;
100	private $_layoutPath;
101	private $_controller;
102	private $_homeUrl;
103	private $_theme;
104
105
106	/**
107	 * Processes the current request.
108	 * It first resolves the request into controller and action,
109	 * and then creates the controller to perform the action.
110	 */
111	public function processRequest()
112	{
113		if(is_array($this->catchAllRequest) && isset($this->catchAllRequest[0]))
114		{
115			$route=$this->catchAllRequest[0];
116			foreach(array_splice($this->catchAllRequest,1) as $name=>$value)
117				$_GET[$name]=$value;
118		}
119		else
120			$route=$this->getUrlManager()->parseUrl($this->getRequest());
121		$this->runController($route);
122	}
123
124	/**
125	 * Registers the core application components.
126	 * This method overrides the parent implementation by registering additional core components.
127	 * @see setComponents
128	 */
129	protected function registerCoreComponents()
130	{
131		parent::registerCoreComponents();
132
133		$components=array(
134			'session'=>array(
135				'class'=>'CHttpSession',
136			),
137			'assetManager'=>array(
138				'class'=>'CAssetManager',
139			),
140			'user'=>array(
141				'class'=>'CWebUser',
142			),
143			'themeManager'=>array(
144				'class'=>'CThemeManager',
145			),
146			'authManager'=>array(
147				'class'=>'CPhpAuthManager',
148			),
149			'clientScript'=>array(
150				'class'=>'CClientScript',
151			),
152			'widgetFactory'=>array(
153				'class'=>'CWidgetFactory',
154			),
155		);
156
157		$this->setComponents($components);
158	}
159
160	/**
161	 * @return IAuthManager the authorization manager component
162	 */
163	public function getAuthManager()
164	{
165		return $this->getComponent('authManager');
166	}
167
168	/**
169	 * @return CAssetManager the asset manager component
170	 */
171	public function getAssetManager()
172	{
173		return $this->getComponent('assetManager');
174	}
175
176	/**
177	 * @return CHttpSession the session component
178	 */
179	public function getSession()
180	{
181		return $this->getComponent('session');
182	}
183
184	/**
185	 * @return CWebUser the user session information
186	 */
187	public function getUser()
188	{
189		return $this->getComponent('user');
190	}
191
192	/**
193	 * Returns the view renderer.
194	 * If this component is registered and enabled, the default
195	 * view rendering logic defined in {@link CBaseController} will
196	 * be replaced by this renderer.
197	 * @return IViewRenderer the view renderer.
198	 */
199	public function getViewRenderer()
200	{
201		return $this->getComponent('viewRenderer');
202	}
203
204	/**
205	 * Returns the client script manager.
206	 * @return CClientScript the client script manager
207	 */
208	public function getClientScript()
209	{
210		return $this->getComponent('clientScript');
211	}
212
213	/**
214	 * Returns the widget factory.
215	 * @return IWidgetFactory the widget factory
216	 * @since 1.1
217	 */
218	public function getWidgetFactory()
219	{
220		return $this->getComponent('widgetFactory');
221	}
222
223	/**
224	 * @return CThemeManager the theme manager.
225	 */
226	public function getThemeManager()
227	{
228		return $this->getComponent('themeManager');
229	}
230
231	/**
232	 * @return CTheme the theme used currently. Null if no theme is being used.
233	 */
234	public function getTheme()
235	{
236		if(is_string($this->_theme))
237			$this->_theme=$this->getThemeManager()->getTheme($this->_theme);
238		return $this->_theme;
239	}
240
241	/**
242	 * @param string the theme name
243	 */
244	public function setTheme($value)
245	{
246		$this->_theme=$value;
247	}
248
249	/**
250	 * Creates a relative URL based on the given controller and action information.
251	 * @param string the URL route. This should be in the format of 'ControllerID/ActionID'.
252	 * @param array additional GET parameters (name=>value). Both the name and value will be URL-encoded.
253	 * @param string the token separating name-value pairs in the URL.
254	 * @return string the constructed URL
255	 */
256	public function createUrl($route,$params=array(),$ampersand='&')
257	{
258		return $this->getUrlManager()->createUrl($route,$params,$ampersand);
259	}
260
261	/**
262	 * Creates an absolute URL based on the given controller and action information.
263	 * @param string the URL route. This should be in the format of 'ControllerID/ActionID'.
264	 * @param array additional GET parameters (name=>value). Both the name and value will be URL-encoded.
265	 * @param string schema to use (e.g. http, https). If empty, the schema used for the current request will be used.
266	 * @param string the token separating name-value pairs in the URL.
267	 * @return string the constructed URL
268	 */
269	public function createAbsoluteUrl($route,$params=array(),$schema='',$ampersand='&')
270	{
271		return $this->getRequest()->getHostInfo($schema).$this->createUrl($route,$params,$ampersand);
272	}
273
274	/**
275	 * Returns the relative URL for the application.
276	 * This is a shortcut method to {@link CHttpRequest::getBaseUrl()}.
277	 * @param boolean whether to return an absolute URL. Defaults to false, meaning returning a relative one.
278	 * This parameter has been available since 1.0.2.
279	 * @return string the relative URL for the application
280	 * @see CHttpRequest::getBaseUrl()
281	 */
282	public function getBaseUrl($absolute=false)
283	{
284		return $this->getRequest()->getBaseUrl($absolute);
285	}
286
287	/**
288	 * @return string the homepage URL
289	 */
290	public function getHomeUrl()
291	{
292		if($this->_homeUrl===null)
293		{
294			if($this->getUrlManager()->showScriptName)
295				return $this->getRequest()->getScriptUrl();
296			else
297				return $this->getRequest()->getBaseUrl().'/';
298		}
299		else
300			return $this->_homeUrl;
301	}
302
303	/**
304	 * @param string the homepage URL
305	 */
306	public function setHomeUrl($value)
307	{
308		$this->_homeUrl=$value;
309	}
310
311	/**
312	 * Creates the controller and performs the specified action.
313	 * @param string the route of the current request. See {@link createController} for more details.
314	 * @throws CHttpException if the controller could not be created.
315	 */
316	public function runController($route)
317	{
318		if(($ca=$this->createController($route))!==null)
319		{
320			list($controller,$actionID)=$ca;
321			$oldController=$this->_controller;
322			$this->_controller=$controller;
323			$controller->init();
324			$controller->run($actionID);
325			$this->_controller=$oldController;
326		}
327		else
328			throw new CHttpException(404,Yii::t('yii','Unable to resolve the request "{route}".',
329				array('{route}'=>$route===''?$this->defaultController:$route)));
330	}
331
332	/**
333	 * Creates a controller instance based on a route.
334	 * The route should contain the controller ID and the action ID.
335	 * It may also contain additional GET variables. All these must be concatenated together with slashes.
336	 *
337	 * This method will attempt to create a controller in the following order:
338	 * <ol>
339	 * <li>If the first segment is found in {@link controllerMap}, the corresponding
340	 * controller configuration will be used to create the controller;</li>
341	 * <li>If the first segment is found to be a module ID, the corresponding module
342	 * will be used to create the controller;</li>
343	 * <li>Otherwise, it will search under the {@link controllerPath} to create
344	 * the corresponding controller. For example, if the route is "admin/user/create",
345	 * then the controller will be created using the class file "protected/controllers/admin/UserController.php".</li>
346	 * </ol>
347	 * @param string the route of the request.
348	 * @param CWebModule the module that the new controller will belong to. Defaults to null, meaning the application
349	 * instance is the owner.
350	 * @return array the controller instance and the action ID. Null if the controller class does not exist or the route is invalid.
351	 */
352	public function createController($route,$owner=null)
353	{
354		if($owner===null)
355			$owner=$this;
356		if(($route=trim($route,'/'))==='')
357			$route=$owner->defaultController;
358		$caseSensitive=$this->getUrlManager()->caseSensitive;
359
360		$route.='/';
361		while(($pos=strpos($route,'/'))!==false)
362		{
363			$id=substr($route,0,$pos);
364			if(!preg_match('/^\w+$/',$id))
365				return null;
366			if(!$caseSensitive)
367				$id=strtolower($id);
368			$route=(string)substr($route,$pos+1);
369			if(!isset($basePath))  // first segment
370			{
371				if(isset($owner->controllerMap[$id]))
372				{
373					return array(
374						Yii::createComponent($owner->controllerMap[$id],$id,$owner===$this?null:$owner),
375						$this->parseActionParams($route),
376					);
377				}
378
379				if(($module=$owner->getModule($id))!==null)
380					return $this->createController($route,$module);
381
382				$basePath=$owner->getControllerPath();
383				$controllerID='';
384			}
385			else
386				$controllerID.='/';
387			$className=ucfirst($id).'Controller';
388			$classFile=$basePath.DIRECTORY_SEPARATOR.$className.'.php';
389			if(is_file($classFile))
390			{
391				if(!class_exists($className,false))
392					require($classFile);
393				if(class_exists($className,false) && is_subclass_of($className,'CController'))
394				{
395					$id[0]=strtolower($id[0]);
396					return array(
397						new $className($controllerID.$id,$owner===$this?null:$owner),
398						$this->parseActionParams($route),
399					);
400				}
401				return null;
402			}
403			$controllerID.=$id;
404			$basePath.=DIRECTORY_SEPARATOR.$id;
405		}
406	}
407
408	/**
409	 * Parses a path info into an action ID and GET variables.
410	 * @param string path info
411	 * @return string action ID
412	 * @since 1.0.3
413	 */
414	protected function parseActionParams($pathInfo)
415	{
416		if(($pos=strpos($pathInfo,'/'))!==false)
417		{
418			$manager=$this->getUrlManager();
419			$manager->parsePathInfo((string)substr($pathInfo,$pos+1));
420			$actionID=substr($pathInfo,0,$pos);
421			return $manager->caseSensitive ? $actionID : strtolower($actionID);
422		}
423		else
424			return $pathInfo;
425	}
426
427	/**
428	 * @return CController the currently active controller
429	 */
430	public function getController()
431	{
432		return $this->_controller;
433	}
434
435	/**
436	 * @param CController the currently active controller
437	 * @since 1.0.6
438	 */
439	public function setController($value)
440	{
441		$this->_controller=$value;
442	}
443
444	/**
445	 * @return string the directory that contains the controller classes. Defaults to 'protected/controllers'.
446	 */
447	public function getControllerPath()
448	{
449		if($this->_controllerPath!==null)
450			return $this->_controllerPath;
451		else
452			return $this->_controllerPath=$this->getBasePath().DIRECTORY_SEPARATOR.'controllers';
453	}
454
455	/**
456	 * @param string the directory that contains the controller classes.
457	 * @throws CException if the directory is invalid
458	 */
459	public function setControllerPath($value)
460	{
461		if(($this->_controllerPath=realpath($value))===false || !is_dir($this->_controllerPath))
462			throw new CException(Yii::t('yii','The controller path "{path}" is not a valid directory.',
463				array('{path}'=>$value)));
464	}
465
466	/**
467	 * @return string the root directory of view files. Defaults to 'protected/views'.
468	 */
469	public function getViewPath()
470	{
471		if($this->_viewPath!==null)
472			return $this->_viewPath;
473		else
474			return $this->_viewPath=$this->getBasePath().DIRECTORY_SEPARATOR.'views';
475	}
476
477	/**
478	 * @param string the root directory of view files.
479	 * @throws CException if the directory does not exist.
480	 */
481	public function setViewPath($path)
482	{
483		if(($this->_viewPath=realpath($path))===false || !is_dir($this->_viewPath))
484			throw new CException(Yii::t('yii','The view path "{path}" is not a valid directory.',
485				array('{path}'=>$path)));
486	}
487
488	/**
489	 * @return string the root directory of system view files. Defaults to 'protected/views/system'.
490	 */
491	public function getSystemViewPath()
492	{
493		if($this->_systemViewPath!==null)
494			return $this->_systemViewPath;
495		else
496			return $this->_systemViewPath=$this->getViewPath().DIRECTORY_SEPARATOR.'system';
497	}
498
499	/**
500	 * @param string the root directory of system view files.
501	 * @throws CException if the directory does not exist.
502	 */
503	public function setSystemViewPath($path)
504	{
505		if(($this->_systemViewPath=realpath($path))===false || !is_dir($this->_systemViewPath))
506			throw new CException(Yii::t('yii','The system view path "{path}" is not a valid directory.',
507				array('{path}'=>$path)));
508	}
509
510	/**
511	 * @return string the root directory of layout files. Defaults to 'protected/views/layouts'.
512	 */
513	public function getLayoutPath()
514	{
515		if($this->_layoutPath!==null)
516			return $this->_layoutPath;
517		else
518			return $this->_layoutPath=$this->getViewPath().DIRECTORY_SEPARATOR.'layouts';
519	}
520
521	/**
522	 * @param string the root directory of layout files.
523	 * @throws CException if the directory does not exist.
524	 */
525	public function setLayoutPath($path)
526	{
527		if(($this->_layoutPath=realpath($path))===false || !is_dir($this->_layoutPath))
528			throw new CException(Yii::t('yii','The layout path "{path}" is not a valid directory.',
529				array('{path}'=>$path)));
530	}
531
532	/**
533	 * The pre-filter for controller actions.
534	 * This method is invoked before the currently requested controller action and all its filters
535	 * are executed. You may override this method with logic that needs to be done
536	 * before all controller actions.
537	 * @param CController the controller
538	 * @param CAction the action
539	 * @return boolean whether the action should be executed.
540	 * @since 1.0.4
541	 */
542	public function beforeControllerAction($controller,$action)
543	{
544		return true;
545	}
546
547	/**
548	 * The post-filter for controller actions.
549	 * This method is invoked after the currently requested controller action and all its filters
550	 * are executed. You may override this method with logic that needs to be done
551	 * after all controller actions.
552	 * @param CController the controller
553	 * @param CAction the action
554	 * @since 1.0.4
555	 */
556	public function afterControllerAction($controller,$action)
557	{
558	}
559
560	/**
561	 * Searches for a module by its ID.
562	 * This method is used internally. Do not call this method.
563	 * @param string module ID
564	 * @return CWebModule the module that has the specified ID. Null if no module is found.
565	 * @since 1.0.3
566	 */
567	public function findModule($id)
568	{
569		if(($controller=$this->getController())!==null && ($module=$controller->getModule())!==null)
570		{
571			do
572			{
573				if(($m=$module->getModule($id))!==null)
574					return $m;
575			} while(($module=$module->getParentModule())!==null);
576		}
577		if(($m=$this->getModule($id))!==null)
578			return $m;
579	}
580
581	/**
582	 * Initializes the application.
583	 * This method overrides the parent implementation by preloading the 'request' component.
584	 */
585	protected function init()
586	{
587		parent::init();
588		// preload 'request' so that it has chance to respond to onBeginRequest event.
589		$this->getRequest();
590	}
591}
592