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: */
58: class 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(($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: