Blame | Letzte Änderung | Log anzeigen | RSS feed
<?php/*** TPageService class file.** @author Qiang Xue <qiang.xue@gmail.com>* @link http://www.pradosoft.com/* @copyright Copyright © 2005-2008 PradoSoft* @license http://www.pradosoft.com/license/* @version $Id: TPageService.php 2597 2009-01-06 14:38:07Z christophe.boulain $* @package System.Web.Services*//*** Include classes to be used by page service*/Prado::using('System.Web.UI.TPage');Prado::using('System.Web.UI.TTemplateManager');Prado::using('System.Web.UI.TThemeManager');/*** TPageService class.** TPageService implements the service for serving user page requests.** Pages that are available to client users are stored under a directory specified by* {@link setBasePath BasePath}. The directory may contain subdirectories.* Pages serving for a similar goal are usually placed under the same directory.* A directory may contain a configuration file <b>config.xml</b> whose content* is similar to that of application configuration file.** A page is requested via page path, which is a dot-connected directory names* appended by the page name. Assume '<BasePath>/Users/Admin' is the directory* containing the page 'Update'. Then the page can be requested via 'Users.Admin.Update'.* By default, the {@link setBasePath BasePath} of the page service is the "pages"* directory under the application base path. You may change this default* by setting {@link setBasePath BasePath} with a different path you prefer.** Page name refers to the file name (without extension) of the page template.* In order to differentiate from the common control template files, the extension* name of the page template files must be '.page'. If there is a PHP file with* the same page name under the same directory as the template file, that file* will be considered as the page class file and the file name is the page class name.* If such a file is not found, the page class is assumed as {@link TPage}.** Modules can be configured and loaded in page directory configurations.* Configuration of a module in a subdirectory will overwrite its parent* directory's configuration, if both configurations refer to the same module.** By default, TPageService will automatically load two modules:* - {@link TTemplateManager} : manages page and control templates* - {@link TThemeManager} : manages themes used in a Prado application** In page directory configurations, static authorization rules can also be specified,* which governs who and which roles can access particular pages.* Refer to {@link TAuthorizationRule} for more details about authorization rules.* Page authorization rules can be configured within an <authorization> tag in* each page directory configuration as follows,* <authorization>* <deny pages="Update" users="?" />* <allow pages="Admin" roles="administrator" />* <deny pages="Admin" users="*" />* </authorization>* where the 'pages' attribute may be filled with a sequence of comma-separated* page IDs. If 'pages' attribute does not appear in a rule, the rule will be* applied to all pages in this directory and all subdirectories (recursively).* Application of authorization rules are in a bottom-up fashion, starting from* the directory containing the requested page up to all parent directories.* The first matching rule will be used. The last rule always allows all users* accessing to any resources.** @author Qiang Xue <qiang.xue@gmail.com>* @version $Id: TPageService.php 2597 2009-01-06 14:38:07Z christophe.boulain $* @package System.Web.Services* @since 3.0*/class TPageService extends TService{/*** Configuration file name*/const CONFIG_FILE='config.xml';/*** Default base path*/const DEFAULT_BASEPATH='pages';/*** Prefix of ID used for storing parsed configuration in cache*/const CONFIG_CACHE_PREFIX='prado:pageservice:';/*** Page template file extension*/const PAGE_FILE_EXT='.page';/*** @var string root path of pages*/private $_basePath=null;/*** @var string base path class in namespace format*/private $_basePageClass='TPage';/*** @var string default page*/private $_defaultPage='Home';/*** @var string requested page (path)*/private $_pagePath=null;/*** @var TPage the requested page*/private $_page=null;/*** @var array list of initial page property values*/private $_properties=array();/*** @var boolean whether service is initialized*/private $_initialized=false;/*** @var TThemeManager theme manager*/private $_themeManager=null;/*** @var TTemplateManager template manager*/private $_templateManager=null;/*** Initializes the service.* This method is required by IService interface and is invoked by application.* @param TXmlElement service configuration*/public function init($config){Prado::trace("Initializing TPageService",'System.Web.Services.TPageService');$pageConfig=$this->loadPageConfig($config);$this->initPageContext($pageConfig);$this->_initialized=true;}/*** Initializes page context.* Page context includes path alias settings, namespace usages,* parameter initialization, module loadings, page initial properties* and authorization rules.* @param TPageConfiguration*/protected function initPageContext($pageConfig){$application=$this->getApplication();foreach($pageConfig->getApplicationConfigurations() as $appConfig)$application->applyConfiguration($appConfig);$this->applyConfiguration($pageConfig);}/*** Applies a page configuration.* @param TPageConfiguration the configuration*/protected function applyConfiguration($config){// initial page properties (to be set when page runs)$this->_properties=array_merge($this->_properties, $config->getProperties());$this->getApplication()->getAuthorizationRules()->mergeWith($config->getRules());$pagePath=$this->getRequestedPagePath();// external configurationsforeach($config->getExternalConfigurations() as $filePath=>$params){list($configPagePath,$condition)=$params;if($condition!==true)$condition=$this->evaluateExpression($condition);if($condition){if(($path=Prado::getPathOfNamespace($filePath,TApplication::CONFIG_FILE_EXT))===null || !is_file($path))throw new TConfigurationException('pageservice_includefile_invalid',$filePath);$c=new TPageConfiguration($pagePath);$c->loadFromFile($path,$configPagePath);$this->applyConfiguration($c);}}}/*** Determines the requested page path.* @return string page path requested*/protected function determineRequestedPagePath(){$pagePath=$this->getRequest()->getServiceParameter();if(empty($pagePath))$pagePath=$this->getDefaultPage();return $pagePath;}/*** Collects configuration for a page.* @param TXmlElement additional configuration specified in the application configuration* @return TPageConfiguration*/protected function loadPageConfig($config){$application=$this->getApplication();$pagePath=$this->getRequestedPagePath();if(($cache=$application->getCache())===null){$pageConfig=new TPageConfiguration($pagePath);if($config!==null)$pageConfig->loadPageConfigurationFromXml($config,$application->getBasePath(),'');$pageConfig->loadFromFiles($this->getBasePath());}else{$configCached=true;$currentTimestamp=array();$arr=$cache->get(self::CONFIG_CACHE_PREFIX.$this->getID().$pagePath);if(is_array($arr)){list($pageConfig,$timestamps)=$arr;if($application->getMode()!==TApplicationMode::Performance){foreach($timestamps as $fileName=>$timestamp){if($fileName===0) // application config file{$appConfigFile=$application->getConfigurationFile();$currentTimestamp[0]=$appConfigFile===null?0:@filemtime($appConfigFile);if($currentTimestamp[0]>$timestamp || ($timestamp>0 && !$currentTimestamp[0]))$configCached=false;}else{$currentTimestamp[$fileName]=@filemtime($fileName);if($currentTimestamp[$fileName]>$timestamp || ($timestamp>0 && !$currentTimestamp[$fileName]))$configCached=false;}}}}else{$configCached=false;$paths=explode('.',$pagePath);$configPath=$this->getBasePath();foreach($paths as $path){$configFile=$configPath.DIRECTORY_SEPARATOR.self::CONFIG_FILE;$currentTimestamp[$configFile]=@filemtime($configFile);$configPath.=DIRECTORY_SEPARATOR.$path;}$appConfigFile=$application->getConfigurationFile();$currentTimestamp[0]=$appConfigFile===null?0:@filemtime($appConfigFile);}if(!$configCached){$pageConfig=new TPageConfiguration($pagePath);if($config!==null)$pageConfig->loadPageConfigurationFromXml($config,$application->getBasePath(),'');$pageConfig->loadFromFiles($this->getBasePath());$cache->set(self::CONFIG_CACHE_PREFIX.$this->getID().$pagePath,array($pageConfig,$currentTimestamp));}}return $pageConfig;}/*** @return TTemplateManager template manager*/public function getTemplateManager(){if(!$this->_templateManager){$this->_templateManager=new TTemplateManager;$this->_templateManager->init(null);}return $this->_templateManager;}/*** @param TTemplateManager template manager*/public function setTemplateManager(TTemplateManager $value){$this->_templateManager=$value;}/*** @return TThemeManager theme manager*/public function getThemeManager(){if(!$this->_themeManager){$this->_themeManager=new TThemeManager;$this->_themeManager->init(null);}return $this->_themeManager;}/*** @param TThemeManager theme manager*/public function setThemeManager(TThemeManager $value){$this->_themeManager=$value;}/*** @return string the requested page path*/public function getRequestedPagePath(){if($this->_pagePath===null){$this->_pagePath=strtr($this->determineRequestedPagePath(),'/\\','..');if(empty($this->_pagePath))throw new THttpException(404,'pageservice_page_required');}return $this->_pagePath;}/*** @return TPage the requested page*/public function getRequestedPage(){return $this->_page;}/*** @return string default page path to be served if no explicit page is request. Defaults to 'Home'.*/public function getDefaultPage(){return $this->_defaultPage;}/*** @param string default page path to be served if no explicit page is request* @throws TInvalidOperationException if the page service is initialized*/public function setDefaultPage($value){if($this->_initialized)throw new TInvalidOperationException('pageservice_defaultpage_unchangeable');else$this->_defaultPage=$value;}/*** @return string the URL for the default page*/public function getDefaultPageUrl(){return $this->constructUrl($this->getDefaultPage());}/*** @return string the root directory for storing pages. Defaults to the 'pages' directory under the application base path.*/public function getBasePath(){if($this->_basePath===null){$basePath=$this->getApplication()->getBasePath().DIRECTORY_SEPARATOR.self::DEFAULT_BASEPATH;if(($this->_basePath=realpath($basePath))===false || !is_dir($this->_basePath))throw new TConfigurationException('pageservice_basepath_invalid',$basePath);}return $this->_basePath;}/*** @param string root directory (in namespace form) storing pages* @throws TInvalidOperationException if the service is initialized already or basepath is invalid*/public function setBasePath($value){if($this->_initialized)throw new TInvalidOperationException('pageservice_basepath_unchangeable');else if(($path=Prado::getPathOfNamespace($value))===null || !is_dir($path))throw new TConfigurationException('pageservice_basepath_invalid',$value);$this->_basePath=realpath($path);}/*** Sets the base page class name (in namespace format).* If a page only has a template file without page class file,* this base page class will be instantiated.* @param string class name*/public function setBasePageClass($value){$this->_basePageClass=$value;}/*** @return string base page class name in namespace format. Defaults to 'TPage'.*/public function getBasePageClass(){return $this->_basePageClass;}/*** Runs the service.* This will create the requested page, initializes it with the property values* specified in the configuration, and executes the page.*/public function run(){Prado::trace("Running page service",'System.Web.Services.TPageService');$this->_page=$this->createPage($this->getRequestedPagePath());$this->runPage($this->_page,$this->_properties);}/*** Creates a page instance based on requested page path.* @param string requested page path* @return TPage the requested page instance* @throws THttpException if requested page path is invalid* @throws TConfigurationException if the page class cannot be found*/protected function createPage($pagePath){$path=$this->getBasePath().DIRECTORY_SEPARATOR.strtr($pagePath,'.',DIRECTORY_SEPARATOR);$hasTemplateFile=is_file($path.self::PAGE_FILE_EXT);$hasClassFile=is_file($path.Prado::CLASS_FILE_EXT);if(!$hasTemplateFile && !$hasClassFile)throw new THttpException(404,'pageservice_page_unknown',$pagePath);if($hasClassFile){$className=basename($path);if(!class_exists($className,false))include_once($path.Prado::CLASS_FILE_EXT);}else{$className=$this->getBasePageClass();Prado::using($className);if(($pos=strrpos($className,'.'))!==false)$className=substr($className,$pos+1);}if(!class_exists($className,false) || ($className!=='TPage' && !is_subclass_of($className,'TPage')))throw new THttpException(404,'pageservice_page_unknown',$pagePath);$page=new $className;$page->setPagePath($pagePath);if($hasTemplateFile)$page->setTemplate($this->getTemplateManager()->getTemplateByFileName($path.self::PAGE_FILE_EXT));return $page;}/*** Executes a page.* @param TPage the page instance to be run* @param array list of initial page properties*/protected function runPage($page,$properties){foreach($properties as $name=>$value)$page->setSubProperty($name,$value);$page->run($this->getResponse()->createHtmlWriter());}/*** Constructs a URL with specified page path and GET parameters.* @param string page path* @param array list of GET parameters, null if no GET parameters required* @param boolean whether to encode the ampersand in URL, defaults to true.* @param boolean whether to encode the GET parameters (their names and values), defaults to true.* @return string URL for the page and GET parameters*/public function constructUrl($pagePath,$getParams=null,$encodeAmpersand=true,$encodeGetItems=true){return $this->getRequest()->constructUrl($this->getID(),$pagePath,$getParams,$encodeAmpersand,$encodeGetItems);}}/*** TPageConfiguration class** TPageConfiguration represents the configuration for a page.* The page is specified by a dot-connected path.* Configurations along this path are merged together to be provided for the page.** @author Qiang Xue <qiang.xue@gmail.com>* @version $Id: TPageService.php 2597 2009-01-06 14:38:07Z christophe.boulain $* @package System.Web.Services* @since 3.0*/class TPageConfiguration extends TComponent{/*** @var array list of application configurations*/private $_appConfigs=array();/*** @var array list of page initial property values*/private $_properties=array();/*** @var TAuthorizationRuleCollection list of authorization rules*/private $_rules=array();/*** @var array list of included configurations*/private $_includes=array();/*** @var string the currently request page in the format of Path.To.PageName*/private $_pagePath='';/*** Constructor.* @param string the currently request page in the format of Path.To.PageName*/public function __construct($pagePath){$this->_pagePath=$pagePath;}/*** @return array list of external configuration files. Each element is like $filePath=>$condition*/public function getExternalConfigurations(){return $this->_includes;}/*** Returns list of page initial property values.* Each array element represents a single property with the key* being the property name and the value the initial property value.* @return array list of page initial property values*/public function getProperties(){return $this->_properties;}/*** Returns list of authorization rules.* The authorization rules are aggregated (bottom-up) from configuration files* along the path to the specified page.* @return TAuthorizationRuleCollection collection of authorization rules*/public function getRules(){return $this->_rules;}/*** @return array list of application configurations specified along page path*/public function getApplicationConfigurations(){return $this->_appConfigs;}/*** Loads configuration for a page specified in a path format.* @param string root path for pages*/public function loadFromFiles($basePath){$paths=explode('.',$this->_pagePath);$page=array_pop($paths);$path=$basePath;$configPagePath='';foreach($paths as $p){$this->loadFromFile($path.DIRECTORY_SEPARATOR.TPageService::CONFIG_FILE,$configPagePath);$path.=DIRECTORY_SEPARATOR.$p;if($configPagePath==='')$configPagePath=$p;else$configPagePath.='.'.$p;}$this->loadFromFile($path.DIRECTORY_SEPARATOR.TPageService::CONFIG_FILE,$configPagePath);$this->_rules=new TAuthorizationRuleCollection($this->_rules);}/*** Loads a specific config file.* @param string config file name* @param string the page path that the config file is associated with. The page path doesn't include the page name.*/public function loadFromFile($fname,$configPagePath){Prado::trace("Loading page configuration file $fname",'System.Web.Services.TPageService');if(empty($fname) || !is_file($fname))return;$dom=new TXmlDocument;if($dom->loadFromFile($fname))$this->loadFromXml($dom,dirname($fname),$configPagePath);elsethrow new TConfigurationException('pageserviceconf_file_invalid',$fname);}/*** Loads a page configuration.* The configuration includes information for both application* and page service.* @param TXmlElement config xml element* @param string the directory containing this configuration* @param string the page path that the config XML is associated with. The page path doesn't include the page name.*/public function loadFromXml($dom,$configPath,$configPagePath){$this->loadApplicationConfigurationFromXml($dom,$configPath);$this->loadPageConfigurationFromXml($dom,$configPath,$configPagePath);}/*** Loads the configuration specific for application part* @param TXmlElement config xml element* @param string base path corresponding to this xml element*/public function loadApplicationConfigurationFromXml($dom,$configPath){$appConfig=new TApplicationConfiguration;$appConfig->loadFromXml($dom,$configPath);$this->_appConfigs[]=$appConfig;}/*** Loads the configuration specific for page service.* @param TXmlElement config xml element* @param string base path corresponding to this xml element* @param string the page path that the config XML is associated with. The page path doesn't include the page name.*/public function loadPageConfigurationFromXml($dom,$configPath,$configPagePath){// authorizationif(($authorizationNode=$dom->getElementByTagName('authorization'))!==null){$rules=array();foreach($authorizationNode->getElements() as $node){$patterns=$node->getAttribute('pages');$ruleApplies=false;if(empty($patterns) || trim($patterns)==='*') // null or empty string$ruleApplies=true;else{foreach(explode(',',$patterns) as $pattern){if(($pattern=trim($pattern))!==''){// we know $configPagePath and $this->_pagePathif($configPagePath!=='') // prepend the pattern with ConfigPagePath$pattern=$configPagePath.'.'.$pattern;if(strcasecmp($pattern,$this->_pagePath)===0){$ruleApplies=true;break;}if($pattern[strlen($pattern)-1]==='*') // try wildcard matching{if(strncasecmp($this->_pagePath,$pattern,strlen($pattern)-1)===0){$ruleApplies=true;break;}}}}}if($ruleApplies)$rules[]=new TAuthorizationRule($node->getTagName(),$node->getAttribute('users'),$node->getAttribute('roles'),$node->getAttribute('verb'),$node->getAttribute('ips'));}$this->_rules=array_merge($rules,$this->_rules);}// pagesif(($pagesNode=$dom->getElementByTagName('pages'))!==null){$this->_properties=array_merge($this->_properties,$pagesNode->getAttributes()->toArray());// at the page folderforeach($pagesNode->getElementsByTagName('page') as $node){$properties=$node->getAttributes();$id=$properties->remove('id');if(empty($id))throw new TConfigurationException('pageserviceconf_page_invalid',$configPath);$matching=false;$id=($configPagePath==='')?$id:$configPagePath.'.'.$id;if(strcasecmp($id,$this->_pagePath)===0)$matching=true;else if($id[strlen($id)-1]==='*') // try wildcard matching$matching=strncasecmp($this->_pagePath,$id,strlen($id)-1)===0;if($matching)$this->_properties=array_merge($this->_properties,$properties->toArray());}}// external configurationsforeach($dom->getElementsByTagName('include') as $node){if(($when=$node->getAttribute('when'))===null)$when=true;if(($filePath=$node->getAttribute('file'))===null)throw new TConfigurationException('pageserviceconf_includefile_required');if(isset($this->_includes[$filePath]))$this->_includes[$filePath]=array($configPagePath,'('.$this->_includes[$filePath][1].') || ('.$when.')');else$this->_includes[$filePath]=array($configPagePath,$when);}}}