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