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