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 © 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