summaryrefslogtreecommitdiff
path: root/framework/Web
diff options
context:
space:
mode:
authorxue <>2005-11-10 12:47:19 +0000
committerxue <>2005-11-10 12:47:19 +0000
commit55c4ac1bfe565f1ca7f537fdd8b7a201be28e581 (patch)
treea0599d5e36fdbb3f1e169ae56bab7d529597e3eb /framework/Web
Initial import of prado framework
Diffstat (limited to 'framework/Web')
-rw-r--r--framework/Web/Javascripts/WebForms.js298
-rw-r--r--framework/Web/Services/TAssetService.php70
-rw-r--r--framework/Web/Services/TPageService.php625
-rw-r--r--framework/Web/TCacheManager.php116
-rw-r--r--framework/Web/THttpRequest.php824
-rw-r--r--framework/Web/THttpResponse.php289
-rw-r--r--framework/Web/THttpSession.php504
-rw-r--r--framework/Web/THttpUtility.php33
-rw-r--r--framework/Web/UI/TClientScriptManager.php236
-rw-r--r--framework/Web/UI/TControl.php1518
-rw-r--r--framework/Web/UI/TForm.php128
-rw-r--r--framework/Web/UI/THiddenFieldPageStatePersister.php59
-rw-r--r--framework/Web/UI/THtmlTextWriter.php235
-rw-r--r--framework/Web/UI/TPage.php617
-rw-r--r--framework/Web/UI/TPageStatePersister.php22
-rw-r--r--framework/Web/UI/TPostBackOptions.php36
-rw-r--r--framework/Web/UI/TTemplate.php494
-rw-r--r--framework/Web/UI/TTemplateControl.php215
-rw-r--r--framework/Web/UI/TTemplateManager.php66
-rw-r--r--framework/Web/UI/TTheme.php64
-rw-r--r--framework/Web/UI/WebControls/TButton.php291
-rw-r--r--framework/Web/UI/WebControls/TCheckBox.php399
-rw-r--r--framework/Web/UI/WebControls/TContent.php47
-rw-r--r--framework/Web/UI/WebControls/TContentPlaceHolder.php47
-rw-r--r--framework/Web/UI/WebControls/TExpression.php61
-rw-r--r--framework/Web/UI/WebControls/TFont.php276
-rw-r--r--framework/Web/UI/WebControls/THiddenField.php123
-rw-r--r--framework/Web/UI/WebControls/THyperLink.php144
-rw-r--r--framework/Web/UI/WebControls/TImage.php122
-rw-r--r--framework/Web/UI/WebControls/TImageButton.php320
-rw-r--r--framework/Web/UI/WebControls/TLabel.php106
-rw-r--r--framework/Web/UI/WebControls/TLiteral.php79
-rw-r--r--framework/Web/UI/WebControls/TPanel.php162
-rw-r--r--framework/Web/UI/WebControls/TPlaceHolder.php25
-rw-r--r--framework/Web/UI/WebControls/TStatements.php61
-rw-r--r--framework/Web/UI/WebControls/TStyle.php334
-rw-r--r--framework/Web/UI/WebControls/TTextBox.php444
-rw-r--r--framework/Web/UI/WebControls/TWebControl.php368
38 files changed, 9858 insertions, 0 deletions
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 @@
+<?php
+
+/**
+<module id="asset" PrivateLocation="xxx" PublicLocation="xxx" BaseUrl="xxx" />
+ */
+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 @@
+<?php
+/**
+ * TPageService class file.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 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 <qiang.xue@gmail.com>
+ * @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 <qiang.xue@gmail.com>
+ * @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 @@
+<?php
+/**
+<configuration>
+ <modules>
+ <module id="cache" Expiry="xxx" CacheStorage="file"
+ Location="xxx"
+ MemcacheServer="localhost" MemcachePort="1111" />
+ <module id="security" MachineKey="xxx" .... />
+ <module id="authenticator" ... />
+ </modules>
+ <services>
+ <service id="page" Location="xxx">
+ <module id="asset" UseService="false" Location="xxx" Url="xxx" />
+ <module id="authorizer" />
+ </service>
+ </services>
+</configuration>
+
+<module id="cache" Storage="sqlite,memcache" UniquePrefix="xxx"
+ SqliteFile="xxx" MemcacheServer="localhost" MemcachePort="1111"/>
+<module id="asset" UseService="true" Location="xxx" Url="xxx" />
+<module id="authenticator" LoginUrl="xxxx" />
+<module id="authorizer" /> // need to investigate the security of memory cache
+ */
+
+/**
+<module id="cache" type="System.Modules.TMemCache" Server="localhost" Port="1FileName="xxx" UniquePrefix="" />
+*/
+
+/**
+<module id="cache" type="System.Modules.TSqliteCache" DbFile="xxx" />
+*/
+
+
+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 @@
+<?php
+/**
+ * THttpRequest class
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 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 <qiang.xue@gmail.com>
+ * @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 <qiang.xue@gmail.com>
+ * @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 <qiang.xue@gmail.com>
+ * @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 <qiang.xue@gmail.com>
+ * @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 @@
+<?php
+/**
+ * THttpResponse class
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 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 <qiang.xue@gmail.com>
+ * @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 @@
+<?php
+/**
+ * THttpSession class
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 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,
+ * <code>
+ * $session=new THttpSession;
+ * $session->open();
+ * foreach($session->Items as $key=>$value)
+ * ; // read data in session
+ * $session->Items['key']=$data; // store new data into session
+ * </code>
+ *
+ * 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 <qiang.xue@gmail.com>
+ * @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 <qiang.xue@gmail.com>
+ * @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 @@
+<?php
+
+class THttpUtility
+{
+ private static $entityTable=null;
+
+ public static function htmlEncode($s)
+ {
+ return htmlspecialchars($s);
+ }
+
+ public static function htmlDecode($s)
+ {
+ if(!self::$entityTable)
+ self::buildEntityTable();
+ return strtr($s,self::$entityTable);
+ }
+
+ private static function buildEntityTable()
+ {
+ self::$entityTable=array_flip(get_html_translation_table(HTML_ENTITIES,ENT_QUOTES));
+ }
+
+ public static function quoteJavaScriptString($js,$forUrl=false)
+ {
+ if($forUrl)
+ return strtr($js,array('%'=>'%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 @@
+<?php
+
+class TClientScriptManager extends TComponent
+{
+ private $_owner;
+ private $_hiddenFields=array();
+ private $_scriptBlocks=array();
+ private $_startupScripts=array();
+ private $_scriptIncludes=array();
+ private $_onSubmitStatements=array();
+ private $_arrayDeclares=array();
+
+ public function __construct(TPage $owner)
+ {
+ $this->_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<script type=\"text/javascript\">\n<!--\n";
+ foreach($this->_arrayDeclares as $name=>$array)
+ {
+ $str.="var $name=new Array(";
+ $flag=true;
+ foreach($array as $value)
+ {
+ if($flag)
+ {
+ $flag=false;
+ $str.=$value;
+ }
+ else
+ $str.=','.$value;
+ }
+ $str.=");\n";
+ }
+ $str.="// -->\n</script>\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<script type=\"text/javascript\">\n<!--\n".$str."// -->\n</script>\n");
+ }
+
+ final public function renderClientStartupScripts($writer)
+ {
+ if(count($this->_startupScripts))
+ {
+ $str="\n<script type=\"text/javascript\">\n<!--\n";
+ foreach($this->_startupScripts as $script)
+ $str.=$script;
+ $str.="// -->\n</script>\n";
+ $writer->write($str);
+ }
+ }
+
+ final public function renderHiddenFields($writer)
+ {
+ $str='';
+ foreach($this->_hiddenFields as $name=>$value)
+ {
+ $value=THttpUtility::htmlEncode($value);
+ $str.="\n<input type=\"hidden\" name=\"$name\" id=\"$name\" value=\"$value\" />";
+ }
+ 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 @@
+<?php
+/**
+ * TControl, TControlList, TEventParameter and INamingContainer class file
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 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 <qiang.xue@gmail.com>
+ * @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,
+ * <code>
+ * $menuBar=$this->menuBar;
+ * </code>
+ * 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<self::CS_INITIALIZED)
+ {
+ $this->_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->_stage<self::CS_LOADED)
+ $this->onLoad(null);
+ if($this->getHasControls())
+ {
+ foreach($this->_rf[self::RF_CONTROLS] as $control)
+ if($control instanceof TControl)
+ $control->loadRecursive();
+ }
+ if($this->_stage<self::CS_LOADED)
+ $this->_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 <qiang.xue@gmail.com>
+ * @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 <qiang.xue@gmail.com>
+ * @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 <qiang.xue@gmail.com>
+ * @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 <qiang.xue@gmail.com>
+ * @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 <qiang.xue@gmail.com>
+ * @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 <qiang.xue@gmail.com>
+ * @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 <b>OnCommand</b>
+ * event of button controls. You can access the name of the command via
+ * <b>Name</b> property, and the parameter carried with the command via
+ * <b>Parameter</b> property.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @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 @@
+<?php
+
+class TForm extends TControl
+{
+ protected function onInit($param)
+ {
+ parent::onInit($param);
+ $this->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 @@
+<?php
+
+require_once(PRADO_DIR.'/Web/UI/TPageStatePersister.php');
+
+class THiddenFieldPageStatePersister extends TPageStatePersister
+{
+ private $_page;
+
+ public function __construct($page)
+ {
+ $this->_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 @@
+<?php
+
+// todo: test if an attribute is a url
+// keep nonclosing tag only
+// add more utility methods (e.g. render....)
+// implment encoding (for text and url)
+class THtmlTextWriter extends TComponent implements ITextWriter
+{
+ const TAG_INLINE=0;
+ const TAG_NONCLOSING=1;
+ const TAG_OTHER=2;
+ const CHAR_NEWLINE="\n";
+ const CHAR_TAB="\t";
+ private static $_tagTypes=array(
+ '*'=>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('</'.$tagName.'>');
+ }
+}
+
+?> \ 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 @@
+<?php
+
+Prado::using('System.Web.*');
+Prado::using('System.Web.UI.*');
+Prado::using('System.Web.UI.WebControls.*');
+
+class TPage extends TTemplateControl
+{
+ private $_application;
+ private $_contentTemplateCollection=null;
+ private $_maxPageStateFieldLength=10;
+ private $_enableViewStateMac=true;
+ private $_performPreRendering=true;
+ private $_performRendering=true;
+ private $_supportsStyleSheet=true;
+ private $_theme=null;
+ private $_themeName='';
+ private $_styleSheet=null;
+ private $_styleSheetName='';
+
+ private $_clientScript=null;
+ private $_form=null;
+ private $_formRendered=false;
+ private $_inFormRender=false;
+ private $_pageState='';
+ private $_requirePostBackScript=false;
+ private $_postBackScriptRendered=false;
+ private $_isCrossPagePostBack=false;
+ private $_previousPagePath='';
+ private $_preInitWorkComplete=false;
+ private $_changedPostDataConsumers=array();
+ private $_postData;
+ private $_restPostData;
+ private $_pageStateChanged=false;
+ private $_controlsRequiringPostBack=array();
+ private $_registeredControlThatRequireRaiseEvent=null;
+ private $_registeredControlsThatRequirePostBack=null;
+ private $_validators=array();
+ private $_validated=false;
+ private $_autoPostBackControl=null;
+ private $_webFormsScriptRendered=false;
+ private $_requireWebFormsScript=false;
+ private static $_systemPostFields=array('__EVENTTARGET','__EVENTPARAM','__STATE','__PREVPAGE','__CALLBACKID','__CALLBACKPARAM','__LASTFOCUS');
+ private $_contents=array();
+ private $_templateFile=null;
+
+ public function __construct($initProperties=null)
+ {
+ $this->_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<input type=\"hidden\" name=\"__STATE\" id=\"__STATE\" value=\"".$this->_pageState."\" />\n");
+ }
+
+ private function renderPostBackScript($writer)
+ {
+ $id=$this->_form->getUniqueID();
+ $str=<<<EOD
+\n<script type="text/javascript">
+<!--
+var theForm=document.forms['$id'];
+if(!theForm)
+ theForm=document.$id;
+function __doPostBack(eventTarget,eventParam) {
+ if(!theForm.onsubmit || (theForm.onsubmit()!=false)) {
+ theForm.__EVENTTARGET.value = eventTarget;
+ theForm.__EVENTPARAM.value = eventParam;
+ theForm.submit();
+ }
+}
+// -->
+</script>\n
+EOD;
+ $writer->write($str);
+ $this->_postBackScriptRendered=true;
+ }
+
+ private function renderWebFormsScript($writer)
+ {
+ $writer->write("\n<script src=\"js/WebForms.js\" type=\"text/javascript\"></script>\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 @@
+<?php
+
+abstract class TPageStatePersister extends TComponent
+{
+ private $_page;
+
+ public function __construct($page)
+ {
+ $this->_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 @@
+<?php
+
+class TPostBackOptions extends TComponent
+{
+ public $ActionUrl;
+ public $Argument;
+ public $AutoPostBack;
+ public $ClientSubmit;
+ public $PerformValidation;
+ public $RequiresJavaScriptProtocol;
+ public $TargetControl;
+ public $TrackFocus;
+ public $ValidationGroup;
+
+ public function __construct($targetControl=null,
+ $argument='',
+ $actionUrl='',
+ $autoPostBack=false,
+ $requiresJavaScriptProtocol=false,
+ $trackFocus=false,
+ $clientSubmit=true,
+ $performValidation=false,
+ $validationGroup='')
+ {
+ $this->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 @@
+<?php
+/**
+ * TTemplate class file.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 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 &lt;% properyt name-value pairs %&gt;
+ * - expressions: expressions are shorthand of {@link TExpression} and {@link TStatements}
+ * controls. They are in the formate of &lt;= PHP expression &gt; and &lt; PHP statements &gt;
+ * - comments: There are two kinds of comments, regular HTML comments and special template comments.
+ * The former is in the format of &lt;!-- comments --&gt;, which will be treated as text strings.
+ * The latter is in the format of &lt;%* comments %&gt;, which will be stripped out.
+ *
+ * Tags are not required to be well-formed.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @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,'<com:')===0) // opening component tag
+ {
+ if($expectPropEnd)
+ continue;
+ if($matchStart>$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,'</com:')===0) // closing component tag
+ {
+ if($expectPropEnd)
+ continue;
+ if($matchStart>$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,"</com:$type>");
+ }
+
+ $name=array_pop($stack);
+ if($name!==$type)
+ {
+ if($name[0]==='@')
+ $tag='</prop:'.substr($name,1).'>';
+ else
+ $tag='</com:'.$name.'>';
+ $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,'<prop:')===0) // opening property
+ {
+ $prop=strtolower($match[3][0]);
+ array_push($stack,'@'.$prop);
+ if(!$expectPropEnd)
+ {
+ if($matchStart>$textStart)
+ $tpl[$c++]=array($container,substr($input,$textStart,$matchStart-$textStart));
+ $textStart=$matchEnd+1;
+ $expectPropEnd=true;
+ }
+ }
+ else if(strpos($str,'</prop:')===0) // closing property
+ {
+ $prop=strtolower($match[3][0]);
+ if(empty($stack))
+ {
+ $line=count(explode("\n",substr($input,0,$matchEnd+1)));
+ throw new TTemplateParsingException('unexpected_closing_tag',$line,"</prop:$prop>");
+ }
+ $name=array_pop($stack);
+ if($name!=='@'.$prop)
+ {
+ if($name[0]==='@')
+ $tag='</prop:'.substr($name,1).'>';
+ else
+ $tag='</com:'.$name.'>';
+ $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,'<!--')===0) // HTML comments
+ {
+ $state=0;
+ }
+ else if(strpos($str,'<!')===0) // template comments
+ {
+ if($expectPropEnd)
+ {
+ $line=count(explode("\n",substr($input,0,$matchEnd+1)));
+ throw new TTemplateParsingException('no_comments_in_property',$line);
+ }
+ if($matchStart>$textStart)
+ $tpl[$c++]=array($container,substr($input,$textStart,$matchStart-$textStart));
+ $textStart=$matchEnd+1;
+ }
+ else
+ throw new TTemplateParsingException('unexpected_matching',$match);
+ }
+ if(!empty($stack))
+ {
+ $name=array_pop($stack);
+ if($name[0]==='@')
+ $tag='</prop:'.substr($name,1).'>';
+ else
+ $tag='</com:'.$name.'>';
+ $line=count(explode("\n",substr($input,0,$matchEnd+1)));
+ throw new TTemplateParsingException('expecting_closing_tag',$line,$tag);
+ }
+ if($textStart<strlen($input))
+ $tpl[$c++]=array($container,substr($input,$textStart));
+ return $tpl;
+ }
+
+ /**
+ * Parses the attributes of a tag from a string.
+ * @param string the string to be parsed.
+ * @return array attribute values indexed by names.
+ */
+ protected function parseAttributes($str)
+ {
+ if($str==='')
+ return array();
+ $pattern='/([\w\.]+)=(\'.*?\'|".*?"|<%.*?%>)/msS';
+ $attributes=array();
+ $n=preg_match_all($pattern,$str,$matches,PREG_SET_ORDER);
+ for($i=0;$i<$n;++$i)
+ {
+ $name=strtolower($matches[$i][1]);
+ $value=$matches[$i][2];
+ if($value[0]==='<')
+ {
+ if($value[2]==='#') // databind
+ $attributes[$name]=array(0,substr($value,3,strlen($value)-5));
+ else if($value[2]==='=') // a dynamic initialization
+ $attributes[$name]=array(1,substr($value,3,strlen($value)-5));
+ else
+ $attributes[$name]=substr($value,2,strlen($value)-4);
+ }
+ else
+ $attributes[$name]=substr($value,1,strlen($value)-2);
+ }
+ return $attributes;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Web/UI/TTemplateControl.php b/framework/Web/UI/TTemplateControl.php
new file mode 100644
index 00000000..89bfe12f
--- /dev/null
+++ b/framework/Web/UI/TTemplateControl.php
@@ -0,0 +1,215 @@
+<?php
+/**
+ * TTemplateControl class file.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2005 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI
+ */
+
+/**
+ * include TTemplate class file
+ */
+require_once(PRADO_DIR.'/Web/UI/TTemplate.php');
+
+/**
+ * TTemplateControl class.
+ * TTemplateControl is the base class for all controls that use templates.
+ * By default, a control template is assumed to be in a file under the same
+ * directory with the control class file. They have the same file name and
+ * different extension name. For template file, the extension name is ".tpl".
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI
+ * @since 3.0
+ */
+class TTemplateControl extends TControl implements INamingContainer
+{
+ /**
+ * template file extension.
+ */
+ const EXT_TEMPLATE='.tpl';
+ /**
+ * template cache file extension
+ */
+ const EXT_TEMPLATE_CACHE='.tpc';
+
+ /**
+ * @var ITemplate the parsed template structure shared by the same control class
+ */
+ protected static $_template=null;
+ /**
+ * @var ITemplate the parsed template structure specific for this control instance
+ */
+ protected $_localTemplate=null;
+ /**
+ * @var TTemplateControl the master control if any
+ */
+ private $_master=null;
+ /**
+ * @var string master control class name
+ */
+ private $_masterClass='';
+ /**
+ * @var array list of TContent controls
+ */
+ private $_contents=array();
+ /**
+ * @var array list of TContentPlaceHolder controls
+ */
+ private $_placeholders=array();
+
+ /**
+ * Constructor.
+ * Loads template for the control class if not loaded.
+ * Applies template directive if any.
+ */
+ public function __construct()
+ {
+ if(($tpl=$this->getTemplate(true))!==null)
+ {
+ foreach($tpl->getDirective() as $name=>$value)
+ $this->setPropertyByPath($name,$value);
+ }
+ }
+
+ /**
+ * @param boolean whether to attempt loading template if it is not loaded yet
+ * @return ITemplate|null the parsed template, null if none
+ */
+ protected function getTemplate($load=false)
+ {
+ if($this->_localTemplate===null)
+ {
+ eval('$tpl='.get_class($this).'::$_template;');
+ return ($tpl===null && $load)?$this->loadTemplate():$tpl;
+ }
+ else
+ return $this->_localTemplate;
+ }
+
+ /**
+ * Sets the parsed template.
+ * Note, the template will be applied to the whole control class.
+ * This method should only be used by framework and control developers.
+ * @param ITemplate the parsed template
+ */
+ protected function setTemplate($value)
+ {
+ $this->_localTemplate=$value;
+ }
+
+ /**
+ * Loads and parses the control template
+ * @return ITemplate the parsed template structure
+ */
+ protected function loadTemplate()
+ {
+ $template=Prado::getApplication()->getService()->getTemplateManager()->loadTemplateByClassName(get_class($this));
+ eval(get_class($this).'::$_template=$template;');
+ return $template;
+ }
+
+ /**
+ * Creates child controls.
+ * This method is overriden to load and instantiate control template.
+ * This method should only be used by framework and control developers.
+ */
+ protected function createChildControls()
+ {
+ if($tpl=$this->getTemplate())
+ $tpl->instantiateIn($this);
+ }
+
+ /**
+ * Registers a content control.
+ * @param TContent
+ */
+ public function registerContent(TContent $object)
+ {
+ $this->_contents[$object->getID()]=$object;
+ }
+
+ /**
+ * @return string master class name (in namespace form)
+ */
+ public function getMasterClass()
+ {
+ return $this->_masterClass;
+ }
+
+ /**
+ * @param string master control class name (in namespace form)
+ */
+ public function setMasterClass($value)
+ {
+ $this->_masterClass=$value;
+ }
+
+ /**
+ * @return TTemplateControl|null master control associated with this control, null if none
+ */
+ public function getMaster()
+ {
+ return $this->_master;
+ }
+
+ /**
+ * Registers a content placeholder to this template control.
+ * This method should only be used by framework and control developers.
+ * @param string ID of the placeholder
+ * @param TControl control that directly enloses the placeholder
+ * @param integer the index in the control list of the parent control that the placeholder is at
+ */
+ public function registerContentPlaceHolder($id,$parent,$loc)
+ {
+ $this->_placeholders[$id]=array($parent,$loc);
+ }
+
+ /**
+ * Injects all content controls (and their children) to the corresponding content placeholders.
+ * This method should only be used by framework and control developers.
+ * @param string ID of the content control
+ * @param TContent the content to be injected
+ */
+ public function injectContent($id,$content)
+ {
+ if(isset($this->_placeholders[$id]))
+ {
+ list($parent,$loc)=$this->_placeholders[$id];
+ $parent->getControls()->addAt($loc,$content);
+ }
+ }
+
+ /**
+ * Performs the OnInit step for the control and all its child controls.
+ * This method overrides the parent implementation
+ * by ensuring child controls are created first,
+ * and if master class is set, master will be applied.
+ * Only framework developers should use this method.
+ * @param TControl the naming container control
+ */
+ protected function initRecursive($namingContainer)
+ {
+ $this->ensureChildControls();
+ if($this->_masterClass!=='')
+ {
+ $master=Prado::createComponent($this->_masterClass);
+ if(!($master instanceof TTemplateControl))
+ throw new TInvalidDataValueException('tplcontrol_required',get_class($master));
+ $this->_master=$master;
+ $this->getControls()->clear();
+ $this->getControls()->add($master);
+ $master->ensureChildControls();
+ foreach($this->_contents as $id=>$content)
+ $master->injectContent($id,$content);
+ }
+ parent::initRecursive($namingContainer);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Web/UI/TTemplateManager.php b/framework/Web/UI/TTemplateManager.php
new file mode 100644
index 00000000..2f8ae688
--- /dev/null
+++ b/framework/Web/UI/TTemplateManager.php
@@ -0,0 +1,66 @@
+<?php
+
+class TTemplateManager extends TComponent implements IModule
+{
+ const TEMPLATE_FILE_EXT='.tpl';
+ const TEMPLATE_CACHE_PREFIX='prado:template:';
+ private $_application;
+ /**
+ * @var string module ID
+ */
+ private $_id;
+
+ public function init($application,$config)
+ {
+ $this->_application=$application;
+ }
+
+ /**
+ * @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;
+ }
+
+ public function loadTemplateByClassName($className)
+ {
+ $class=new ReflectionClass($className);
+ $tplFile=dirname($class->getFileName()).'/'.$className.self::TEMPLATE_FILE_EXT;
+ return $this->loadTemplateByFileName($tplFile);
+ }
+
+ public function loadTemplateByFileName($fileName)
+ {
+ if(is_file($fileName))
+ {
+ if(($cache=$this->_application->getCache())===null)
+ return new TTemplate(file_get_contents($fileName));
+ else
+ {
+ $array=$cache->get(self::TEMPLATE_CACHE_PREFIX.$fileName);
+ if(is_array($array))
+ {
+ list($template,$timestamp)=$array;
+ if(filemtime($fileName)<$timestamp)
+ return $template;
+ }
+ $template=new TTemplate(file_get_contents($fileName));
+ $cache->set(self::TEMPLATE_CACHE_PREFIX.$fileName,array($template,time()));
+ return $template;
+ }
+ }
+ else
+ return null;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Web/UI/TTheme.php b/framework/Web/UI/TTheme.php
new file mode 100644
index 00000000..1e3d8fab
--- /dev/null
+++ b/framework/Web/UI/TTheme.php
@@ -0,0 +1,64 @@
+<?php
+
+class TTheme extends TComponent
+{
+ private $_themePath;
+ private $_skins=array();
+
+ public function __construct($name)
+ {
+ $this->_themePath=$name;
+ $this->initialize();
+ }
+
+ private function initialize()
+ {
+ if(($theme=opendir($this->_themePath))===false)
+ throw new Exception("Invalid theme ".$this->_themePath);
+ while(($file=readdir($theme))!==false)
+ {
+ if(basename($file,'.skin')!==$file)
+ $this->parseSkinFile($this->_themePath.'/'.$file);
+ }
+ closedir($theme);
+ }
+
+ private function parseSkinFile($fileName)
+ {
+ if(($skin=simplexml_load_file($fileName))===false)
+ throw new Exception("Parsing $fileName failed.");
+ foreach($skin->children() as $type=>$control)
+ {
+ $attributes=array();
+ foreach($control->attributes() as $name=>$value)
+ {
+ $attributes[strtolower($name)]=(string)$value;
+ }
+ $skinID=isset($attributes['skinid'])?(string)$attributes['skinid']:0;
+ unset($attributes['skinid']);
+ if(isset($this->_skins[$type][$skinID]))
+ throw new Exception("Duplicated skin $type.$skinID");
+ else
+ $this->_skins[$type][$skinID]=$attributes;
+ }
+ }
+
+ public function applySkin($control)
+ {
+ $type=get_class($control);
+ if(($id=$control->getSkinID())==='')
+ $id=0;
+ if(isset($this->_skins[$type][$id]))
+ {
+ foreach($this->_skins[$type][$id] as $name=>$value)
+ {
+ $control->setPropertyByPath($name,$value);
+ }
+ }
+ else
+ return;
+ }
+}
+
+
+?> \ No newline at end of file
diff --git a/framework/Web/UI/WebControls/TButton.php b/framework/Web/UI/WebControls/TButton.php
new file mode 100644
index 00000000..f2ac6e21
--- /dev/null
+++ b/framework/Web/UI/WebControls/TButton.php
@@ -0,0 +1,291 @@
+<?php
+/**
+ * TButton class file.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.xisc.com/
+ * @copyright Copyright &copy; 2004-2005, Qiang Xue
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ */
+
+/**
+ * TButton class
+ *
+ * TButton creates a click button on the page.
+ *
+ * You can create either a <b>submit</b> button or a <b>client</b> button by setting
+ * <b>UseSubmitBehavior</b> property. Set <b>Text</b> property to specify the button's caption.
+ * Upon clicking on the button, on the server side two events are raised by the button:
+ * <b>OnClick</b> and <b>OnCommand</b>. You can attach event handlers to these events
+ * to respond to the button click action. For <b>OnCommand</b> event, you can associate
+ * it with a command name and parameter by setting <b>CommandName</b> and <b>CommandParameter</b>
+ * properties, respectively. They are passed as the event parameter to the <b>OnCommand</b>
+ * event handler (see {@link TCommandEventParameter}).
+ *
+ * Clicking on button can trigger form validation, if <b>CausesValidation</b> is true.
+ * And the validation may be restricted within a certain group of validator controls by
+ * setting <b>ValidationGroup</b> property.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ * @since 3.0
+ */
+class TButton extends TWebControl implements IPostBackEventHandler
+{
+ /**
+ * @return string tag name of the button
+ */
+ protected function getTagName()
+ {
+ return 'input';
+ }
+
+ /**
+ * Processes an object that is created during parsing template.
+ * This overrides the parent implementation by forbidding any body components.
+ * @param mixed the newly created object in template
+ * @throws TInvalidOperationException if a component is found within body
+ */
+ public function addParsedObject($object)
+ {
+ if(!is_string($object))
+ throw new TInvalidOperationException('body_contents_not_allowed',get_class($this).':'.$this->getUniqueID());
+ }
+
+ /**
+ * Adds attribute name-value pairs to renderer.
+ * This overrides the parent implementation with additional button specific attributes.
+ * @param THtmlTextWriter the writer used for the rendering purpose
+ */
+ protected function addAttributesToRender($writer)
+ {
+ $page=$this->getPage();
+ $page->ensureRenderInForm($this);
+ if($this->getUseSubmitBehavior())
+ $writer->addAttribute('type','submit');
+ else
+ $writer->addAttribute('type','button');
+ if(($uniqueID=$this->getUniqueID())!=='')
+ $writer->addAttribute('name',$uniqueID);
+ $writer->addAttribute('value',$this->getText());
+
+ $onclick='';
+ if($this->getEnabled(true))
+ {
+ $onclick=$this->getOnClientClick();
+ if($onclick!=='')
+ $onclick=rtrim($onclick,';').';';
+ $onclick.=$page->getClientScript()->getPostBackEventReference($this->getPostBackOptions());
+ }
+ else if($this->getEnabled()) // in this case, parent will not render 'disabled'
+ $writer->addAttribute('disabled','disabled');
+ if($onclick!=='')
+ $writer->addAttribute('onclick','javascript:'.$onclick);
+ parent::addAttributesToRender($writer);
+ }
+
+ /**
+ * Renders the body content enclosed between the control tag.
+ * This overrides the parent implementation with nothing to be rendered.
+ * @param THtmlTextWriter the writer used for the rendering purpose
+ */
+ protected function renderContents($writer)
+ {
+ }
+
+ /**
+ * OnClick event raiser.
+ * This method raises OnClick event.
+ * Be sure to invoke the parent implementation if this method is overriden.
+ * @param TEventParameter the event parameter
+ */
+ protected function onClick($param)
+ {
+ $this->raiseEvent('Click',$this,$param);
+ }
+
+ /**
+ * OnCommand event raiser.
+ * This method raises OnCommand event.
+ * Be sure to invoke the parent implementation if this method is overriden.
+ * @param TCommandEventParameter the event parameter
+ */
+ protected function onCommand($param)
+ {
+ $this->raiseEvent('Command',$this,$param);
+ $this->raiseBubbleEvent($this,$param);
+ }
+
+ /**
+ * Raises the postback event.
+ * This method is required by IPostBackEventHandler interface.
+ * If <b>CausesValidation</b> is true, it will invokes the page's {@validate}
+ * method first.
+ * It will raise <b>OnClick</b> and <b>OnCommand</b> events.
+ * This method is mainly used by framework and control developers.
+ * @param TEventParameter the event parameter
+ */
+ public function raisePostBackEvent($param)
+ {
+ if($this->getCausesValidation())
+ $this->getPage()->validate($this->getValidationGroup());
+ $this->onClick(new TEventParameter);
+ $this->onCommand(new TCommandEventParameter($this->getCommandName(),$this->getCommandParameter()));
+ }
+
+ /**
+ * Returns postback specifications for the button.
+ * This method is used by framework and control developers.
+ * @return TPostBackOptions parameters about how the button defines its postback behavior.
+ */
+ protected function getPostBackOptions()
+ {
+ $options=new TPostBackOptions($this);
+ $options->ClientSubmit=false;
+ $page=$this->getPage();
+ if($this->getCausesValidation() && $page->getValidators($this->getValidationGroup())->getCount()>0)
+ {
+ $options->PerformValidation=true;
+ $options->ValidationGroup=$this->getValidationGroup();
+ }
+ if($this->getPostBackUrl()!=='')
+ $options->ActionUrl=$this->getPostBackUrl();
+ $options->ClientSubmit=!$this->getUseSubmitBehavior();
+ return $options;
+ }
+
+ /**
+ * @return string caption of the button
+ */
+ public function getText()
+ {
+ return $this->getViewState('Text','');
+ }
+
+ /**
+ * @param string caption of the button
+ */
+ public function setText($value)
+ {
+ $this->setViewState('Text',$value,'');
+ }
+
+ /**
+ * @return boolean whether postback event trigger by this button will cause input validation, default is true
+ */
+ public function getCausesValidation()
+ {
+ return $this->getViewState('CausesValidation',true);
+ }
+
+ /**
+ * @param boolean whether postback event trigger by this button will cause input validation
+ */
+ public function setCausesValidation($value)
+ {
+ $this->setViewState('CausesValidation',TPropertyValue::ensureBoolean($value),true);
+ }
+
+ /**
+ * @return string the command name associated with the <b>OnCommand</b> event.
+ */
+ public function getCommandName()
+ {
+ return $this->getViewState('CommandName','');
+ }
+
+ /**
+ * Sets the command name associated with the <b>OnCommand</b> event.
+ * @param string the text caption to be set
+ */
+ public function setCommandName($value)
+ {
+ $this->setViewState('CommandName',$value,'');
+ }
+
+ /**
+ * @return string the parameter associated with the <b>OnCommand</b> event
+ */
+ public function getCommandParameter()
+ {
+ return $this->getViewState('CommandParameter','');
+ }
+
+ /**
+ * Sets the parameter associated with the <b>OnCommand</b> event.
+ * @param string the text caption to be set
+ */
+ public function setCommandParameter($value)
+ {
+ $this->setViewState('CommandParameter',$value,'');
+ }
+
+ /**
+ * @return boolean whether to use the button as a submit button, default is true.
+ */
+ public function getUseSubmitBehavior()
+ {
+ return $this->getViewState('UseSubmitBehavior',true);
+ }
+
+ /**
+ * @param boolean whether to use the button as a submit button
+ */
+ public function setUseSubmitBehavior($value)
+ {
+ $this->setViewState('UseSubmitBehavior',TPropertyValue::ensureBoolean($value),true);
+ }
+
+ /**
+ * @return string the group of validators which the button causes validation upon postback
+ */
+ public function getValidationGroup()
+ {
+ return $this->getViewState('ValidationGroup','');
+ }
+
+ /**
+ * @param string the group of validators which the button causes validation upon postback
+ */
+ public function setValidationGroup($value)
+ {
+ $this->setViewState('ValidationGroup',$value,'');
+ }
+
+ /**
+ * @return string the URL of the page to post to when the button is clicked, default is empty meaning post to the current page itself
+ */
+ public function getPostBackUrl()
+ {
+ return $this->getViewState('PostBackUrl','');
+ }
+
+ /**
+ * @param string the URL of the page to post to from the current page when the button is clicked, empty if post to the current page itself
+ */
+ public function setPostBackUrl($value)
+ {
+ $this->setViewState('PostBackUrl',$value,'');
+ }
+
+ /**
+ * @return string the javascript to be executed when the button is clicked
+ */
+ public function getOnClientClick()
+ {
+ return $this->getViewState('ClientClick','');
+ }
+
+ /**
+ * @param string the javascript to be executed when the button is clicked. Do not prefix it with "javascript:".
+ */
+ public function setOnClientClick($value)
+ {
+ $this->setViewState('OClientClick',$value,'');
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Web/UI/WebControls/TCheckBox.php b/framework/Web/UI/WebControls/TCheckBox.php
new file mode 100644
index 00000000..02167544
--- /dev/null
+++ b/framework/Web/UI/WebControls/TCheckBox.php
@@ -0,0 +1,399 @@
+<?php
+/**
+ * TCheckBox class file
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.xisc.com/
+ * @copyright Copyright &copy; 2004-2005, Qiang Xue
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ */
+
+/**
+ * TCheckBox class
+ *
+ * TCheckBox creates a check box on the page.
+ * You can specify the caption to display beside the check box by setting
+ * the <b>Text</b> property. The caption can appear either on the right
+ * or left of the check box, which is determined by the <b>TextAlign</b>
+ * property.
+ *
+ * To determine whether the TCheckBox component is checked,
+ * test the <b>Checked</b> property. The <b>OnCheckedChanged</b> event
+ * is raised when the <b>Checked</b> state of the TCheckBox component changes
+ * between posts to the server. You can provide an event handler for
+ * the <b>OnCheckedChanged</b> event to to programmatically
+ * control the actions performed when the state of the TCheckBox component changes
+ * between posts to the server.
+ *
+ * Note, <b>Text</b> will be HTML encoded before it is displayed in the TCheckBox component.
+ * If you don't want it to be so, set <b>EncodeText</b> to false.
+ *
+ * Namespace: System.Web.UI.WebControls
+ *
+ * Properties
+ * - <b>Text</b>, string, kept in viewstate
+ * <br>Gets or sets the text caption displayed in the TCheckBox component.
+ * - <b>EncodeText</b>, boolean, default=true, kept in viewstate
+ * <br>Gets or sets the value indicating whether Text should be HTML-encoded when rendering.
+ * - <b>TextAlign</b>, Left|Right, default=Right, kept in viewstate
+ * <br>Gets or sets the alignment of the text label associated with the TCheckBox component.
+ * - <b>Checked</b>, boolean, default=false, kept in viewstate
+ * <br>Gets or sets a value indicating whether the TCheckBox component is checked.
+ * - <b>AutoPostBack</b>, boolean, default=false, kept in viewstate
+ * <br>Gets or sets a value indicating whether the TCheckBox automatically posts back to the server when clicked.
+ *
+ * Events
+ * - <b>OnCheckedChanged</b> Occurs when the value of the <b>Checked</b> property changes between posts to the server.
+ *
+ * Examples
+ * - On a page template file, insert the following line to create a TCheckBox component,
+ * <code>
+ * <com:TCheckBox Text="Agree" OnCheckedChanged="checkAgree" />
+ * </code>
+ * The checkbox will show "Agree" text on its right side. If the user makes any change
+ * to the <b>Checked</b> state, the checkAgree() method of the page class will be invoked automatically.
+ *
+ * TFont encapsulates the CSS style fields related with font settings.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ * @since 3.0
+ */
+class TCheckBox extends TWebControl implements IPostBackDataHandler, IValidatable
+{
+ public static $TEXT_ALIGN=array('Left','Right');
+
+ protected function getTagName()
+ {
+ return 'input';
+ }
+
+ protected function addAttributesToRender($writer)
+ {
+ }
+
+ /**
+ * Loads user input data.
+ * This method is primarly used by framework developers.
+ * @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)
+ {
+ $checked=$this->getChecked();
+ if(isset($values[$key])!=$checked)
+ {
+ $this->setChecked(!$checked);
+ return true;
+ }
+ else
+ return false;
+ }
+
+
+ /**
+ * Raises postdata changed event.
+ * This method calls {@link onCheckedChanged} method.
+ * This method is primarly used by framework developers.
+ */
+ public function raisePostDataChangedEvent()
+ {
+ $page=$this->getPage();
+ if($this->getAutoPostBack() && !$page->getIsPostBackEventControlRegistered())
+ {
+ $page->setAutoPostBackControl($this);
+ if($this->getCausesValidation())
+ $page->validate($this->getValidationGroup());
+ }
+ $this->onCheckedChanged(new TEventParameter);
+ }
+
+ /**
+ * This method is invoked when the value of the <b>Checked</b> property changes between posts to the server.
+ * The method raises 'CheckedChanged' event to fire up the event delegates.
+ * If you override this method, be sure to call the parent implementation
+ * so that the event delegates can be invoked.
+ * @param TEventParameter event parameter to be passed to the event handlers
+ */
+ protected function onCheckedChanged($param)
+ {
+ $this->raiseEvent('CheckedChanged',$this,$param);
+ }
+
+ /**
+ * Returns the value of the property that needs validation.
+ * @return mixed the property value to be validated
+ */
+ public function getValidationPropertyValue()
+ {
+ return $this->getChecked();
+ }
+
+ /**
+ * @return string the text caption of the checkbox
+ */
+ public function getText()
+ {
+ return $this->getViewState('Text','');
+ }
+
+ /**
+ * Sets the text caption of the checkbox.
+ * @param string the text caption to be set
+ */
+ public function setText($value)
+ {
+ $this->setViewState('Text',$value,'');
+ }
+
+ /**
+ * @return string the alignment of the text caption
+ */
+ public function getTextAlign()
+ {
+ return $this->getViewState('TextAlign','Right');
+ }
+
+ /**
+ * Sets the text alignment of the checkbox
+ * @param string either 'Left' or 'Right'
+ */
+ public function setTextAlign($value)
+ {
+ $this->setViewState('TextAlign',TPropertyValue::ensureEnum($value,self::$TEXT_ALIGN),'Right');
+ }
+
+ /**
+ * @return boolean whether the checkbox is checked
+ */
+ public function getChecked()
+ {
+ return $this->getViewState('Checked',false);
+ }
+
+ /**
+ * Sets a value indicating whether the checkbox is to be checked or not.
+ * @param boolean whether the checkbox is to be checked or not.
+ */
+ public function setChecked($value)
+ {
+ $this->setViewState('Checked',$value,false);
+ }
+
+ /**
+ * @return boolean whether clicking on the checkbox will post the page.
+ */
+ public function getAutoPostBack()
+ {
+ return $this->getViewState('AutoPostBack',false);
+ }
+
+ /**
+ * Sets a value indicating whether clicking on the checkbox will post the page.
+ * @param boolean whether clicking on the checkbox will post the page.
+ */
+ public function setAutoPostBack($value)
+ {
+ $this->setViewState('AutoPostBack',$value,false);
+ }
+
+ /**
+ * @return boolean whether postback event trigger by this checkbox will cause input validation, default is true.
+ */
+ public function getCausesValidation()
+ {
+ return $this->getViewState('CausesValidation',true);
+ }
+
+ /**
+ * Sets the value indicating whether postback event trigger by this checkbox will cause input validation.
+ * @param boolean whether postback event trigger by this checkbox will cause input validation.
+ */
+ public function setCausesValidation($value)
+ {
+ $this->setViewState('CausesValidation',TPropertyValue::ensureBoolean($value),true);
+ }
+
+ /**
+ * @return string the group of validators which the checkbox causes validation upon postback
+ */
+ public function getValidationGroup()
+ {
+ return $this->getViewState('ValidationGroup','');
+ }
+
+ /**
+ * @param string the group of validators which the checkbox causes validation upon postback
+ */
+ public function setValidationGroup($value)
+ {
+ $this->setViewState('ValidationGroup',$value,'');
+ }
+
+ /**
+ * Returns the attributes to be rendered.
+ * This method overrides the parent's implementation.
+ * @return ArrayObject attributes to be rendered
+ */
+ protected function getAttributesToRender()
+ {
+ $attributes=parent::getAttributesToRender();
+ if(isset($attributes['id'])) unset($attributes['id']);
+ if(isset($attributes['accesskey'])) unset($attributes['accesskey']);
+ if(isset($attributes['tabindex'])) unset($attributes['tabindex']);
+ return $attributes;
+ }
+
+ /**
+ * Renders the body content of the control.
+ * This method overrides the parent's implementation.
+ * @return string the rendering result.
+ */
+ protected function renderBody()
+ {
+ $name=$this->getUniqueID();
+ $disabled=!$this->isEnabled();
+ $id=$this->getClientID();
+
+ $input="<input id=\"$id\" type=\"checkbox\" name=\"$name\"";
+ if($this->isChecked())
+ $input.=" checked=\"checked\"";
+ if($disabled)
+ $input.=" disabled=\"disabled\"";
+ if($this->isAutoPostBack())
+ {
+ $page=$this->getPage();
+ $script=$page->getPostBackClientEvent($this,'');
+ $input.=" onclick=\"javascript:$script\"";
+ }
+ $accessKey=$this->getAccessKey();
+ if(strlen($accessKey))
+ $input.=" accesskey=\"$accessKey\"";
+ $tabIndex=$this->getTabIndex();
+ if(!empty($tabIndex))
+ $input.=" tabindex=\"$tabIndex\"";
+ $input.='/>';
+ $text=$this->isEncodeText()?pradoEncodeData($this->getText()):$this->getText();
+ if(strlen($text))
+ {
+ $label="<label for=\"$name\">$text</label>";
+ if($this->getTextAlign()=='Left')
+ $input="{$label}{$input}";
+ else
+ $input.=$label;
+ }
+ return $input;
+ }
+
+ protected function renderControl($writer)
+ {
+ $this->addAttributesToRender($writer);
+ $page=$this->getPage();
+ $page->ensureRenderInForm($this);
+ $needSpan=true;
+ if($this->getStyleCreated())
+ {
+ $this->getStyle()->addAttributesToRender($writer);
+ $needSpan=true;
+ }
+ if(!$this->getEnabled(true))
+ {
+ $writer->addAttribute('disabled','disabled');
+ $needSpan=true;
+ }
+ if(($tooltip=$this->getToolTip())!=='')
+ {
+ $writer->addAttribute('title',$tooltip);
+ $needSpan=true;
+ }
+ $onclick=null;
+ if($this->getHasAttributes())
+ {
+ $attributes=$this->getAttributes();
+ $value=$attributes->remove('value');
+ $onclick=$attributes->remove('onclick');
+ if($attributes->getCount())
+ {
+ foreach($attributes as $name=>$value)
+ $writer->addAttribute($name,$value);
+ }
+ $needSpan=true;
+ if($value!==null)
+ $attributes->add('value',$value);
+ }
+ if($needSpan)
+ $writer->renderBeginTag('span');
+ $clientID=$this->getClientID();
+ if(($text=$this->getText())!=='')
+ {
+ if($this->getTextAlign()==='Left')
+ {
+ $this->renderLabel($writer,$text,$clientID);
+ $this->renderInputTag($writer,$clientID,$onclick);
+ }
+ else
+ {
+ $this->renderInputTag($writer,$clientID,$onclick);
+ $this->renderLabel($writer,$text,$clientID);
+ }
+ }
+ else
+ $this->renderInputTag($writer,$clientID,$onclick);
+ if($needSpan)
+ $writer->renderEndTag();
+ }
+
+ private function renderLabel($writer,$text,$clientID)
+ {
+ $writer->addAttribute('for',$clientID);
+ // todo: custom label attributes rendering
+ $writer->renderBeginTag('label');
+ $writer->write($text);
+ $writer->renderEndTag();
+ }
+
+ protected function renderInputTag($writer,$clientID,$onclick)
+ {
+ if($clientID!=='')
+ $writer->addAttribute('id',$clientID);
+ $writer->addAttribute('type','checkbox');
+ if(($uniqueID=$this->getUniqueID())!=='')
+ $writer->addAttribute('name',$uniqueID);
+ //todo: render value attribute here
+ if($this->getChecked())
+ $writer->addAttribute('checked','checked');
+ if(!$this->getEnabled(true))
+ $writer->addAttribute('disabled','disabled');
+ $page=$this->getPage();
+ if($this->getAutoPostBack() && $page->getClientSupportsJavaScript())
+ {
+ $option=new TPostBackOptions($this);
+ if($this->getCausesValidation() && $page->getValidators($this->getValidationGroup())->getCount())
+ {
+ $option->PerformValidation=true;
+ $option->ValidationGroup=$this->getValidationGroup;
+ }
+ if($page->getForm())
+ $option->AutoPostBack=true;
+ if(!empty($onclick))
+ $onclick=rtrim($onclick,';').';';
+ $onclick.=$page->getClientScript()->getPostBackEventReference($option);
+ }
+ if(!empty($onclick))
+ $writer->addAttribute('onclick',$onclick);
+ if(($accesskey=$this->getAccessKey())!=='')
+ $writer->addAttribute('accesskey',$accesskey);
+ if(($tabindex=$this->getTabIndex())>0)
+ $writer->addAttribute('tabindex',$tabindex);
+ //todo: render input attributes
+ $writer->renderBeginTag('input');
+ $writer->renderEndTag();
+ }
+ // todo: onprerender???
+}
+
+?> \ No newline at end of file
diff --git a/framework/Web/UI/WebControls/TContent.php b/framework/Web/UI/WebControls/TContent.php
new file mode 100644
index 00000000..61786c01
--- /dev/null
+++ b/framework/Web/UI/WebControls/TContent.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * TContent class file.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.xisc.com/
+ * @copyright Copyright &copy; 2004-2005, Qiang Xue
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ */
+
+/**
+ * TContent class
+ *
+ * TContent specifies a block of content on a control's template
+ * that will be injected at somewhere of the master control's template.
+ * TContentPlaceHolder and {@link TContent} together implement a decoration
+ * pattern for prado templated controls. A template control
+ * (called content control) can specify a master control
+ * whose template contains some TContentPlaceHolder controls.
+ * {@link TContent} controls on the content control's template will replace the corresponding
+ * {@link TContentPlaceHolder} controls on the master control's template.
+ * This is called content injection. It is done by matching the IDs of
+ * {@link TContent} and {@link TContentPlaceHolder} controls.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ * @since 3.0
+ */
+class TContent extends TControl implements INamingContainer
+{
+ /**
+ * This method is invoked after the control is instantiated on a template.
+ * This overrides the parent implementation by registering the content control
+ * to the template owner control.
+ * @param TControl potential parent of this control
+ */
+ public function createdOnTemplate($parent)
+ {
+ $this->getTemplateControl()->registerContent($this);
+ parent::createdOnTemplate($parent);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Web/UI/WebControls/TContentPlaceHolder.php b/framework/Web/UI/WebControls/TContentPlaceHolder.php
new file mode 100644
index 00000000..fc832fae
--- /dev/null
+++ b/framework/Web/UI/WebControls/TContentPlaceHolder.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * TContentPlaceHolder class file.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.xisc.com/
+ * @copyright Copyright &copy; 2004-2005, Qiang Xue
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ */
+
+/**
+ * TContentPlaceHolder class
+ *
+ * TContentPlaceHolder reserves a place on a template where a {@link TContent}
+ * control can inject itself and its children in. TContentPlaceHolder and {@link TContent}
+ * together implement a decoration pattern for prado templated controls.
+ * A template control (called content control) can specify a master control
+ * whose template contains some TContentPlaceHolder controls.
+ * {@link TContent} controls on the content control's template will replace the corresponding
+ * {@link TContentPlaceHolder} controls on the master control's template.
+ * This is called content injection. It is done by matching the IDs of
+ * {@link TContent} and {@link TContentPlaceHolder} controls.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ * @since 3.0
+ */
+class TContentPlaceHolder extends TControl
+{
+ /**
+ * This method is invoked after the control is instantiated on a template.
+ * This overrides the parent implementation by registering the content placeholder
+ * control to the template owner control. The placeholder control will NOT
+ * be added to the potential parent control!
+ * @param TControl potential parent of this control
+ */
+ public function createdOnTemplate($parent)
+ {
+ $loc=$parent->getHasControls()?$parent->getControls()->getCount():0;
+ $this->getTemplateControl()->registerContentPlaceHolder($this->getID(),$parent,$loc);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Web/UI/WebControls/TExpression.php b/framework/Web/UI/WebControls/TExpression.php
new file mode 100644
index 00000000..6cecf9c4
--- /dev/null
+++ b/framework/Web/UI/WebControls/TExpression.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * TExpression class file
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.xisc.com/
+ * @copyright Copyright &copy; 2004-2005, Qiang Xue
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ */
+
+/**
+ * TExpression class
+ *
+ * TExpression evaluates a PHP expression and renders the result.
+ * The expression is evaluated during rendering stage. You can set
+ * it via the property <b>Expression</b>. You should also specify
+ * the context object by <b>Context</b> property which is used as
+ * the object in which the expression is evaluated. If the <b>Context</b>
+ * property is not set, the TExpression component itself will be
+ * assumed as the context.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ * @since 3.0
+ */
+class TExpression extends TControl
+{
+ private $_e='';
+
+ /**
+ * @return string the expression to be evaluated
+ */
+ public function getExpression()
+ {
+ return $this->_e;
+ }
+
+ /**
+ * Sets the expression of the TExpression
+ * @param string the expression to be set
+ */
+ public function setExpression($value)
+ {
+ $this->_e=$value;
+ }
+
+ /**
+ * Renders the evaluation result of the expression.
+ * @param THtmlTextWriter the writer used for the rendering purpose
+ */
+ protected function render($writer)
+ {
+ if($this->_e!=='')
+ $writer->write($this->evaluateExpression($this->_e));
+ }
+}
+
+?>
diff --git a/framework/Web/UI/WebControls/TFont.php b/framework/Web/UI/WebControls/TFont.php
new file mode 100644
index 00000000..468aa9f9
--- /dev/null
+++ b/framework/Web/UI/WebControls/TFont.php
@@ -0,0 +1,276 @@
+<?php
+/**
+ * TFont class file.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.xisc.com/
+ * @copyright Copyright &copy; 2004-2005, Qiang Xue
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ */
+
+/**
+ * TFont class
+ *
+ * TFont encapsulates the CSS style fields related with font settings.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ * @since 3.0
+ */
+class TFont extends TComponent
+{
+ /**
+ * Bits indicating the font states.
+ */
+ const IS_BOLD=0x01;
+ const IS_ITALIC=0x02;
+ const IS_OVERLINE=0x04;
+ const IS_STRIKEOUT=0x08;
+ const IS_UNDERLINE=0x10;
+
+ /**
+ * Bits indicating whether particular font states are changed.
+ */
+ const IS_SET_BOLD=0x01000;
+ const IS_SET_ITALIC=0x02000;
+ const IS_SET_OVERLINE=0x04000;
+ const IS_SET_STRIKEOUT=0x08000;
+ const IS_SET_UNDERLINE=0x10000;
+ const IS_SET_SIZE=0x20000;
+ const IS_SET_NAME=0x40000;
+
+ /**
+ * @var integer bits representing various states
+ */
+ private $_flags=0;
+ /**
+ * @var string font name
+ */
+ private $_name='';
+ /**
+ * @var string font size
+ */
+ private $_size='';
+
+ /**
+ * @return boolean whether the font is in bold face
+ */
+ public function getBold()
+ {
+ return ($this->_flags & self::IS_BOLD)!==0;
+ }
+
+ /**
+ * @param boolean whether the font is in bold face
+ */
+ public function setBold($value)
+ {
+ $this->_flags |= self::IS_SET_BOLD;
+ if($value)
+ $this->_flags |= self::IS_BOLD;
+ else
+ $this->_flags &= ~self::IS_BOLD;
+ }
+
+ /**
+ * @return boolean whether the font is in italic face
+ */
+ public function getItalic()
+ {
+ return ($this->_flags & self::IS_ITALIC)!==0;
+ }
+
+ /**
+ * @param boolean whether the font is italic
+ */
+ public function setItalic($value)
+ {
+ $this->_flags |= self::IS_SET_ITALIC;
+ if($value)
+ $this->_flags |= self::IS_ITALIC;
+ else
+ $this->_flags &= ~self::IS_ITALIC;
+ }
+
+ /**
+ * @return boolean whether the font is overlined
+ */
+ public function getOverline()
+ {
+ return ($this->_flags & self::IS_OVERLINE)!==0;
+ }
+
+ /**
+ * @param boolean whether the font is overlined
+ */
+ public function setOverline($value)
+ {
+ $this->_flags |= self::IS_SET_OVERLINE;
+ if($value)
+ $this->_flags |= self::IS_OVERLINE;
+ else
+ $this->_flags &= ~self::IS_OVERLINE;
+ }
+
+ /**
+ * @return string the font size
+ */
+ public function getSize()
+ {
+ return $this->_size;
+ }
+
+ /**
+ * @param string the font size
+ */
+ public function setSize($value)
+ {
+ $this->_flags |= self::IS_SET_SIZE;
+ $this->_size=$value;
+ }
+
+ /**
+ * @return boolean whether the font is strikeout
+ */
+ public function getStrikeout()
+ {
+ return ($this->_flags & self::IS_STRIKEOUT)!==0;
+ }
+
+ /**
+ * @param boolean whether the font is strikeout
+ */
+ public function setStrikeout($value)
+ {
+ $this->_flags |= self::IS_SET_STRIKEOUT;
+ if($value)
+ $this->_flags |= self::IS_STRIKEOUT;
+ else
+ $this->_flags &= ~self::IS_STRIKEOUT;
+ }
+
+ /**
+ * @return boolean whether the font is underlined
+ */
+ public function getUnderline()
+ {
+ return ($this->_flags & self::IS_UNDERLINE)!==0;
+ }
+
+ /**
+ * @param boolean whether the font is underlined
+ */
+ public function setUnderline($value)
+ {
+ $this->_flags |= self::IS_SET_UNDERLINE;
+ if($value)
+ $this->_flags |= self::IS_UNDERLINE;
+ else
+ $this->_flags &= ~self::IS_UNDERLINE;
+ }
+
+ /**
+ * @return string the font name (family)
+ */
+ public function getName()
+ {
+ return $this->_name;
+ }
+
+ /**
+ * @param string the font name (family)
+ */
+ public function setName($value)
+ {
+ $this->_flags |= self::IS_SET_NAME;
+ $this->_name=$value;
+ }
+
+ /**
+ * @return boolean whether the font is empty
+ */
+ public function getIsEmpty()
+ {
+ return !$this->_flags;
+ }
+
+ /**
+ * Clears up the font.
+ */
+ public function reset()
+ {
+ $this->_flags=0;
+ $this->_name='';
+ $this->_size='';
+ }
+
+ /**
+ * Merges the font with a new one.
+ * The fields in the new font will overwrite the current ones.
+ * @param TFont the new font
+ */
+ public function mergeWith($font)
+ {
+ if($font===null)
+ return;
+ if($font->_flags & self::IS_SET_BOLD)
+ $this->setBold($font->getBold());
+ if($font->_flags & self::IS_SET_ITALIC)
+ $this->setItalic($font->getItalic());
+ if($font->_flags & self::IS_SET_OVERLINE)
+ $this->setOverline($font->getOverline());
+ if($font->_flags & self::IS_SET_STRIKEOUT)
+ $this->setStrikeout($font->getStrikeout());
+ if($font->_flags & self::IS_SET_UNDERLINE)
+ $this->setUnderline($font->getUnderline());
+ if($font->_flags & self::IS_SET_SIZE)
+ $this->setSize($font->getSize());
+ if($font->_flags & self::IS_SET_NAMES)
+ $this->setName($font->getName());
+ }
+
+ /**
+ * Copies the font from a new one.
+ * The existing font will be cleared up first.
+ * @param TFont the new font.
+ */
+ public function copyFrom($font)
+ {
+ $this->reset();
+ $this->mergeWith($font);
+ }
+
+ /**
+ * @return string the font in a css style string representation.
+ */
+ public function toString()
+ {
+ if($this->getIsEmpty())
+ return '';
+ $str='';
+ if($this->_flags & self::IS_SET_BOLD)
+ $str.='font-weight:'.(($this->_flags & self::IS_BOLD)?'bold;':'normal;');
+ if($this->_flags & self::IS_SET_ITALIC)
+ $str.='font-style:'.(($this->_flags & self::IS_ITALIC)?'italic;':'normal;');
+ $textDec='';
+ if($this->_flags & self::IS_UNDERLINE)
+ $textDec.='underline';
+ if($this->_flags & self::IS_OVERLINE)
+ $textDec.=' overline';
+ if($this->_flags & self::IS_STRIKEOUT)
+ $textDec.=' line-through';
+ $textDec=ltrim($textDec);
+ if($textDec!=='')
+ $str.='text-decoration:'.$textDec.';';
+ if($this->_size!=='')
+ $str.='font-size:'.$this->_size.';';
+ if($this->_name!=='')
+ $str.='font-family:'.$this->_name.';';
+ return $str;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Web/UI/WebControls/THiddenField.php b/framework/Web/UI/WebControls/THiddenField.php
new file mode 100644
index 00000000..c46f1cda
--- /dev/null
+++ b/framework/Web/UI/WebControls/THiddenField.php
@@ -0,0 +1,123 @@
+<?php
+/**
+ * THiddenField class file.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.xisc.com/
+ * @copyright Copyright &copy; 2004-2005, Qiang Xue
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ */
+
+/**
+ * THiddenField class
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ * @since 3.0
+ */
+class THiddenField extends TControl implements IPostBackDataHandler
+{
+ /**
+ * @return string tag name of the hyperlink
+ */
+ protected function getTagName()
+ {
+ return 'input';
+ }
+
+ public function focus()
+ {
+ throw new TInvalidOperationException('xxx');
+ }
+
+ protected function addAttributesToRender($writer)
+ {
+ $page=$this->getPage();
+ $page->ensureRenderInForm($this);
+ $writer->addAttribute('type','hidden');
+ if(($uid=$this->getUniqueID())!=='')
+ $writer->addAttribute('name',$uid);
+ if(($id=$this->getID())!=='')
+ $writer->addAttribute('id',$id);
+ if(($value=$this->getValue())!=='')
+ $writer->addAttribute('value',$value);
+ }
+
+ /**
+ * @return string the value of the THiddenField
+ */
+ public function getValue()
+ {
+ return $this->getViewState('Value','');
+ }
+
+ /**
+ * Sets the value of the THiddenField
+ * @param string the value to be set
+ */
+ public function setValue($value)
+ {
+ $this->setViewState('Value',$value,'');
+ }
+
+ public function getEnableTheming()
+ {
+ return false;
+ }
+
+ public function setEnableTheming($value)
+ {
+ throw new TInvalidOperationException('no_theming_support');
+ }
+
+ public function setSkinID($value)
+ {
+ throw new TInvalidOperationException('no_theming_support');
+ }
+
+ /**
+ * Loads hidden field data.
+ * This method is primarly used by framework developers.
+ * @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 component has been changed
+ */
+ public function loadPostData($key,$values)
+ {
+ $value=$values[$key];
+ if($value===$this->getValue())
+ return false;
+ else
+ {
+ $this->setValue($value);
+ return true;
+ }
+ }
+
+ /**
+ * Raises postdata changed event.
+ * This method calls {@link onValueChanged} method.
+ * This method is primarly used by framework developers.
+ */
+ public function raisePostDataChangedEvent()
+ {
+ $this->onValueChanged(new TEventParameter);
+ }
+
+ /**
+ * This method is invoked when the value of the <b>Value</b> property changes between posts to the server.
+ * The method raises 'ValueChanged' event to fire up the event delegates.
+ * If you override this method, be sure to call the parent implementation
+ * so that the event delegates can be invoked.
+ * @param TEventParameter event parameter to be passed to the event handlers
+ */
+ public function onValueChanged($param)
+ {
+ $this->raiseEvent('ValueChanged',$this,$param);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Web/UI/WebControls/THyperLink.php b/framework/Web/UI/WebControls/THyperLink.php
new file mode 100644
index 00000000..2b57e101
--- /dev/null
+++ b/framework/Web/UI/WebControls/THyperLink.php
@@ -0,0 +1,144 @@
+<?php
+/**
+ * THyperLink class file.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.xisc.com/
+ * @copyright Copyright &copy; 2004-2005, Qiang Xue
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ */
+
+/**
+ * THyperLink class
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ * @since 3.0
+ */
+class THyperLink extends TWebControl
+{
+ // todo: TControl::resolveClientUrl
+ /**
+ * @return string tag name of the hyperlink
+ */
+ protected function getTagName()
+ {
+ return 'a';
+ }
+
+ protected function addAttributesToRender($writer)
+ {
+ $isEnabled=$this->getEnabled(true);
+ if($this->getEnabled() && !$isEnabled)
+ $writer->addAttribute('disabled','disabled');
+ parent::addAttributesToRender($writer);
+ if(($url=$this->getNavigateUrl())!=='' && $isEnabled)
+ {
+ // todo
+ //$url=$this->resolveClientUrl($url);
+ $writer->addAttribute('href',$url);
+ }
+ if(($target=$this->getTarget())!=='')
+ $writer->addAttribute('target',$target);
+ }
+
+ /**
+ * Renders the body content of the hyperlink.
+ * @param THtmlTextWriter the writer for rendering
+ */
+ protected function renderContents($writer)
+ {
+ if(($imageUrl=$this->getImageUrl())==='')
+ {
+ if($this->getHasControls())
+ parent::renderContents($writer);
+ else
+ $writer->write($this->getText());
+ }
+ else
+ {
+ $image=new TImage;
+ $image->setImageUrl($this->resolveClientUrl($imageUrl));
+ if(($toolTip=$this->getToolTip())!=='')
+ $image->setToolTip($toolTip);
+ if(($text=$this->getText())!=='')
+ $image->setAlternateText($text);
+ $image->renderControl($writer);
+ }
+ }
+
+ /**
+ * @return string the text caption of the THyperLink
+ */
+ public function getText()
+ {
+ return $this->getViewState('Text','');
+ }
+
+ /**
+ * Sets the text caption of the THyperLink.
+ * @param string the text caption to be set
+ */
+ public function setText($value)
+ {
+ if($this->getHasControls())
+ $this->getControls()->clear();
+ $this->setViewState('Text',$value,'');
+ }
+
+ /**
+ * @return string the location of the image file for the THyperLink
+ */
+ public function getImageUrl()
+ {
+ return $this->getViewState('ImageUrl','');
+ }
+
+ /**
+ * Sets the location of image file of the THyperLink.
+ * @param string the image file location
+ */
+ public function setImageUrl($value)
+ {
+ $this->setViewState('ImageUrl',$value,'');
+ }
+
+ /**
+ * @return string the URL to link to when the THyperLink component is clicked.
+ */
+ public function getNavigateUrl()
+ {
+ return $this->getViewState('NavigateUrl','');
+ }
+
+ /**
+ * Sets the URL to link to when the THyperLink component is clicked.
+ * @param string the URL
+ */
+ public function setNavigateUrl($value)
+ {
+ $this->setViewState('NavigateUrl',$value,'');
+ }
+
+ /**
+ * @return string the target window or frame to display the Web page content linked to when the THyperLink component is clicked.
+ */
+ public function getTarget()
+ {
+ return $this->getViewState('Target','');
+ }
+
+ /**
+ * Sets the target window or frame to display the Web page content linked to when the THyperLink component is clicked.
+ * @param string the target window, valid values include '_blank', '_parent', '_self', '_top' and empty string.
+ */
+ public function setTarget($value)
+ {
+ $this->setViewState('Target',$value,'');
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Web/UI/WebControls/TImage.php b/framework/Web/UI/WebControls/TImage.php
new file mode 100644
index 00000000..46e61083
--- /dev/null
+++ b/framework/Web/UI/WebControls/TImage.php
@@ -0,0 +1,122 @@
+<?php
+/**
+ * TImage class file.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.xisc.com/
+ * @copyright Copyright &copy; 2004-2005, Qiang Xue
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ */
+
+/**
+ * TImage class
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ * @since 3.0
+ */
+class TImage extends TWebControl
+{
+ public static $IMAGE_ALIGN=array('NotSet','AbsBottom','AbsMiddle','Baseline','Bottom','Left','Middle','Right','TextTop','Top');
+ // todo: TControl::resolveClientUrl()
+ /**
+ * @return string tag name of the image
+ */
+ protected function getTagName()
+ {
+ return 'img';
+ }
+
+ protected function addAttributesToRender($writer)
+ {
+ $writer->addAttribute('src',$this->getImageUrl());
+ $writer->addAttribute('alt',$this->getAlternateText());
+ if(($desc=$this->getDescriptionUrl())!=='')
+ $writer->addAttribute('longdesc',$this->resolveClientUrl($desc));
+ if(($align=$this->getImageAlign())!=='NotSet')
+ $writer->addAttribute('align',strtolower($align));
+ parent::addAttributesToRender($writer);
+ }
+
+ /**
+ * Renders the body content of the image.
+ * None will be rendered for an image.
+ * @param THtmlTextWriter the writer for rendering
+ */
+ protected function renderContents($writer)
+ {
+ }
+
+ /**
+ * @return string the alternative text displayed in the TImage component when the image is unavailable.
+ */
+ public function getAlternateText()
+ {
+ return $this->getViewState('AlternateText','');
+ }
+
+ /**
+ * Sets the alternative text to be displayed in the TImage when the image is unavailable.
+ * @param string the alternative text
+ */
+ public function setAlternateText($value)
+ {
+ $this->setViewState('AlternateText',$value,'');
+ }
+
+ /**
+ * @return string the alignment of the image with respective to other elements on the page.
+ */
+ public function getImageAlign()
+ {
+ return $this->getViewState('ImageAlign','');
+ }
+
+ /**
+ * Sets the alignment of the image with respective to other elements on the page.
+ * @param string the alignment of the image
+ */
+ public function setImageAlign($value)
+ {
+ $this->setViewState('ImageAlign',TPropertyValue::ensureEnum($value,self::$IMAGE_ALIGN),'NotSet');
+ }
+
+ /**
+ * @return string the location of the image file to be displayed
+ */
+ public function getImageUrl()
+ {
+ return $this->getViewState('ImageUrl','');
+ }
+
+ /**
+ * Sets the location of the image file to be displayed.
+ * @param string the location of the image file (file path or URL)
+ */
+ public function setImageUrl($value)
+ {
+ $this->setViewState('ImageUrl',$value,'');
+ }
+
+ /**
+ * @return string link to long description
+ */
+ public function getDescriptionUrl()
+ {
+ return $this->getViewState('DescriptionUrl','');
+ }
+
+ /**
+ * Sets the link to long description
+ * @param string the link to long description
+ */
+ public function setDescriptionUrl($value)
+ {
+ $this->setViewState('DescriptionUrl',$value,'');
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Web/UI/WebControls/TImageButton.php b/framework/Web/UI/WebControls/TImageButton.php
new file mode 100644
index 00000000..6452c651
--- /dev/null
+++ b/framework/Web/UI/WebControls/TImageButton.php
@@ -0,0 +1,320 @@
+<?php
+/**
+ * TImageButton class file
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.xisc.com/
+ * @copyright Copyright &copy; 2004-2005, Qiang Xue
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ */
+
+/**
+ * TImage class file
+ */
+require_once(dirname(__FILE__).'/TImage.php');
+
+/**
+ * TImageButton class
+ *
+ * TImageButton displays an image on the Web page and responds to mouse clicks on the image.
+ * It is similar to the TButton component except that the TImageButton also captures the
+ * coordinates where the image is clicked.
+ *
+ * Write a <b>OnClick</b> event handler to programmatically determine the coordinates
+ * where the image is clicked. The coordinates can be accessed through <b>x</b> and <b>y</b>
+ * properties of the event parameter which is of type <b>TImageClickEventParameter</b>.
+ * Note the origin (0, 0) is located at the upper left corner of the image.
+ *
+ * Write a <b>OnCommand</b> event handler to make the TImageButton component behave
+ * like a command button. A command name can be associated with the component by using
+ * the <b>CommandName</b> property. The <b>CommandParameter</b> property
+ * can also be used to pass additional information about the command,
+ * such as specifying ascending order. This allows multiple TImageButton components to be placed
+ * on the same Web page. In the event handler, you can also determine
+ * the <b>CommandName</b> property value and the <b>CommandParameter</b> property value
+ * through <b>name</b> and <b>parameter</b> of the event parameter which is of
+ * type <b>TCommandEventParameter</b>.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ * @since 3.0
+ */
+
+class TImageButton extends TImage implements IPostBackDataHandler, IPostBackEventHandler
+{
+ private $_x=0;
+ private $_y=0;
+
+ /**
+ * @return string tag name of the button
+ */
+ protected function getTagName()
+ {
+ return 'input';
+ }
+
+ /**
+ * Adds attribute name-value pairs to renderer.
+ * This overrides the parent implementation with additional button specific attributes.
+ * @param THtmlTextWriter the writer used for the rendering purpose
+ */
+ protected function addAttributesToRender($writer)
+ {
+ $page=$this->getPage();
+ $page->ensureRenderInForm($this);
+ $writer->addAttribute('type','image');
+ if(($uniqueID=$this->getUniqueID())!=='')
+ $writer->addAttribute('name',$uniqueID);
+
+ $onclick='';
+ if($this->getEnabled(true))
+ {
+ $onclick=$this->getOnClientClick();
+ if($onclick!=='')
+ $onclick=rtrim($onclick,';').';';
+ $onclick.=$page->getClientScript()->getPostBackEventReference($this->getPostBackOptions());
+ }
+ else if($this->getEnabled()) // in this case, parent will not render 'disabled'
+ $writer->addAttribute('disabled','disabled');
+ if($onclick!=='')
+ $writer->addAttribute('onclick','javascript:'.$onclick);
+ parent::addAttributesToRender($writer);
+ }
+
+ /**
+ * Returns postback specifications for the button.
+ * This method is used by framework and control developers.
+ * @return TPostBackOptions parameters about how the button defines its postback behavior.
+ */
+ protected function getPostBackOptions()
+ {
+ $options=new TPostBackOptions($this);
+ $options->ClientSubmit=false;
+ $page=$this->getPage();
+ if($this->getCausesValidation() && $page->getValidators($this->getValidationGroup())->getCount()>0)
+ {
+ $options->PerformValidation=true;
+ $options->ValidationGroup=$this->getValidationGroup();
+ }
+ if($this->getPostBackUrl()!=='')
+ $options->ActionUrl=$this->getPostBackUrl();
+ return $options;
+ }
+
+ /**
+ * This method checks if the TImageButton is clicked and loads the coordinates of the clicking position.
+ * This method is primarly used by framework developers.
+ * @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 component has been changed
+ */
+ public function loadPostData($key,$values)
+ {
+ $uid=$this->getUniqueID();
+ if(isset($values["{$uid}_x"]) && isset($values["{$uid}_y"]))
+ {
+ $this->_x=intval($values["{$uid}_x"]);
+ $this->_y=intval($values["{$uid}_y"]);
+ $page=$this->getPage()->registerRequiresRaiseEvent($this);
+ }
+ return false;
+ }
+
+ /**
+ * 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.
+ * This method is primarily used by framework developers.
+ * @param string the parameter associated with the postback event
+ */
+ public function raisePostBackEvent($param)
+ {
+ if($this->getCausesValidation())
+ $this->getPage()->validate($this->getValidationGroup());
+ $this->onClick(new TImageClickEventParameter($this->_x,$this->_y));
+ $this->onCommand(new TCommandEventParameter($this->getCommandName(),$this->getCommandParameter()));
+ }
+
+ /**
+ * A dummy implementation for the IPostBackDataHandler interface.
+ */
+ public function raisePostDataChangedEvent()
+ {
+ // no post data to handle
+ }
+
+ /**
+ * This method is invoked when the component is clicked.
+ * The method raises 'Click' event to fire up the event delegates.
+ * If you override this method, be sure to call the parent implementation
+ * so that the event delegates can be invoked.
+ * @param TEventParameter event parameter to be passed to the event handlers
+ */
+ public function onClick($param)
+ {
+ $this->raiseEvent('Click',$this,$param);
+ }
+
+ /**
+ * This method is invoked when the component is clicked.
+ * The method raises 'Command' event to fire up the event delegates.
+ * If you override this method, be sure to call the parent implementation
+ * so that the event delegates can be invoked.
+ * @param TCommandEventParameter event parameter to be passed to the event handlers
+ */
+ public function onCommand($param)
+ {
+ $this->raiseEvent('Command',$this,$param);
+ $this->raiseBubbleEvent($this,$param);
+ }
+
+ protected function onPreRender($param)
+ {
+ parent::onPreRender($param);
+ $this->getPage()->registerRequiresPostBack($this);
+ }
+
+ /**
+ * @return string the command name associated with the <b>OnCommand</b> event.
+ */
+ public function getCommandName()
+ {
+ return $this->getViewState('CommandName','');
+ }
+
+ /**
+ * Sets the command name associated with the <b>OnCommand</b> event.
+ * @param string the text caption to be set
+ */
+ public function setCommandName($value)
+ {
+ $this->setViewState('CommandName',$value,'');
+ }
+
+ /**
+ * @return string the parameter associated with the <b>OnCommand</b> event
+ */
+ public function getCommandParameter()
+ {
+ return $this->getViewState('CommandParameter','');
+ }
+
+ /**
+ * Sets the parameter associated with the <b>OnCommand</b> event.
+ * @param string the text caption to be set
+ */
+ public function setCommandParameter($value)
+ {
+ $this->setViewState('CommandParameter',$value,'');
+ }
+
+ /**
+ * @return boolean whether postback event trigger by this button will cause input validation
+ */
+ public function getCausesValidation()
+ {
+ return $this->getViewState('CausesValidation',true);
+ }
+
+ /**
+ * Sets the value indicating whether postback event trigger by this button will cause input validation.
+ * @param string the text caption to be set
+ */
+ public function setCausesValidation($value)
+ {
+ $this->setViewState('CausesValidation',$value,true);
+ }
+
+ /**
+ * @return string the group of validators which the button causes validation upon postback
+ */
+ public function getValidationGroup()
+ {
+ return $this->getViewState('ValidationGroup','');
+ }
+
+ /**
+ * @param string the group of validators which the button causes validation upon postback
+ */
+ public function setValidationGroup($value)
+ {
+ $this->setViewState('ValidationGroup',$value,'');
+ }
+
+ /**
+ * @return string the URL of the page to post to when the button is clicked, default is empty meaning post to the current page itself
+ */
+ public function getPostBackUrl()
+ {
+ return $this->getViewState('PostBackUrl','');
+ }
+
+ /**
+ * @param string the URL of the page to post to from the current page when the button is clicked, empty if post to the current page itself
+ */
+ public function setPostBackUrl($value)
+ {
+ $this->setViewState('PostBackUrl',$value,'');
+ }
+
+ /**
+ * @return string the javascript to be executed when the button is clicked
+ */
+ public function getOnClientClick()
+ {
+ return $this->getViewState('OnClientClick','');
+ }
+
+ /**
+ * @param string the javascript to be executed when the button is clicked. Do not prefix it with "javascript:".
+ */
+ public function setOnClientClick($value)
+ {
+ $this->setViewState('OnClientClick',$value,'');
+ }
+
+ /**
+ * @return string caption of the button
+ */
+ public function getText()
+ {
+ return $this->getAlternateText();
+ }
+
+ /**
+ * @param string caption of the button
+ */
+ public function setText($value)
+ {
+ $this->setAlternateText($value);
+ }
+}
+
+/**
+ * TImageClickEventParameter class
+ *
+ * TImageClickEventParameter encapsulates the parameter data for <b>OnClick</b>
+ * event of TImageButton components.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version v1.0, last update on 2004/08/13 21:44:52
+ * @package System.Web.UI.WebControls
+ */
+class TImageClickEventParameter extends TEventParameter
+{
+ /**
+ * the X coordinate of the clicking point
+ * @var integer
+ */
+ public $x=0;
+ /**
+ * the Y coordinate of the clicking point
+ * @var integer
+ */
+ public $y=0;
+}
+
+?> \ No newline at end of file
diff --git a/framework/Web/UI/WebControls/TLabel.php b/framework/Web/UI/WebControls/TLabel.php
new file mode 100644
index 00000000..464a4cd7
--- /dev/null
+++ b/framework/Web/UI/WebControls/TLabel.php
@@ -0,0 +1,106 @@
+<?php
+/**
+ * TLabel class file.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.xisc.com/
+ * @copyright Copyright &copy; 2004-2005, Qiang Xue
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ */
+
+/**
+ * TLabel class
+ *
+ * TLabel represents a label control that displays text on a Web pagge.
+ * Use <b>Text</b> property to set the text to be displayed.
+ * TLabel will render the contents enclosed within its component tag
+ * if <b>Text</b> is empty.
+ * To use TLabel as a form label, associate it with a control by setting the
+ * <b>AssociateControlID</b> property. The associated control must be locatable
+ * within the label's naming container.
+ *
+ * Note, <b>Text</b> will NOT be encoded for rendering.
+ * Make usre it does not contain dangerous characters that you want to avoid.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ * @since 3.0
+ */
+class TLabel extends TWebControl
+{
+ /**
+ * @return string tag name of the label, returns 'label' if there is an associated control, 'span' otherwise.
+ */
+ protected function getTagName()
+ {
+ return ($this->getAssociatedControlID()==='')?'span':'label';
+ }
+
+ /**
+ * Adds attributes to renderer.
+ * @param THtmlTextWriter the renderer
+ * @throws TInvalidDataValueException if associated control cannot be found using the ID
+ */
+ protected function addAttributesToRender($writer)
+ {
+ if(($aid=$this->getAssociatedControlID())!=='')
+ {
+ if($control=$this->findControl($aid))
+ $writer->addAttribute('for',$control->getClientID());
+ else
+ throw new TInvalidDataValueException('control_not_found',$aid);
+ }
+ parent::addAttributesToRender($writer);
+ }
+
+ /**
+ * Renders the body content of the label.
+ * @param THtmlTextWriter the renderer
+ */
+ protected function renderContents($writer)
+ {
+ if(($text=$this->getText())==='')
+ parent::renderContents($writer);
+ else
+ $writer->write($text);
+ }
+
+ /**
+ * @return string the text value of the label
+ */
+ public function getText()
+ {
+ return $this->getViewState('Text','');
+ }
+
+ /**
+ * @param string the text value of the label
+ */
+ public function setText($value)
+ {
+ $this->setViewState('Text',$value,'');
+ }
+
+ /**
+ * @return string the associated control ID
+ */
+ public function getAssociatedControlID()
+ {
+ return $this->getViewState('AssociatedControlID','');
+ }
+
+ /**
+ * Sets the ID of the control that the label is associated with.
+ * The control must be locatable via {@link TControl::findControl} using the ID.
+ * @param string the associated control ID
+ */
+ public function setAssociatedControlID($value)
+ {
+ $this->setViewState('AssociatedControlID',$value,'');
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Web/UI/WebControls/TLiteral.php b/framework/Web/UI/WebControls/TLiteral.php
new file mode 100644
index 00000000..0509724a
--- /dev/null
+++ b/framework/Web/UI/WebControls/TLiteral.php
@@ -0,0 +1,79 @@
+<?php
+/**
+ * TLiteral class file
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.xisc.com/
+ * @copyright Copyright &copy; 2004-2005, Qiang Xue
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ */
+
+/**
+ * TLiteral class
+ *
+ * TLiteral reserves a location on the Web page to display static text or body content.
+ * The TLiteral control is similar to the TLabel control, except the TLiteral
+ * control does not allow you to apply a style to the displayed text.
+ * You can programmatically control the text displayed in the control by setting
+ * the <b>Text</b> property. If the <b>Text</b> property is empty, the content
+ * enclosed within the TLiteral control will be displayed. This is very useful
+ * for reserving a location on a page because you can add text and controls
+ * as children of TLiteral control and they will be rendered at the place.
+ *
+ * Note, <b>Text</b> is not HTML encoded before it is displayed in the TLiteral component.
+ * If the values for the component come from user input, be sure to validate the values
+ * to help prevent security vulnerabilities.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ * @since 3.0
+ */
+class TLiteral extends TControl
+{
+ /**
+ * @return string the static text of the TLiteral
+ */
+ public function getText()
+ {
+ return $this->getViewState('Text','');
+ }
+
+ /**
+ * Sets the static text of the TLiteral
+ * @param string the text to be set
+ */
+ public function setText($value)
+ {
+ $this->setViewState('Text',$value,'');
+ }
+
+ public function getEncode()
+ {
+ return $this->getViewState('Encode',false);
+ }
+
+ public function setEncode($value)
+ {
+ $this->setViewState('Encode',$value,false);
+ }
+
+ /**
+ * Renders the evaluation result of the statements.
+ * @param THtmlTextWriter the writer used for the rendering purpose
+ */
+ protected function render($writer)
+ {
+ if(($text=$this->getText())!=='')
+ {
+ if($this->getEncode())
+ $writer->write(THttpUtility::htmlEncode($text));
+ else
+ $writer->write($text);
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Web/UI/WebControls/TPanel.php b/framework/Web/UI/WebControls/TPanel.php
new file mode 100644
index 00000000..36f70479
--- /dev/null
+++ b/framework/Web/UI/WebControls/TPanel.php
@@ -0,0 +1,162 @@
+<?php
+/**
+ * TPanel class file
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.xisc.com/
+ * @copyright Copyright &copy; 2004-2005, Qiang Xue
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ */
+
+/**
+ * TPanel class
+ *
+ * TPanel represents a component that acts as a container for other component.
+ * It is especially useful when you want to generate components programmatically or hide/show a group of components.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ * @since 3.0
+ */
+class TPanel extends TWebControl
+{
+ /**
+ * @return string tag name of the panel
+ */
+ protected function getTagName()
+ {
+ return 'div';
+ }
+
+ /**
+ * Adds attributes to renderer.
+ * @param THtmlTextWriter the renderer
+ */
+ protected function addAttributesToRender($writer)
+ {
+ $url=trim($this->getBackImageUrl());
+ if($url!=='')
+ $this->getStyle
+ base.AddAttributesToRender(writer);
+ string text1 = this.BackImageUrl;
+ if (text1.Trim().Length > 0)
+ {
+ writer.AddStyleAttribute(HtmlTextWriterStyle.BackgroundImage, "url(" + base.ResolveClientUrl(text1) + ")");
+ }
+ this.AddScrollingAttribute(this.ScrollBars, writer);
+ HorizontalAlign align1 = this.HorizontalAlign;
+ if (align1 != HorizontalAlign.NotSet)
+ {
+ TypeConverter converter1 = TypeDescriptor.GetConverter(typeof(HorizontalAlign));
+ writer.AddStyleAttribute(HtmlTextWriterStyle.TextAlign, converter1.ConvertToInvariantString(align1).ToLowerInvariant());
+ }
+ if (!this.Wrap)
+ {
+ if (base.EnableLegacyRendering)
+ {
+ writer.AddAttribute(HtmlTextWriterAttribute.Nowrap, "nowrap", false);
+ }
+ else
+ {
+ writer.AddStyleAttribute(HtmlTextWriterStyle.WhiteSpace, "nowrap");
+ }
+ }
+ if (this.Direction == ContentDirection.LeftToRight)
+ {
+ writer.AddAttribute(HtmlTextWriterAttribute.Dir, "ltr");
+ }
+ else if (this.Direction == ContentDirection.RightToLeft)
+ {
+ writer.AddAttribute(HtmlTextWriterAttribute.Dir, "rtl");
+ }
+ if (((!base.DesignMode && (this.Page != null)) && ((this.Page.Request != null) && (this.Page.Request.Browser.EcmaScriptVersion.Major > 0))) && ((this.Page.Request.Browser.W3CDomVersion.Major > 0) && (this.DefaultButton.Length > 0)))
+ {
+ Control control1 = this.FindControl(this.DefaultButton);
+ if (control1 is IButtonControl)
+ {
+ this.Page.ClientScript.RegisterDefaultButtonScript(control1, writer, true);
+ }
+ else
+ {
+ object[] objArray1 = new object[1] { this.ID } ;
+ throw new InvalidOperationException(SR.GetString("HtmlForm_OnlyIButtonControlCanBeDefaultButton", objArray1));
+ }
+ }
+
+ }
+
+ /**
+ * @return boolean whether the content wraps within the panel.
+ */
+ public function getWrap()
+ {
+ return $this->getViewState('Wrap',true);
+ }
+
+ /**
+ * Sets the value indicating whether the content wraps within the panel.
+ * @param boolean whether the content wraps within the panel.
+ */
+ public function setWrap($value)
+ {
+ $this->setViewState('Wrap',$value,true);
+ }
+
+ /**
+ * @return string the horizontal alignment of the contents within the panel.
+ */
+ public function getHorizontalAlign()
+ {
+ return $this->getViewState('HorizontalAlign','');
+ }
+
+ /**
+ * Sets the horizontal alignment of the contents within the panel.
+ * Valid values include 'justify', 'left', 'center', 'right' or empty string.
+ * @param string the horizontal alignment
+ */
+ public function setHorizontalAlign($value)
+ {
+ $this->setViewState('HorizontalAlign',$value,'');
+ }
+
+ /**
+ * @return string the URL of the background image for the panel component.
+ */
+ public function getBackImageUrl()
+ {
+ return $this->getViewState('BackImageUrl','');
+ }
+
+ /**
+ * Sets the URL of the background image for the panel component.
+ * @param string the URL
+ */
+ public function setBackImageUrl($value)
+ {
+ $this->setViewState('BackImageUrl',$value,'');
+ }
+
+ /**
+ * This overrides the parent implementation by rendering more TPanel-specific attributes.
+ * @return ArrayObject the attributes to be rendered
+ */
+ protected function getAttributesToRender()
+ {
+ $url=$this->getBackImageUrl();
+ if(strlen($url))
+ $this->setStyle(array('background-image'=>"url($url)"));
+ $attributes=parent::getAttributesToRender();
+ $align=$this->getHorizontalAlign();
+ if(strlen($align))
+ $attributes['align']=$align;
+ if(!$this->isWrap())
+ $attributes['nowrap']='nowrap';
+ return $attributes;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Web/UI/WebControls/TPlaceHolder.php b/framework/Web/UI/WebControls/TPlaceHolder.php
new file mode 100644
index 00000000..9149e180
--- /dev/null
+++ b/framework/Web/UI/WebControls/TPlaceHolder.php
@@ -0,0 +1,25 @@
+<?php
+/**
+ * TPlaceHolder class file
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.xisc.com/
+ * @copyright Copyright &copy; 2004-2005, Qiang Xue
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ */
+
+/**
+ * TPlaceHolder class
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ * @since 3.0
+ */
+class TPlaceHolder extends TControl
+{
+}
+
+?>
diff --git a/framework/Web/UI/WebControls/TStatements.php b/framework/Web/UI/WebControls/TStatements.php
new file mode 100644
index 00000000..e0892f2b
--- /dev/null
+++ b/framework/Web/UI/WebControls/TStatements.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * TStatements class file
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.xisc.com/
+ * @copyright Copyright &copy; 2004-2005, Qiang Xue
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ */
+
+/**
+ * TStatements class
+ *
+ * TStatements executes a set of PHP statements and renders the display
+ * generated by the statements. The execution happens during rendering stage.
+ * You can set the statements via the property <b>Statements</b>.
+ * You should also specify the context object by <b>Context</b> property
+ * which is used as the object in which the statements is evaluated.
+ * If the <b>Context</b> property is not set, the TStatements component
+ * itself will be assumed as the context.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ * @since 3.0
+ */
+class TStatements extends TControl
+{
+ private $_s='';
+
+ /**
+ * @return string the statements to be executed
+ */
+ public function getStatements()
+ {
+ return $this->_s;
+ }
+
+ /**
+ * Sets the statements of the TStatements
+ * @param string the statements to be set
+ */
+ public function setStatements($value)
+ {
+ $this->_s=$value;
+ }
+
+ /**
+ * Renders the evaluation result of the statements.
+ * @param THtmlTextWriter the writer used for the rendering purpose
+ */
+ protected function render($writer)
+ {
+ if($this->_s!=='')
+ $writer->write($this->evaluateStatements($this->_s));
+ }
+}
+
+?>
diff --git a/framework/Web/UI/WebControls/TStyle.php b/framework/Web/UI/WebControls/TStyle.php
new file mode 100644
index 00000000..e1e92b1b
--- /dev/null
+++ b/framework/Web/UI/WebControls/TStyle.php
@@ -0,0 +1,334 @@
+<?php
+/**
+ * TStyle class file.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.xisc.com/
+ * @copyright Copyright &copy; 2004-2005, Qiang Xue
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ */
+
+/**
+ * TStyle class
+ *
+ * TStyle encapsulates the CSS style applied to a control.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ * @since 3.0
+ */
+class TStyle extends TComponent
+{
+ /**
+ * @var array The enumerable type for border styles
+ */
+ public static $ENUM_BORDER_STYLE=array('NotSet','None','Dashed','Dotted','Solid','Double','Groove','Ridge','Inset','Outset');
+
+ /**
+ * Various CSS fields
+ */
+ const FLD_BACKCOLOR=0;
+ const FLD_BORDERCOLOR=1;
+ const FLD_BORDERWIDTH=2;
+ const FLD_BORDERSTYLE=3;
+ const FLD_FONT=4;
+ const FLD_FORECOLOR=5;
+ const FLD_HEIGHT=6;
+ const FLD_WIDTH=7;
+ const FLD_CSSCLASS=8;
+ const FLD_STYLE=9;
+
+ /**
+ * @var array storage of CSS fields
+ */
+ private $_data=array();
+
+ /**
+ * @return string the background color of the control
+ */
+ public function getBackColor()
+ {
+ return isset($this->_data[self::FLD_BACKCOLOR])?$this->_data[self::FLD_BACKCOLOR]:'';
+ }
+
+ /**
+ * @param string the background color of the control
+ */
+ public function setBackColor($value)
+ {
+ if($value==='')
+ unset($this->_data[self::FLD_BACKCOLOR]);
+ else
+ $this->_data[self::FLD_BACKCOLOR]=$value;
+ }
+
+ /**
+ * @return string the border color of the control
+ */
+ public function getBorderColor()
+ {
+ return isset($this->_data[self::FLD_BORDERCOLOR])?$this->_data[self::FLD_BORDERCOLOR]:'';
+ }
+
+ /**
+ * @param string the border color of the control
+ */
+ public function setBorderColor($value)
+ {
+ if($value==='')
+ unset($this->_data[self::FLD_BORDERCOLOR]);
+ else
+ $this->_data[self::FLD_BORDERCOLOR]=$value;
+ }
+
+ /**
+ * @return string the border style of the control
+ */
+ public function getBorderStyle()
+ {
+ return isset($this->_data[self::FLD_BORDERSTYLE])?$this->_data[self::FLD_BORDERSTYLE]:'';
+ }
+
+ /**
+ * Sets the border style of the control.
+ * Valid values include:
+ * 'NotSet','None','Dashed','Dotted','Solid','Double','Groove','Ridge','Inset','Outset'
+ * @param string the border style of the control
+ */
+ public function setBorderStyle($value)
+ {
+ if($value==='')
+ unset($this->_data[self::FLD_BORDERSTYLE]);
+ else
+ $this->_data[self::FLD_BORDERSTYLE]=TPropertyValue::ensureEnum($value,self::$ENUM_BORDER_STYLE);
+ }
+
+ /**
+ * @return string the border width of the control
+ */
+ public function getBorderWidth()
+ {
+ return isset($this->_data[self::FLD_BORDERWIDTH])?$this->_data[self::FLD_BORDERWIDTH]:'';
+ }
+
+ /**
+ * @param string the border width of the control
+ */
+ public function setBorderWidth($value)
+ {
+ if($value==='')
+ unset($this->_data[self::FLD_BORDERWIDTH]);
+ else
+ $this->_data[self::FLD_BORDERWIDTH]=$value;
+ }
+
+ /**
+ * @return string the CSS class of the control
+ */
+ public function getCssClass()
+ {
+ return isset($this->_data[self::FLD_CSSCLASS])?$this->_data[self::FLD_CSSCLASS]:'';
+ }
+
+ /**
+ * @param string the name of the CSS class of the control
+ */
+ public function setCssClass($value)
+ {
+ if($value==='')
+ unset($this->_data[self::FLD_CSSCLASS]);
+ else
+ $this->_data[self::FLD_CSSCLASS]=$value;
+ }
+
+ /**
+ * @return TFont the font of the control
+ */
+ public function getFont()
+ {
+ if(!isset($this->_data[self::FLD_FONT]))
+ $this->_data[self::FLD_FONT]=new TFont;
+ return $this->_data[self::FLD_FONT];
+ }
+
+ /**
+ * @return string the foreground color of the control
+ */
+ public function getForeColor()
+ {
+ return isset($this->_data[self::FLD_FORECOLOR])?$this->_data[self::FLD_FORECOLOR]:'';
+ }
+
+ /**
+ * @param string the foreground color of the control
+ */
+ public function setForeColor($value)
+ {
+ if($value==='')
+ unset($this->_data[self::FLD_FORECOLOR]);
+ else
+ $this->_data[self::FLD_FORECOLOR]=$value;
+ }
+
+ /**
+ * @return string the height of the control
+ */
+ public function getHeight()
+ {
+ return isset($this->_data[self::FLD_HEIGHT])?$this->_data[self::FLD_HEIGHT]:'';
+ }
+
+ /**
+ * @param string the height of the control
+ */
+ public function setHeight($value)
+ {
+ if($value==='')
+ unset($this->_data[self::FLD_HEIGHT]);
+ else
+ $this->_data[self::FLD_HEIGHT]=$value;
+ }
+
+ /**
+ * @return string the custom style of the control
+ */
+ public function getStyle()
+ {
+ return isset($this->_data[self::FLD_STYLE])?$this->_data[self::FLD_STYLE]:'';
+ }
+
+ /**
+ * @param string the custom style of the control
+ */
+ public function setStyle($value)
+ {
+ if($value==='')
+ unset($this->_data[self::FLD_STYLE]);
+ else
+ $this->_data[self::FLD_STYLE]=$value;
+ }
+
+ /**
+ * @return string the width of the control
+ */
+ public function getWidth()
+ {
+ return isset($this->_data[self::FLD_WIDTH])?$this->_data[self::FLD_WIDTH]:'';
+ }
+
+ /**
+ * @param string the width of the control
+ */
+ public function setWidth($value)
+ {
+ if($value==='')
+ unset($this->_data[self::FLD_WIDTH]);
+ else
+ $this->_data[self::FLD_WIDTH]=$value;
+ }
+
+ /**
+ * @param boolean if the style contains nothing
+ */
+ public function getIsEmpty()
+ {
+ return empty($this->_data) || (isset($this->_data[self::FLD_FONT]) && $this->_data[self::FLD_FONT]->getIsEmpty());
+ }
+
+ /**
+ * Resets the style to the original empty state.
+ */
+ public function reset()
+ {
+ $this->_data=array();
+ $this->flags=0;
+ }
+
+ /**
+ * Merges the current style with another one.
+ * If the two styles have the same style field, the new one
+ * will overwrite the current one.
+ * @param TStyle the new style
+ */
+ public function mergeWith($style)
+ {
+ if($style===null)
+ return;
+ if(isset($style->_data[self::FLD_BACKCOLOR]))
+ $this->_data[self::FLD_BACKCOLOR]=$style->_data[self::FLD_BACKCOLOR];
+ if(isset($style->_data[self::FLD_BORDERCOLOR]))
+ $this->_data[self::FLD_BORDERCOLOR]=$style->_data[self::FLD_BORDERCOLOR];
+ if(isset($style->_data[self::FLD_BORDERWIDTH]))
+ $this->_data[self::FLD_BORDERWIDTH]=$style->_data[self::FLD_BORDERWIDTH];
+ if(isset($style->_data[self::FLD_BORDERSTYLE]))
+ $this->_data[self::FLD_BORDERSTYLE]=$style->_data[self::FLD_BORDERSTYLE];
+ if(isset($style->_data[self::FLD_FORECOLOR]))
+ $this->_data[self::FLD_FORECOLOR]=$style->_data[self::FLD_FORECOLOR];
+ if(isset($style->_data[self::FLD_HEIGHT]))
+ $this->_data[self::FLD_HEIGHT]=$style->_data[self::FLD_HEIGHT];
+ if(isset($style->_data[self::FLD_WIDTH]))
+ $this->_data[self::FLD_WIDTH]=$style->_data[self::FLD_WIDTH];
+ if(isset($style->_data[self::FLD_FONT]))
+ $this->getFont()->mergeWith($style->_data[self::FLD_FONT]);
+ if(isset($style->_data[self::FLD_CSSCLASS]))
+ $this->_data[self::FLD_CSSCLASS]=$style->_data[self::FLD_CSSCLASS];
+ }
+
+ /**
+ * Copies from a style.
+ * Existing style will be reset first.
+ * @param TStyle the new style
+ */
+ public function copyFrom($style)
+ {
+ $this->reset();
+ $this->mergeWith($style);
+ }
+
+ /**
+ * Converts the style into a string representation suitable for rendering.
+ * @return string the string representation of the style
+ */
+ public function toString()
+ {
+ if($this->getIsEmpty())
+ return '';
+ if(($str=$this->getStyle())!=='')
+ $str=rtrim($str).';';
+ if(isset($this->_data[self::FLD_BACKCOLOR]))
+ $str.='background-color:'.$this->_data[self::FLD_BACKCOLOR].';';
+ if(isset($this->_data[self::FLD_BORDERCOLOR]))
+ $str.='border-color:'.$this->_data[self::FLD_BORDERCOLOR].';';
+ if(isset($this->_data[self::FLD_BORDERWIDTH]))
+ $str.='border-width:'.$this->_data[self::FLD_BORDERWIDTH].';';
+ if(isset($this->_data[self::FLD_BORDERSTYLE]))
+ $str.='border-style:'.$this->_data[self::FLD_BORDERSTYLE].';';
+ if(isset($this->_data[self::FLD_FORECOLOR]))
+ $str.='color:'.$this->_data[self::FLD_FORECOLOR].';';
+ if(isset($this->_data[self::FLD_HEIGHT]))
+ $str.='height:'.$this->_data[self::FLD_HEIGHT].';';
+ if(isset($this->_data[self::FLD_WIDTH]))
+ $str.='width:'.$this->_data[self::FLD_WIDTH].';';
+ if(isset($this->_data[self::FLD_FONT]))
+ $str.=$this->_data[self::FLD_FONT]->toString();
+ return $str;
+ }
+
+ /**
+ * Adds attributes related to CSS styles to renderer.
+ * @param THtmlTextWriter the writer used for the rendering purpose
+ */
+ public function addAttributesToRender($writer)
+ {
+ $str=$this->toString();
+ if($str!=='')
+ $writer->addAttribute('style',$str);
+ if(isset($this->_data[self::FLD_CSSCLASS]))
+ $writer->addAttribute('class',$this->_data[self::FLD_CSSCLASS]);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Web/UI/WebControls/TTextBox.php b/framework/Web/UI/WebControls/TTextBox.php
new file mode 100644
index 00000000..700906e8
--- /dev/null
+++ b/framework/Web/UI/WebControls/TTextBox.php
@@ -0,0 +1,444 @@
+<?php
+/**
+ * TTextBox class file.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.xisc.com/
+ * @copyright Copyright &copy; 2004-2005, Qiang Xue
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ */
+
+/**
+ * TTextBox class
+ *
+ * TTextBox displays a text box on the Web page for user input.
+ * The text displayed in the TTextBox control is determined by the <b>Text</b> property.
+ * You can create a <b>SingleLine</b>, a <b>MultiLine</b>, or a <b>Password</b> text box
+ * by setting the <b>TextMode</b> property.
+ * If the TTextBox control is a multiline text box, the number of rows
+ * it displays is determined by the <b>Rows</b> property, and the <b>Wrap</b> property
+ * can be used to determine whether to wrap the text in the component.
+ *
+ * To specify the display width of the text box, in characters, set the <b>Columns</b> property.
+ * To prevent the text displayed in the component from being modified,
+ * set the <b>ReadOnly</b> property to true. If you want to limit the user input
+ * to a specified number of characters, set the <b>MaxLength</b> property. To use AutoComplete
+ * feature, set the <b>AutoCompleteType</b> property.
+ *
+ * If <b>AutoPostBack</b> is set true, updating the text box and then changing the focus out of it
+ * will cause postback action. And if <b>CausesValidation</b> is true, validation will also
+ * be processed, which can be further restricted within a <b>ValidationGroup</b>.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ * @since 3.0
+ */
+class TTextBox extends TWebControl implements IPostBackDataHandler, IValidatable
+{
+ /**
+ * @var array enumeration of the valid AutoCompleteType values.
+ */
+ public static $AUTO_COMPLETE_TYPE=array('BusinessCity','BusinessCountryRegion','BusinessFax','BusinessPhone','BusinessState','BusinessStreetAddress','BusinessUrl','BusinessZipCode','Cellular','Company','Department','Disabled','DisplayName','Email','FirstName','Gender','HomeCity','HomeCountryRegion','HomeFax','Homepage','HomePhone','HomeState','HomeStreetAddress','HomeZipCode','JobTitle','LastName','MiddleName','None','Notes','Office','Pager','Search');
+ /**
+ * @var array enumeration of the valid TextMode values.
+ */
+ public static $TEXT_MODE=array('SingleLine','MultiLine','Password');
+
+ /**
+ * @return string tag name of the textbox
+ */
+ protected function getTagName()
+ {
+ return ($this->getTextMode()==='MultiLine')?'textarea':'input';
+ }
+
+ /**
+ * Processes an object that is created during parsing template.
+ * This overrides the parent implementation by forbidding any body components.
+ * @param mixed the newly created object in template
+ * @throws TInvalidOperationException if a component is found within body
+ */
+ public function addParsedObject($object)
+ {
+ if(!is_string($object))
+ throw new TInvalidOperationException('body_contents_not_allowed',get_class($this).':'.$this->getUniqueID());
+ }
+
+ /**
+ * Adds attribute name-value pairs to renderer.
+ * This overrides the parent implementation with additional textbox specific attributes.
+ * @param THtmlTextWriter the writer used for the rendering purpose
+ */
+ protected function addAttributesToRender($writer)
+ {
+ $page=$this->getPage();
+ $page->ensureRenderInForm($this);
+ if(($uid=$this->getUniqueID())!=='')
+ $writer->addAttribute('name',$uid);
+ if(($textMode=$this->getTextMode())==='MultiLine')
+ {
+ if(($rows=$this->getRows())>0)
+ $writer->addAttribute('rows',$rows);
+ if(($cols=$this->getColumns())>0)
+ $writer->addAttribute('cols',$cols);
+ if(!$this->getWrap())
+ $writer->addAttribute('wrap','off');
+ }
+ else
+ {
+ if($textMode==='SingleLine')
+ {
+ $writer->addAttribute('type','text');
+ if(($text=$this->getText())!=='')
+ $writer->addAttribute('value',$text);
+ if(($act=$this->getAutoCompleteType())!=='None')
+ {
+ if($act==='Disabled')
+ $writer->addAttribute('autocomplete','off');
+ else if($act==='Search')
+ $writer->addAttribute('vcard_name','search');
+ else if($act==='HomeCountryRegion')
+ $writer->addAttribute('vcard_name','HomeCountry');
+ else if($act==='BusinessCountryRegion')
+ $writer->addAttribute('vcard_name','BusinessCountry');
+ else
+ {
+ if(($pos=strpos($act,'Business'))===0)
+ $act='Business'.'.'.substr($act,8);
+ else if(($pos=strpos($act,'Home'))===0)
+ $act='Home'.'.'.substr($act,4);
+ $writer->addAttribute('vcard_name','vCard.'.$act);
+ }
+ }
+ }
+ else
+ {
+ $writer->addAttribute('type','password');
+ }
+ if(($cols=$this->getColumns())>0)
+ $writer->addAttribute('size',$cols);
+ if(($maxLength=$this->getMaxLength())>0)
+ $writer->addAttribute('maxlength',$maxLength);
+ }
+ if($this->getReadOnly())
+ $writer->addAttribute('readonly','readonly');
+ if(!$this->getEnabled(true) && $this->getEnabled()) // in this case parent will not render 'disabled'
+ $writer->addAttribute('disabled','disabled');
+ if($this->getAutoPostBack() && $page->getClientSupportsJavaScript())
+ {
+ $onchange='';
+ $onkeypress='if (WebForm_TextBoxKeyHandler() == false) return false;';
+ if($this->getHasAttributes())
+ {
+ $attributes=$this->getAttributes();
+ $onchange=$attributes->itemAt('onchange');
+ if($onchange!=='')
+ $onchange=rtrim($onchange,';').';';
+ $attributes->remove('onchange');
+ $onkeypress.=$attributes->itemAt('onkeypress');
+ $attributes->remove('onkeypress');
+ }
+
+ $option=new TPostBackOptions($this);
+ if($this->getCausesValidation())
+ {
+ $option->PerformValidation=true;
+ $option->ValidationGroup=$this->getValidationGroup();
+ }
+ if($page->getForm())
+ $option->AutoPostBack=true;
+ $onchange.=$page->getClientScript()->getPostBackEventReference($option);
+ $writer->addAttribute('onchange',$onchange);
+ $writer->addAttribute('onkeypress',$onkeypress);
+ }
+ parent::addAttributesToRender($writer);
+ }
+
+ /**
+ * Loads user input data.
+ * This method is primarly used by framework developers.
+ * @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 component has been changed
+ */
+ public function loadPostData($key,$values)
+ {
+ $value=$values[$key];
+ if(!$this->getReadOnly() && $this->getText()!==$value)
+ {
+ $this->setText($value);
+ return true;
+ }
+ else
+ return false;
+ }
+
+ protected function onPreRender($param)
+ {
+ parent::onPreRender($param);
+ if(($page=$this->getPage()) && $this->getEnabled(true))
+ {
+ // TODO
+ //if($this->getTextMode()==='Password' || ($this->hasEventHandler('TextChanged') && $this->getVisible()))
+ // $page->registerEnabledControl($this);
+ if($this->getAutoPostBack())
+ {
+ $page->registerWebFormsScript();
+ $page->registerPostBackScript();
+ $page->registerFocusScript();
+ }
+ }
+ }
+
+ /**
+ * Returns the value to be validated.
+ * This methid is required by IValidatable interface.
+ * @return mixed the value of the property to be validated.
+ */
+ public function getValidationPropertyValue()
+ {
+ return $this->getText();
+ }
+
+ /**
+ * This method is invoked when the value of the <b>Text</b> property changes on postback.
+ * The method raises 'TextChanged' event.
+ * If you override this method, be sure to call the parent implementation to ensure
+ * the invocation of the attached event handlers.
+ * @param TEventParameter event parameter to be passed to the event handlers
+ */
+ protected function onTextChanged($param)
+ {
+ $this->raiseEvent('TextChanged',$this,$param);
+ }
+
+ /**
+ * Raises postdata changed event.
+ * This method is required by IPostBackDataHandler interface.
+ * It is invoked by the framework when <b>Text</b> property is changed on postback.
+ * This method is primarly used by framework developers.
+ */
+ public function raisePostDataChangedEvent()
+ {
+ $page=$this->getPage();
+ if($this->getAutoPostBack() && !$page->getIsPostBackEventControlRegistered())
+ {
+ $page->setAutoPostBackControl($this);
+ if($this->getCausesValidation())
+ $page->validate($this->getValidationGroup());
+ }
+ $this->onTextChanged(new TEventParameter);
+ }
+
+ /**
+ * Renders the body content of the textbox when it is in MultiLine text mode.
+ * @param THtmlTextWriter the writer for rendering
+ */
+ protected function renderContents($writer)
+ {
+ if($this->getTextMode()==='MultiLine')
+ $writer->write(THttpUtility::htmlEncode($this->getText()));
+ }
+
+ /**
+ * @return string the AutoComplete type of the textbox
+ */
+ public function getAutoCompleteType()
+ {
+ return $this->getViewState('AutoCompleteType','None');
+ }
+
+ /**
+ * @param string the AutoComplete type of the textbox, default value is 'None'.
+ * Valid values include:
+ * 'BusinessCity','BusinessCountryRegion','BusinessFax','BusinessPhone',
+ * 'BusinessState','BusinessStreetAddress','BusinessUrl','BusinessZipCode',
+ * 'Cellular','Company','Department','Disabled','DisplayName','Email',
+ * 'FirstName','Gender','HomeCity','HomeCountryRegion','HomeFax','Homepage',
+ * 'HomePhone','HomeState','HomeStreetAddress','HomeZipCode','JobTitle',
+ * 'LastName','MiddleName','None','Notes','Office','Pager','Search'
+ * @throws TInvalidDataValueException if the input parameter is not a valid AutoComplete type
+ */
+ public function setAutoCompleteType($value)
+ {
+ $this->setViewState('AutoCompleteType',TPropertyValue::ensureEnum($value,self::$AUTO_COMPLETE_TYPE),'None');
+ }
+
+ /**
+ * @return boolean a value indicating whether an automatic postback to the server
+ * will occur whenever the user modifies the text in the TTextBox control and
+ * then tabs out of the component.
+ */
+ public function getAutoPostBack()
+ {
+ return $this->getViewState('AutoPostBack',false);
+ }
+
+ /**
+ * Sets the value indicating if postback automatically.
+ * An automatic postback to the server will occur whenever the user
+ * modifies the text in the TTextBox control and then tabs out of the component.
+ * @param boolean the value indicating if postback automatically
+ */
+ public function setAutoPostBack($value)
+ {
+ $this->setViewState('AutoPostBack',TPropertyValue::ensureBoolean($value),false);
+ }
+
+ /**
+ * @return boolean whether postback event trigger by this text box will cause input validation, default is true.
+ */
+ public function getCausesValidation()
+ {
+ return $this->getViewState('CausesValidation',true);
+ }
+
+ /**
+ * Sets the value indicating whether postback event trigger by this text box will cause input validation.
+ * @param boolean whether postback event trigger by this button will cause input validation.
+ */
+ public function setCausesValidation($value)
+ {
+ $this->setViewState('CausesValidation',TPropertyValue::ensureBoolean($value),true);
+ }
+
+ /**
+ * @return integer the display width of the text box in characters, default is 0 meaning not set.
+ */
+ public function getColumns()
+ {
+ return $this->getViewState('Columns',0);
+ }
+
+ /**
+ * Sets the display width of the text box in characters.
+ * @param integer the display width, set it 0 to clear the setting
+ */
+ public function setColumns($value)
+ {
+ $this->setViewState('Columns',TPropertyValue::ensureInteger($value),0);
+ }
+
+ /**
+ * @return integer the maximum number of characters allowed in the text box, default is 0 meaning not set.
+ */
+ public function getMaxLength()
+ {
+ return $this->getViewState('MaxLength',0);
+ }
+
+ /**
+ * Sets the maximum number of characters allowed in the text box.
+ * @param integer the maximum length, set it 0 to clear the setting
+ */
+ public function setMaxLength($value)
+ {
+ $this->setViewState('MaxLength',TPropertyValue::ensureInteger($value),0);
+ }
+
+ /**
+ * @return boolean whether the textbox is read only, default is false
+ */
+ public function getReadOnly()
+ {
+ return $this->getViewState('ReadOnly',false);
+ }
+
+ /**
+ * @param boolean whether the textbox is read only
+ */
+ public function setReadOnly($value)
+ {
+ $this->setViewState('ReadOnly',TPropertyValue::ensureBoolean($value),false);
+ }
+
+ /**
+ * @return integer the number of rows displayed in a multiline text box, default is 4
+ */
+ public function getRows()
+ {
+ return $this->getViewState('Rows',4);
+ }
+
+ /**
+ * Sets the number of rows displayed in a multiline text box.
+ * @param integer the number of rows, set it 0 to clear the setting
+ */
+ public function setRows($value)
+ {
+ $this->setViewState('Rows',TPropertyValue::ensureInteger($value),4);
+ }
+
+ /**
+ * @return string the text content of the TTextBox control.
+ */
+ public function getText()
+ {
+ return $this->getViewState('Text','');
+ }
+
+ /**
+ * Sets the text content of the TTextBox control.
+ * @param string the text content
+ */
+ public function setText($value)
+ {
+ $this->setViewState('Text',$value,'');
+ }
+
+ /**
+ * @return string the behavior mode (SingleLine, MultiLine, or Password) of the TTextBox component.
+ */
+ public function getTextMode()
+ {
+ return $this->getViewState('TextMode','SingleLine');
+ }
+
+ /**
+ * Sets the behavior mode (SingleLine, MultiLine, or Password) of the TTextBox component.
+ * @param string the text mode
+ * @throws TInvalidDataValueException if the input value is not a valid text mode.
+ */
+ public function setTextMode($value)
+ {
+ $this->setViewState('TextMode',TPropertyValue::ensureEnum($value,self::$TEXT_MODE),'SingleLine');
+ }
+
+ /**
+ * @return string the group of validators which the text box causes validation upon postback
+ */
+ public function getValidationGroup()
+ {
+ return $this->getViewState('ValidationGroup','');
+ }
+
+ /**
+ * @param string the group of validators which the text box causes validation upon postback
+ */
+ public function setValidationGroup($value)
+ {
+ $this->setViewState('ValidationGroup',$value,'');
+ }
+
+ /**
+ * @return boolean whether the text content wraps within a multiline text box.
+ */
+ public function getWrap()
+ {
+ return $this->getViewState('Wrap',true);
+ }
+
+ /**
+ * Sets the value indicating whether the text content wraps within a multiline text box.
+ * @param boolean whether the text content wraps within a multiline text box.
+ */
+ public function setWrap($value)
+ {
+ $this->setViewState('Wrap',TPropertyValue::ensureBoolean($value),true);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Web/UI/WebControls/TWebControl.php b/framework/Web/UI/WebControls/TWebControl.php
new file mode 100644
index 00000000..8a9765f7
--- /dev/null
+++ b/framework/Web/UI/WebControls/TWebControl.php
@@ -0,0 +1,368 @@
+<?php
+/**
+ * TWebControl class file.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.xisc.com/
+ * @copyright Copyright &copy; 2004-2005, Qiang Xue
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ */
+
+/**
+ * TWebControl class
+ *
+ * TWebControl is the base class for controls that share a common set
+ * of UI-related properties and methods. TWebControl derived controls
+ * are usually corresponding to HTML tags. They thus have tag name, attributes
+ * and body contents. You can override {@link getTagName} to specify the tag name,
+ * {@link addAttributesToRender} to specify the attributes to be rendered,
+ * and {@link renderContents} to customize the body content rendering.
+ * TWebControl encapsulates a set of properties related with CSS style fields,
+ * such as <b>BackColor</b>, <b>BorderWidth</b>, etc.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ * @since 3.0
+ */
+class TWebControl extends TControl
+{
+ /**
+ * @return string the access key of the control
+ */
+ public function getAccessKey()
+ {
+ return $this->getViewState('AccessKey','');
+ }
+
+ /**
+ * Sets the access key of the control.
+ * Only one-character string can be set, or an exception will be raised.
+ * Pass empty string if you want to disable access key.
+ * @param string the access key to be set
+ * @throws TInvalidDataValueException if the access key is specified with more than one character
+ */
+ public function setAccessKey($value)
+ {
+ if(strlen($value)>1)
+ throw new TInvalidDataValueException('invalid_accesskey',get_class($this));
+ $this->setViewState('AccessKey',$value,'');
+ }
+
+ /**
+ * @return string the background color of the control
+ */
+ public function getBackColor()
+ {
+ if($style=$this->getViewState('Style',null))
+ return $style->getBackColor();
+ else
+ return '';
+ }
+
+ /**
+ * @param string the background color of the control
+ */
+ public function setBackColor($value)
+ {
+ $this->getStyle()->setBackColor($value);
+ }
+
+ /**
+ * @return string the border color of the control
+ */
+ public function getBorderColor()
+ {
+ if($style=$this->getViewState('Style',null))
+ return $style->getBorderColor();
+ else
+ return '';
+ }
+
+ /**
+ * @param string the border color of the control
+ */
+ public function setBorderColor($value)
+ {
+ $this->getStyle()->setBorderColor($value);
+ }
+
+ /**
+ * @return string the border style of the control
+ */
+ public function getBorderStyle()
+ {
+ if($style=$this->getViewState('Style',null))
+ return $style->getBorderStyle();
+ else
+ return '';
+ }
+
+ /**
+ * @param string the border style of the control
+ */
+ public function setBorderStyle($value)
+ {
+ $this->getStyle()->setBorderStyle($value);
+ }
+
+ /**
+ * @return string the border width of the control
+ */
+ public function getBorderWidth()
+ {
+ if($style=$this->getViewState('Style',null))
+ return $style->getBorderWidth();
+ else
+ return '';
+ }
+
+ /**
+ * @param string the border width of the control
+ */
+ public function setBorderWidth($value)
+ {
+ $this->getStyle()->setBorderWidth($value);
+ }
+
+ /**
+ * @return TFont the font of the control
+ */
+ public function getFont()
+ {
+ return $this->getStyle()->getFont();
+ }
+
+ /**
+ * @return string the foreground color of the control
+ */
+ public function getForeColor()
+ {
+ if($style=$this->getViewState('Style',null))
+ return $style->getForeColor();
+ else
+ return '';
+ }
+
+ /**
+ * @param string the foreground color of the control
+ */
+ public function setForeColor($value)
+ {
+ $this->getStyle()->setForeColor($value);
+ }
+
+ /**
+ * @return string the height of the control
+ */
+ public function getHeight()
+ {
+ if($style=$this->getViewState('Style',null))
+ return $style->getHeight();
+ else
+ return '';
+ }
+
+ /**
+ * @param string the css class of the control
+ */
+ public function setCssClass($value)
+ {
+ $this->getStyle()->setCssClass($value);
+ }
+
+ /**
+ * @return string the css class of the control
+ */
+ public function getCssClass()
+ {
+ if($style=$this->getViewState('Style',null))
+ return $style->getCssClass();
+ else
+ return '';
+ }
+
+ /**
+ * @param string the height of the control
+ */
+ public function setHeight($value)
+ {
+ $this->getStyle()->setHeight($value);
+ }
+
+ public function getStyleCreated()
+ {
+ return $this->getViewState('Style',null)!==null;
+ }
+
+ /**
+ * @return TStyle the object representing the css style of the control
+ */
+ public function getStyle()
+ {
+ if($style=$this->getViewState('Style',null))
+ return $style;
+ else
+ {
+ $style=new TStyle;
+ $this->setViewState('Style',$style,null);
+ return $style;
+ }
+ }
+
+ /**
+ * Sets the css style string of the control.
+ * The style string will be prefixed to the styles set via other control properties (e.g. Height, Width).
+ * @param string the css style string
+ * @throws TInvalidDataValueException if the parameter is not a string
+ */
+ public function setStyle($value)
+ {
+ if(is_string($value))
+ $this->getStyle()->setStyle($value);
+ else
+ throw new TInvalidDataValueException('invalid_style_value',get_class($this));
+ }
+
+ /**
+ * @return integer the tab index of the control
+ */
+ public function getTabIndex()
+ {
+ return $this->getViewState('TabIndex',0);
+ }
+
+ /**
+ * Sets the tab index of the control.
+ * Pass 0 if you want to disable tab index.
+ * @param integer the tab index to be set
+ */
+ public function setTabIndex($value)
+ {
+ $this->setViewState('TabIndex',TPropertyValue::ensureInteger($value),0);
+ }
+
+ /**
+ * Returns the tag name used for this control.
+ * By default, the tag name is 'span'.
+ * You can override this method to provide customized tag names.
+ * @return string tag name of the control to be rendered
+ */
+ protected function getTagName()
+ {
+ return 'span';
+ }
+
+ /**
+ * @return string the tooltip of the control
+ */
+ public function getToolTip()
+ {
+ return $this->getViewState('ToolTip','');
+ }
+
+ /**
+ * Sets the tooltip of the control.
+ * Pass empty string if you want to disable tooltip.
+ * @param string the tooltip to be set
+ */
+ public function setToolTip($value)
+ {
+ $this->setViewState('ToolTip',$value,'');
+ }
+
+ /**
+ * @return string the width of the control
+ */
+ public function getWidth()
+ {
+ if($style=$this->getViewState('Style',null))
+ return $style->getWidth();
+ else
+ return '';
+ }
+
+ /**
+ * @param string the width of the control
+ */
+ public function setWidth($value)
+ {
+ $this->getStyle()->setWidth($value);
+ }
+
+ /**
+ * Adds attribute name-value pairs to renderer.
+ * This method can be overriden to provide customized attributes to be rendered.
+ * @param THtmlTextWriter the writer used for the rendering purpose
+ */
+ protected function addAttributesToRender($writer)
+ {
+ if($this->getID()!=='')
+ $writer->addAttribute('id',$this->getClientID());
+ if(($accessKey=$this->getAccessKey())!=='')
+ $writer->addAttribute('accesskey',$accessKey);
+ if(!$this->getEnabled())
+ $writer->addAttribute('disabled','disabled');
+ if(($tabIndex=$this->getTabIndex())>0)
+ $writer->addAttribute('tabindex',$tabIndex);
+ if(($toolTip=$this->getToolTip())!=='')
+ $writer->addAttribute('title',$toolTip);
+ if($style=$this->getViewState('Style',null))
+ $style->addAttributesToRender($writer);
+ if($attributes=$this->getViewState('Attributes',null))
+ {
+ foreach($attributes as $name=>$value)
+ $writer->addAttribute($name,$value);
+ }
+ }
+
+ /**
+ * Renders the control.
+ * This method overrides the parent implementation by replacing it with
+ * the following sequence:
+ * - {@link renderBeginTag}
+ * - {@link renderContents}
+ * - {@link renderEndTag}
+ * @param THtmlTextWriter the writer used for the rendering purpose
+ */
+ protected function render($writer)
+ {
+ $this->renderBeginTag($writer);
+ $this->renderContents($writer);
+ $this->renderEndTag($writer);
+ }
+
+ /**
+ * Renders the openning tag for the control (including attributes)
+ * @param THtmlTextWriter the writer used for the rendering purpose
+ */
+ protected function renderBeginTag($writer)
+ {
+ $this->addAttributesToRender($writer);
+ $writer->renderBeginTag($this->getTagName());
+ }
+
+ /**
+ * Renders the body content enclosed between the control tag.
+ * By default, child controls and text strings will be rendered.
+ * You can override this method to provide customized content rendering.
+ * @param THtmlTextWriter the writer used for the rendering purpose
+ */
+ protected function renderContents($writer)
+ {
+ parent::renderChildren($writer);
+ }
+
+ /**
+ * Renders the closing tag for the control
+ * @param THtmlTextWriter the writer used for the rendering purpose
+ */
+ protected function renderEndTag($writer)
+ {
+ $writer->renderEndTag();
+ }
+}
+
+?> \ No newline at end of file