From 55c4ac1bfe565f1ca7f537fdd8b7a201be28e581 Mon Sep 17 00:00:00 2001 From: xue <> Date: Thu, 10 Nov 2005 12:47:19 +0000 Subject: Initial import of prado framework --- framework/Web/Javascripts/WebForms.js | 298 ++++ framework/Web/Services/TAssetService.php | 70 + framework/Web/Services/TPageService.php | 625 ++++++++ framework/Web/TCacheManager.php | 116 ++ framework/Web/THttpRequest.php | 824 +++++++++++ framework/Web/THttpResponse.php | 289 ++++ framework/Web/THttpSession.php | 504 +++++++ framework/Web/THttpUtility.php | 33 + framework/Web/UI/TClientScriptManager.php | 236 +++ framework/Web/UI/TControl.php | 1518 ++++++++++++++++++++ framework/Web/UI/TForm.php | 128 ++ .../Web/UI/THiddenFieldPageStatePersister.php | 59 + framework/Web/UI/THtmlTextWriter.php | 235 +++ framework/Web/UI/TPage.php | 617 ++++++++ framework/Web/UI/TPageStatePersister.php | 22 + framework/Web/UI/TPostBackOptions.php | 36 + framework/Web/UI/TTemplate.php | 494 +++++++ framework/Web/UI/TTemplateControl.php | 215 +++ framework/Web/UI/TTemplateManager.php | 66 + framework/Web/UI/TTheme.php | 64 + framework/Web/UI/WebControls/TButton.php | 291 ++++ framework/Web/UI/WebControls/TCheckBox.php | 399 +++++ framework/Web/UI/WebControls/TContent.php | 47 + .../Web/UI/WebControls/TContentPlaceHolder.php | 47 + framework/Web/UI/WebControls/TExpression.php | 61 + framework/Web/UI/WebControls/TFont.php | 276 ++++ framework/Web/UI/WebControls/THiddenField.php | 123 ++ framework/Web/UI/WebControls/THyperLink.php | 144 ++ framework/Web/UI/WebControls/TImage.php | 122 ++ framework/Web/UI/WebControls/TImageButton.php | 320 +++++ framework/Web/UI/WebControls/TLabel.php | 106 ++ framework/Web/UI/WebControls/TLiteral.php | 79 + framework/Web/UI/WebControls/TPanel.php | 162 +++ framework/Web/UI/WebControls/TPlaceHolder.php | 25 + framework/Web/UI/WebControls/TStatements.php | 61 + framework/Web/UI/WebControls/TStyle.php | 334 +++++ framework/Web/UI/WebControls/TTextBox.php | 444 ++++++ framework/Web/UI/WebControls/TWebControl.php | 368 +++++ 38 files changed, 9858 insertions(+) create mode 100644 framework/Web/Javascripts/WebForms.js create mode 100644 framework/Web/Services/TAssetService.php create mode 100644 framework/Web/Services/TPageService.php create mode 100644 framework/Web/TCacheManager.php create mode 100644 framework/Web/THttpRequest.php create mode 100644 framework/Web/THttpResponse.php create mode 100644 framework/Web/THttpSession.php create mode 100644 framework/Web/THttpUtility.php create mode 100644 framework/Web/UI/TClientScriptManager.php create mode 100644 framework/Web/UI/TControl.php create mode 100644 framework/Web/UI/TForm.php create mode 100644 framework/Web/UI/THiddenFieldPageStatePersister.php create mode 100644 framework/Web/UI/THtmlTextWriter.php create mode 100644 framework/Web/UI/TPage.php create mode 100644 framework/Web/UI/TPageStatePersister.php create mode 100644 framework/Web/UI/TPostBackOptions.php create mode 100644 framework/Web/UI/TTemplate.php create mode 100644 framework/Web/UI/TTemplateControl.php create mode 100644 framework/Web/UI/TTemplateManager.php create mode 100644 framework/Web/UI/TTheme.php create mode 100644 framework/Web/UI/WebControls/TButton.php create mode 100644 framework/Web/UI/WebControls/TCheckBox.php create mode 100644 framework/Web/UI/WebControls/TContent.php create mode 100644 framework/Web/UI/WebControls/TContentPlaceHolder.php create mode 100644 framework/Web/UI/WebControls/TExpression.php create mode 100644 framework/Web/UI/WebControls/TFont.php create mode 100644 framework/Web/UI/WebControls/THiddenField.php create mode 100644 framework/Web/UI/WebControls/THyperLink.php create mode 100644 framework/Web/UI/WebControls/TImage.php create mode 100644 framework/Web/UI/WebControls/TImageButton.php create mode 100644 framework/Web/UI/WebControls/TLabel.php create mode 100644 framework/Web/UI/WebControls/TLiteral.php create mode 100644 framework/Web/UI/WebControls/TPanel.php create mode 100644 framework/Web/UI/WebControls/TPlaceHolder.php create mode 100644 framework/Web/UI/WebControls/TStatements.php create mode 100644 framework/Web/UI/WebControls/TStyle.php create mode 100644 framework/Web/UI/WebControls/TTextBox.php create mode 100644 framework/Web/UI/WebControls/TWebControl.php (limited to 'framework/Web') diff --git a/framework/Web/Javascripts/WebForms.js b/framework/Web/Javascripts/WebForms.js new file mode 100644 index 00000000..79e0eeaa --- /dev/null +++ b/framework/Web/Javascripts/WebForms.js @@ -0,0 +1,298 @@ +function WebForm_PostBackOptions(eventTarget, eventArgument, validation, validationGroup, actionUrl, trackFocus, clientSubmit) { + this.eventTarget = eventTarget; + this.eventArgument = eventArgument; + this.validation = validation; + this.validationGroup = validationGroup; + this.actionUrl = actionUrl; + this.trackFocus = trackFocus; + this.clientSubmit = clientSubmit; +} +function WebForm_DoPostBackWithOptions(options) { + var validationResult = true; + if (options.validation) { + if (typeof(Page_ClientValidate) == 'function') { + validationResult = Page_ClientValidate(options.validationGroup); + } + } + if (validationResult) { + if ((typeof(options.actionUrl) != "undefined") && (options.actionUrl != null) && (options.actionUrl.length > 0)) { + theForm.action = options.actionUrl; + } + if (options.trackFocus) { + var lastFocus = theForm.elements["__LASTFOCUS"]; + if ((typeof(lastFocus) != "undefined") && (lastFocus != null)) { + if (typeof(document.activeElement) == "undefined") { + lastFocus.value = options.eventTarget; + } + else { + var active = document.activeElement; + if ((typeof(active.id) != "undefined") && (active != null)) { + if ((typeof(active.id) != "undefined") && (active.id != null) && (active.id.length > 0)) { + lastFocus.value = active.id; + } + else if (typeof(active.name) != "undefined") { + lastFocus.value = active.name; + } + } + } + } + } + } + if (options.clientSubmit) { + __doPostBack(options.eventTarget, options.eventArgument); + } +} +var __callbackObject = new Object(); +function WebForm_DoCallback(eventTarget, eventArgument, eventCallback, context, errorCallback, useAsync) { + var postData = __theFormPostData + + "__CALLBACKID=" + WebForm_EncodeCallback(eventTarget) + + "&__CALLBACKPARAM=" + WebForm_EncodeCallback(eventArgument); + var xmlRequest; + var usePost = false; + if (__nonMSDOMBrowser) { + // http: + // And: http: + xmlRequest = new XMLHttpRequest(); + if (pageUrl.length + postData.length + 1 > 10000) { + usePost = true; + } + if (usePost) { + xmlRequest.open("POST", pageUrl, false); + xmlRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + xmlRequest.send(postData); + } + else { + if (pageUrl.indexOf("?") != -1) { + xmlRequest.open("GET", pageUrl + "&" + postData, false); + } + else { + xmlRequest.open("GET", pageUrl + "?" + postData, false); + } + xmlRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + xmlRequest.send(null); + } + var response = xmlRequest.responseText; + if (response.charAt(0) == "s") { + if ((typeof(eventCallback) != "undefined") && (eventCallback != null)) { + eventCallback(response.substring(1), context); + } + } + else { + if ((typeof(errorCallback) != "undefined") && (errorCallback != null)) { + errorCallback(response.substring(1), context); + } + } + } + else { + xmlRequest = new ActiveXObject("Microsoft.XMLHTTP"); + xmlRequest.onreadystatechange = WebForm_CallbackComplete; + __callbackObject.xmlRequest = xmlRequest; + __callbackObject.eventCallback = eventCallback; + __callbackObject.context = context; + __callbackObject.errorCallback = errorCallback; + if (pageUrl.length + postData.length + 1 > 2067) { + usePost = true; + } + if (usePost) { + xmlRequest.open("POST", pageUrl, useAsync); + xmlRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + xmlRequest.send(postData); + } + else { + if (pageUrl.indexOf("?") != -1) { + xmlRequest.open("GET", pageUrl + "&" + postData, useAsync); + } + else { + xmlRequest.open("GET", pageUrl + "?" + postData, useAsync); + } + xmlRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + xmlRequest.send(); + } + } +} +function WebForm_CallbackComplete() { + if (__callbackObject.xmlRequest.readyState == 4) { + var response = __callbackObject.xmlRequest.responseText; + if (response.charAt(0) == "s") { + if ((typeof(__callbackObject.eventCallback) != "undefined") && (__callbackObject.eventCallback != null)) { + __callbackObject.eventCallback(response.substring(1), __callbackObject.context); + } + } + else { + if ((typeof(__callbackObject.errorCallback) != "undefined") && (__callbackObject.errorCallback != null)) { + __callbackObject.errorCallback(response.substring(1), __callbackObject.context); + } + } + } +} +var __nonMSDOMBrowser = (window.navigator.appName.toLowerCase().indexOf('explorer') == -1); +var __theFormPostData = ""; +function WebForm_InitCallback() { + var count = theForm.elements.length; + var element; + for (var i = 0; i < count; i++) { + element = theForm.elements[i]; + var tagName = element.tagName.toLowerCase(); + if (tagName == "input") { + var type = element.type; + if (type == "text" || type == "hidden" || type == "password" || + ((type == "checkbox" || type == "radio") && element.checked)) { + __theFormPostData += element.name + "=" + WebForm_EncodeCallback(element.value) + "&"; + } + } + else if (tagName == "select") { + var selectCount = element.children.length; + for (var j = 0; j < selectCount; j++) { + var selectChild = element.children[j]; + if ((selectChild.tagName.toLowerCase() == "option") && (selectChild.selected == true)) { + __theFormPostData += element.name + "=" + WebForm_EncodeCallback(selectChild.value) + "&"; + } + } + } + else if (tagName == "textarea") { + __theFormPostData += element.name + "=" + WebForm_EncodeCallback(element.value) + "&"; + } + } +} +function WebForm_EncodeCallback(parameter) { + if (encodeURIComponent) { + return encodeURIComponent(parameter); + } + else { + return escape(parameter); + } +} +var __disabledControlArray = new Array(); +function WebForm_ReEnableControls() { + if (typeof(__enabledControlArray) == 'undefined') { + return false; + } + var disabledIndex = 0; + for (var i = 0; i < __enabledControlArray.length; i++) { + var c; + if (__nonMSDOMBrowser) { + c = document.getElementById(__enabledControlArray[i]); + } + else { + c = document.all[__enabledControlArray[i]]; + } + if ((typeof(c) != "undefined") && (c != null) && (c.disabled == true)) { + c.disabled = false; + __disabledControlArray[disabledIndex++] = c; + } + } + setTimeout("WebForm_ReDisableControls()", 0); + return true; +} +function WebForm_ReDisableControls() { + for (var i = 0; i < __disabledControlArray.length; i++) { + __disabledControlArray[i].disabled = true; + } +} +var __defaultFired = false; +function WebForm_FireDefaultButton(event, target) { + if (!__defaultFired && event.keyCode == 13) { + var defaultButton; + if (__nonMSDOMBrowser) { + defaultButton = document.getElementById(target); + } + else { + defaultButton = document.all[target]; + } + if (defaultButton.click != "undefined") { + __defaultFired = true; + defaultButton.click(); + event.cancelBubble = true; + return false; + } + } + return true; +} +function WebForm_GetScrollX() { + if (__nonMSDOMBrowser) { + return window.pageXOffset; + } + else { + if (document.documentElement && document.documentElement.scrollLeft) { + return document.documentElement.scrollLeft; + } + else if (document.body) { + return document.body.scrollLeft; + } + } + return 0; +} +function WebForm_GetScrollY() { + if (__nonMSDOMBrowser) { + return window.pageYOffset; + } + else { + if (document.documentElement && document.documentElement.scrollTop) { + return document.documentElement.scrollTop; + } + else if (document.body) { + return document.body.scrollTop; + } + } + return 0; +} +function WebForm_SaveScrollPositionSubmit() { + if (__nonMSDOMBrowser) { + theForm.elements['__SCROLLPOSITIONY'].value = window.pageYOffset; + theForm.elements['__SCROLLPOSITIONX'].value = window.pageXOffset; + } + else { + theForm.__SCROLLPOSITIONX.value = WebForm_GetScrollX(); + theForm.__SCROLLPOSITIONY.value = WebForm_GetScrollY(); + } + if ((typeof(WebForm_ScrollPositionSubmit) != "undefined") && (WebForm_ScrollPositionSubmit != null)) { + if (WebForm_ScrollPositionSubmit.apply) { + return WebForm_ScrollPositionSubmit.apply(this); + } + else { + return WebForm_ScrollPositionSubmit(); + } + } + return true; +} +function WebForm_SaveScrollPositionOnSubmit() { + theForm.__SCROLLPOSITIONX.value = WebForm_GetScrollX(); + theForm.__SCROLLPOSITIONY.value = WebForm_GetScrollY(); + if ((typeof(WebForm_ScrollPositionOnSubmit) != "undefined") && (WebForm_ScrollPositionOnSubmit != null)) { + if (WebForm_ScrollPositionOnSubmit.apply) { + return WebForm_ScrollPositionOnSubmit.apply(this); + } + else { + return WebForm_ScrollPositionOnSubmit(); + } + } + return true; +} +function WebForm_RestoreScrollPosition() { + if (__nonMSDOMBrowser) { + window.scrollTo(theForm.elements['__SCROLLPOSITIONX'].value, theForm.elements['__SCROLLPOSITIONY'].value); + } + else { + window.scrollTo(theForm.__SCROLLPOSITIONX.value, theForm.__SCROLLPOSITIONY.value); + } + if ((typeof(WebForm_ScrollPositionLoad) != "undefined") && (WebForm_ScrollPositionLoad != null)) { + if (WebForm_ScrollPositionLoad.apply) { + return WebForm_ScrollPositionLoad.apply(this); + } + else { + return WebForm_ScrollPositionLoad(); + } + } + return true; +} +function WebForm_TextBoxKeyHandler() { + if (event.keyCode == 13) { + if ((typeof(event.srcElement) != "undefined") && (event.srcElement != null)) { + if (typeof(event.srcElement.onchange) != "undefined") { + event.srcElement.onchange(); + return false; + } + } + } + return true; +} diff --git a/framework/Web/Services/TAssetService.php b/framework/Web/Services/TAssetService.php new file mode 100644 index 00000000..914aa1a5 --- /dev/null +++ b/framework/Web/Services/TAssetService.php @@ -0,0 +1,70 @@ + + */ +class TAssetManager extends TComponent implements IModule +{ + private $_pubDir=null; + private $_pubUrl=null; + + public function init($context) + { + if(is_null($this->_pubDir)) + throw new TCongiruationException('cache_public_location_required'); + if(is_null($this->_pubUrl)) + throw new TCongiruationException('cache_public_url_required'); + } + + public function getPublicLocation() + { + return $this->_pubDir; + } + + public function setPublicLocation($value) + { + if(is_dir($value)) + $this->_pubDir=realpath($value); + else + throw new TInvalidDataValueException('cache_public_location_invalid'); + } + + public function getPublicUrl() + { + return $this->_pubUrl; + } + + public function setPublicUrl($value) + { + $this->_pubUrl=rtrim($value,'/'); + } + + public function publishLocation($path,$forceOverwrite=false) + { + $name=basename($path); + $prefix=md5(dirname($path)); + } + + public function publishFile($path,$forceOverwrite=false) + { + if(($fullpath=realpath($path))!==false) + { + return $this->_pubUrl.'/'.$fullpath; + } + else + throw new TInvalidDataValueException('cachemanager_path_unpublishable'); + } + + public function unpublishPath($path) + { + } +} + +class TMemcache extends TComponent +{ +} + +class TSqliteCache extends TComponent +{ +} +?> \ No newline at end of file diff --git a/framework/Web/Services/TPageService.php b/framework/Web/Services/TPageService.php new file mode 100644 index 00000000..6a19273d --- /dev/null +++ b/framework/Web/Services/TPageService.php @@ -0,0 +1,625 @@ + + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Revision: $ $Date: $ + * @package System.Web.Services + */ + +Prado::using('System.Web.UI.TPage'); +/** + * TPageService class. + * + * TPageService implements a service that can serve user requested pages. + * + * @author Qiang Xue + * @version $Revision: $ $Date: $ + * @package System.Services + * @since 3.0 + */ +class TPageService extends TComponent implements IService +{ + /** + * Configuration file name + */ + const CONFIG_FILE='config.xml'; + /** + * Prefix of ID used for storing parsed configuration in cache + */ + const CONFIG_CACHE_PREFIX='prado:pageservice:'; + /** + * @var string id of this service (page) + */ + private $_id; + /** + * @var string root path of pages + */ + private $_rootPath; + /** + * @var string default page + */ + private $_defaultPage=null; + /** + * @var string requested page (path) + */ + private $_pagePath; + /** + * @var string requested page type + */ + private $_pageType; + /** + * @var array list of initial page property values + */ + private $_properties; + /** + * @var integer cache expiration + */ + private $_cacheExpire=-1; + /** + * @var boolean whether service is initialized + */ + private $_initialized=false; + /** + * @var IApplication application + */ + private $_application; + + /** + * Initializes the service. + * This method is required by IService interface and is invoked by application. + * @param IApplication application + * @param TXmlElement service configuration + */ + public function init($application,$config) + { + $this->_application=$application; + + if(($rootPath=Prado::getPathOfNamespace($this->_rootPath))===null || !is_dir($rootPath)) + throw new TConfigurationException('pageservice_rootpath_invalid',$this->_rootPath); + + $this->_pagePath=$application->getRequest()->getServiceParameter(); + if(empty($this->_pagePath)) + $this->_pagePath=$this->_defaultPage; + if(empty($this->_pagePath)) + throw new THttpException('pageservice_page_required'); + + if(($cache=$application->getCache())===null) + { + $config=new TPageConfiguration; + $config->loadConfiguration($this->_pagePath,$rootPath); + } + else + { + $configCached=true; + $arr=$cache->get(self::CONFIG_CACHE_PREFIX.$this->_pagePath); + if(is_array($arr)) + { + list($config,$timestamp)=$arr; + if($this->_cacheExpire<0) + { + // check to see if cache is the latest + $paths=explode('.',$this->_pagePath); + array_pop($paths); + $configPath=$rootPath; + foreach($paths as $path) + { + if(@filemtime($configPath.'/'.self::CONFIG_FILE)>$timestamp) + { + $configCached=false; + break; + } + $configPath.='/'.$path; + } + if(@filemtime($configPath.'/'.self::CONFIG_FILE)>$timestamp) + $configCached=false; + } + } + else + $configCached=false; + if(!$configCached) + { + $config=new TPageConfiguration; + $config->loadConfiguration($this->_pagePath,$rootPath); + $cache->set(self::CONFIG_CACHE_PREFIX.$this->_pagePath,array($config,time()),$this->_cacheExpire<0?0:$this->_cacheExpire); + } + } + + $this->_pageType=$config->getPageType(); + + // set path aliases and using namespaces + foreach($config->getAliases() as $alias=>$path) + Prado::setPathAlias($alias,$path); + foreach($config->getUsings() as $using) + Prado::using($using); + + $this->_properties=$config->getProperties(); + + // load parameters + $parameters=$application->getParameters(); + foreach($config->getParameters() as $id=>$parameter) + { + if(is_string($parameter)) + $parameters->add($id,$parameter); + else + { + $component=Prado::createComponent($parameter[0]); + foreach($parameter[1] as $name=>$value) + $component->setPropertyByPath($name,$value); + $parameters->add($id,$component); + } + } + + // load modules specified in app config + foreach($config->getModules() as $id=>$moduleConfig) + { + $module=Prado::createComponent($moduleConfig[0]); + $application->setModule($id,$module); + foreach($moduleConfig[1] as $name=>$value) + $module->setPropertyByPath($name,$value); + $module->init($this->_application,$moduleConfig[2]); + } + + if(($auth=$application->getAuthManager())!==null) + $auth->getAuthorizationRules()->mergeWith($config->getRules()); + + $this->_initialized=true; + } + + /** + * @return string id of this module + */ + public function getID() + { + return $this->_id; + } + + /** + * @param string id of this module + */ + public function setID($value) + { + $this->_id=$value; + } + + /** + * @return TTemplateManager template manager + */ + public function getTemplateManager() + { + return $this->_application->getModule('template'); + } + + /** + * @return boolean true if the pagepath is currently being requested, false otherwise + */ + public function isRequestingPage($pagePath) + { + return $this->_pagePath===$pagePath; + } + + /** + * @return integer the expiration time of the configuration saved in cache, + * -1 (default) ensures the cached configuration always catches up the latest configuration files, + * 0 means never expire, + * a number less or equal than 60*60*24*30 means the number of seconds that the value will remain valid. + * a number greater than 60 means a UNIX timestamp after which the value will expire. + */ + public function getCacheExpire() + { + return $this->_cacheExpire; + } + + /** + * Sets the expiration time of the configuration saved in cache. + * TPageService will try to use cache to save parsed configuration files. + * CacheExpire is used to control the caching policy. + * If you have changed this property, make sure to clean up cache first. + * @param integer the expiration time of the configuration saved in cache, + * -1 (default) ensures the cached configuration always catches up the latest configuration files, + * 0 means never expire, + * a number less or equal than 60*60*24*30 means the number of seconds that the value will remain valid. + * a number greater than 60 means a UNIX timestamp after which the value will expire. + * @throws TInvalidOperationException if the service is already initialized + */ + public function setCacheExpire($value) + { + if($this->_initialized) + throw new TInvalidOperationException('pageservice_cacheexpire_unchangeable'); + else + $this->_cacheExpire=TPropertyValue::ensureInteger($value); + } + + /** + * @return string default page path to be served if no explicit page is request + */ + 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 root directory (in namespace form) storing pages + */ + public function getRootPath() + { + return $this->_rootPath; + } + + /** + * @param string root directory (in namespace form) storing pages + * @throws TInvalidOperationException if application is initialized + */ + public function setRootPath($value) + { + if($this->_initialized) + throw new TInvalidOperationException('pageservice_rootpath_unchangeable'); + else + $this->_rootPath=$value; + } + + /** + * 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() + { + $page=null; + if(($pos=strpos($this->_pageType,'.'))===false) + { + $className=$this->_pageType; + if(!class_exists($className,false)) + { + $p=explode('.',$this->_pagePath); + array_pop($p); + array_push($p,$className); + $path=Prado::getPathOfNamespace($this->_rootPath).'/'.implode('/',$p).Prado::CLASS_FILE_EXT; + require_once($path); + } + } + else + { + $className=substr($this->_pageType,$pos+1); + if(($path=self::getPathOfNamespace($this->_pageType,Prado::CLASS_FILE_EXT))!==null) + { + if(!class_exists($className,false)) + { + require_once($path); + } + } + } + if(class_exists($className,false)) + $page=new $className($this->_properties); + else + throw new THttpException('pageservice_page_unknown',$this->_pageType); + $writer=new THtmlTextWriter($this->_application->getResponse()); + $page->run($writer); + $writer->flush(); + } + + /** + * 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 + * @return string URL for the page and GET parameters + */ + public function constructUrl($pagePath,$getParams=null) + { + return $this->_application->getRequest()->constructUrl($this->_id,$pagePath,$getParams); + } +} + + +/** + * 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 + * @version $Revision: $ $Date: $ + * @package System.Services + * @since 3.0 + */ +class TPageConfiguration extends TComponent +{ + /** + * @var array list of page initial property values + */ + private $_properties=array(); + /** + * @var string page type + */ + private $_pageType=null; + /** + * @var array list of namespaces to be used + */ + private $_usings=array(); + /** + * @var array list of path aliases + */ + private $_aliases=array(); + /** + * @var array list of module configurations + */ + private $_modules=array( + 'template'=>array('System.Web.UI.TTemplateManager',array(),null), + ); + /** + * @var array list of parameters + */ + private $_parameters=array(); + /** + * @var TAuthorizationRuleCollection list of authorization rules + */ + private $_rules=array(); + + /** + * 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; + } + + /** + * @return string the requested page type + */ + public function getPageType() + { + return $this->_pageType; + } + + /** + * Returns list of path alias definitions. + * The definitions are aggregated (top-down) from configuration files along the path + * to the specified page. Each array element represents a single alias definition, + * with the key being the alias name and the value the absolute path. + * @return array list of path alias definitions + */ + public function getAliases() + { + return $this->_aliases; + } + + /** + * Returns list of namespaces to be used. + * The namespaces are aggregated (top-down) from configuration files along the path + * to the specified page. Each array element represents a single namespace usage, + * with the value being the namespace to be used. + * @return array list of namespaces to be used + */ + public function getUsings() + { + return $this->_usings; + } + + /** + * Returns list of module configurations. + * The module configurations are aggregated (top-down) from configuration files + * along the path to the specified page. Each array element represents + * a single module configuration, with the key being the module ID and + * the value the module configuration. Each module configuration is + * stored in terms of an array with the following content + * ([0]=>module type, [1]=>module properties, [2]=>complete module configuration) + * The module properties are an array of property values indexed by property names. + * The complete module configuration is a TXmlElement object representing + * the raw module configuration which may contain contents enclosed within + * module tags. + * @return array list of module configurations to be used + */ + public function getModules() + { + return $this->_modules; + } + + /** + * Returns list of parameter definitions. + * The parameter definitions are aggregated (top-down) from configuration files + * along the path to the specified page. Each array element represents + * a single parameter definition, with the key being the parameter ID and + * the value the parameter definition. A parameter definition can be either + * a string representing a string-typed parameter, or an array. + * The latter defines a component-typed parameter whose format is as follows, + * ([0]=>component type, [1]=>component properties) + * The component properties are an array of property values indexed by property names. + * @return array list of parameter definitions to be used + */ + public function getParameters() + { + return $this->_parameters; + } + + /** + * 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; + } + + /** + * Loads configuration for a page specified in a path format. + * @param string path to the page (dot-connected format) + * @param string root path for pages + */ + public function loadConfiguration($pagePath,$rootPath) + { + $paths=explode('.',$pagePath); + $page=array_pop($paths); + $path=$rootPath; + foreach($paths as $p) + { + $this->loadFromFile($path.'/'.TPageService::CONFIG_FILE,null); + $path.='/'.$p; + } + $this->loadFromFile($path.'/'.TPageService::CONFIG_FILE,$page); + $this->_rules=new TAuthorizationRuleCollection($this->_rules); + } + + /** + * Loads a specific config file. + * @param string config file name + * @param string page name, null if page is not required + */ + private function loadFromFile($fname,$page) + { + if(empty($fname) || !is_file($fname)) + { + if($page===null) + return; + } + $configPath=dirname($fname); + $dom=new TXmlDocument; + $dom->loadFromFile($fname); + + // paths + if(($pathsNode=$dom->getElementByTagName('paths'))!==null) + { + foreach($pathsNode->getElementsByTagName('alias') as $aliasNode) + { + if(($id=$aliasNode->getAttribute('id'))!==null && ($p=$aliasNode->getAttribute('path'))!==null) + { + $p=str_replace('\\','/',$p); + $path=realpath(preg_match('/^\\/|.:\\//',$p)?$p:$configPath.'/'.$p); + if($path===false || !is_dir($path)) + throw new TConfigurationException('pageservice_alias_path_invalid',$fname,$id,$p); + if(isset($this->_aliases[$id])) + throw new TConfigurationException('pageservice_alias_redefined',$fname,$id); + $this->_aliases[$id]=$path; + } + else + throw new TConfigurationException('pageservice_alias_element_invalid',$fname); + } + foreach($pathsNode->getElementsByTagName('using') as $usingNode) + { + if(($namespace=$usingNode->getAttribute('namespace'))!==null) + $this->_usings[]=$namespace; + else + throw new TConfigurationException('pageservice_using_element_invalid',$fname); + } + } + + // modules + if(($modulesNode=$dom->getElementByTagName('modules'))!==null) + { + foreach($modulesNode->getElementsByTagName('module') as $node) + { + $properties=$node->getAttributes(); + $type=$properties->remove('type'); + if(($id=$properties->itemAt('id'))===null) + throw new TConfigurationException('pageservice_module_element_invalid',$fname); + if(isset($this->_modules[$id])) + { + if($type===null) + { + $this->_modules[$id][1]=array_merge($this->_modules[$id][1],$properties->toArray()); + $elements=$this->_modules[$id][2]->getElements(); + foreach($node->getElements() as $element) + $elements->add($element); + } + else + throw new TConfigurationException('pageservice_module_redefined',$fname,$id); + } + else if($type===null) + throw new TConfigurationException('pageservice_module_element_invalid',$fname); + else + { + $node->setParent(null); + $this->_modules[$id]=array($type,$properties->toArray(),$node); + } + } + } + + // parameters + if(($parametersNode=$dom->getElementByTagName('parameters'))!==null) + { + foreach($parametersNode->getElementsByTagName('parameter') as $node) + { + $properties=$node->getAttributes(); + if(($id=$properties->remove('id'))===null) + throw new TConfigurationException('pageservice_parameter_element_invalid'); + if(($type=$properties->remove('type'))===null) + $this->_parameters[$id]=$node->getValue(); + else + $this->_parameters[$id]=array($type,$properties->toArray()); + } + } + + // authorization + if(($authorizationNode=$dom->getElementByTagName('authorization'))!==null) + { + $rules=array(); + foreach($authorizationNode->getElements() as $node) + { + $pages=$node->getAttribute('pages'); + $ruleApplies=false; + if(empty($pages)) + $ruleApplies=true; + else if($page!==null) + { + $ps=explode(',',$pages); + foreach($ps as $p) + { + if($page===trim($p)) + { + $ruleApplies=true; + break; + } + } + } + if($ruleApplies) + $rules[]=new TAuthorizationRule($node->getTagName(),$node->getAttribute('users'),$node->getAttribute('roles'),$node->getAttribute('verb')); + } + $this->_rules=array_merge($rules,$this->_rules); + } + + // pages + if($page!==null && ($pagesNode=$dom->getElementByTagName('pages'))!==null) + { + $baseProperties=$pagesNode->getAttributes(); + foreach($pagesNode->getElementsByTagName('page') as $node) + { + $properties=$node->getAttributes(); + $type=$properties->remove('type'); + $id=$properties->itemAt('id'); + if($id===null || $type===null) + throw new TConfigurationException('pageservice_page_element_invalid',$fname); + if($id===$page) + { + $this->_properties=array_merge($baseProperties->toArray(),$properties->toArray()); + $this->_pageType=$type; + } + } + } + if($page!==null && $this->_pageType===null) + throw new THttpException('pageservice_page_inexistent',$page); + } +} + + + +?> \ No newline at end of file diff --git a/framework/Web/TCacheManager.php b/framework/Web/TCacheManager.php new file mode 100644 index 00000000..f1c9edd8 --- /dev/null +++ b/framework/Web/TCacheManager.php @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + // need to investigate the security of memory cache + */ + +/** + +*/ + +/** + +*/ + + +class TAuthencator extends TComponent +{ +} + +class TAuthorizer extends TComponent +{ +} + +$cm->generateUniqueID('button:',$id) +$cm->saveValue('template:'.$tmpFile,$template); +$cm->saveValue('application:ID',$appID); +$cm->saveValue('application:hashkey',$key); + +class TTemplateManager extends TComponent implements IModule +{ +} + +class TAssetManager extends TComponent implements IModule +{ + private $_pubDir=null; + private $_pubUrl=null; + + public function init($context) + { + if(is_null($this->_pubDir)) + throw new TCongiruationException('cache_public_location_required'); + if(is_null($this->_pubUrl)) + throw new TCongiruationException('cache_public_url_required'); + } + + public function getPublicLocation() + { + return $this->_pubDir; + } + + public function setPublicLocation($value) + { + if(is_dir($value)) + $this->_pubDir=realpath($value); + else + throw new TInvalidDataValueException('cache_public_location_invalid'); + } + + public function getPublicUrl() + { + return $this->_pubUrl; + } + + public function setPublicUrl($value) + { + $this->_pubUrl=rtrim($value,'/'); + } + + public function publishLocation($path,$forceOverwrite=false) + { + $name=basename($path); + $prefix=md5(dirname($path)); + } + + public function publishFile($path,$forceOverwrite=false) + { + if(($fullpath=realpath($path))!==false) + { + return $this->_pubUrl.'/'.$fullpath; + } + else + throw new TInvalidDataValueException('cachemanager_path_unpublishable'); + } + + public function unpublishPath($path) + { + } +} + +class TMemcache extends TComponent +{ +} + +class TSqliteCache extends TComponent +{ +} +?> \ No newline at end of file diff --git a/framework/Web/THttpRequest.php b/framework/Web/THttpRequest.php new file mode 100644 index 00000000..25d3027e --- /dev/null +++ b/framework/Web/THttpRequest.php @@ -0,0 +1,824 @@ + + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Revision: $ $Date: $ + * @package System.Web + */ + +/** + * THttpRequest class + * + * THttpRequest provides storage and access scheme for user request sent via HTTP. + * It also encapsulates a uniform way to parse and construct URLs. + * + * THttpRequest is the default "request" module for prado application. + * + * @author Qiang Xue + * @version $Revision: $ $Date: $ + * @package System.Web + * @since 3.0 + */ +class THttpRequest extends TComponent implements IModule +{ + /** + * GET variable name to store service information + */ + const SERVICE_VAR='sp'; + /** + * @var boolean whether the module is initialized + */ + private $_initialized=false; + /** + * @var string module ID + */ + private $_id; + /** + * @var string requested service ID + */ + private $_serviceID=null; + /** + * @var string requested service parameter + */ + private $_serviceParam=null; + /** + * @var THttpCookieCollection cookies sent from user + */ + private $_cookies=null; + /** + * @var string requested URI (URL w/o host info) + */ + private $_requestUri; + /** + * @var string path info of URL + */ + private $_pathInfo; + /** + * @var TMap list of input variables (including GET and POST) + */ + private $_items; + + private $_authenticated=false; + + /** + * Constructor. + * Analyzes and resolves user request. + */ + public function __construct() + { + // Info about server variables: + // PHP_SELF contains real URI (w/ path info, w/o query string) + // SCRIPT_NAME is the real URI for the requested script (w/o path info and query string) + // REQUEST_URI contains the URI part entered in the browser address bar + // SCRIPT_FILENAME is the file path to the executing script + parent::__construct(); + if(isset($_SERVER['REQUEST_URI'])) + $this->_requestUri=$_SERVER['REQUEST_URI']; + else // TBD: in this case, SCRIPT_NAME need to be escaped + $this->_requestUri=$_SERVER['SCRIPT_NAME'].(empty($_SERVER['QUERY_STRING'])?'':'?'.$_SERVER['QUERY_STRING']); + + if(isset($_SERVER['PATH_INFO'])) + $this->_pathInfo=$_SERVER['PATH_INFO']; + else if(strpos($_SERVER['PHP_SELF'],$_SERVER['SCRIPT_NAME'])===0) + $this->_pathInfo=substr($_SERVER['PHP_SELF'],strlen($_SERVER['SCRIPT_NAME'])); + else + $this->_pathInfo=''; + + if(get_magic_quotes_gpc()) + { + if(isset($_GET)) + $_GET=array_map(array($this,'stripSlashes'),$_GET); + if(isset($_POST)) + $_POST=array_map(array($this,'stripSlashes'),$_POST); + if(isset($_REQUEST)) + $_REQUEST=array_map(array($this,'stripSlashes'),$_REQUEST); + if(isset($_COOKIE)) + $_COOKIE=array_map(array($this,'stripSlashes'),$_COOKIE); + } + + $this->_items=new TMap(array_merge($_POST,$_GET)); + + $this->resolveRequest(); + } + + /** + * Strips slashes from input data. + * This method is applied when magic quotes is enabled. + * Do not call this method. + * @param mixed input data to be processed + * @param mixed processed data + */ + public function stripSlashes(&$data) + { + return is_array($data)?array_map('pradoStripSlashes',$data):stripslashes($data); + } + + /** + * Initializes the module. + * This method is required by IModule and is invoked by application. + * @param IApplication application + * @param TXmlElement module configuration + */ + public function init($application,$config) + { + $this->_initialized=true; + } + + /** + * @return string id of this module + */ + public function getID() + { + return $this->_id; + } + + /** + * @param string id of this module + */ + public function setID($value) + { + $this->_id=$value; + } + + /** + * @return TUri the request URL + */ + public function getUrl() + { + if($this->_url===null) + { + $secure=$this->getIsSecureConnection(); + $url=$secure?'https://':'http://'; + if(empty($_SERVER['HTTP_HOST'])) + { + $url.=$_SERVER['SERVER_NAME']; + $port=$_SERVER['SERVER_PORT']; + if(($port!=80 && !$secure) || ($port!=443 && $secure)) + $url.=':'.$port; + } + else + $url.=$_SERVER['HTTP_HOST']; + $url.=$this->getRequestUri(); + $this->_url=new TUri($url); + } + return $this->_url; + } + + /** + * @return string request type, can be GET, POST, HEAD, or PUT + */ + public function getRequestType() + { + return $_SERVER['REQUEST_METHOD']; + } + + /** + * @return boolean if the request is sent via secure channel (https) + */ + public function getIsSecureConnection() + { + return !empty($_SERVER['HTTPS']); + } + + /** + * @return string part of the request URL after script name and before question mark. + */ + public function getPathInfo() + { + return $this->_pathInfo; + } + + /** + * @return string part of that request URL after the question mark + */ + public function getQueryString() + { + return isset($_SERVER['QUERY_STRING'])?$_SERVER['QUERY_STRING']:''; + } + + /** + * @return string part of that request URL after the host info (including pathinfo and query string) + */ + public function getRequestUri() + { + return $this->_requestUri; + } + + /** + * @return string entry script URL (w/o host part) + */ + public function getApplicationPath() + { + return $_SERVER['SCRIPT_NAME']; + } + + /** + * @return string application entry script file path + */ + public function getPhysicalApplicationPath() + { + return $_SERVER['SCRIPT_FILENAME']; + } + + /** + * @return string server name + */ + public function getServerName() + { + return $_SERVER['SERVER_NAME']; + } + + /** + * @return integer server port number + */ + public function getServerPort() + { + return $_SERVER['SERVER_PORT']; + } + + /** + * @return string URL referrer, null if not present + */ + public function getUrlReferrer() + { + return isset($_SERVER['HTTP_REFERER'])?$_SERVER['HTTP_REFERER']:null; + } + + /** + * @return array user browser capabilities + * @see get_browser + */ + public function getBrowser() + { + return get_browser(); + } + + /** + * @return string user agent + */ + public function getUserAgent() + { + return $_SERVER['HTTP_USER_AGENT']; + } + + /** + * @return string user IP address + */ + public function getUserHostAddress() + { + return $_SERVER['REMOTE_ADDR']; + } + + /** + * @return string user host name, null if cannot be determined + */ + public function getUserHost() + { + return isset($_SERVER['REMOTE_HOST'])?$_SERVER['REMOTE_HOST']:null; + } + + /** + * @return string user browser accept types + */ + public function getAcceptTypes() + { + // TBD: break it into array?? + return $_SERVER['HTTP_ACCEPT']; + } + + /** + * @return string languages user browser supports + */ + public function getUserLanguages() + { + // TBD ask wei about this + return $_SERVER['HTTP_ACCEPT_LANGUAGE']; + } + + /** + * @return TMap list of input variables, include GET, POST + */ + public function getItems() + { + return $this->_items; + } + + /** + * @return THttpCookieCollection list of cookies to be sent + */ + public function getCookies() + { + if($this->_cookies===null) + { + $this->_cookies=new THttpCookieCollection; + foreach($_COOKIE as $key=>$value) + $this->_cookies->add(new THttpCookie($key,$value)); + } + return $this->_cookies; + } + + /** + * @return TMap list of uploaded files. + */ + public function getUploadedFiles() + { + if($this->_files===null) + $this->_files=new TMap($_FILES); + return $this->_files; + } + + /** + * @return TMap list of server variables. + */ + public function getServerVariables() + { + if($this->_server===null) + $this->_server=new TMap($_SERVER); + return $this->_server; + } + + /** + * @return TMap list of environment variables. + */ + public function getEnvironmentVariables() + { + if($this->_env===null) + $this->_env=new TMap($_ENV); + return $this->_env; + } + + /** + * Constructs a URL that is recognizable by Prado. + * You may override this method to provide your own way of URL formatting. + * The URL is constructed as the following format: + * /entryscript.php?sp=serviceID.serviceParameter&get1=value1&... + * @param string service ID + * @param string service parameter + * @param array GET parameters, null if not needed + * @return string URL + */ + public function constructUrl($serviceID,$serviceParam,$getItems=null) + { + $url=$this->getApplicationPath(); + $url.='?'.self::SERVICE_VAR.'='.$serviceID; + if(!empty($serviceParam)) + $url.='.'.$serviceParam; + if(is_array($getItems) || $getItems instanceof Traversable) + { + foreach($getItems as $name=>$value) + $url.='&'.urlencode($name).'='.urlencode($value); + } + if(defined('SID') && SID != '') + $url.='&'.SID; + return $url; + } + + /** + * Resolves the requested servie. + * This method implements a URL-based service resolution. + * A URL in the format of /index.php?sp=serviceID.serviceParameter + * will be resolved with the serviceID and the serviceParameter. + * You may override this method to provide your own way of service resolution. + * @see constructUrl + */ + protected function resolveRequest() + { + if(($sp=$this->_items->itemAt(self::SERVICE_VAR))!==null) + { + if(($pos=strpos($sp,'.'))===false) + $this->setServiceID($sp); + else + { + $this->setServiceID(substr($sp,0,$pos)); + $this->setServiceParameter(substr($sp,$pos+1)); + } + } + } + + /** + * @return string requested service ID + */ + public function getServiceID() + { + return $this->_serviceID; + } + + /** + * Sets the requested service ID. + * @param string requested service ID + */ + protected function setServiceID($value) + { + $this->_serviceID=$value; + } + + /** + * @return string requested service parameter + */ + public function getServiceParameter() + { + return $this->_serviceParam; + } + + /** + * Sets the requested service parameter. + * @param string requested service parameter + */ + protected function setServiceParameter($value) + { + $this->_serviceParam=$value; + } + + public function getIsAuthenticated() + { + return $this->_authenticated; + } + + public function setIsAuthenticated($value) + { + $this->_authenticated=$value; + } +} + +/** + * THttpCookieCollection class. + * + * THttpCookieCollection implements a collection class to store cookies. + * + * @author Qiang Xue + * @version $Revision: $ $Date: $ + * @package System.Web + * @since 3.0 + */ +class THttpCookieCollection extends TList +{ + /** + * @var mixed owner of this collection + */ + private $_o; + + /** + * Constructor. + * @param mixed owner of this collection. + */ + public function __construct($owner=null) + { + parent::__construct(); + $this->_o=$owner; + } + + /** + * Adds the cookie if owner of this collection is of THttpResponse. + * This method will be invoked whenever an item is added to the collection. + */ + protected function addedItem($item) + { + if($this->_o instanceof THttpResponse) + $this->_o->addCookie($item); + } + + /** + * Removes the cookie if owner of this collection is of THttpResponse. + * This method will be invoked whenever an item is removed from the collection. + */ + protected function removedItem($item) + { + if($this->_o instanceof THttpResponse) + $this->_o->removeCookie($item); + } + + /** + * Restricts acceptable item of this collection to THttpCookie. + * This method will be invoked whenever an item is to be added into the collection. + */ + protected function canAddItem($item) + { + return ($item instanceof THttpCookie); + } +} + +/** + * THttpCookie class. + * + * A THttpCookie instance stores a single cookie, including the cookie name, value, + * domain, path, expire, and secure. + * + * @author Qiang Xue + * @version $Revision: $ $Date: $ + * @package System.Web + * @since 3.0 + */ +class THttpCookie extends TComponent +{ + /** + * @var string domain of the cookie + */ + private $_domain=''; + /** + * @var string name of the cookie + */ + private $_name; + /** + * @var string value of the cookie + */ + private $_value=0; + /** + * @var integer expire of the cookie + */ + private $_expire=0; + /** + * @var string path of the cookie + */ + private $_path='/'; + /** + * @var boolean whether cookie should be sent via secure connection + */ + private $_secure=false; + + /** + * Constructor. + * @param string name of this cookie + * @param string value of this cookie + */ + public function __construct($name,$value) + { + parent::__construct(); + $this->_name=$name; + $this->_value=$value; + } + + /** + * @return string the domain to associate the cookie with + */ + public function getDomain() + { + return $this->_domain; + } + + /** + * @param string the domain to associate the cookie with + */ + public function setDomain($value) + { + $this->_domain=$value; + } + + /** + * @return integer the time the cookie expires. This is a Unix timestamp so is in number of seconds since the epoch. + */ + public function getExpire() + { + return $this->_expire; + } + + /** + * @param integer the time the cookie expires. This is a Unix timestamp so is in number of seconds since the epoch. + */ + public function setExpire($value) + { + $this->_expire=TPropertyValue::ensureInteger($value); + } + + /** + * @return string the name of the cookie + */ + public function getName() + { + return $this->_name; + } + + /** + * @param string the name of the cookie + */ + public function setName($value) + { + $this->_name=$value; + } + + /** + * @return string the value of the cookie + */ + public function getValue() + { + return $this->_value; + } + + /** + * @param string the value of the cookie + */ + public function setValue($value) + { + $this->_value=$value; + } + + /** + * @return string the path on the server in which the cookie will be available on, default is '/' + */ + public function getPath() + { + return $this->_path; + } + + /** + * @param string the path on the server in which the cookie will be available on + */ + public function setPath($value) + { + $this->_path=$value; + } + + /** + * @return boolean whether the cookie should only be transmitted over a secure HTTPS connection + */ + public function getSecure() + { + return $this->_secure; + } + + /** + * @param boolean ether the cookie should only be transmitted over a secure HTTPS connection + */ + public function setSecure($value) + { + $this->_secure=TPropertyValue::ensureBoolean($value); + } +} + +/** + * TUri class + * + * TUri represents a URI. Given a URI + * http://joe:whatever@example.com:8080/path/to/script.php?param=value#anchor + * it will be decomposed as follows, + * - scheme: http + * - host: example.com + * - port: 8080 + * - user: joe + * - password: whatever + * - path: /path/to/script.php + * - query: param=value + * - fragment: anchor + * + * @author Qiang Xue + * @version $Revision: $ $Date: $ + * @package System.Web + * @since 3.0 + */ +class TUri extends TComponent +{ + /** + * @var array list of default ports for known schemes + */ + private static $_defaultPort=array( + 'ftp'=>21, + 'gopher'=>70, + 'http'=>80, + 'https'=>443, + 'news'=>119, + 'nntp'=>119, + 'wais'=>210, + 'telnet'=>23 + ); + /** + * @var string scheme of the URI + */ + private $_scheme; + /** + * @var string host name of the URI + */ + private $_host; + /** + * @var integer port of the URI + */ + private $_port; + /** + * @var string user of the URI + */ + private $_user; + /** + * @var string password of the URI + */ + private $_pass; + /** + * @var string path of the URI + */ + private $_path; + /** + * @var string query string of the URI + */ + private $_query; + /** + * @var string fragment of the URI + */ + private $_fragment; + /** + * @var string the URI + */ + private $_uri; + + /** + * Constructor. + * Decomposes the specified URI into parts. + * @param string URI to be represented + * @throws TInvalidDataValueException if URI is of bad format + */ + public function __construct($uri) + { + parent::__construct(); + if(($ret=@parse_url($uri))!==false) + { + // decoding??? + $this->_scheme=$ret['scheme']; + $this->_host=$ret['host']; + $this->_port=$ret['port']; + $this->_user=$ret['user']; + $this->_pass=$ret['pass']; + $this->_path=$ret['path']; + $this->_query=$ret['query']; + $this->_fragment=$ret['fragment']; + $this->_uri=$uri; + } + else + { + throw new TInvalidDataValueException('uri_format_invalid',$uri); + } + } + + /** + * @return string URI + */ + public function getUri() + { + return $this->_uri; + } + + /** + * @return string scheme of the URI, such as 'http', 'https', 'ftp', etc. + */ + public function getScheme() + { + return $this->_scheme; + } + + /** + * @return string hostname of the URI + */ + public function getHost() + { + return $this->_host; + } + + /** + * @return integer port number of the URI + */ + public function getPort() + { + return $this->_port; + } + + /** + * @return string username of the URI + */ + public function getUser() + { + return $this->_user; + } + + /** + * @return string password of the URI + */ + public function getPassword() + { + return $this->_pass; + } + + /** + * @return string path of the URI + */ + public function getPath() + { + return $this->_path; + } + + /** + * @return string query string of the URI + */ + public function getQuery() + { + return $this->_query; + } + + /** + * @return string fragment of the URI + */ + public function getFragment() + { + return $this->_fragment; + } +} + +?> \ No newline at end of file diff --git a/framework/Web/THttpResponse.php b/framework/Web/THttpResponse.php new file mode 100644 index 00000000..96859300 --- /dev/null +++ b/framework/Web/THttpResponse.php @@ -0,0 +1,289 @@ + + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Revision: $ $Date: $ + * @package System.Web + */ + +/** + * THttpResponse class + * + * THttpResponse implements a scheme to output response to user requests. + * + * THttpResponse is the default "response" module for prado application. + * + * @author Qiang Xue + * @version $Revision: $ $Date: $ + * @package System.Web + * @since 3.0 + */ +class THttpResponse extends TComponent implements IModule, ITextWriter +{ + /** + * @var string id of this module (response) + */ + private $_id; + /** + * @var boolean whether to buffer output + */ + private $_bufferOutput=true; + /** + * @var boolean if the application is initialized + */ + private $_initialized=false; + /** + * @var THttpCookieCollection list of cookies to return + */ + private $_cookies=null; + /** + * @var integer status code + */ + private $_status=200; + + /** + * Destructor. + * Flushes any existing content in buffer. + */ + public function __destruct() + { + if($this->_bufferOutput) + @ob_end_flush(); + parent::__destruct(); + } + + /** + * Initializes the module. + * This method is required by IModule and is invoked by application. + * It starts output buffer if it is enabled. + * @param IApplication application + * @param TXmlElement module configuration + */ + public function init($application,$config) + { + if($this->_bufferOutput) + ob_start(); + $this->_initialized=true; + } + + /** + * @return string id of this module + */ + public function getID() + { + return $this->_id; + } + + /** + * @param string id of this module + */ + public function setID($value) + { + $this->_id=$value; + } + + + /** + * @return integer time-to-live for cached session pages in minutes, this has no effect for nocache limiter. Defaults to 180. + */ + public function getCacheExpire() + { + return session_cache_expire(); + } + + /** + * @param integer time-to-live for cached session pages in minutes, this has no effect for nocache limiter. + */ + public function setCacheExpire($value) + { + session_cache_expire(TPropertyValue::ensureInteger($value)); + } + + /** + * @return string cache control method to use for session pages + */ + public function getCacheControl() + { + return session_cache_limiter(); + } + + /** + * @param string cache control method to use for session pages. Valid values + * include none/nocache/private/private_no_expire/public + */ + public function setCacheControl($value) + { + session_cache_limiter(TPropertyValue::ensureEnum($value,array('none','nocache','private','private_no_expire','public'))); + } + + /** + * @return boolean whether to enable output buffer + */ + public function getBufferOutput() + { + return $this->_bufferOutput; + } + + /** + * @param boolean whether to enable output buffer + * @throws TInvalidOperationException if session is started already + */ + public function setBufferOutput($value) + { + if($this->_initialized) + throw new TInvalidOperationException('httpresponse_bufferoutput_unchangeable'); + else + $this->_bufferOutput=TPropertyValue::ensureBoolean($value); + } + + /** + * @return integer HTTP status code, defaults to 200 + */ + public function getStatusCode() + { + return $this->_status; + } + + /** + * @param integer HTTP status code + */ + public function setStatusCode($status) + { + $this->_status=TPropertyValue::ensureInteger($status); + } + + /** + * @return THttpCookieCollection list of output cookies + */ + public function getCookies() + { + if($this->_cookies===null) + $this->_cookies=new THttpCookieCollection($this); + return $this->_cookies; + } + + /** + * Outputs a string. + * It may not be sent back to user immediately if output buffer is enabled. + * @param string string to be output + */ + public function write($str) + { + echo $str; + } + + /** + * Sends a file back to user. + * Make sure not to output anything else after calling this method. + * @param string file name + * @throws TInvalidDataValueException if the file cannot be found + */ + public function writeFile($fileName) + { + static $defaultMimeTypes=array( + 'css'=>'text/css', + 'gif'=>'image/gif', + 'jpg'=>'image/jpeg', + 'jpeg'=>'image/jpeg', + 'htm'=>'text/html', + 'html'=>'text/html', + 'js'=>'javascript/js' + ); + + if(!is_file($fileName)) + throw new TInvalidDataValueException('httpresponse_file_inexistent',$fileName); + header('Pragma: public'); + header('Expires: 0'); + header('Cache-Component: must-revalidate, post-check=0, pre-check=0'); + $mimeType='text/plain'; + if(function_exists('mime_content_type')) + $mimeType=mime_content_type($fileName); + else + { + $ext=array_pop(explode('.',$fileName)); + if(isset($defaultMimeTypes[$ext])) + $mimeType=$defaultMimeTypes[$ext]; + } + $fn=basename($fileName); + header("Content-type: $mimeType"); + header('Content-Length: '.filesize($fileName)); + header("Content-Disposition: attachment; filename=\"$fn\""); + header('Content-Transfer-Encoding: binary'); + readfile($fileName); + } + + /** + * Redirects the browser to the specified URL. + * The current application will be terminated after this method is invoked. + * @param string URL to be redirected to + */ + public function redirect($url) + { + header('Location:'.$url); + exit(); + } + + /** + * Outputs the buffered content. + */ + public function flush() + { + if($this->_bufferOutput) + ob_flush(); + } + + /** + * Clears any existing buffered content. + */ + public function clear() + { + if($this->_bufferOutput) + ob_clean(); + } + + /** + * Sends a header. + * @param string header + */ + public function appendHeader($value) + { + header($value); + } + + /** + * Writes a log message into system log. + * @param string message to be written + * @param integer priority level of this message + * @see http://us2.php.net/manual/en/function.syslog.php + */ + public function appendLog($message,$priority=LOG_INFO) + { + syslog($priority,$message); + } + + /** + * Sends a cookie. + * Do not call this method directly. Operate with the result of {@link getCookies} instead. + * @param THttpCookie cook to be sent + */ + public function addCookie($cookie) + { + setcookie($cookie->getName(),$cookie->getValue(),$cookie->getExpire(),$cookie->getPath(),$cookie->getDomain(),$cookie->getSecure()); + } + + /** + * Deletes a cookie. + * Do not call this method directly. Operate with the result of {@link getCookies} instead. + * @param THttpCookie cook to be deleted + */ + public function removeCookie($cookie) + { + setcookie($cookie->getName(),null,0,$cookie->getPath(),$cookie->getDomain(),$cookie->getSecure()); + } +} + +?> \ No newline at end of file diff --git a/framework/Web/THttpSession.php b/framework/Web/THttpSession.php new file mode 100644 index 00000000..fc8f99c6 --- /dev/null +++ b/framework/Web/THttpSession.php @@ -0,0 +1,504 @@ + + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Revision: $ $Date: $ + * @package System.Web + */ + +/** + * THttpSession class + * + * THttpSession provides session-level data management and the related configurations. + * To start the session, call {@open}; to complete and send out session data, call {@close}; + * to destroy the session, call {@destroy}. If AutoStart is true, then the session + * will be started once the session module is loaded and initialized. + * + * To access data stored in session, use the Items property. For example, + * + * $session=new THttpSession; + * $session->open(); + * foreach($session->Items as $key=>$value) + * ; // read data in session + * $session->Items['key']=$data; // store new data into session + * + * + * The following configurations are available for session: + * AutoStart, Cookie, CacheExpire, CacheLimiter, SavePath, Storage, GCProbability, CookieUsage, Timeout. + * See the corresponding setter and getter documentation for more information. + * Note, these properties must be set before the session is started. + * + * THttpSession can be inherited with customized session storage method. + * Override {@link _open}, {@link _close}, {@link _read}, {@link _write}, {@link _destroy} and {@link _gc} + * and set Storage as 'user' to store session using methods other than files and shared memory. + * + * @author Qiang Xue + * @version $Revision: $ $Date: $ + * @package System.Web + * @since 3.0 + */ +class THttpSession extends TComponent implements IModule +{ + /** + * @var string ID of this module + */ + private $_id; + /** + * @var THttpSessionCollection list of session variables + */ + private $_items; + /** + * @var boolean whether this module has been initialized + */ + private $_initialized=false; + /** + * @var boolean whether the session has started + */ + private $_started=false; + /** + * @var boolean whether the session should be started when the module is initialized + */ + private $_autoStart=false; + /** + * @var THttpCookie cookie to be used to store session ID and other data + */ + private $_cookie=null; + + /** + * Initializes the module. + * This method is required by IModule. + * If AutoStart is true, the session will be started. + * @param IApplication prado application instance + */ + public function init($application,$config) + { + if($this->_autoStart) + session_start(); + $this->_initialized=true; + } + + /** + * Starts the session if it has not started yet. + */ + public function open() + { + if(!$this->_started) + { + if($this->_cookie!==null) + session_set_cookie_params($this->_cookie->getExpire(),$this->_cookie->getPath(),$this->_cookie->getDomain(),$this->_cookie->getSecure()); + session_start(); + $this->_started=true; + } + } + + /** + * Ends the current session and store session data. + */ + public function close() + { + if($this->_started) + { + session_write_close(); + $this->_started=false; + } + } + + /** + * Destroys all data registered to a session. + */ + public function destroy() + { + if($this->_started) + { + session_destroy(); + $this->_started=false; + } + } + + /** + * @return string the ID of this session module (not session ID) + */ + public function getID() + { + return $this->_id; + } + + /** + * @param string the ID of this session module (not session ID) + */ + public function setID($value) + { + $this->_id=$value; + } + + /** + * @return THttpSessionCollection list of session variables + */ + public function getItems() + { + if($this->_items===null) + $this->_items=new THttpSessionCollection($_SESSION); + return $this->_items; + } + + /** + * @return boolean whether the session has started + */ + public function getIsStarted() + { + return $this->_started; + } + + /** + * @return string the current session ID + */ + public function getSessionID() + { + return session_id(); + } + + /** + * @param string the session ID for the current session + * @throws TInvalidOperationException if session is started already + */ + public function setSessionID($value) + { + if($this->_started) + throw new TInvalidOperationException('httpsession_sessionid_unchangeable'); + else + session_id($value); + } + + /** + * @return string the current session name + */ + public function getSessionName() + { + return session_name(); + } + + /** + * @param string the session name for the current session, must be an alphanumeric string, defaults to PHPSESSID + * @throws TInvalidOperationException if session is started already + */ + public function setSessionName($value) + { + if($this->_started) + throw new TInvalidOperationException('httpsession_sessionname_unchangeable'); + else if(ctype_alnum($value)) + session_name($value); + else + throw new TInvalidDataValueException('httpsession_sessionname_invalid',$name); + } + + /** + * @return string the current session save path, defaults to '/tmp'. + */ + public function getSavePath() + { + return session_save_path(); + } + + /** + * @param string the current session save path + * @throws TInvalidOperationException if session is started already + */ + public function setSavePath($value) + { + if($this->_started) + throw new TInvalidOperationException('httpsession_cachelimiter_unchangeable'); + else if(is_dir($value)) + session_save_path($value); + else + throw new TInvalidDataValueException('httpsession_savepath_invalid',$value); + } + + /** + * @return string (files|mm|user) storage mode of session, defaults to 'files'. + */ + public function getStorage() + { + return session_module_name(); + } + + /** + * @param string (files|mm|user) storage mode of session. By default, the session + * data is stored in files. You may change to shared memory (mm) for better performance. + * Or you may choose your own storage (user). If you do so, make sure you + * override {@link _open}, {@link _close}, {@link _read}, {@link _write}, + * {@link _destroy}, and {@link _gc}. + * @throws TInvalidOperationException if session is started already + */ + public function setStorage($value) + { + if($this->_started) + throw new TInvalidOperationException('httpsession_storage_unchangeable'); + else + { + $value=TPropertyValue::ensureEnum($value,array('files','mm','user')); + if($value==='user') + session_set_save_handler(array($this,'_open'),array($this,'_close'),array($this,'_read'),array($this,'_write'),array($this,'_destroy'),array($this,'_gc')); + session_module_name($value); + } + } + + /** + * @return THttpCookie cookie that will be used to store session ID + */ + public function getCookie() + { + if($this->_cookie===null) + $this->_cookie=new THttpCookie($this->getSessionName(),$this->getSessionID()); + return $this->_cookie; + } + + /** + * @return string (none|allow|only) how to use cookie to store session ID + * 'none' means not using cookie, 'allow' means using cookie, and 'only' means using cookie only, defaults to 'allow'. + */ + public function getCookieMode() + { + if(ini_get('session.use_cookies')==='0') + return 'none'; + else if(ini_get('session.use_only_cookies')==='0') + return 'allow'; + else + return 'only'; + } + + /** + * @param string (none|allow|only) how to use cookie to store session ID + * 'none' means not using cookie, 'allow' means using cookie, and 'only' means using cookie only. + * @throws TInvalidOperationException if session is started already + */ + public function setCookieMode($value) + { + if($this->_started) + throw new TInvalidOperationException('httpsession_cookiemode_unchangeable'); + else + { + $value=TPropertyValue::ensureEnum($value,array('none','allow','only')); + if($value==='none') + ini_set('session.use_cookies','0'); + else if($value==='allow') + { + ini_set('session.use_cookies','1'); + ini_set('session.use_only_cookies','0'); + } + else + { + ini_set('session.use_cookies','1'); + ini_set('session.use_only_cookies','1'); + } + } + } + + /** + * @return boolean whether the session should be automatically started when the session module is initialized, defaults to false. + */ + public function getAutoStart() + { + return $this->_autoStart; + } + + /** + * @param boolean whether the session should be automatically started when the session module is initialized, defaults to false. + * @throws TInvalidOperationException if session is started already + */ + public function setAutoStart($value) + { + if($this->_initialized) + throw new TInvalidOperationException('httpsession_autostart_unchangeable'); + else + $this->_autoStart=TPropertyValue::ensureBoolean($value); + } + + /** + * @return integer the probability (percentage) that the gc (garbage collection) process is started on every session initialization, defaults to 1 meaning 1% chance. + */ + public function getGCProbability() + { + return TPropertyValue::ensureInteger(ini_get('session.gc_probability')); + } + + /** + * @param integer the probability (percentage) that the gc (garbage collection) process is started on every session initialization. + * @throws TInvalidOperationException if session is started already + * @throws TInvalidDataValueException if the value is beyond [0,100]. + */ + public function setGCProbability($value) + { + if($this->_started) + throw new TInvalidOperationException('httpsession_gcprobability_unchangeable'); + else + { + $value=TPropertyValue::ensureInteger($value); + if($value>=0 && $value<=100) + { + ini_set('session.gc_probability',$value); + ini_set('session.gc_divisor','100'); + } + else + throw new TInvalidDataValueException('httpsession_gcprobability_invalid',$value); + } + } + + /** + * @return boolean whether transparent sid support is enabled or not, defaults to false. + */ + public function getUseTransparentSessionID() + { + return ini_get('session.use_trans_sid')==='1'?true:false; + } + + /** + * @param boolean whether transparent sid support is enabled or not. + */ + public function setUseTransparentSessionID($value) + { + if($this->_started) + throw new TInvalidOperationException('httpsession_transid_unchangeable'); + else + ini_set('session.use_only_cookies',TPropertyValue::ensureBoolean($value)?'1':'0'); + } + + /** + * @return integer the number of seconds after which data will be seen as 'garbage' and cleaned up, defaults to 1440 seconds. + */ + public function getTimeout() + { + return TPropertyValue::ensureInteger(ini_get('session.gc_maxlifetime')); + } + + /** + * @param integer the number of seconds after which data will be seen as 'garbage' and cleaned up + * @throws TInvalidOperationException if session is started already + */ + public function setTimeout($value) + { + if($this->_started) + throw new TInvalidOperationException('httpsession_maxlifetime_unchangeable'); + else + ini_set('session.gc_maxlifetime',$value); + } + + /** + * Session open handler. + * This method should be overriden if session Storage is set as 'user'. + * @param string session save path + * @param string session name + * @return boolean whether session is opened successfully + */ + public function _open($savePath,$sessionName) + { + return true; + } + + /** + * Session close handler. + * This method should be overriden if session Storage is set as 'user'. + * @return boolean whether session is closed successfully + */ + public function _close() + { + return true; + } + + /** + * Session read handler. + * This method should be overriden if session Storage is set as 'user'. + * @param string session ID + * @return string the session data + */ + public function _read($id) + { + return ''; + } + + /** + * Session write handler. + * This method should be overriden if session Storage is set as 'user'. + * @param string session ID + * @param string session data + * @return boolean whether session write is successful + */ + public function _write($id,$data) + { + return true; + } + + /** + * Session destroy handler. + * This method should be overriden if session Storage is set as 'user'. + * @param string session ID + * @return boolean whether session is destroyed successfully + */ + public function _destroy($id) + { + return true; + } + + /** + * Session GC (garbage collection) handler. + * This method should be overriden if session Storage is set as 'user'. + * @param integer the number of seconds after which data will be seen as 'garbage' and cleaned up. + * @return boolean whether session is GCed successfully + */ + public function _gc($maxLifetime) + { + return true; + } +} + +/** + * THttpSessionCollection class. + * + * THttpSessionCollection implements a collection class to store session data items. + * + * @author Qiang Xue + * @version $Revision: $ $Date: $ + * @package System.Web + * @since 3.0 + */ +class THttpSessionCollection extends TMap +{ + /** + * @var boolean whether the initial session data has been loaded into the collection + */ + private $_initialized=false; + + /** + * Constructor. + * Initializes the list with an array or an iterable object. + * @param array|Iterator the intial data. + */ + public function __construct($data=null) + { + parent::__construct($data); + $this->_initialized=true; + } + + /** + * Adds the item into session. + * This method will be invoked whenever an item is added to the collection. + */ + protected function addedItem($key,$value) + { + if($this->_initialized) + $_SESSION[$key]=$value; + } + + /** + * Removes the item from session. + * This method will be invoked whenever an item is removed from the collection. + */ + protected function removedItem($key,$value) + { + unset($_SESSION[$key]); + } +} +?> \ No newline at end of file diff --git a/framework/Web/THttpUtility.php b/framework/Web/THttpUtility.php new file mode 100644 index 00000000..9d2aa7b2 --- /dev/null +++ b/framework/Web/THttpUtility.php @@ -0,0 +1,33 @@ +'%25',"\t"=>'\t',"\n"=>'\n',"\r"=>'\r','"'=>'\"','\''=>'\\\'','\\'=>'\\\\')); + else + return strtr($js,array("\t"=>'\t',"\n"=>'\n',"\r"=>'\r','"'=>'\"','\''=>'\\\'','\\'=>'\\\\')); + } +} + +?> \ No newline at end of file diff --git a/framework/Web/UI/TClientScriptManager.php b/framework/Web/UI/TClientScriptManager.php new file mode 100644 index 00000000..0f760251 --- /dev/null +++ b/framework/Web/UI/TClientScriptManager.php @@ -0,0 +1,236 @@ +_owner=$owner; + } + + final public function getPostBackEventReference($options) + { + if($options->RequiresJavaScriptProtocol) + $str='javascript:'; + else + $str=''; + if($options->AutoPostBack) + $str.="setTimeout('"; + if(!$options->PerformValidation && !$options->TrackFocus && $options->ClientSubmit && $options->ActionUrl==='') + { + $this->_owner->registerPostBackScript(); + $postback="__doPostBack('".$options->TargetControl->getUniqueID()."','".THttpUtility::quoteJavaScriptString($options->Argument)."')"; + if($options->AutoPostBack) + { + $str.=THttpUtility::quoteJavaScriptString($postback); + $str.="',0)"; + } + else + $str.=$postback; + return $str; + } + $str.='WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions("'; + $str.=$options->TargetControl->getUniqueID().'", '; + if(($arg=$options->Argument)==='') + $str.='"", '; + else + $str.='"'.THttpUtility::quoteJavaScriptString($arg).'", '; + $flag=false; + if($options->PerformValidation) + { + $flag=true; + $str.='true, '; + } + else + $str.='false, '; + if($options->ValidationGroup!=='') + { + $flag=true; + $str.='"'.$options->ValidationGroup.'", '; + } + else + $str.='"", '; + if($options->ActionUrl!=='') + { + $flag=true; + $this->_owner->setContainsCrossPagePost(true); + $str.='"'.THttpUtility::quoteJavaScriptString($options->ActionUrl).'", '; + } + else + $str.='"", '; + if($options->TrackFocus) + { + $this->_owner->registerFocusScript(); + $flag=true; + $str.='true, '; + } + else + $str.='false, '; + if($options->ClientSubmit) + { + $flag=true; + $this->_owner->registerPostBackScript(); + $str.='true))'; + } + else + $str.='false))'; + if($options->AutoPostBack) + $str.="', 0)"; + if($flag) + { + $this->_owner->registerWebFormsScript(); + return $str; + } + else + return ''; + } + + final public function isHiddenFieldRegistered($key) + { + return isset($this->_hiddenFields[$key]); + } + + final public function isClientScriptBlockRegistered($key) + { + return isset($this->_scriptBlocks[$key]); + } + + final public function isClientScriptIncludeRegistered($key) + { + return isset($this->_scriptIncludes[$key]); + } + + final public function isStartupScriptRegistered($key) + { + return isset($this->_startupScripts[$key]); + } + + final public function isOnSubmitStatementRegistered($key) + { + return isset($this->_onSubmitStatements[$key]); + } + + final public function registerArrayDeclaration($name,$value) + { + $this->_arrayDeclares[$name][]=$value; + } + + final public function registerClientScriptBlock($key,$script) + { + $this->_criptBlocks[$key]=$script; + } + + final public function registerClientScriptInclude($key,$url) + { + $this->_scriptIncludes[$key]=$url; + } + + // todo: register an asset + + final public function registerHiddenField($name,$value) + { + $this->_hiddenFields[$name]=$value; + } + + final public function registerOnSubmitStatement($key,$script) + { + $this->_onSubmitStatements[$key]=$script; + } + + final public function registerStartupScript($key,$script) + { + $this->_startupScripts[$key]=$script; + } + + final public function renderArrayDeclarations($writer) + { + if(count($this->_arrayDeclares)) + { + $str="\n\n"; + $writer->write($str); + } + } + + final public function renderClientScriptBlocks($writer) + { + $str=''; + foreach($this->_scriptBlocks as $script) + $str.=$script; + if($this->_owner->getClientOnSubmitEvent()!=='' && $this->_owner->getClientSupportsJavaScript()) + { + $str.="function WebForm_OnSubmit() {\n"; + foreach($this->_onSubmitStatements as $script) + $str.=$script; + $str.="\nreturn true;\n}"; + } + if($str!=='') + $writer->write("\n\n"); + } + + final public function renderClientStartupScripts($writer) + { + if(count($this->_startupScripts)) + { + $str="\n\n"; + $writer->write($str); + } + } + + final public function renderHiddenFields($writer) + { + $str=''; + foreach($this->_hiddenFields as $name=>$value) + { + $value=THttpUtility::htmlEncode($value); + $str.="\n"; + } + if($str!=='') + $writer->write($str); + $this->_hiddenFields=array(); + } + + /** + * @internal + */ + final public function getHasHiddenFields() + { + return count($this->_hiddenFields)>0; + } + + /** + * @internal + */ + final public function getHasSubmitStatements() + { + return count($this->_onSubmitStatements)>0; + } +} + +?> \ No newline at end of file diff --git a/framework/Web/UI/TControl.php b/framework/Web/UI/TControl.php new file mode 100644 index 00000000..0d7fb333 --- /dev/null +++ b/framework/Web/UI/TControl.php @@ -0,0 +1,1518 @@ + + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Revision: $ $Date: $ + * @package System.Web.UI + */ + +/** + * TControl class + * + * TControl is the base class for all components on a page hierarchy. + * It implements the following features for UI-related functionalities: + * - databinding feature + * - naming container and containee relationship + * - parent and child relationship + * - viewstate and controlstate features + * - rendering scheme + * - control lifecycles + * + * A property can be data-bound with an expression. By calling {@link dataBind} + * expressions bound to properties will be evaluated and the results will be + * set to the corresponding properties. + * + * A naming container control implements INamingContainer and ensures that + * its containee controls can be differentiated by their ID property values. + * Naming container and containee realtionship specifies a protocol to uniquely + * identify an arbitrary control on a page hierarchy by an ID path (concatenation + * of all naming containers' IDs and the target control's ID). + * + * Parent and child relationship determines how the presentation of controls are + * enclosed within each other. A parent will determine where to place + * the presentation of its child controls. For example, a TPanel will enclose + * all its child controls' presentation within a div html tag. + * + * Viewstate and controlstate are two approaches to preserve state across + * page postback requests. ViewState is mainly related with UI specific state + * and can be disabled if not needed. ControlState represents crucial logic state + * and cannot be disabled. + * + * Each control on a page will undergo a series of lifecycles, including + * control construction, OnInit, OnLoad, OnPreRender, Render, and OnUnload. + * They work together with page lifecycles to process a page request. + * + * @author Qiang Xue + * @version $Revision: $ $Date: $ + * @package System.Web.UI + * @since 3.0 + */ +class TControl extends TComponent +{ + /** + * format of control ID + */ + const ID_FORMAT='/^[a-zA-Z_]\\w*$/'; + /** + * separator char between IDs in a UniqueID + */ + const ID_SEPARATOR='$'; + /** + * separator char between IDs in a ClientID + */ + const CLIENT_ID_SEPARATOR='_'; + /** + * prefix to an ID automatically generated + */ + const AUTOMATIC_ID_PREFIX='ctl'; + + /** + * the stage of lifecycles that the control is currently at + */ + const CS_CONSTRUCTED=0; + const CS_CHILD_INITIALIZED=1; + const CS_INITIALIZED=2; + const CS_STATE_LOADED=3; + const CS_LOADED=4; + const CS_PRERENDERED=5; + + /** + * State bits. + */ + const IS_ID_SET=0x01; + const IS_INVISIBLE=0x02; + const IS_DISABLE_VIEWSTATE=0x04; + const IS_SKIN_APPLIED=0x08; + const IS_STYLESHEET_APPLIED=0x10; + const IS_DISABLE_THEMING=0x20; + const IS_CHILD_CREATED=0x40; + const IS_CREATING_CHILD=0x80; + + /** + * Indexes for the rare fields. + * In order to save memory, rare fields will only be created if they are needed. + */ + const RF_CONTROLS=0; // cihld controls + const RF_CHILD_STATE=1; // child state field + const RF_NAMED_CONTROLS=2; // list of controls whose namingcontainer is this control + const RF_NAMED_CONTROLS_ID=3; // counter for automatic id + const RF_SKIN_ID=4; // skin ID + const RF_DATA_BINDINGS=5; // data bindings + const RF_EVENTS=6; // event handlers + const RF_CONTROLSTATE=7; // controlstate + const RF_NAMED_OBJECTS=8; // controls declared with ID on template + + /** + * @var string control ID + */ + private $_id=''; + /** + * @var string control unique ID + */ + private $_uid=''; + /** + * @var TControl parent of the control + */ + private $_parent=null; + /** + * @var TPage page that the control resides in + */ + private $_page=null; + /** + * @var TControl naming container of the control + */ + private $_namingContainer=null; + /** + * @var TTemplateControl control whose template contains the control + */ + private $_tplControl=null; + /** + * @var TMap viewstate data + */ + private $_viewState=array(); + /** + * @var integer the current stage of the control lifecycles + */ + private $_stage=0; + /** + * @var integer representation of the state bits + */ + private $_flags=0; + /** + * @var array a collection of rare control data + */ + private $_rf=array(); + + + /** + * Returns a property value by name or a control by ID. + * This overrides the parent implementation by allowing accessing + * a control via its ID using the following syntax, + * + * $menuBar=$this->menuBar; + * + * Note, the control must be configured in the template + * with explicit ID. If the name matches both a property and a control ID, + * the control ID will take the precedence. + * + * @param string the property name or control ID + * @return mixed the property value or the target control + * @throws TInvalidOperationException if the property is not defined. + * @see registerObject + */ + public function __get($name) + { + if(isset($this->_rf[self::RF_NAMED_OBJECTS][$name])) + return $this->_rf[self::RF_NAMED_OBJECTS][$name]; + else + return parent::__get($name); + } + + /** + * @return TControl the parent of this control + */ + public function getParent() + { + return $this->_parent; + } + + /** + * @return TControl the naming container of this control + */ + public function getNamingContainer() + { + if(!$this->_namingContainer && $this->_parent) + $this->_namingContainer=$this->_parent->getNamingContainer(); + return $this->_namingContainer; + } + + /** + * @return TPage the page that contains this control + */ + public function getPage() + { + if(!$this->_page && $this->_parent) + $this->_page=$this->_parent->getPage(); + return $this->_page; + } + + /** + * Sets the page for a control. + * Only framework developers should use this method. + * @param TPage the page that contains this control + */ + public function setPage($page) + { + $this->_page=$page; + } + + /** + * Sets the control whose template contains this control. + * Only framework developers should use this method. + * @param TTemplateControl the control whose template contains this control + */ + public function setTemplateControl($control) + { + $this->_tplControl=$control; + } + + /** + * @return TTemplateControl the control whose template contains this control + */ + public function getTemplateControl() + { + if(!$this->_tplControl && $this->_parent) + $this->_tplControl=$this->_parent->getTemplateControl(); + return $this->_tplControl; + } + + /** + * @return IApplication the application object that the current page is using + */ + public function getApplication() + { + return Prado::getApplication(); + } + + /** + * Returns the id of the control. + * Control ID can be either manually set or automatically generated. + * If $hideAutoID is true, automatically generated ID will be returned as an empty string. + * @param boolean whether to hide automatically generated ID + * @return string the ID of the control + */ + public function getID($hideAutoID=true) + { + if($hideAutoID) + return ($this->_flags & self::IS_ID_SET) ? $this->_id : ''; + else + return $this->_id; + } + + /** + * @param string the new control ID. The value must consist of word characters [a-zA-Z0-9_] only + * @throws TInvalidDataValueException if ID is in a bad format + */ + public function setID($id) + { + if(!preg_match(self::ID_FORMAT,$id)) + throw new TInvalidDataValueException('control_id_invalid',$id,get_class($this)); + $this->_id=$id; + $this->_flags |= self::IS_ID_SET; + $this->clearCachedUniqueID($this instanceof INamingContainer); + if($this->_namingContainer) + $this->_namingContainer->clearNameTable(); + } + + /** + * Returns a unique ID that identifies the control in the page hierarchy. + * A unique ID is the contenation of all naming container controls' IDs and the control ID. + * These IDs are separated by '$' character. + * Control users should not rely on the specific format of UniqueID, however. + * @return string a unique ID that identifies the control in the page hierarchy + */ + public function getUniqueID() + { + if($this->_uid==='') // need to build the UniqueID + { + if($namingContainer=$this->getNamingContainer()) + { + if($this->_page===$namingContainer) + return ($this->_uid=$this->_id); + else if(($prefix=$namingContainer->getUniqueID())==='') + return $this->_id; + else + return ($this->_uid=$prefix.self::ID_SEPARATOR.$this->_id); + } + else // no naming container + return $this->_id; + } + else + return $this->_uid; + } + + /** + * Returns the client ID of the control. + * The client ID can be used to uniquely identify + * the control in client-side scripts (such as JavaScript). + * Do not rely on the explicit format of the return ID. + * @return string the client ID of the control + */ + public function getClientID() + { + return strtr($this->getUniqueID(),self::ID_SEPARATOR,self::CLIENT_ID_SEPARATOR); + } + + /** + * @return string the skin ID of this control + */ + public function getSkinID() + { + return isset($this->_rf[self::RF_SKIN_ID])?$this->_rf[self::RF_SKIN_ID]:''; + } + + /** + * @param string the skin ID of this control + * @throws TInvalidOperationException if the SkinID is set in a stage later than PreInit, or if the skin is applied already. + */ + public function setSkinID($value) + { + if(($this->_flags & self::IS_SKIN_APPLIED) || $this->_stage>=self::CS_CHILD_INITIALIZED) + throw new TInvalidOperationException('control_skinid_unchangeable',get_class($this),$this->getUniqueID()); + else + $this->_rf[self::RF_SKIN_ID]=$value; + } + + /** + * @return boolean whether theming is enabled for this control. + * The theming is enabled if the control and all its parents have it enabled. + */ + public function getEnableTheming() + { + if($this->_flags & self::IS_DISABLE_THEMING) + return false; + else + return $this->_parent?$this->_parent->getEnableTheming():true; + } + + /** + * @param boolean whether to enable theming + * @throws TInvalidOperationException if this method is invoked after OnPreInit + */ + public function setEnableTheming($value) + { + if($this->_stage>=self::CS_CHILD_INITIALIZED) + throw new TInvalidOperationException('control_enabletheming_unchangeable',get_class($this),$this->getUniqueID()); + else if(TPropertyValue::ensureBoolean($value)) + $this->_flags &= ~self::IS_DISABLE_THEMING; + else + $this->_flags |= self::IS_DISABLE_THEMING; + } + + /** + * @return boolean whether the control has child controls + */ + public function getHasControls() + { + return isset($this->_rf[self::RF_CONTROLS]) && $this->_rf[self::RF_CONTROLS]->getCount()>0; + } + + /** + * @return TControlList the child control collection + */ + public function getControls() + { + if(!isset($this->_rf[self::RF_CONTROLS])) + $this->_rf[self::RF_CONTROLS]=new TControlList($this); + return $this->_rf[self::RF_CONTROLS]; + } + + /** + * @param boolean whether the control is visible + */ + public function setVisible($value) + { + if(TPropertyValue::ensureBoolean($value)) + $this->_flags &= ~self::IS_INVISIBLE; + else + $this->_flags |= self::IS_INVISIBLE; + } + + /** + * @return boolean whether the control is visible (default=true). + * A control is visible if all its parents and itself are visible. + */ + public function getVisible() + { + if($this->_flags & self::IS_INVISIBLE) + return false; + else + return $this->_parent?$this->_parent->getVisible():true; + } + + /** + * Returns a value indicating whether the control is enabled. + * A control is enabled if it allows client user interaction. + * If $checkParents is true, all parent controls will be checked, + * and unless they are all enabled, false will be returned. + * The property Enabled is mainly used for {@link TWebControl} + * derived controls. + * @param boolean whether the parents should also be checked enabled + * @return boolean whether the control is enabled. + */ + public function getEnabled($checkParents=false) + { + if($checkParents) + { + for($control=$this;$control;$control=$control->_parent) + if(!$control->getEnabled()) + return false; + return true; + } + else + return $this->getViewState('Enabled',true); + } + + /** + * @param boolean whether the control is to be enabled. + */ + public function setEnabled($value) + { + $this->setViewState('Enabled',TPropertyValue::ensureBoolean($value),true); + } + + /** + * @return boolean whether the control has custom attributes + */ + public function getHasAttributes() + { + if($attributes=$this->getViewState('Attributes',null)) + return $attributes->getCount()>0; + else + return false; + } + + /** + * Returns a value indicating whether this control type can take attributes in template. + * This method can be overriden. + * Only framework developers and control developers should use this method. + * @return boolean whether the control allows attributes in template (default=true) + */ + public function getAllowCustomAttributes() + { + return true; + } + + /** + * Returns the list of custom attributes. + * Custom attributes are name-value pairs that may be rendered + * as HTML tags' attributes. + * @return TMap the list of custom attributes + */ + public function getAttributes() + { + if($attributes=$this->getViewState('Attributes',null)) + return $attributes; + else + { + $attributes=new TMap; + $this->setViewState('Attributes',$attributes,null); + return $attributes; + } + } + + /** + * @return boolean whether viewstate is enabled + */ + public function getEnableViewState() + { + return !($this->_flags & self::IS_DISABLE_VIEWSTATE); + } + + /** + * @param boolean set whether to enable viewstate + */ + public function setEnableViewState($value) + { + if(TPropertyValue::ensureBoolean($value)) + $this->_flags &= ~self::IS_DISABLE_VIEWSTATE; + else + $this->_flags |= self::IS_DISABLE_VIEWSTATE; + } + + /** + * Returns a controlstate value. + * + * This function is mainly used in defining getter functions for control properties + * that must be kept in controlstate. + * @param string the name of the controlstate value to be returned + * @param mixed the default value. If $key is not found in controlstate, $defaultValue will be returned + * @return mixed the controlstate value corresponding to $key + */ + protected function getControlState($key,$defaultValue=null) + { + return isset($this->_rf[self::RF_CONTROLSTATE][$key])?$this->_rf[self::RF_CONTROLSTATE][$key]:$defaultValue; + } + + /** + * Sets a controlstate value. + * + * This function is very useful in defining setter functions for control properties + * that must be kept in controlstate. + * Make sure that the controlstate value must be serializable and unserializable. + * @param string the name of the controlstate value + * @param mixed the controlstate value to be set + * @param mixed default value. If $value===$defaultValue, the item will be cleared from controlstate + */ + protected function setControlState($key,$value,$defaultValue=null) + { + if($value===$defaultValue) + unset($this->_rf[self::RF_CONTROLSTATE][$key]); + else + $this->_rf[self::RF_CONTROLSTATE][$key]=$value; + } + + /** + * Returns a viewstate value. + * + * This function is very useful in defining getter functions for component properties + * that must be kept in viewstate. + * @param string the name of the viewstate value to be returned + * @param mixed the default value. If $key is not found in viewstate, $defaultValue will be returned + * @return mixed the viewstate value corresponding to $key + */ + protected function getViewState($key,$defaultValue=null) + { + return isset($this->_viewState[$key])?$this->_viewState[$key]:$defaultValue; + } + + /** + * Sets a viewstate value. + * + * This function is very useful in defining setter functions for control properties + * that must be kept in viewstate. + * Make sure that the viewstate value must be serializable and unserializable. + * @param string the name of the viewstate value + * @param mixed the viewstate value to be set + * @param mixed default value. If $value===$defaultValue, the item will be cleared from the viewstate. + */ + protected function setViewState($key,$value,$defaultValue=null) + { + if($value===$defaultValue) + unset($this->_viewState[$key]); + else + $this->_viewState[$key]=$value; + } + + /** + * Sets up the binding between a property (or property path) and an expression. + * The context of the expression is the control itself. + * @param string the property name, or property path + * @param string the expression + */ + public function bindProperty($name,$expression) + { + $this->_rf[self::RF_DATA_BINDINGS][$name]=$expression; + } + + /** + * Breaks the binding between a property (or property path) and an expression. + * @param string the property name (or property path) + */ + public function unbindProperty($name) + { + unset($this->_rf[self::RF_DATA_BINDINGS][$name]); + } + + /** + * Performs the databinding for this component. + * Databinding a property includes evaluating the binded expression + * and setting the property with the evaluation result. + * @param boolean whether to raise OnDataBinding event. + * @throws TInvalidOperationException if some bounded property is invalid + * @throws TExpressionInvalidException if some bounded expression is invalid + */ + public function dataBind($raiseOnDataBinding=true) + { + if(isset($this->_rf[self::RF_DATA_BINDINGS])) + { + foreach($this->_rf[self::RF_DATA_BINDINGS] as $property=>$expression) + $this->setPropertyByPath($property,$this->evaluateExpression($expression)); + if($raiseOnDataBinding) + $this->onDataBinding(null); + if(isset($this->_rf[self::RF_CONTROLS])) + { + foreach($this->_rf[self::RF_CONTROLS] as $control) + if($control instanceof TControl) + $control->dataBind($raiseOnDataBinding); + } + } + } + + /** + * @return boolean whether child controls have been created + */ + final protected function getChildControlsCreated() + { + return ($this->_flags & self::IS_CHILD_CREATED)!==0; + } + + /** + * Sets a value indicating whether child controls are created. + * If false, any existing child controls will be cleared up. + * @param boolean whether child controls are created + */ + final protected function setChildControlsCreated($value) + { + if($value) + $this->_flags |= self::IS_CHILD_CREATED; + else + { + if($this->hasControl() && ($this->_flags & self::IS_CHILD_CREATED)) + $this->getControls()->clear(); + $this->_flags &= ~self::IS_CHILD_CREATED; + } + } + + /** + * Ensures child controls are created. + * If child controls are not created yet, this method will invoke + * {@link createChildControl} to create them. + */ + public function ensureChildControls() + { + if(!($this->_flags & self::IS_CHILD_CREATED) && !($this->_flags & self::IS_CREATING_CHILD)) + { + try + { + $this->_flags |= self::IS_CREATING_CHILD; + $this->createChildControls(); + $this->_flags &= ~self::IS_CREATING_CHILD; + $this->_flags |= self::IS_CHILD_CREATED; + } + catch(Exception $e) + { + $this->_flags &= ~self::IS_CREATING_CHILD; + $this->_flags |= self::IS_CHILD_CREATED; + throw $e; + } + } + } + + /** + * Creates child controls. + * This method can be overriden for controls who want to have their controls. + * Do not call this method directly. Instead, call {@link ensureChildControls} + * to ensure child controls are created only once. + */ + protected function createChildControls() + { + } + + /** + * Finds a control by ID path within the current naming container. + * The current naming container is either the control itself + * if it implements {@link INamingContainer} or the control's naming container. + * The ID path is an ID sequence separated by {@link TControl::ID_SEPARATOR}. + * For example, 'Repeater1:Item1:Button1' looks for a control with ID 'Button1' + * whose naming container is 'Item1' whose naming container is 'Repeater1'. + * @param string ID of the control to be looked up + * @return TControl|null the control found, null if not found + * @throws TInvalidDataValueException if a control's ID is found not unique within its naming container. + */ + public function findControl($id) + { + $container=($this instanceof INamingContainer)?$this:$this->getNamingContainer(); + if(!$container || !$container->getHasControls()) + return null; + if(!isset($container->_rf[self::RF_NAMED_CONTROLS])) + { + $container->_rf[self::RF_NAMED_CONTROLS]=array(); + $container->fillNameTable($container,$container->_rf[self::RF_CONTROLS]); + } + if(($pos=strpos($id,self::ID_SEPARATOR))===false) + return isset($container->_rf[self::RF_NAMED_CONTROLS][$id])?$container->_rf[self::RF_NAMED_CONTROLS][$id]:null; + else + { + $cid=substr($id,0,$pos); + $sid=substr($id,$pos+1); + if(isset($container->_rf[self::RF_NAMED_CONTROLS][$cid])) + return $container->_rf[self::RF_NAMED_CONTROLS][$cid]->findControl($sid); + else + return null; + } + } + + /** + * Resets the control as a naming container. + * Only framework developers should use this method. + */ + public function clearNamingContainer() + { + unset($this->_rf[self::RF_NAMED_CONTROLS_ID]); + $this->clearNameTable(); + } + + /** + * Registers an object by a name. + * A registered object can be accessed like a public member variable. + * This method should only be used by framework and control developers. + * @param string name of the object + * @param object object to be declared + * @see __get + */ + public function registerObject($name,$object) + { + $this->_rf[self::RF_NAMED_OBJECTS][$name]=$object; + } + + /** + * This method is invoked after the control is instantiated by a template. + * When this method is invoked, the control should have a valid TemplateControl + * and has its properties initialized according to template configurations. + * The control, however, has not been added to the page hierarchy yet. + * The default implementation of this method will invoke + * the potential parent control's {@link addParsedObject} to add the control as a child. + * This method can be overriden. + * @param TControl potential parent of this control + * @see addParsedObject + */ + public function createdOnTemplate($parent) + { + $parent->addParsedObject($this); + } + + /** + * Processes an object that is created during parsing template. + * The object can be either a control or a static text string. + * By default, the object will be added into the child control collection. + * This method can be overriden to customize the handling of newly created objects in template. + * Only framework developers and control developers should use this method. + * @param string|TComponent text string or component parsed and instantiated in template + * @see createdOnTemplate + */ + public function addParsedObject($object) + { + $this->getControls()->add($object); + } + + /** + * Clears up the child state data. + * After a control loads its state, those state that do not belong to + * any existing child controls are stored as child state. + * This method will remove these state. + * Only frameworker developers and control developers should use this method. + */ + final protected function clearChildState() + { + unset($this->_rf[self::RF_CHILD_STATE]); + } + + /** + * @param TControl the potential ancestor control + * @return boolean if the control is a descendent (parent, parent of parent, etc.) + * of the specified control + */ + final protected function isDescendentOf($ancestor) + { + $control=$this; + while($control!==$ancestor && $control->_parent) + $control=$control->_parent; + return $control===$ancestor; + } + + /** + * Adds a control into the child collection of the control. + * Control lifecycles will be caught up during the addition. + * Only framework developers should use this method. + * @param TControl the new child control + */ + public function addedControl($control) + { + if($control->_parent) + $control->_parent->getControls()->remove($control); + $control->_parent=$this; + $control->_page=$this->getPage(); + $namingContainer=($this instanceof INamingContainer)?$this:$this->_namingContainer; + if($namingContainer) + { + $control->_namingContainer=$namingContainer; + if($control->_id==='') + $control->generateAutomaticID(); + else + $namingContainer->clearNameTable(); + } + + if($this->_stage>=self::CS_INITIALIZED) + { + $control->initRecursive($namingContainer); + if($this->_stage>=self::CS_STATE_LOADED) + { + if(isset($this->_rf[self::RF_CHILD_STATE])) + $state=$this->_rf[self::RF_CHILD_STATE]->remove($control->_id); + else + $state=null; + $control->loadStateRecursive($state,!($this->_flags & self::IS_DISABLE_VIEWSTATE)); + if($this->_stage>=self::CS_LOADED) + { + $control->loadRecursive(); + if($this->_stage>=self::CS_PRERENDERED) + $control->preRenderRecursive(); + } + } + } + } + + /** + * Removes a control from the child collection of the control. + * Only framework developers should use this method. + * @param TControl the child control removed + */ + public function removedControl($control) + { + if($this->_namingContainer) + $this->_namingContainer->clearNameTable(); + $control->unloadRecursive(); + $control->_parent=null; + $control->_page=null; + $control->_namingContainer=null; + $control->_tplControl=null; + if(!($control->_flags & self::IS_ID_SET)) + $control->_id=''; + $control->clearCachedUniqueID(true); + } + + /** + * Performs the Init step for the control and all its child controls. + * Only framework developers should use this method. + * @param TControl the naming container control + */ + protected function initRecursive($namingContainer) + { + $this->ensureChildControls(); + if($this->getHasControls()) + { + if($this instanceof INamingContainer) + $namingContainer=$this; + $page=$this->getPage(); + foreach($this->_rf[self::RF_CONTROLS] as $control) + { + if($control instanceof TControl) + { + $control->_namingContainer=$namingContainer; + $control->_page=$page; + if($control->_id==='' && $namingContainer) + $control->generateAutomaticID(); + $control->initRecursive($namingContainer); + } + } + } + if($this->_stage_stage=self::CS_CHILD_INITIALIZED; + if(($page=$this->getPage()) && $page->getContainsTheme() && $this->getEnableTheming() && !($this->_flags & self::IS_SKIN_APPLIED)) + { + $page->applyControlSkin($this); + $this->_flags |= self::IS_SKIN_APPLIED; + } + $this->onInit(null); + $this->_stage=self::CS_INITIALIZED; + } + } + + /** + * Performs the Load step for the control and all its child controls. + * Only framework developers should use this method. + */ + protected function loadRecursive() + { + if($this->_stageonLoad(null); + if($this->getHasControls()) + { + foreach($this->_rf[self::RF_CONTROLS] as $control) + if($control instanceof TControl) + $control->loadRecursive(); + } + if($this->_stage_stage=self::CS_LOADED; + } + + /** + * Performs the PreRender step for the control and all its child controls. + * Only framework developers should use this method. + */ + protected function preRenderRecursive() + { + if($this->getVisible()) + { + $this->_flags &= ~self::IS_INVISIBLE; + $this->onPreRender(null); + if($this->getHasControls()) + { + foreach($this->_rf[self::RF_CONTROLS] as $control) + if($control instanceof TControl) + $control->preRenderRecursive(); + } + } + else + { + $this->_flags |= self::IS_INVISIBLE; + } + $this->_stage=self::CS_PRERENDERED; + } + + /** + * Performs the Unload step for the control and all its child controls. + * Only framework developers should use this method. + */ + protected function unloadRecursive() + { + if(!($this->_flags & self::IS_ID_SET)) + $this->_id=''; + if($this->getHasControls()) + { + foreach($this->_rf[self::RF_CONTROLS] as $control) + if($control instanceof TControl) + $control->unloadRecursive(); + } + $this->onUnload(null); + } + + /** + * This method is invoked when the control enters 'Init' stage. + * The method raises 'Init' event. + * If you override this method, be sure to call the parent implementation + * so that the event handlers can be invoked. + * @param TEventParameter event parameter to be passed to the event handlers + */ + protected function onInit($param) + { + $this->raiseEvent('Init',$this,$param); + } + + /** + * This method is invoked when the control enters 'Load' stage. + * The method raises 'Load' event. + * If you override this method, be sure to call the parent implementation + * so that the event handlers can be invoked. + * @param TEventParameter event parameter to be passed to the event handlers + */ + protected function onLoad($param) + { + $this->raiseEvent('Load',$this,$param); + } + + /** + * Raises 'DataBinding' event. + * This method is invoked when {@link dataBind} is invoked. + * @param TEventParameter event parameter to be passed to the event handlers + */ + protected function onDataBinding($param) + { + $this->raiseEvent('DataBinding',$this,$param); + } + + + /** + * This method is invoked when the control enters 'Unload' stage. + * The method raises 'Unload' event. + * If you override this method, be sure to call the parent implementation + * so that the event handlers can be invoked. + * @param TEventParameter event parameter to be passed to the event handlers + */ + protected function onUnload($param) + { + $this->raiseEvent('Unload',$this,$param); + } + + /** + * This method is invoked when the control enters 'PreRender' stage. + * The method raises 'PreRender' event. + * If you override this method, be sure to call the parent implementation + * so that the event handlers can be invoked. + * @param TEventParameter event parameter to be passed to the event handlers + */ + protected function onPreRender($param) + { + $this->raiseEvent('PreRender',$this,$param); + } + + /** + * Invokes the parent's onBubbleEvent method. + * A control who wants to bubble an event must call this method in its onEvent method. + * @param TControl sender of the event + * @param TEventParameter event parameter + * @see onBubbleEvent + */ + protected function raiseBubbleEvent($sender,$param) + { + $control=$this; + while($control=$control->_parent) + { + if($control->onBubbleEvent($sender,$param)) + break; + } + } + + /** + * This method responds to a bubbled event. + * This method should be overriden to provide customized response to a bubbled event. + * Check the type of event parameter to determine what event is bubbled currently. + * @param TControl sender of the event + * @param TEventParameter event parameters + * @return boolean true if the event bubbling is handled and no more bubbling. + * @see raiseBubbleEvent + */ + protected function onBubbleEvent($sender,$param) + { + return false; + } + + /** + * Renders the control. + * Only when the control is visible will the control be rendered. + * @param THtmlTextWriter the writer used for the rendering purpose + */ + protected function renderControl($writer) + { + if(!($this->_flags & self::IS_INVISIBLE)) + $this->render($writer); + } + + /** + * Renders the control. + * This method is invoked by {@link renderControl} when the control is visible. + * You can override this method to provide customized rendering of the control. + * By default, the control simply renders all its child contents. + * @param THtmlTextWriter the writer used for the rendering purpose + */ + protected function render($writer) + { + $this->renderChildren($writer); + } + + /** + * Renders the children of the control. + * This method iterates through all child controls and static text strings + * and renders them in order. + * @param THtmlTextWriter the writer used for the rendering purpose + */ + protected function renderChildren($writer) + { + if($this->getHasControls()) + { + foreach($this->_rf[self::RF_CONTROLS] as $control) + { + if($control instanceof TControl) + $control->renderControl($writer); + else if(is_string($control)) + $writer->write($control); + } + } + } + + /** + * This method is invoked when control state is to be saved. + * You can override this method to do last step state saving. + * Parent implementation must be invoked. + * @param TEventParameter event parameter + */ + protected function onSaveState($param) + { + $this->setViewState('Visible',!($this->_flags & self::IS_INVISIBLE),true); + $this->raiseEvent('SaveState',$this,$param); + } + + /** + * This method is invoked right after the control has loaded its state. + * You can override this method to initialize data from the control state. + * Parent implementation must be invoked. + * @param TEventParameter + */ + protected function onLoadState($param) + { + $this->setVisible($this->getViewState('Visible',true)); + $this->raiseEvent('LoadState',$this,$param); + } + + /** + * Loads state (viewstate and controlstate) into a control and its children. + * @param TMap the collection of the state + * @param boolean whether the viewstate should be loaded + */ + final protected function loadStateRecursive(&$state,$needViewState=true) + { + // A null state means the stateful properties all take default values. + // So if the state is enabled, we have to assign the null value. + $needViewState=($needViewState && !($this->_flags & self::IS_DISABLE_VIEWSTATE)); + if(is_array($state)) + { + if(isset($state[1])) + { + $this->_rf[self::RF_CONTROLSTATE]=&$state[1]; + unset($state[1]); + } + else + unset($this->_rf[self::RF_CONTROLSTATE]); + if($needViewState) + { + if(isset($state[0])) + $this->_viewState=&$state[0]; + else + $this->_viewState=array(); + } + unset($state[0]); + if($this->getHasControls()) + { + foreach($this->_rf[self::RF_CONTROLS] as $control) + { + if($control instanceof TControl) + { + if(isset($state[$control->_id])) + { + $s=&$state[$control->_id]; + unset($state[$control->_id]); + } + else + $s=null; + $control->loadStateRecursive($s,$needViewState); + } + } + } + if(!empty($state)) + $this->_rf[self::RF_CHILD_STATE]=&$state; + } + else + { + unset($this->_rf[self::RF_CONTROLSTATE]); + if($needViewState) + $this->_viewState=array(); + if($this->getHasControls()) + { + foreach($this->_rf[self::RF_CONTROLS] as $control) + { + $s=null; + if($control instanceof TControl) + $control->loadStateRecursive($s,$needViewState); + } + } + } + $this->onLoadState(null); + $this->_stage=self::CS_STATE_LOADED; + } + + /** + * Saves the all control state (viewstate and controlstate) as a collection. + * @param boolean whether the viewstate should be saved + * @return TMap the collection of the control state (including its children's state). + */ + final protected function &saveStateRecursive($needViewState=true) + { + $this->onSaveState(null); + $needViewState=($needViewState && !($this->_flags & self::IS_DISABLE_VIEWSTATE)); + $state=array(); + if($this->getHasControls()) + { + foreach($this->_rf[self::RF_CONTROLS] as $control) + { + if($control instanceof TControl) + { + $cs=&$control->saveStateRecursive($needViewState); + if(!empty($cs)) + $state[$control->_id]=&$cs; + } + } + } + if($needViewState && !empty($this->_viewState)) + $state[0]=&$this->_viewState; + if(isset($this->_rf[self::RF_CONTROLSTATE]) && !empty($this->_rf[self::RF_CONTROLSTATE])) + $state[1]=&$this->_rf[self::RF_CONTROLSTATE]; + return $state; + } + + /** + * Applies a stylesheet skin to a control. + * @param TPage the page containing the control + * @throws TInvalidOperationException if the stylesheet skin is applied already + */ + public function applyStyleSheetSkin($page) + { + if($page && !($this->_flags & self::IS_STYLESHEET_APPLIED)) + { + $page->applyControlStyleSheet($this); + $this->_flags |= self::IS_STYLESHEET_APPLIED; + } + else if($this->_flags & self::IS_STYLESHEET_APPLIED) + throw new TInvalidOperationException('control_stylesheet_applied',get_class($this),$this->getUniqueID()); + } + + /** + * Clears the cached UniqueID. + * If $recursive=true, all children's cached UniqueID will be cleared as well. + * @param boolean whether the clearing is recursive. + */ + private function clearCachedUniqueID($recursive) + { + $this->_uid=''; + if($recursive && isset($this->_rf[self::RF_CONTROLS])) + { + foreach($this->_rf[self::RF_CONTROLS] as $control) + if($control instanceof TControl) + $control->clearCachedUniqueID($recursive); + } + } + + /** + * Generates an automatic ID for the control. + */ + private function generateAutomaticID() + { + $this->_flags &= ~self::IS_ID_SET; + if(!isset($this->_namingContainer->_rf[self::RF_NAMED_CONTROLS_ID])) + $this->_namingContainer->_rf[self::RF_NAMED_CONTROLS_ID]=0; + $id=$this->_namingContainer->_rf[self::RF_NAMED_CONTROLS_ID]++; + $this->_id=self::AUTOMATIC_ID_PREFIX . $id; + $this->_namingContainer->clearNameTable(); + } + + /** + * Clears the list of the controls whose IDs are managed by the specified naming container. + */ + private function clearNameTable() + { + unset($this->_rf[self::RF_NAMED_CONTROLS]); + } + + /** + * Updates the list of the controls whose IDs are managed by the specified naming container. + * @param TControl the naming container + * @param TControlList list of controls + * @throws TInvalidDataValueException if a control's ID is not unique within its naming container. + */ + private function fillNameTable($container,$controls) + { + foreach($controls as $control) + { + if($control instanceof TControl) + { + if($control->_id!=='') + { + if(isset($container->_rf[self::RF_NAMED_CONTROLS][$control->_id])) + throw new TInvalidDataValueException('control_id_not_unique',$control->_id,get_class($control)); + else + $container->_rf[self::RF_NAMED_CONTROLS][$control->_id]=$control; + } + if(!($control instanceof INamingContainer) && $control->getHasControls()) + $this->fillNameTable($container,$control->_rf[self::RF_CONTROLS]); + } + } + } +} + + +/** + * TControlList class + * + * TControlList implements a collection that enables + * controls to maintain a list of their child controls. + * + * @author Qiang Xue + * @version $Revision: $ $Date: $ + * @package System.Web.UI + * @since 3.0 + */ +class TControlList extends TList +{ + /** + * the control that owns this collection. + * @var TControl + */ + private $_o; + + /** + * Constructor. + * @param TControl the control that owns this collection. + */ + public function __construct(TControl $owner) + { + parent::__construct(); + $this->_o=$owner; + } + + /** + * @return TControl the control that owns this collection. + */ + protected function getOwner() + { + return $this->_o; + } + + /** + * Overrides the parent implementation with customized processing of the newly added item. + * @param mixed the newly added item + */ + protected function addedItem($item) + { + if($item instanceof TControl) + $this->_o->addedControl($item); + } + + /** + * Overrides the parent implementation with customized processing of the removed item. + * @param mixed the removed item + */ + protected function removedItem($item) + { + if($item instanceof TControl) + $this->_o->removedControl($item); + } + + /** + * Only string or instance of TControl can be added into collection. + * @param mixed the item to be added + */ + protected function canAddItem($item) + { + return is_string($item) || ($item instanceof TControl); + } + + /** + * Overrides the parent implementation by invoking {@link TControl::clearNamingContainer} + */ + public function clear() + { + parent::clear(); + if($this->_o instanceof INamingContainer) + $this->_o->clearNamingContainer(); + } +} + +/** + * INamingContainer interface. + * INamingContainer marks a control as a naming container. + * + * @author Qiang Xue + * @version $Revision: $ $Date: $ + * @package System.Web.UI + * @since 3.0 + */ +interface INamingContainer +{ +} + +/** + * IPostBackEventHandler interface + * + * If a control wants to respond to postback event, it must implement this interface. + * + * @author Qiang Xue + * @version $Revision: $ $Date: $ + * @package System.Web.UI + * @since 3.0 + */ +interface IPostBackEventHandler +{ + /** + * Raises postback event. + * The implementation of this function should raise appropriate event(s) (e.g. OnClick, OnCommand) + * indicating the component is responsible for the postback event. + * @param string the parameter associated with the postback event + */ + public function raisePostBackEvent($param); +} + + +/** + * IPostBackDataHandler interface + * + * If a control wants to load post data, it must implement this interface. + * + * @author Qiang Xue + * @version $Revision: $ $Date: $ + * @package System.Web.UI + * @since 3.0 + */ +interface IPostBackDataHandler +{ + /** + * Loads user input data. + * The implementation of this function can use $values[$key] to get the user input + * data that are meant for the particular control. + * @param string the key that can be used to retrieve data from the input data collection + * @param array the input data collection + * @return boolean whether the data of the control has been changed + */ + public function loadPostData($key,$values); + /** + * Raises postdata changed event. + * The implementation of this function should raise appropriate event(s) (e.g. OnTextChanged) + * indicating the control data is changed. + */ + public function raisePostDataChangedEvent(); +} + + +/** + * IValidator interface + * + * If a control wants to validate user input, it must implement this interface. + * + * @author Qiang Xue + * @version $Revision: $ $Date: $ + * @package System.Web.UI + * @since 3.0 + */ +interface IValidator +{ + /** + * Validates certain data. + * The implementation of this function should validate certain data + * (e.g. data entered into TTextBox control). + * @return boolean whether the data passes the validation + */ + public function validate(); + /** + * @return boolean whether the previous {@link validate()} is successful. + */ + public function getIsValid(); + /** + * @param boolean whether the validator validates successfully + */ + public function setIsValid($value); + /** + * @return string error message during last validate + */ + public function getErrorMessage(); + /** + * @param string error message for the validation + */ + public function setErrorMessage($value); +} + + +/** + * IValidatable interface + * + * If a control wants to be validated by a validator, it must implement this interface. + * + * @author Qiang Xue + * @version $Revision: $ $Date: $ + * @package System.Web.UI + * @since 3.0 + */ +interface IValidatable +{ + /** + * @return mixed the value of the property to be validated. + */ + public function getValidationPropertyValue(); +} + +/** + * TCommandEventParameter class + * + * TCommandEventParameter encapsulates the parameter data for OnCommand + * event of button controls. You can access the name of the command via + * Name property, and the parameter carried with the command via + * Parameter property. + * + * @author Qiang Xue + * @version $Revision: $ $Date: $ + * @package System.Web.UI + * @since 3.0 + */ +class TCommandEventParameter extends TEventParameter +{ + private $_name; + private $_param; + + /** + * Constructor. + * @param string name of the command + * @param string parameter of the command + */ + public function __construct($name='',$parameter='') + { + $this->_name=$name; + $this->_param=$parameter; + } + + /** + * @return string name of the command + */ + public function getName() + { + return $this->_name; + } + + /** + * @param string name of the command + */ + public function setName($value) + { + $this->_name=$value; + } + + /** + * @return string parameter of the command + */ + public function getParameter() + { + return $this->_param; + } + + /** + * @param string parameter of the command + */ + public function setParameter($value) + { + $this->_param=$value; + } +} + +?> \ No newline at end of file diff --git a/framework/Web/UI/TForm.php b/framework/Web/UI/TForm.php new file mode 100644 index 00000000..0edb976b --- /dev/null +++ b/framework/Web/UI/TForm.php @@ -0,0 +1,128 @@ +getPage()->setForm($this); + } + + protected function addAttributesToRender($writer) + { + $attributes=$this->getAttributes(); + $writer->addAttribute('name',$this->getName()); + $writer->addAttribute('method',$this->getMethod()); + $writer->addAttribute('action',$this->getApplication()->getRequest()->getRequestURI()); + $attributes->remove('name'); + $attributes->remove('method'); + $attributes->remove('action'); + + $page=$this->getPage(); + $onsubmit=$page->getClientOnSubmitEvent(); + if($onsubmit!=='') + { + if(($existing=$attributes->itemAt('onsubmit'))!=='') + { + $page->getClientScript()->registerOnSubmitStatement('TForm:OnSubmitScript',$existing); + $attributes->remove('onsubmit'); + } + if($page->getClientSupportsJavaScript()) + $writer->addAttribute('onsubmit',$onsubmit); + } + if($this->getDefaultButton()!=='') + {//todo + $control=$this->findControl($this->getDefaultButton()); + if(!$control) + $control=$page->findControl($this->getDefaultButton()); + if($control instanceof IButtonControl) + $page->getClientScript()->registerDefaultButtonScript($control,$writer,false); + else + throw new Exception('Only IButtonControl can be default button.'); + } + $writer->addAttribute('id',$this->getUniqueID()); + foreach($attributes as $name=>$value) + $writer->addAttribute($name,$value); + } + + /** + * @internal + */ + protected function render($writer) + { + $this->addAttributesToRender($writer); + $writer->renderBeginTag('form'); + $page=$this->getPage(); + $page->beginFormRender($writer); + $this->renderChildren($writer); + $page->endFormRender($writer); + $writer->renderEndTag(); + } + + public function getDefaultButton() + { + return $this->getViewState('DefaultButton',''); + } + + public function setDefaultButton($value) + { + $this->setViewState('DefaultButton',$value,''); + } + + public function getDefaultFocus() + { + return $this->getViewState('DefaultFocus',''); + } + + public function setDefaultFocus($value) + { + $this->setViewState('DefaultFocus',$value,''); + } + + public function getMethod() + { + return $this->getViewState('Method','post'); + } + + public function setMethod($value) + { + $this->setViewState('Method',$value,'post'); + } + + public function getEnctype() + { + return $this->getViewState('Enctype',''); + } + + public function setEnctype($value) + { + $this->setViewState('Enctype',$value,''); + } + + public function getSubmitDisabledControls() + { + return $this->getViewState('SubmitDisabledControls',false); + } + + public function setSubmitDisabledControls($value) + { + $this->setViewState('SubmitDisabledControls',TPropertyValue::ensureBoolean($value),false); + } + + public function getName() + { + return $this->getUniqueID(); + } + + public function getTarget() + { + return $this->getViewState('Target',''); + } + + public function setTarget($value) + { + $this->setViewState('Target',$value,''); + } +} + +?> \ No newline at end of file diff --git a/framework/Web/UI/THiddenFieldPageStatePersister.php b/framework/Web/UI/THiddenFieldPageStatePersister.php new file mode 100644 index 00000000..d2cb5226 --- /dev/null +++ b/framework/Web/UI/THiddenFieldPageStatePersister.php @@ -0,0 +1,59 @@ +_page=$page; + } + + public function save($state) + { + $data=Prado::serialize($state); + $hmac=$this->computeHMAC($data,$this->getKey()); + if(function_exists('gzuncompress') && function_exists('gzcompress')) + $data=gzcompress($hmac.$data); + else + $data=$hmac.$data; + $this->_page->saveStateField($data); + } + + public function load() + { + $str=$this->_page->loadStateField(); + if($str==='') + return null; + if(function_exists('gzuncompress') && function_exists('gzcompress')) + $data=gzuncompress($str); + else + $data=$str; + if($data!==false && strlen($data)>32) + { + $hmac=substr($data,0,32); + $state=substr($data,32); + if($hmac===$this->computeHMAC($state,$this->getKey())) + return Prado::unserialize($state); + } + throw new Exception('viewstate data is corrupted.'); + } + + private function getKey() + { + return 'abcdefe'; + } + + private function computeHMAC($data,$key) + { + if (strlen($key) > 64) + $key = pack('H32', md5($key)); + elseif (strlen($key) < 64) + $key = str_pad($key, 64, "\0"); + return md5((str_repeat("\x5c", 64) ^ substr($key, 0, 64)) . pack('H32', md5((str_repeat("\x36", 64) ^ substr($key, 0, 64)) . $data))); + } +} + +?> \ No newline at end of file diff --git a/framework/Web/UI/THtmlTextWriter.php b/framework/Web/UI/THtmlTextWriter.php new file mode 100644 index 00000000..4ea78383 --- /dev/null +++ b/framework/Web/UI/THtmlTextWriter.php @@ -0,0 +1,235 @@ +2, + 'a'=>0, + 'acronym'=>0, + 'address'=>2, + 'area'=>1, + 'b'=>0, + 'base'=>1, + 'basefont'=>1, + 'bdo'=>0, + 'bgsound'=>1, + 'big'=>0, + 'blockquote'=>2, + 'body'=>2, + 'br'=>2, + 'button'=>0, + 'caption'=>2, + 'center'=>2, + 'cite'=>0, + 'code'=>0, + 'col'=>1, + 'colgroup'=>2, + 'del'=>0, + 'dd'=>0, + 'dfn'=>0, + 'dir'=>2, + 'div'=>2, + 'dl'=>2, + 'dt'=>0, + 'em'=>0, + 'embed'=>1, + 'fieldset'=>2, + 'font'=>0, + 'form'=>2, + 'frame'=>1, + 'frameset'=>2, + 'h1'=>2, + 'h2'=>2, + 'h3'=>2, + 'h4'=>2, + 'h5'=>2, + 'h6'=>2, + 'head'=>2, + 'hr'=>1, + 'html'=>2, + 'i'=>0, + 'iframe'=>2, + 'img'=>1, + 'input'=>1, + 'ins'=>0, + 'isindex'=>1, + 'kbd'=>0, + 'label'=>0, + 'legend'=>2, + 'li'=>0, + 'link'=>1, + 'map'=>2, + 'marquee'=>2, + 'menu'=>2, + 'meta'=>1, + 'nobr'=>0, + 'noframes'=>2, + 'noscript'=>2, + 'object'=>2, + 'ol'=>2, + 'option'=>2, + 'p'=>0, + 'param'=>2, + 'pre'=>2, + 'ruby'=>2, + 'rt'=>2, + 'q'=>0, + 's'=>0, + 'samp'=>0, + 'script'=>2, + 'select'=>2, + 'small'=>2, + 'span'=>0, + 'strike'=>0, + 'strong'=>0, + 'style'=>2, + 'sub'=>0, + 'sup'=>0, + 'table'=>2, + 'tbody'=>2, + 'td'=>0, + 'textarea'=>0, + 'tfoot'=>2, + 'th'=>0, + 'thead'=>2, + 'title'=>2, + 'tr'=>2, + 'tt'=>0, + 'u'=>0, + 'ul'=>2, + 'var'=>0, + 'wbr'=>1, + 'xml'=>2 + ); + private static $_attrEncode=array( + 'abbr'=>true, + 'accesskey'=>true, + 'align'=>false, + 'alt'=>true, + 'autocomplete'=>false, + 'axis'=>true, + 'background'=>true, + 'bgcolor'=>false, + 'border'=>false, + 'bordercolor'=>false, + 'cellpadding'=>false, + 'cellspacing'=>false, + 'checked'=>false, + 'class'=>true, + 'cols'=>false, + 'colspan'=>false, + 'content'=>true, + 'coords'=>false, + 'dir'=>false, + 'disabled'=>false, + 'for'=>false, + 'headers'=>true, + 'height'=>false, + 'href'=>true, + 'id'=>false, + 'longdesc'=>true, + 'maxlength'=>false, + 'multiple'=>false, + 'name'=>false, + 'nowrap'=>false, + 'onclick'=>true, + 'onchange'=>true, + 'readonly'=>false, + 'rel'=>false, + 'rows'=>false, + 'rowspan'=>false, + 'rules'=>false, + 'scope'=>false, + 'selected'=>false, + 'shape'=>false, + 'size'=>false, + 'src'=>true, + 'style'=>false, + 'tabindex'=>false, + 'target'=>false, + 'title'=>true, + 'type'=>false, + 'usemap'=>false, + 'valign'=>false, + 'value'=>true, + 'vcard_name'=>false, + 'width'=>false, + 'wrap'=>false + ); + + private $_attributes=array(); + private $_openTags=array(); + private $_writer=null; + + public function __construct($writer) + { + $this->_writer=$writer; + } + + public function isValidFormAttribute($name) + { + return true; + } + + public function addAttribute($name,$value) + { + $this->_attributes[$name]=isset(self::$_attrEncode[$name])?THttpUtility::htmlEncode($value):$value; + } + + public function flush() + { + $this->_writer->flush(); + } + + public function write($str) + { + $this->_writer->write($str); + } + + public function writeLine($str='') + { + $this->_writer->write($str.self::CHAR_NEWLINE); + } + + public function writeAttribute($name,$value,$encode=false) + { + $this->_writer->write(' '.$name.='"'.($encode?THttpUtility::htmlEncode($value):$value).'"'); + } + + public function renderBeginTag($tagName) + { + $tagType=isset(self::$_tagTypes[$tagName])?self::$_tagTypes[$tagName]:self::TAG_OTHER; + $str='<'.$tagName; + foreach($this->_attributes as $name=>$value) + $str.=' '.$name.'="'.$value.'"'; + if($tagType===self::TAG_NONCLOSING) + { + $str.=' />'; + array_push($this->_openTags,''); + } + else + { + $str.='>'; + array_push($this->_openTags,$tagName); + } + $this->_writer->write($str); + $this->_attributes=array(); + } + + public function renderEndTag() + { + if(!empty($this->_openTags) && ($tagName=array_pop($this->_openTags))!=='') + $this->_writer->write(''); + } +} + +?> \ No newline at end of file diff --git a/framework/Web/UI/TPage.php b/framework/Web/UI/TPage.php new file mode 100644 index 00000000..bb8f2253 --- /dev/null +++ b/framework/Web/UI/TPage.php @@ -0,0 +1,617 @@ +_application=Prado::getApplication(); + $this->setPage($this); + if(is_array($initProperties)) + { + foreach($initProperties as $name=>$value) + $this->setPropertyByPath($name,$value); + } + parent::__construct(); + } + + /** + * Loads and parses the control template + * @return ITemplate the parsed template structure + */ + protected function loadTemplate() + { + if($this->_templateFile===null) + return parent::loadTemplate(); + else + { + $template=Prado::getApplication()->getService()->getTemplateManager()->loadTemplateByFileName(Prado::getPathOfNamespace($this->_templateFile,'.tpl')); + $this->setTemplate($template); + return $template; + } + } + + public function getTemplateFile() + { + return $this->_templateFile; + } + + public function setTemplateFile($value) + { + $this->_templateFile=$value; + } + + final public function setForm($form) + { + $this->_form=$form; + } + + final public function getForm() + { + return $this->_form; + } + + public function validate($validationGroup='') + { + $this->_validated=true; + if($validationGroup==='') + { + foreach($this->_validators as $validator) + $validator->validate(); + } + else + { + foreach($this->_validators as $validator) + if($validator->getValidationGroup()===$validationGroup) + $validator->validate(); + } + } + + public function RegisterEnabledControl($control) + { + $this->getEna.EnabledControls.Add(control); + } + + + + /** + * @internal + */ + public function registerPostBackScript() + { + if($this->getClientSupportsJavaScript() && !$this->_postBackScriptRendered) + { + if(!$this->_requirePostBackScript) + { + $this->getClientScript()->registerHiddenField('__EVENTTARGET',''); + $this->getClientScript()->registerHiddenField('__EVENTPARAM',''); + $this->_requirePostBackScript=true; + } + } + } + + public function registerWebFormsScript() + { + if($this->getClientSupportsJavaScript() && !$this->_webFormsScriptRendered) + { + $this->registerPostBackScript(); + $this->_requireWebFormsScript=true; + } + } + + + public function ensureRenderInForm($control) + { + if(!$this->_inFormRender) + throw new THttpException('control_not_in_form',$control->getUniqueID()); + } + + /** + * @internal + */ + final protected function addContentTemplate($name,$template) + { + if(!$this->_contentTemplateCollection) + $this->_contentTemplateCollection=new TMap; + if($this->_contentTemplateCollection->has($name)) + throw new Exception("Content '$name' duplicated."); + $this->_contentTemplateCollection->add($name,$template); + } + + /** + * @internal + */ + final public function applyControlSkin($control) + { + if($this->_theme) + $this->_theme->applySkin($control); + } + + /** + * @internal + */ + final public function applyControlStyleSheet($control) + { + if($this->_styleSheet) + { + $this->_styleSheet->applySkin($control); + return true; + } + else + return false; + } + + private function renderStateFields($writer) + { + $writer->write("\n_pageState."\" />\n"); + } + + private function renderPostBackScript($writer) + { + $id=$this->_form->getUniqueID(); + $str=<< + +\n +EOD; + $writer->write($str); + $this->_postBackScriptRendered=true; + } + + private function renderWebFormsScript($writer) + { + $writer->write("\n\n"); + $this->_webFormsScriptRendered=true; + } + + final public function getClientSupportsJavaScript() + { + // todo + return true; + } + + /** + * @internal + */ + final public function beginFormRender($writer) + { + if($this->_formRendered) + throw new THttpException('multiple_form_not_allowed'); + $this->_formRendered=true; + $this->_inFormRender=true; + + $this->getClientScript()->renderHiddenFields($writer); + //$this->renderStateFields($writer); + if($this->getClientSupportsJavaScript()) + { + /* + if($this->getMaintainScrollPositionOnPostBack() && !$this->_requireScrollScript) + { + $cs=$this->getClientScript(); + $cs->registerHiddenField('_SCROLLPOSITIONX',$this->_scrollPositionX); + $cs->registerHiddenField('_SCROLLPOSITIONY',$this->_scrollPositionY); + $cs->registerStartupScript(get_class($this),"PageScrollPositionScript", "\r\nvar WebForm_ScrollPositionSubmit = theForm.submit;\r\ntheForm.submit = WebForm_SaveScrollPositionSubmit;\r\n\r\nvar WebForm_ScrollPositionOnSubmit = theForm.onsubmit;\r\ntheForm.onsubmit = WebForm_SaveScrollPositionOnSubmit;\r\n\r\nvar WebForm_ScrollPositionLoad = window.onload;\r\nwindow.onload = WebForm_RestoreScrollPosition;\r\n", true); + $this->registerWebFormScript(); + $this->_requireScrollScript=true; + } + */ + if($this->_requirePostBackScript) + $this->renderPostBackScript($writer,$this->_form->getUniqueID()); + if($this->_requireWebFormsScript) + $this->renderWebFormsScript($writer); + } + $this->getClientScript()->renderClientScriptBlocks($writer); + // todo: more .... + } + + final public function getIsPostBackEventControlRegistered() + { + return $this->_registeredControlThatRequireRaiseEvent!==null; + } + + /** + * @internal + */ + final public function endFormRender($writer) + { + $cs=$this->getClientScript(); + if($this->getClientSupportsJavaScript()) + $cs->renderArrayDeclarations($writer); + $cs->renderHiddenFields($writer); + if($this->getClientSupportsJavaScript()) + { + if($this->_requirePostBackScript && !$this->_postBackScriptRendered) + $this->renderPostBackScript($writer); + if($this->_requireWebFormsScript && !$this->_webFormsScriptRendered) + $this->renderWebFormsScript($writer); + } + $cs->renderClientStartupScripts($writer); + $this->_inFormRender=false; + } + + final public function getClientScript() + { + if(!$this->_clientScript) + $this->_clientScript=new TClientScriptManager($this); + return $this->_clientScript; + } + + final public function getClientOnSubmitEvent() + { + // todo + if($this->getClientScript()->getHasSubmitStatements()) + return 'javascript:return WebForm_OnSubmit();'; + else + return ''; + } + + final public function getValidators($validationGroup='') + { + if(!$this->_validators) + $this->_validators=new TList; + if($validationGroup==='') + return $this->_validators; + $list=new TList; + foreach($this->_validators as $validator) + if($validator->getValidationGroup()===$validationGroup) + $list->add($validator); + return $list; + } + + protected function initializeCulture() + { + } + + /** + * @internal + */ + public function initializeStyleSheet() + { + if($this->_styleSheet!=='') + $this->_styleSheet=new TTheme($this->_styleSheetName); + } + + private function initializeThemes() + { + if($this->_themeName!=='') + $this->_theme=new TTheme($this->_themeName); + if($this->_styleSheetName!=='') + $this->_styleSheet=new TTheme($this->_styleSheetName); + } + + /** + * @internal + */ + public function loadScrollPosition() + { + if($this->_previousPagePath==='' && $this->_requestValueCollection) + { + if(isset($_REQUEST['__SCROLLPOSITIONX'])) + $this->_scrollPositionX=(integer)$_REQUEST['__SCROLLPOSITIONX']; + if(isset($_REQUEST['__SCROLLPOSITIONY'])) + $this->_scrollPositionX=(integer)$_REQUEST['__SCROLLPOSITIONY']; + } + } + + protected function onInit($param) + { + parent::onInit($param);/* + if($this->_theme) + $this->_theme->setStyleSheet(); + if($this->_styleSheet) + $this->_styleSheet->setStyleSheet();*/ + } + + protected function onInitComplete($param) + { + $this->raiseEvent('InitComplete',$this,$param); + } + + protected function onLoadComplete($param) + { + $this->raiseEvent('LoadComplete',$this,$param); + } + + protected function onPreInit($param) + { + $this->raiseEvent('PreInit',$this,$param); + } + + protected function onPreLoad($param) + { + $this->raiseEvent('PreLoad',$this,$param); + } + + protected function onPreRenderComplete($param) + { + $this->raiseEvent('PreRenderComplete',$this,$param); + } + + protected function onSaveStateComplete($param) + { + $this->raiseEvent('SaveStateComplete',$this,$param); + } + + final public function registerAsyncTask() + { + } + + final public function registerRequiresPostBack($control) + { + if(!$this->_registeredControlsThatRequirePostBack) + $this->_registeredControlsThatRequirePostBack=new TList; + $this->_registeredControlsThatRequirePostBack->add($control->getUniqueID()); + } + + final public function registerRequiresRaiseEvent($control) + { + $this->_registeredControlThatRequireRaiseEvent=$control; + } + + public function getApplication() + { + return $this->_application; + } + + public function loadStateField() + { + return base64_decode($this->_postData->itemAt('__STATE')); + } + + public function saveStateField($state) + { + $this->getClientScript()->registerHiddenField('__STATE',base64_encode($state)); + } + + protected function determinePostBackMode() + { + /* + $application=$this->getApplication(); + if($application->getPreventPostBack()) + return null; + */ + $postData=new TMap($this->_application->getRequest()->getItems()); + if($postData->itemAt('__STATE')!==null || $postData->itemAt('__EVENTTARGET')!==null) + return $postData; + else + return null; + } + + final public function getIsPostBack() + { + if($this->_postData) + { + if($this->_isCrossPagePostBack) + return true; + if($this->_previousPagePath!=='') + return false; + return !$this->_pageStateChanged; + } + else + return false; + } + + protected function getPageStatePersister() + { + require_once(PRADO_DIR.'/Web/UI/THiddenFieldPageStatePersister.php'); + return new THiddenFieldPageStatePersister($this); + } + + protected function loadPageState() + { + $persister=$this->getPageStatePersister(); + $state=$persister->load(); + $this->loadStateRecursive($state,$this->getEnableViewState()); + } + + protected function savePageState() + { + $state=&$this->saveStateRecursive($this->getEnableViewState()); + $persister=$this->getPageStatePersister(); + $persister->save($state); + } + + protected function processPostData($postData,$beforeLoad) + { + $eventTarget=$postData->itemAt('__EVENTTARGET'); + foreach($postData as $key=>$value) + { + if(in_array($key,self::$_systemPostFields)) + continue; + else if($control=$this->findControl($key)) + { + if($control instanceof IPostBackDataHandler) + { + if($control->loadPostData($key,$this->_postData)) + $this->_changedPostDataConsumers[]=$control; + unset($this->_controlsRequiringPostBack[$key]); + } + else + { + if(empty($eventTarget)) + { + if($control instanceof IPostBackEventHandler) + $this->registerRequiresRaiseEvent($control); + } + else + unset($this->_controlsRequiringPostBack[$key]); + } + } + else if($beforeLoad) + $this->_restPostData->add($key,$value); + } + $list=new TMap; + foreach($this->_controlsRequiringPostBack as $key=>$value) + { + if($control=$this->findControl($key)) + { + if($control instanceof IPostBackDataHandler) + { + if($control->loadPostData($key,$this->_postData)) + $this->_changedPostDataConsumers->add($control); + } + else + throw new THttpException('postback_control_not_found',$key); + } + else if($beforeLoad) + $list->add($key,null); + } + $this->_controlsRequiringPostBack=$list; + } + + final public function getAutoPostBackControl() + { + return $this->_autoPostBackControl; + } + + final public function setAutoPostBackControl($control) + { + $this->_autoPostBackControl=$control; + } + + private function raiseChangedEvents() + { + foreach($this->_changedPostDataConsumers as $control) + $control->raisePostDataChangedEvent(); + } + + private function raisePostBackEvent($postData) + { + if($this->_registeredControlThatRequireRaiseEvent) + { + $this->_registeredControlThatRequireRaiseEvent->raisePostBackEvent(null); + } + else + { + $eventTarget=$postData->itemAt('__EVENTTARGET'); + if(!empty($eventTarget) || $this->getAutoPostBackControl()) + { + if(!empty($eventTarget)) + $control=$this->findControl($eventTarget); + else + $control=null; + if($control instanceof IPostBackEventHandler) + $control->raisePostBackEvent($postData->itemAt('__EVENTPARAM')); + } + else + $this->validate(); + } + } + + public function run($writer) + { + $this->_postData=$this->determinePostBackMode(); + $this->_restPostData=new TMap; + + $this->onPreInit(null); + $this->initializeThemes(); + $this->_preInitWorkComplete=true; + + $this->initRecursive(null); + $this->onInitComplete(null); + + if($this->getIsPostBack()) + { + $this->loadPageState(); + $this->processPostData($this->_postData,true); + } + + $this->onPreLoad(null); + $this->loadRecursive(null); + if($this->getIsPostBack()) + { + $this->processPostData($this->_restPostData,false); + $this->raiseChangedEvents(); + $this->raisePostBackEvent($this->_postData); + } + $this->onLoadComplete(null); + + $this->preRenderRecursive(); + $this->onPreRenderComplete(null); + + $this->savePageState(); + $this->onSaveStateComplete(null); + + $this->renderControl($writer); + $this->unloadRecursive(); + } + + public function getTheme() + { + return $this->_themeName; + } + + public function setTheme($value) + { + $this->_themeName=$value; + } + + public function getStyleSheetTheme() + { + return $this->_styleSheetName; + } + + public function setStyleSheetTheme($value) + { + $this->_styleSheetName=$value; + } + + public function getContainsTheme() + { + return $this->_theme!==null; + } +} + +?> \ No newline at end of file diff --git a/framework/Web/UI/TPageStatePersister.php b/framework/Web/UI/TPageStatePersister.php new file mode 100644 index 00000000..6ec9527b --- /dev/null +++ b/framework/Web/UI/TPageStatePersister.php @@ -0,0 +1,22 @@ +_page=$page; + } + + public function getPage() + { + return $this->_page; + } + + abstract public function load(); + + abstract public function save($state); +} + +?> \ No newline at end of file diff --git a/framework/Web/UI/TPostBackOptions.php b/framework/Web/UI/TPostBackOptions.php new file mode 100644 index 00000000..ee615d0b --- /dev/null +++ b/framework/Web/UI/TPostBackOptions.php @@ -0,0 +1,36 @@ +ActionUrl=$actionUrl; + $this->AutoPostBack=$autoPostBack; + $this->ClientSubmit=$clientSubmit; + $this->PerformValidation=$performValidation; + $this->RequiresJavaScriptProtocol=$requiresJavaScriptProtocol; + $this->TargetControl=$targetControl; + $this->TrackFocus=$trackFocus; + $this->ValidationGroup=$validationGroup; + } +} + +?> \ No newline at end of file diff --git a/framework/Web/UI/TTemplate.php b/framework/Web/UI/TTemplate.php new file mode 100644 index 00000000..df9bf813 --- /dev/null +++ b/framework/Web/UI/TTemplate.php @@ -0,0 +1,494 @@ + + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Revision: $ $Date: $ + * @package System.Web.UI + */ + +/** + * TTemplate implements PRADO template parsing logic. + * A TTemplate object represents a parsed PRADO control template. + * It can instantiate the template as child controls of a specified control. + * The template format is like HTML, with the following special tags introduced, + * - component tags: a component tag represents the configuration of a component. + * The tag name is in the format of com:ComponentType, where ComponentType is the component + * class name. Component tags must be well-formed. Attributes of the component tag + * are treated as either property initial values, event handler attachment, or regular + * tag attributes. + * - property tags: property tags are used to set large block of attribute values. + * The property tag name is in the format of prop:AttributeName, where AttributeName + * can be a property name, an event name or a regular tag attribute name. + * - directive: directive specifies the property values for the template owner. + * It is in the format of <% properyt name-value pairs %> + * - expressions: expressions are shorthand of {@link TExpression} and {@link TStatements} + * controls. They are in the formate of <= PHP expression > and < PHP statements > + * - comments: There are two kinds of comments, regular HTML comments and special template comments. + * The former is in the format of <!-- comments -->, which will be treated as text strings. + * The latter is in the format of <%* comments %>, which will be stripped out. + * + * Tags are not required to be well-formed. + * + * @author Qiang Xue + * @version $Revision: $ $Date: $ + * @package System.Web.UI + * @since 3.0 + */ +class TTemplate extends TComponent implements ITemplate +{ + /** + * '' - template comments + * '' - HTML comments + * '<\/?com:([\w\.]+)((?:\s*[\w\.]+=\'.*?\'|\s*[\w\.]+=".*?"|\s*[\w\.]+=<%.*?%>)*)\s*\/?>' - component tags + * '<\/?prop:([\w\.]+)\s*>' - property tags + * '<%@\s*(\w+)((?:\s*[\w\.]+=\'.*?\'|\s*[\w\.]+=".*?")*)\s*%>' - directives + * '<%=?(.*?)%>' - expressions + */ + const REGEX_RULES='/||<\/?com:([\w\.]+)((?:\s*[\w\.]+=\'.*?\'|\s*[\w\.]+=".*?"|\s*[\w\.]+=<%.*?%>)*)\s*\/?>|<\/?prop:([\w\.]+)\s*>|<%@\s*((?:\s*[\w\.]+=\'.*?\'|\s*[\w\.]+=".*?")*)\s*%>|<%=?(.*?)%>/msS'; + + /** + * @var array list of component tags and strings + */ + private $_tpl=array(); + /** + * @var array list of directive settings + */ + private $_directive=array(); + + /** + * Constructor. + * The template will be parsed after construction. + * @param string the template string + */ + public function __construct($template) + { + $this->parse($template); + } + + /** + * @return array name-value pairs declared in the directive + */ + public function getDirective() + { + return $this->_directive; + } + + /** + * Instantiates the template. + * Content in the template will be instantiated as components and text strings + * and passed to the specified parent control. + * @param TControl the parent control + * @throws TTemplateRuntimeException if an error is encountered during the instantiation. + */ + public function instantiateIn($tplControl) + { + $page=$tplControl->getPage(); + $controls=array(); + foreach($this->_tpl as $key=>$object) + { + if(isset($object[2])) // component + { + if(strpos($object[1],'.')===false) + $component=new $object[1]; + else + $component=Prado::createComponent($object[1]); + if($component instanceof TControl) + { + $controls[$key]=$component; + $component->setTemplateControl($tplControl); + if(isset($object[2]['id'])) + $tplControl->registerObject($object[2]['id'],$component); + if(isset($object[2]['skinid'])) + { + $component->setSkinID($object[2]['skinid']); + unset($object[2]['skinid']); + } + $component->applyStyleSheetSkin($page); + // apply attributes + foreach($object[2] as $name=>$value) + { + if($component->hasEvent($name)) // is an event + { + if(is_string($value)) + { + if(strpos($value,'.')===false) + $component->attachEventHandler($name,array($component,'TemplateControl.'.$value)); + else + $component->attachEventHandler($name,array($component,$value)); + } + else + throw new TTemplateRuntimeException('template_event_invalid',$name); + } + else if(strpos($name,'.')===false) // is simple property or custom attribute + { + if($component->hasProperty($name)) + { + if($component->canSetProperty($name)) + { + $setter='set'.$name; + if(is_string($value)) + $component->$setter($value); + else if($value[0]===0) + $component->bindProperty($name,$value[1]); + else + $component->$setter($component->evaluateExpression($value[1])); + } + else + throw new TTemplateRuntimeException('property_read_only',get_class($component).'.'.$name); + } + else if($component->getAllowCustomAttributes()) + $component->getAttributes()->add($name,$value); + else + throw new TTemplateRuntimeException('property_not_defined',get_class($component).'.'.$name); + } + else // complex property + { + if(is_string($value)) + $component->setPropertyByPath($name,$value); + else if($value[0]===0) + $component->bindProperty($name,$value[1]); + else + $component->setPropertyByPath($component->evaluateExpression($value[1])); + } + } + $parent=isset($controls[$object[0]])?$controls[$object[0]]:$tplControl; + $component->createdOnTemplate($parent); + } + else if($component instanceof TComponent) + { + if(isset($object[2]['id'])) + { + $tplControl->registerObject($object[2]['id'],$component); + if(!$component->hasProperty('id')) + unset($object[2]['id']); + } + foreach($object[2] as $name=>$value) + { + if($component->hasProperty($name)) + { + if($component->canSetProperty($name)) + { + $setter='set'.$name; + if(is_string($value)) + $component->$setter($value); + else if($value[0]===1) + $component->$setter($component->evaluateExpression($value[1])); + else + throw new TTemplateRuntimeException('template_component_property_unbindable',get_class($component),$name); + } + else + throw new TTemplateRuntimeException('property_read_only',get_class($component).'.'.$name); + } + else + throw new TTemplateRuntimeException('property_not_defined',get_class($component).'.'.$name); + } + $parent=isset($controls[$object[0]])?$controls[$object[0]]:$tplControl; + $parent->addParsedObject($component); + } + else + throw new TTemplateRuntimeException('must_be_component',$object[1]); + } + else // string + { + if(isset($controls[$object[0]])) + $controls[$object[0]]->addParsedObject($object[1]); + else + $tplControl->addParsedObject($object[1]); + } + } + } + + /** + * NOTE, this method is currently not used!!! + * Processes an attribute set in a component tag. + * The attribute will be checked to see if it represents a property or an event. + * If so, the value will be set to the property, or the value will be treated + * as an event handler and attached to the event. + * Otherwise, it will be added as regualr attribute if the control allows so. + * @param TComponent the component represented by the tag + * @param string attribute name + * @param string attribute value + * @throws TTemplateRuntimeException + */ + public static function applyAttribute($component,$name,$value) + { + $target=$component; + if(strpos($name,'.')===false) + $property=$name; + else + { + $names=explode('.',$name); + $property=array_pop($names); + foreach($names as $p) + { + if(($target instanceof TComponent) && $target->canGetProperty($p)) + { + $getter='get'.$p; + $target=$target->$getter(); + } + else + throw new TTemplateRuntimeException('invalid_subproperty',$name); + } + } + if($target instanceof TControl) + { + if($target->hasProperty($property)) + { + $setter='set'.$property; + if(is_string($value)) + $target->$setter($value); + else if($value[0]===0) + $target->bindProperty($property,$value[1]); + else + { + $target->$setter($target->evaluateExpression($value[1])); + } + } + else if($target->hasEvent($property)) + { + if(strpos($value,'.')===false) + $target->attachEventHandler($property,'TemplateControl.'.$value); + else + $target->attachEventHandler($property,$value); + } + else if($target->getAllowCustomAttributes()) + $target->getAttributes()->add($property,$value); + else + throw new TTemplateRuntimeException('property_not_defined',get_class($target).'.'.$property); + } + else if($target instanceof TComponent) + { + $setter='set'.$property; + $target->$setter($value); + } + else + throw new TTemplateRuntimeException('must_extend_TComponent',get_class($target)); + } + + /** + * Parses a template string. + * + * This template parser recognizes five types of data: + * regular string, well-formed component tags, well-formed property tags, directives, and expressions. + * + * The parsing result is returned as an array. Each array element can be of three types: + * - a string, 0: container index; 1: string content; + * - a component tag, 0: container index; 1: component type; 2: attributes (name=>value pairs) + * If a directive is found in the template, it will be parsed and can be + * retrieved via {@link getDirective}, which returns an array consisting of + * name-value pairs in the directive. + * + * Note, attribute names are treated as case-insensitive and will be turned into lower cases. + * Component and directive types are case-sensitive. + * Container index is the index to the array element that stores the container object. + * If an object has no container, its container index is -1. + * + * @param string the template string + * @return array the parsed result + * @throws TTemplateParsingException if a parsing error is encountered + */ + protected function &parse($input) + { + $tpl=&$this->_tpl; + $n=preg_match_all(self::REGEX_RULES,$input,$matches,PREG_SET_ORDER|PREG_OFFSET_CAPTURE); + $expectPropEnd=false; + $textStart=0; + $stack=array(); + $container=-1; + $c=0; + for($i=0;$i<$n;++$i) + { + $match=&$matches[$i]; + $str=$match[0][0]; + $matchStart=$match[0][1]; + $matchEnd=$matchStart+strlen($str)-1; + if(strpos($str,'$textStart) + $tpl[$c++]=array($container,substr($input,$textStart,$matchStart-$textStart)); + $textStart=$matchEnd+1; + $type=$match[1][0]; + $attributes=$this->parseAttributes($match[2][0]); + $tpl[$c++]=array($container,$type,$attributes); + if($str[strlen($str)-2]!=='/') // open tag + { + array_push($stack,$type); + $container=$c-1; + } + } + else if(strpos($str,'$textStart) + $tpl[$c++]=array($container,substr($input,$textStart,$matchStart-$textStart)); + $textStart=$matchEnd+1; + $type=$match[1][0]; + + if(empty($stack)) + { + $line=count(explode("\n",substr($input,0,$matchEnd+1))); + throw new TTemplateParsingException('unexpected_closing_tag',$line,""); + } + + $name=array_pop($stack); + if($name!==$type) + { + if($name[0]==='@') + $tag=''; + else + $tag=''; + $line=count(explode("\n",substr($input,0,$matchEnd+1))); + throw new TTemplateParsingException('expecting_closing_tag',$line,$tag); + } + $container=$tpl[$container][0]; + } + else if(strpos($str,'<%@')===0) // directive + { + if($expectPropEnd) + continue; + if($matchStart>$textStart) + $tpl[$c++]=array($container,substr($input,$textStart,$matchStart-$textStart)); + $textStart=$matchEnd+1; + if(isset($tpl[0])) + { + $line=count(explode("\n",substr($input,0,$matchEnd+1))); + throw new TTemplateParsingException('nonunique_template_directive',$line); + } + $this->_directive=$this->parseAttributes($match[4][0]); + } + else if(strpos($str,'<%')===0) // expression + { + if($expectPropEnd) + continue; + if($matchStart>$textStart) + $tpl[$c++]=array($container,substr($input,$textStart,$matchStart-$textStart)); + $textStart=$matchEnd+1; + if($str[2]==='=') + $tpl[$c++]=array($container,'TExpression',array('Expression'=>$match[5][0])); + else + $tpl[$c++]=array($container,'TStatements',array('Statements'=>$match[5][0])); + } + else if(strpos($str,'$textStart) + $tpl[$c++]=array($container,substr($input,$textStart,$matchStart-$textStart)); + $textStart=$matchEnd+1; + $expectPropEnd=true; + } + } + else if(strpos($str,'"); + } + $name=array_pop($stack); + if($name!=='@'.$prop) + { + if($name[0]==='@') + $tag=''; + else + $tag=''; + $line=count(explode("\n",substr($input,0,$matchEnd+1))); + throw new TTemplateParsingException('expecting_closing_tag',$line,$tag); + } + if(($last=count($stack))<1 || $stack[$last-1][0]!=='@') + { + if($matchStart>$textStart && $container>=0) + { + $value=substr($input,$textStart,$matchStart-$textStart); + if(preg_match('/^<%.*?%>$/msS',$value)) + { + if($value[2]==='#') // databind + $tpl[$container][2][$prop]=array(0,substr($value,3,strlen($value)-5)); + else if($value[2]==='=') // a dynamic initialization + $tpl[$container][2][$prop]=array(1,substr($value,3,strlen($value)-5)); + else + $tpl[$container][2][$prop]=$value; + } + else + $tpl[$container][2][$prop]=$value; + $textStart=$matchEnd+1; + } + $expectPropEnd=false; + } + } + else if(strpos($str,'