summaryrefslogtreecommitdiff
path: root/framework
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
Initial import of prado framework
Diffstat (limited to 'framework')
-rw-r--r--framework/.htaccess1
-rw-r--r--framework/Collections/TList.php452
-rw-r--r--framework/Collections/TMap.php384
-rw-r--r--framework/Data/TMemCache.php268
-rw-r--r--framework/Data/TSqliteCache.php277
-rw-r--r--framework/Data/TXmlDocument.php446
-rw-r--r--framework/Exceptions/TErrorHandler.php48
-rw-r--r--framework/Exceptions/TException.php175
-rw-r--r--framework/Exceptions/messages.en28
-rw-r--r--framework/IO/TTextWriter.php19
-rw-r--r--framework/Security/TAuthManager.php205
-rw-r--r--framework/Security/TAuthorizationRule.php213
-rw-r--r--framework/Security/TMembershipManager.php109
-rw-r--r--framework/Security/TStaticMembershipProvider.php324
-rw-r--r--framework/Security/TUserManager.php192
-rw-r--r--framework/TApplication.php766
-rw-r--r--framework/TComponent.php535
-rw-r--r--framework/TODO.txt306
-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
-rw-r--r--framework/core.php735
-rw-r--r--framework/prado.php56
58 files changed, 15397 insertions, 0 deletions
diff --git a/framework/.htaccess b/framework/.htaccess
new file mode 100644
index 00000000..e0198322
--- /dev/null
+++ b/framework/.htaccess
@@ -0,0 +1 @@
+deny from all
diff --git a/framework/Collections/TList.php b/framework/Collections/TList.php
new file mode 100644
index 00000000..09d9516b
--- /dev/null
+++ b/framework/Collections/TList.php
@@ -0,0 +1,452 @@
+<?php
+/**
+ * TList, TListIterator classes
+ *
+ * @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.Collections
+ */
+
+/**
+ * TList class
+ *
+ * TList implements an integer-indexed collection class.
+ *
+ * You can access, append, insert, remove an item by using
+ * {@link itemAt}, {@link add}, {@link addAt}, {@link remove}, and {@link removeAt}.
+ * To get the number of the items in the list, use {@link getCount}.
+ * TList can also be used like a regular array as follows,
+ * <code>
+ * $list[]=$item; // append at the end
+ * $list[$index]=$item; // $index must be between 0 and $list->Count
+ * unset($list[$index]); // remove the item at $index
+ * if(isset($list[$index])) // if the list has an item at $index
+ * foreach($list as $index=>$item) // traverse each item in the list
+ * </code>
+ * Note, count($list) will always return 1. You should use {@link getCount()}
+ * to determine the number of items in the list.
+ *
+ * To extend TList by doing additional operations with each added or removed item,
+ * you can override {@link addedItem} and {@link removedItem}.
+ * You can also override {@link canAddItem} and {@link canRemoveItem} to
+ * control whether to add or remove a particular item.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Collections
+ * @since 3.0
+ */
+class TList extends TComponent implements IteratorAggregate,ArrayAccess
+{
+ /**
+ * internal data storage
+ * @var array
+ */
+ private $_d=array();
+ /**
+ * number of items
+ * @var integer
+ */
+ private $_c=0;
+
+ /**
+ * Constructor.
+ * Initializes the list with an array or an iterable object.
+ * @param array|Iterator the intial data. Default is null, meaning no initialization.
+ * @throws TInvalidDataTypeException If data is not null and neither an array nor an iterator.
+ */
+ public function __construct($data=null)
+ {
+ parent::__construct();
+ if($data!==null)
+ $this->copyFrom($data);
+ }
+
+ /**
+ * Returns an iterator for traversing the items in the list.
+ * This method is required by the interface IteratorAggregate.
+ * @return Iterator an iterator for traversing the items in the list.
+ */
+ public function getIterator()
+ {
+ return new TListIterator($this->_d);
+ }
+
+ /**
+ * @return integer the number of items in the list
+ */
+ public function getCount()
+ {
+ return $this->_c;
+ }
+
+ /**
+ * Returns the item at the specified offset.
+ * This method is exactly the same as {@link offsetGet}.
+ * @param integer the index of the item
+ * @return mixed the item at the index
+ * @throws TIndexOutOfRangeException if the index is out of the range
+ */
+ public function itemAt($index)
+ {
+ if(isset($this->_d[$index]))
+ return $this->_d[$index];
+ else
+ throw new TIndexOutOfRangeException('list_index_invalid',$index);
+ }
+
+ /**
+ * Appends an item at the end of the list.
+ * @param mixed new item
+ * @throws TInvalidOperationException If the item is not allowed to be added
+ */
+ public function add($item)
+ {
+ if($this->canAddItem($item))
+ {
+ $this->_d[$this->_c++]=$item;
+ $this->addedItem($item);
+ }
+ else
+ throw new TInvalidOperationException('list_addition_disallowed');
+ }
+
+ /**
+ * Inserts an item at the specified position.
+ * Original item at the position and the next items
+ * will be moved one step towards the end.
+ * @param integer the speicified position.
+ * @param mixed new item
+ * @throws TIndexOutOfRangeException If the index specified exceeds the bound
+ * @throws TInvalidOperationException If the item is not allowed to be added
+ */
+ public function addAt($index,$item)
+ {
+ if($this->canAddItem($item))
+ {
+ if($index===$this->_c)
+ $this->add($item);
+ else if($index>=0 && $index<$this->_c)
+ {
+ array_splice($this->_d,$index,0,array($item));
+ $this->_c++;
+ $this->addedItem($item);
+ }
+ else
+ throw new TIndexOutOfRangeException('list_index_invalid',$index);
+ }
+ else
+ throw new TInvalidOperationException('list_addition_disallowed');
+
+ }
+
+ /**
+ * Removes an item from the list.
+ * The list will first search for the item.
+ * The first item found will be removed from the list.
+ * @param mixed the item to be removed.
+ * @throws TInvalidOperationException If the item cannot be removed
+ * @throws TInvalidDataValueException If the item does not exist
+ */
+ public function remove($item)
+ {
+ if(($index=$this->indexOf($item))>=0)
+ {
+ if($this->canRemoveItem($item))
+ $this->removeAt($index);
+ else
+ throw new TInvalidOperationException('list_item_unremovable');
+ }
+ else
+ throw new TInvalidDataValueException('list_item_inexistent');
+ }
+
+ /**
+ * Removes an item at the specified position.
+ * @param integer the index of the item to be removed.
+ * @return mixed the removed item.
+ * @throws TOutOfRangeException If the index specified exceeds the bound
+ * @throws TInvalidOperationException If the item cannot be removed
+ */
+ public function removeAt($index)
+ {
+ if(isset($this->_d[$index]))
+ {
+ $item=$this->_d[$index];
+ if($this->canRemoveItem($item))
+ {
+ if($index===$this->_c-1)
+ unset($this->_d[$index]);
+ else
+ array_splice($this->_d,$index,1);
+ $this->_c--;
+ $this->removedItem($item);
+ return $item;
+ }
+ else
+ throw new TInvalidOperationException('list_item_unremovable');
+ }
+ else
+ throw new TIndexOutOfRangeException('list_index_invalid',$index);
+ }
+
+ /**
+ * Removes all items in the list.
+ */
+ public function clear()
+ {
+ for($i=$this->_c-1;$i>=0;--$i)
+ $this->removeAt($i);
+ }
+
+ /**
+ * @param mixed the item
+ * @return boolean whether the list contains the item
+ */
+ public function contains($item)
+ {
+ return $this->indexOf($item)>=0;
+ }
+
+ /**
+ * @param mixed the item
+ * @return integer the index of the item in the list (0 based), -1 if not found.
+ */
+ public function indexOf($item)
+ {
+ if(($index=array_search($item,$this->_d,true))===false)
+ return -1;
+ else
+ return $index;
+ }
+
+ /**
+ * @return array the list of items in array
+ */
+ public function toArray()
+ {
+ return $this->_d;
+ }
+
+ /**
+ * Copies iterable data into the list.
+ * Note, existing data in the list will be cleared first.
+ * @param mixed the data to be copied from, must be an array or object implementing Traversable
+ * @throws TInvalidDataTypeException If data is neither an array nor a Traversable.
+ */
+ public function copyFrom($data)
+ {
+ if(is_array($data) || ($data instanceof Traversable))
+ {
+ if($this->_c>0)
+ $this->clear();
+ foreach($data as $item)
+ $this->add($item);
+ }
+ else
+ throw new TInvalidDataTypeException('list_data_not_iterable');
+ }
+
+ /**
+ * Merges iterable data into the map.
+ * New data will be appended to the end of the existing data.
+ * @param mixed the data to be merged with, must be an array or object implementing Traversable
+ * @throws TInvalidDataTypeException If data is neither an array nor an iterator.
+ */
+ public function mergeWith($data)
+ {
+ if(is_array($data) || ($data instanceof Traversable))
+ {
+ foreach($data as $item)
+ $this->add($item);
+ }
+ else
+ throw new TInvalidDataTypeException('list_data_not_iterable');
+ }
+
+ /**
+ * Returns whether there is an item at the specified offset.
+ * This method is required by the interface ArrayAccess.
+ * @param integer the offset to check on
+ * @return boolean
+ */
+ public function offsetExists($offset)
+ {
+ return isset($this->_d[$offset]);
+ }
+
+ /**
+ * Returns the item at the specified offset.
+ * This method is required by the interface ArrayAccess.
+ * @param integer the offset to retrieve item.
+ * @return mixed the item at the offset
+ * @throws TIndexOutOfRangeException if the offset is invalid
+ */
+ public function offsetGet($offset)
+ {
+ if(isset($this->_d[$offset]))
+ return $this->_d[$offset];
+ else
+ throw new TIndexOutOfRangeException('list_index_invalid',$offset);
+ }
+
+ /**
+ * Sets the item at the specified offset.
+ * This method is required by the interface ArrayAccess.
+ * @param integer the offset to set item
+ * @param mixed the item value
+ * @throws TOutOfRangeException If the index specified exceeds the bound
+ */
+ public function offsetSet($offset,$item)
+ {
+ if($offset===null || $offset===$this->_c)
+ $this->add($item);
+ else
+ {
+ $this->removeAt($offset);
+ $this->addAt($offset,$item);
+ }
+ }
+
+ /**
+ * Unsets the item at the specified offset.
+ * This method is required by the interface ArrayAccess.
+ * @param integer the offset to unset item
+ * @throws TOutOfRangeException If the index specified exceeds the bound
+ */
+ public function offsetUnset($offset)
+ {
+ $this->removeAt($offset);
+ }
+
+ /**
+ * This method is invoked after an item is successfully added to the list.
+ * You can override this method to provide customized processing of the addition.
+ * @param mixed the newly added item
+ */
+ protected function addedItem($item)
+ {
+ }
+
+ /**
+ * This method is invoked after an item is successfully removed from the list.
+ * You can override this method to provide customized processing of the removal.
+ * @param mixed the removed item
+ */
+ protected function removedItem($item)
+ {
+ }
+
+ /**
+ * This method is invoked before adding an item to the list.
+ * If it returns true, the item will be added to the list, otherwise not.
+ * You can override this method to decide whether a specific can be added.
+ * @param mixed item to be added
+ * @return boolean whether the item can be added to the list
+ */
+ protected function canAddItem($item)
+ {
+ return true;
+ }
+
+ /**
+ * This method is invoked before removing an item from the list.
+ * If it returns true, the item will be removed from the list, otherwise not.
+ * You can override this method to decide whether a specific can be removed.
+ * @param mixed item to be removed
+ * @return boolean whether the item can be removed to the list
+ */
+ protected function canRemoveItem($item)
+ {
+ return true;
+ }
+}
+
+
+/**
+ * TListIterator class
+ *
+ * TListIterator implements Iterator interface.
+ *
+ * TListIterator is used by TList. It allows TList to return a new iterator
+ * for traversing the items in the list.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Collections
+ * @since 3.0
+ */
+class TListIterator implements Iterator
+{
+ /**
+ * @var array the data to be iterated through
+ */
+ private $_d;
+ /**
+ * @var integer index of the current item
+ */
+ private $_i;
+
+ /**
+ * Constructor.
+ * @param array the data to be iterated through
+ */
+ public function __construct(&$data)
+ {
+ $this->_d=&$data;
+ $this->_i=0;
+ }
+
+ /**
+ * Rewinds internal array pointer.
+ * This method is required by the interface Iterator.
+ */
+ public function rewind()
+ {
+ $this->_i=0;
+ }
+
+ /**
+ * Returns the key of the current array item.
+ * This method is required by the interface Iterator.
+ * @return integer the key of the current array item
+ */
+ public function key()
+ {
+ return $this->_i;
+ }
+
+ /**
+ * Returns the current array item.
+ * This method is required by the interface Iterator.
+ * @return mixed the current array item
+ */
+ public function current()
+ {
+ return $this->_d[$this->_i];
+ }
+
+ /**
+ * Moves the internal pointer to the next array item.
+ * This method is required by the interface Iterator.
+ */
+ public function next()
+ {
+ $this->_i++;
+ }
+
+ /**
+ * Returns whether there is an item at current position.
+ * This method is required by the interface Iterator.
+ * @return boolean
+ */
+ public function valid()
+ {
+ return isset($this->_d[$this->_i]);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Collections/TMap.php b/framework/Collections/TMap.php
new file mode 100644
index 00000000..7c2333d9
--- /dev/null
+++ b/framework/Collections/TMap.php
@@ -0,0 +1,384 @@
+<?php
+/**
+ * TMap, TMapIterator classes
+ *
+ * @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.Collections
+ */
+
+/**
+ * TMap class
+ *
+ * TMap implements a collection that takes key-value pairs.
+ *
+ * You can access, add or remove an item with a key by using
+ * {@link itemAt}, {@link add}, and {@link remove}.
+ * To get the number of the items in the map, use {@link getCount}.
+ * TMap can also be used like a regular array as follows,
+ * <code>
+ * $map[$key]=$value; // add a key-value pair
+ * unset($map[$key]); // remove the value with the specified key
+ * if(isset($map[$key])) // if the map contains the key
+ * foreach($map as $key=>$value) // traverse the items in the map
+ * </code>
+ * Note, count($map) will always return 1. You should use {@link getCount()}
+ * to determine the number of items in the map.
+ *
+ * To extend TMap by doing additional operations with each added or removed item,
+ * you can override {@link addedItem} and {@link removedItem}.
+ * You can also override {@link canAddItem} and {@link canRemoveItem} to
+ * control whether to add or remove a particular item.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Collections
+ * @since 3.0
+ */
+class TMap extends TComponent implements IteratorAggregate,ArrayAccess
+{
+ /**
+ * @var array internal data storage
+ */
+ private $_d=array();
+
+ /**
+ * Constructor.
+ * Initializes the list with an array or an iterable object.
+ * @param array|Iterator the intial data. Default is null, meaning no initialization.
+ * @throws TInvalidDataTypeException If data is not null and neither an array nor an iterator.
+ */
+ public function __construct($data=null)
+ {
+ parent::__construct();
+ if($data!==null)
+ $this->copyFrom($data);
+ }
+
+ /**
+ * Returns an iterator for traversing the items in the list.
+ * This method is required by the interface IteratorAggregate.
+ * @return Iterator an iterator for traversing the items in the list.
+ */
+ public function getIterator()
+ {
+ return new TMapIterator($this->_d);
+ }
+
+ /**
+ * @return integer the number of items in the map
+ */
+ public function getCount()
+ {
+ return count($this->_d);
+ }
+
+ /**
+ * @return array the key list
+ */
+ public function getKeys()
+ {
+ return array_keys($this->_d);
+ }
+
+ /**
+ * Returns the item with the specified key.
+ * This method is exactly the same as {@link offsetGet}.
+ * @param mixed the key
+ * @return mixed the element at the offset, null if no element is found at the offset
+ */
+ public function itemAt($key)
+ {
+ return isset($this->_d[$key]) ? $this->_d[$key] : null;
+ }
+
+ /**
+ * Adds an item into the map.
+ * Note, if the specified key already exists, the old value will be removed first.
+ * @param mixed key
+ * @param mixed value
+ * @throws TInvalidOperationException if the item cannot be added
+ */
+ public function add($key,$value)
+ {
+ if($this->canAddItem($key,$value))
+ {
+ if(isset($this->_d[$key]))
+ $this->remove($key);
+ $this->_d[$key]=$value;
+ $this->addedItem($key,$value);
+ }
+ else
+ throw new TInvalidOperationException('map_item_invalid');
+ }
+
+ /**
+ * Removes an item from the map by its key.
+ * @param mixed the key of the item to be removed
+ * @return mixed the removed value, null if no such key exists.
+ * @throws TInvalidOperationException if the item cannot be removed
+ */
+ public function remove($key)
+ {
+ if(isset($this->_d[$key]))
+ {
+ $value=$this->_d[$key];
+ if($this->canRemoveItem($key,$value))
+ {
+ unset($this->_d[$key]);
+ $this->removedItem($key,$value);
+ return $value;
+ }
+ else
+ throw new TInvalidOperationException('map_item_unremovable');
+ }
+ else
+ return null;
+ }
+
+ /**
+ * Removes all items in the map.
+ */
+ public function clear()
+ {
+ foreach(array_keys($this->_d) as $key)
+ $this->remove($key);
+ }
+
+ /**
+ * @param mixed the key
+ * @return boolean whether the map contains an item with the specified key
+ */
+ public function contains($key)
+ {
+ return isset($this->_d[$key]);
+ }
+
+ /**
+ * @return array the list of items in array
+ */
+ public function toArray()
+ {
+ return $this->_d;
+ }
+
+ /**
+ * Copies iterable data into the map.
+ * Note, existing data in the map will be cleared first.
+ * @param mixed the data to be copied from, must be an array or object implementing Traversable
+ * @throws TInvalidDataTypeException If data is neither an array nor an iterator.
+ */
+ public function copyFrom($data)
+ {
+ if(is_array($data) || $data instanceof Traversable)
+ {
+ if($this->getCount()>0)
+ $this->clear();
+ foreach($data as $key=>$value)
+ $this->add($key,$value);
+ }
+ else
+ throw new TInvalidDataTypeException('map_data_not_iterable');
+ }
+
+ /**
+ * Merges iterable data into the map.
+ * Existing data in the map will be kept and overwritten if the keys are the same.
+ * @param mixed the data to be merged with, must be an array or object implementing Traversable
+ * @throws TInvalidDataTypeException If data is neither an array nor an iterator.
+ */
+ public function mergeWith($data)
+ {
+ if(is_array($data) || $data instanceof Traversable)
+ {
+ foreach($data as $key=>$value)
+ $this->add($key,$value);
+ }
+ else
+ throw new TInvalidDataTypeException('map_data_not_iterable');
+ }
+
+ /**
+ * Returns whether there is an element at the specified offset.
+ * This method is required by the interface ArrayAccess.
+ * @param mixed the offset to check on
+ * @return boolean
+ */
+ public function offsetExists($offset)
+ {
+ return $this->contains($offset);
+ }
+
+ /**
+ * Returns the element at the specified offset.
+ * This method is required by the interface ArrayAccess.
+ * @param integer the offset to retrieve element.
+ * @return mixed the element at the offset, null if no element is found at the offset
+ */
+ public function offsetGet($offset)
+ {
+ return isset($this->_d[$offset]) ? $this->_d[$offset] : null;
+ }
+
+ /**
+ * Sets the element at the specified offset.
+ * This method is required by the interface ArrayAccess.
+ * @param integer the offset to set element
+ * @param mixed the element value
+ */
+ public function offsetSet($offset,$item)
+ {
+ $this->add($offset,$item);
+ }
+
+ /**
+ * Unsets the element at the specified offset.
+ * This method is required by the interface ArrayAccess.
+ * @param mixed the offset to unset element
+ */
+ public function offsetUnset($offset)
+ {
+ $this->remove($offset);
+ }
+
+ /**
+ * This method is invoked after an item is successfully added to the map.
+ * You can override this method provide customized processing of the addition.
+ * @param string key to the item
+ * @param mixed the newly added item
+ */
+ protected function addedItem($key,$item)
+ {
+ }
+
+ /**
+ * This method is invoked after an item is successfully removed from the map.
+ * You can override this method provide customized processing of the removal.
+ * @param string key to the item
+ * @param mixed the removed item
+ */
+ protected function removedItem($key,$item)
+ {
+ }
+
+ /**
+ * This method is invoked before adding an item to the map.
+ * If it returns true, the item will be added to the map, otherwise not.
+ * You can override this method to decide whether a specific can be added.
+ * @param string key to the item
+ * @param mixed item to be added
+ * @return boolean whether the item can be added to the map
+ */
+ protected function canAddItem($key,$item)
+ {
+ return true;
+ }
+
+ /**
+ * This method is invoked before removing an item from the map.
+ * If it returns true, the item will be removed from the map, otherwise not.
+ * You can override this method to decide whether a specific can be removed.
+ * @param string key to the item
+ * @param mixed item to be removed
+ * @return boolean whether the item can be removed to the map
+ */
+ protected function canRemoveItem($key,$item)
+ {
+ return true;
+ }
+}
+
+/**
+ * TMapIterator class
+ *
+ * TMapIterator implements Iterator interface.
+ *
+ * TMapIterator is used by TMap. It allows TMap to return a new iterator
+ * for traversing the items in the map.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Collections
+ * @since 3.0
+ */
+class TMapIterator implements Iterator
+{
+ /**
+ * @var array the data to be iterated through
+ */
+ private $_d;
+ /**
+ * @var array list of keys in the map
+ */
+ private $_keys;
+ /**
+ * @var mixed current key
+ */
+ private $_key;
+
+ /**
+ * Constructor.
+ * @param array the data to be iterated through
+ */
+ public function __construct(&$data)
+ {
+ $this->_d=&$data;
+ $this->_keys=array_keys($data);
+ }
+
+ /**
+ * Rewinds internal array pointer.
+ * This method is required by the interface Iterator.
+ */
+ public function rewind()
+ {
+ $this->_key=reset($this->_keys);
+ }
+
+ /**
+ * Returns the key of the current array element.
+ * This method is required by the interface Iterator.
+ * @return mixed the key of the current array element
+ */
+ public function key()
+ {
+ return $this->_key;
+ }
+
+ /**
+ * Returns the current array element.
+ * This method is required by the interface Iterator.
+ * @return mixed the current array element
+ */
+ public function current()
+ {
+ return $this->_d[$this->_key];
+ }
+
+ /**
+ * Moves the internal pointer to the next array element.
+ * This method is required by the interface Iterator.
+ */
+ public function next()
+ {
+ do
+ {
+ $this->_key=next($this->_keys);
+ }
+ while(!isset($this->_d[$this->_key]) && $this->_key!==false);
+ }
+
+ /**
+ * Returns whether there is an element at current position.
+ * This method is required by the interface Iterator.
+ * @return boolean
+ */
+ public function valid()
+ {
+ return $this->_key!==false;
+ }
+}
+?> \ No newline at end of file
diff --git a/framework/Data/TMemCache.php b/framework/Data/TMemCache.php
new file mode 100644
index 00000000..73eec99f
--- /dev/null
+++ b/framework/Data/TMemCache.php
@@ -0,0 +1,268 @@
+<?php
+/**
+ * TMemCache 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.Data
+ */
+
+/**
+ * TMemCache class
+ *
+ * TMemCache implements a cache application module based on {@link http://www.danga.com/memcached/ memcached}.
+ *
+ * TMemCache can be configured with the Host and Port properties, which
+ * specify the host and port of the memcache server to be used.
+ * By default, they take the value 'localhost' and 11211, respectively.
+ * These properties must be set before {@link init} is invoked.
+ *
+ * The following basic cache operations are implemented:
+ * - {@link get} : retrieve the value with a key (if any) from cache
+ * - {@link set} : store the value with a key into cache
+ * - {@link add} : store the value only if cache does not have this key
+ * - {@link replace} : store the value only if cache has this key
+ * - {@link delete} : delete the value with the specified key from cache
+ * - {@link flush} : delete all values from cache
+ *
+ * Each value is associated with an expiration time. The {@link get} operation
+ * ensures that any expired value will not be returned. The expiration time can
+ * be specified by the number of seconds (maximum 60*60*24*30)
+ * or a UNIX timestamp. A expiration time 0 represents never expire.
+ *
+ * By definition, cache does not ensure the existence of a value
+ * even if it never expires. Cache is not meant to be an persistent storage.
+ *
+ * Also note, there is no security measure to protected data in memcache.
+ * All data in memcache can be accessed by any process running in the system.
+ *
+ * To use this module, the memcache PHP extension must be loaded.
+ *
+ * Some usage examples of TMemCache are as follows,
+ * <code>
+ * $cache=new TMemCache; // TMemCache may also be loaded as a Prado application module
+ * $cache->init(null);
+ * $cache->add('object',$object);
+ * $object2=$cache->get('object');
+ * </code>
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Data
+ * @since 3.0
+ */
+class TMemCache extends TComponent implements IModule, ICache
+{
+ /**
+ * @var boolean if the module is initialized
+ */
+ private $_initialized=false;
+ /**
+ * @var Memcache the Memcache instance
+ */
+ private $_cache=null;
+ /**
+ * @var string a unique prefix used to identify this cache instance from the others
+ */
+ private $_prefix=null;
+ /**
+ * @var string host name of the memcache server
+ */
+ private $_host='localhost';
+ /**
+ * @var integer the port number of the memcache server
+ */
+ private $_port=11211;
+ /**
+ * @var string ID of this module
+ */
+ private $_id='';
+
+ /**
+ * Destructor.
+ * Disconnect the memcache server.
+ */
+ public function __destruct()
+ {
+ if($this->_cache!==null)
+ $this->_cache->close();
+ parent::__destruct();
+ }
+
+ /**
+ * Initializes this module.
+ * This method is required by the IModule interface. It makes sure that
+ * UniquePrefix has been set, creates a Memcache instance and connects
+ * to the memcache server.
+ * @param IApplication Prado application, can be null
+ * @param TXmlElement configuration for this module, can be null
+ * @throws TConfigurationException if memcache extension is not installed or memcache sever connection fails
+ */
+ public function init($application,$config)
+ {
+ if(!extension_loaded('memcache'))
+ throw new TConfigurationException('memcache_extension_required');
+ $this->_cache=new Memcache;
+ if($this->_cache->connect($this->_host,$this->_port)===false)
+ throw new TInvalidConfigurationException('memcache_connection_failed');
+ if($application instanceof IApplication)
+ $this->_prefix=$application->getUniqueID();
+ $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 string host name of the memcache server
+ */
+ public function getHost()
+ {
+ return $this->_host;
+ }
+
+ /**
+ * @param string host name of the memcache server
+ * @throws TInvalidOperationException if the module is already initialized
+ */
+ public function setHost($value)
+ {
+ if($this->_initialized)
+ throw new TInvalidOperationException('memcache_host_unchangeable');
+ else
+ $this->_host=$value;
+ }
+
+ /**
+ * @return integer port number of the memcache server
+ */
+ public function getPort()
+ {
+ return $this->_port;
+ }
+
+ /**
+ * @param integer port number of the memcache server
+ * @throws TInvalidOperationException if the module is already initialized
+ */
+ public function setPort($value)
+ {
+ if($this->_initialized)
+ throw new TInvalidOperationException('memcache_port_unchangeable');
+ else
+ $this->_port=TPropertyValue::ensureInteger($value);
+ }
+
+ /**
+ * Retrieves a value from cache with a specified key.
+ * @return mixed the value stored in cache, false if the value is not in the cache or expired.
+ */
+ public function get($key)
+ {
+ return $this->_cache->get($this->generateUniqueKey($key));
+ }
+
+ /**
+ * Stores a value identified by a key into cache.
+ * If the cache already contains such a key, the existing value and
+ * expiration time will be replaced with the new ones.
+ *
+ * Note, avoid using this method whenever possible. Database insertion is
+ * very expensive. Try using {@link add} instead, which will not store the value
+ * if the key is already in cache.
+ *
+ * @param string the key identifying the value to be cached
+ * @param mixed the value to be cached
+ * @param integer the expiration time of the value,
+ * 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.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ public function set($key,$value,$expire=0)
+ {
+ return $this->_cache->set($this->generateUniqueKey($key),$value,0,$expire);
+ }
+
+ /**
+ * Stores a value identified by a key into cache if the cache does not contain this key.
+ * Nothing will be done if the cache already contains the key.
+ * @param string the key identifying the value to be cached
+ * @param mixed the value to be cached
+ * @param integer the expiration time of the value,
+ * 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.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ public function add($key,$value,$expiry=0)
+ {
+ return $this->_cache->add($this->generateUniqueKey($key),$value,0,$expire);
+ }
+
+ /**
+ * Stores a value identified by a key into cache only if the cache contains this key.
+ * The existing value and expiration time will be overwritten with the new ones.
+ * @param string the key identifying the value to be cached
+ * @param mixed the value to be cached
+ * @param integer the expiration time of the value,
+ * 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.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ public function replace($key,$value,$expiry=0)
+ {
+ return $this->_cache->replace($this->generateUniqueKey($key),$value,0,$expire);
+ }
+
+ /**
+ * Deletes a value with the specified key from cache
+ * @param string the key of the value to be deleted
+ * @return boolean if no error happens during deletion
+ */
+ public function delete($key)
+ {
+ return $this->_cache->delete($this->generateUniqueKey($key));
+ }
+
+ /**
+ * Deletes all values from cache.
+ * Be careful of performing this operation if the cache is shared by multiple applications.
+ */
+ public function flush()
+ {
+ return $this->_cache->flush();
+ }
+
+ /**
+ * Generates a unique key based on a given user key.
+ * This method generates a unique key with the memcache.
+ * The key is made unique by prefixing with a unique string that is supposed
+ * to be unique among applications using the same memcache.
+ * @param string user key
+ * @param string a unique key
+ */
+ protected function generateUniqueKey($key)
+ {
+ return md5($this->_prefix.$key);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Data/TSqliteCache.php b/framework/Data/TSqliteCache.php
new file mode 100644
index 00000000..8d59b035
--- /dev/null
+++ b/framework/Data/TSqliteCache.php
@@ -0,0 +1,277 @@
+<?php
+/**
+ * TSqliteCache 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.Data
+ */
+
+/**
+ * TSqliteCache class
+ *
+ * TSqliteCache implements a cache application module based on SQLite database.
+ *
+ * The database file is specified by the DbFile property. This property must
+ * be set before {@link init} is invoked. If the specified database file does not
+ * exist, it will be created automatically. Make sure the database file is writable.
+ *
+ * The following basic cache operations are implemented:
+ * - {@link get} : retrieve the value with a key (if any) from cache
+ * - {@link set} : store the value with a key into cache
+ * - {@link add} : store the value only if cache does not have this key
+ * - {@link replace} : store the value only if cache has this key
+ * - {@link delete} : delete the value with the specified key from cache
+ * - {@link flush} : delete all values from cache
+ *
+ * Each value is associated with an expiration time. The {@link get} operation
+ * ensures that any expired value will not be returned. The expiration time can
+ * be specified by the number of seconds (maximum 60*60*24*30)
+ * or a UNIX timestamp. A expiration time 0 represents never expire.
+ *
+ * By definition, cache does not ensure the existence of a value
+ * even if it never expires. Cache is not meant to be an persistent storage.
+ *
+ * Do not use the same database file for multiple applications using TSqliteCache.
+ * Also note, cache is shared by all user sessions of an application.
+ *
+ * To use this module, the sqlite PHP extension must be loaded. Sqlite extension
+ * is no longer loaded by default since PHP 5.1.
+ *
+ * Some usage examples of TSqliteCache are as follows,
+ * <code>
+ * $cache=new TSqliteCache; // TSqliteCache may also be loaded as a Prado application module
+ * $cache->setDbFile($dbFilePath);
+ * $cache->init(null);
+ * $cache->add('object',$object);
+ * $object2=$cache->get('object');
+ * </code>
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Data
+ * @since 3.0
+ */
+class TSqliteCache extends TComponent implements IModule, ICache
+{
+ /**
+ * name of the table storing cache data
+ */
+ const CACHE_TABLE='cache';
+ /**
+ * extension of the db file name
+ */
+ const DB_FILE_EXT='.db';
+ /**
+ * maximum number of seconds specified as expire
+ */
+ const EXPIRE_LIMIT=2592000; // 30 days
+
+ /**
+ * @var boolean if the module has been initialized
+ */
+ private $_initialized=false;
+ /**
+ * @var SQLiteDatabase the sqlite database instance
+ */
+ private $_db=null;
+ /**
+ * @var string the database file name
+ */
+ private $_file=null;
+ /**
+ * @var string id of this module
+ */
+ private $_id='';
+
+ /**
+ * Destructor.
+ * Disconnect the db connection.
+ */
+ public function __destruct()
+ {
+ $this->_db=null;
+ parent::__destruct();
+ }
+
+ /**
+ * Initializes this module.
+ * This method is required by the IModule interface. It checks if the DbFile
+ * property is set, and creates a SQLiteDatabase instance for it.
+ * The database or the cache table does not exist, they will be created.
+ * Expired values are also deleted.
+ * @param IApplication Prado application, can be null
+ * @param TXmlElement configuration for this module, can be null
+ * @throws TConfigurationException if sqlite extension is not installed,
+ * DbFile is set invalid, or any error happens during creating database or cache table.
+ */
+ public function init($application,$config)
+ {
+ if(!function_exists('sqlite_open'))
+ throw new TConfigurationException('sqlitecache_extension_required');
+ if($this->_file===null)
+ throw new TConfigurationException('sqlitecache_filename_required');
+ $error='';
+ if(($fname=Prado::getPathOfNamespace($this->_file,self::DB_FILE_EXT))===null)
+ throw new TConfigurationException('sqlitecache_dbfile_invalid',$this->_file);
+ if(($this->_db=new SQLiteDatabase($fname,0666,$error))===false)
+ throw new TConfigurationException('sqlitecache_connection_failed',$error);
+ if(($res=$this->_db->query('SELECT * FROM sqlite_master WHERE tbl_name=\''.self::CACHE_TABLE.'\' AND type=\'table\''))!=false)
+ {
+ if($res->numRows()===0)
+ {
+ if($this->_db->query('CREATE TABLE '.self::CACHE_TABLE.' (key CHAR(128) PRIMARY KEY, value BLOB, serialized INT, expire INT)')===false)
+ throw new TConfigurationException('sqlitecache_table_creation_failed',sqlite_error_string(sqlite_last_error()));
+ }
+ }
+ else
+ throw new TConfigurationException('sqlitecache_table_creation_failed',sqlite_error_string(sqlite_last_error()));
+ $this->_initialized=true;
+ $this->_db->query('DELETE FROM '.self::CACHE_TABLE.' WHERE expire<>0 AND expire<'.time());
+ }
+
+ /**
+ * @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 string database file path (in namespace form)
+ */
+ public function getDbFile()
+ {
+ return $this->_file;
+ }
+
+ /**
+ * @param string database file path (in namespace form)
+ * @throws TInvalidOperationException if the module is already initialized
+ */
+ public function setDbFile($value)
+ {
+ if($this->_initialized)
+ throw new TInvalidOperationException('sqlitecache_dbfile_unchangeable');
+ else
+ $this->_file=$value;
+ }
+
+ /**
+ * Retrieves a value from cache with a specified key.
+ * @return mixed the value stored in cache, false if the value is not in the cache or expired.
+ */
+ public function get($key)
+ {
+ $sql='SELECT serialized,value FROM '.self::CACHE_TABLE.' WHERE key=\''.md5($key).'\' AND (expire=0 OR expire>'.time().')';
+ if(($ret=$this->_db->query($sql))!=false && ($row=$ret->fetch(SQLITE_ASSOC))!==false)
+ return $row['serialized']?Prado::unserialize($row['value']):$row['value'];
+ else
+ return false;
+ }
+
+ /**
+ * Stores a value identified by a key into cache.
+ * If the cache already contains such a key, the existing value and
+ * expiration time will be replaced with the new ones.
+ *
+ * Note, avoid using this method whenever possible. Database insertion is
+ * very expensive. Try using {@link add} instead, which will not store the value
+ * if the key is already in cache.
+ *
+ * @param string the key identifying the value to be cached
+ * @param mixed the value to be cached
+ * @param integer the expiration time of the value,
+ * 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.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ public function set($key,$value,$expire=0)
+ {
+ $serialized=is_string($value)?0:1;
+ $value1=sqlite_escape_string($serialized?Prado::serialize($value):$value);
+ if($expire && $expire<=self::EXPIRE_LIMIT)
+ $expire=time()+$expire;
+ $sql='REPLACE INTO '.self::CACHE_TABLE.' VALUES(\''.md5($key).'\',\''.$value1.'\','.$serialized.','.$expire.')';
+ return $this->_db->query($sql)!==false;
+ }
+
+ /**
+ * Stores a value identified by a key into cache if the cache does not contain this key.
+ * Nothing will be done if the cache already contains the key.
+ * @param string the key identifying the value to be cached
+ * @param mixed the value to be cached
+ * @param integer the expiration time of the value,
+ * 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.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ public function add($key,$value,$expire=0)
+ {
+ $serialized=is_string($value)?0:1;
+ $value1=sqlite_escape_string($serialized?Prado::serialize($value):$value);
+ if($expire && $expire<=self::EXPIRE_LIMIT)
+ $expire=time()+$expire;
+ $sql='INSERT INTO '.self::CACHE_TABLE.' VALUES(\''.md5($key).'\',\''.$value1.'\','.$serialized.','.$expire.')';
+ return @$this->_db->query($sql)!==false;
+ }
+
+ /**
+ * Stores a value identified by a key into cache only if the cache contains this key.
+ * The existing value and expiration time will be overwritten with the new ones.
+ * @param string the key identifying the value to be cached
+ * @param mixed the value to be cached
+ * @param integer the expiration time of the value,
+ * 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.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ public function replace($key,$value,$expire=0)
+ {
+ $serialized=is_string($value)?0:1;
+ $value1=sqlite_escape_string($serialized?Prado::serialize($value):$value);
+ if($expire && $expire<=self::EXPIRE_LIMIT)
+ $expire=time()+$expire;
+ $sql='UPDATE '.self::CACHE_TABLE.' SET value=\''.$value1.'\', serialized='.$serialized.',expire='.$expire.' WHERE key=\''.md5($key).'\'';
+ $this->_db->query($sql);
+ $ret=$this->_db->query('SELECT serialized FROM '.self::CACHE_TABLE.' WHERE key=\''.md5($key).'\'');
+ return ($ret!=false && $ret->numRows()>0);
+ }
+
+ /**
+ * Deletes a value with the specified key from cache
+ * @param string the key of the value to be deleted
+ * @return boolean if no error happens during deletion
+ */
+ public function delete($key)
+ {
+ $sql='DELETE FROM '.self::CACHE_TABLE.' WHERE key=\''.md5($key).'\'';
+ return $this->_db->query($sql)!==false;
+ }
+
+ /**
+ * Deletes all values from cache.
+ * Be careful of performing this operation if the cache is shared by multiple applications.
+ */
+ public function flush()
+ {
+ return $this->_db->query('DELETE FROM '.self::CACHE_TABLE)!==false;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Data/TXmlDocument.php b/framework/Data/TXmlDocument.php
new file mode 100644
index 00000000..f8ba5dc2
--- /dev/null
+++ b/framework/Data/TXmlDocument.php
@@ -0,0 +1,446 @@
+<?php
+/**
+ * TXmlElement, TXmlDocument, TXmlElementList 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.Data
+ */
+
+/**
+ * TXmlElement class.
+ *
+ * TXmlElement represents an XML element node.
+ * You can obtain its tagname, attributes, text between the openning and closing
+ * tags via the TagName, Attributes, and Value properties, respectively.
+ * You can also retrieve its parent and child elements by Parent and Elements
+ * properties, respectively.
+ *
+ * TBD: xpath
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System
+ * @since 3.0
+ */
+class TXmlElement extends TComponent
+{
+ /**
+ * @var TXmlElement parent of this element
+ */
+ private $_parent=null;
+ /**
+ * @var string tagname of this element
+ */
+ private $_tagName;
+ /**
+ * @var string text enclosed between openning and closing tags of this element
+ */
+ private $_value;
+ /**
+ * @var TXmlElementList list of child elements of this element
+ */
+ private $_elements=null;
+ /**
+ * @var TMap attributes of this element
+ */
+ private $_attributes=null;
+
+ /**
+ * Constructor.
+ * @param string tagname for this element
+ */
+ public function __construct($tagName)
+ {
+ parent::__construct();
+ $this->setTagName($tagName);
+ }
+
+ /**
+ * @return TXmlElement parent element of this element
+ */
+ public function getParent()
+ {
+ return $this->_parent;
+ }
+
+ /**
+ * @param TXmlElement parent element of this element
+ */
+ public function setParent($parent)
+ {
+ $this->_parent=$parent;
+ }
+
+ /**
+ * @return string tagname of this element
+ */
+ public function getTagName()
+ {
+ return $this->_tagName;
+ }
+
+ /**
+ * @param string tagname of this element
+ */
+ public function setTagName($tagName)
+ {
+ $this->_tagName=$tagName;
+ }
+
+ /**
+ * @return string text enclosed between opening and closing tag of this element
+ */
+ public function getValue()
+ {
+ return $this->_value;
+ }
+
+ /**
+ * @param string text enclosed between opening and closing tag of this element
+ */
+ public function setValue($value)
+ {
+ $this->_value=$value;
+ }
+
+ /**
+ * @return boolean true if this element has child elements
+ */
+ public function getHasElement()
+ {
+ return $this->_elements!==null && $this->_elements->getCount()>0;
+ }
+
+ /**
+ * @return boolean true if this element has attributes
+ */
+ public function getHasAttribute()
+ {
+ return $this->_attributes!==null && $this->_attributes->getCount()>0;
+ }
+
+ /**
+ * @return string the attribute specified by the name, null if no such attribute
+ */
+ public function getAttribute($name)
+ {
+ if($this->_attributes!==null)
+ return $this->_attributes->itemAt($name);
+ else
+ return null;
+ }
+
+ /**
+ * @return TXmlElementList list of child elements
+ */
+ public function getElements()
+ {
+ if(!$this->_elements)
+ $this->_elements=new TXmlElementList($this);
+ return $this->_elements;
+ }
+
+ /**
+ * @return TMap list of attributes
+ */
+ public function getAttributes()
+ {
+ if(!$this->_attributes)
+ $this->_attributes=new TMap;
+ return $this->_attributes;
+ }
+
+ /**
+ * @return TXmlElement the first child element that has the specified tagname, null if not found
+ */
+ public function getElementByTagName($tagName)
+ {
+ if($this->_elements)
+ {
+ foreach($this->_elements as $element)
+ if($element->_tagName===$tagName)
+ return $element;
+ }
+ return null;
+ }
+
+ /**
+ * @return TList list of all child elements that have the specified tagname
+ */
+ public function getElementsByTagName($tagName)
+ {
+ $list=new TList;
+ if($this->_elements)
+ {
+ foreach($this->_elements as $element)
+ if($element->_tagName===$tagName)
+ $list->add($element);
+ }
+ return $list;
+ }
+
+ /**
+ * @return string string representation of this element
+ */
+ public function toString($indent=0)
+ {
+ $attr='';
+ if($this->_attributes!==null)
+ {
+ foreach($this->_attributes as $name=>$value)
+ $attr.=" $name=\"$value\"";
+ }
+ $prefix=str_repeat(' ',$indent*4);
+ if($this->getHasElement())
+ {
+ $str=$prefix."<{$this->_tagName}$attr>\n";
+ foreach($this->getElements() as $element)
+ $str.=$element->toString($indent+1)."\n";
+ $str.=$prefix."</{$this->_tagName}>";
+ return $str;
+ }
+ else if($this->getValue()!=='')
+ {
+ return $prefix."<{$this->_tagName}$attr>{$this->_value}</{$this->_tagName}>";
+ }
+ else
+ return $prefix."<{$this->_tagName}$attr />";
+ }
+}
+
+/**
+ * TXmlDocument class.
+ *
+ * TXmlDocument represents a DOM representation of an XML file.
+ * Besides all properties and methods inherited from {@link TXmlElement},
+ * you can load an XML file or string by {@link loadFromFile} or {@link loadFromString}.
+ * You can also get the version and encoding of the XML document by
+ * the Version and Encoding properties.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System
+ * @since 3.0
+ */
+class TXmlDocument extends TXmlElement
+{
+ /**
+ * @var string version of this XML document
+ */
+ private $_version;
+ /**
+ * @var string encoding of this XML document
+ */
+ private $_encoding;
+
+ /**
+ * Constructor.
+ * @param string version of this XML document
+ * @param string encoding of this XML document
+ */
+ public function __construct($version='1.0',$encoding='')
+ {
+ parent::__construct('');
+ $this->setversion($version);
+ $this->setEncoding($encoding);
+ }
+
+ /**
+ * @return string version of this XML document
+ */
+ public function getVersion()
+ {
+ return $this->_version;
+ }
+
+ /**
+ * @param string version of this XML document
+ */
+ public function setVersion($version)
+ {
+ $this->_version=$version;
+ }
+
+ /**
+ * @return string encoding of this XML document
+ */
+ public function getEncoding()
+ {
+ return $this->_encoding;
+ }
+
+ /**
+ * @param string encoding of this XML document
+ */
+ public function setEncoding($encoding)
+ {
+ $this->_encoding=$encoding;
+ }
+
+ /**
+ * Loads and parses an XML document.
+ * @param string the XML file path
+ * @throws TIOException if the file fails to be opened.
+ */
+ public function loadFromFile($file)
+ {
+ if(($str=file_get_contents($file))!==false)
+ $this->loadFromString($str);
+ else
+ throw new TIOException('xmldocument_file_read_failed',$file);
+ }
+
+ /**
+ * Loads and parses an XML string.
+ * The version and encoding will be determined based on the parsing result.
+ * @param string the XML string
+ */
+ public function loadFromString($string)
+ {
+ $doc=new DOMDocument();
+ $doc->loadXML($string);
+
+ $this->setEncoding($doc->encoding);
+ $this->setVersion($doc->version);
+
+ $element=$doc->documentElement;
+ $this->setTagName($element->tagName);
+ $this->setValue($element->nodeValue);
+ $elements=$this->getElements();
+ $attributes=$this->getAttributes();
+ $elements->clear();
+ $attributes->clear();
+ foreach($element->attributes as $name=>$attr)
+ $attributes->add($name,$attr->value);
+ foreach($element->childNodes as $child)
+ {
+ if($child instanceof DOMElement)
+ $elements->add($this->buildElement($child));
+ }
+ }
+
+ /**
+ * Saves this XML document as an XML file.
+ * @param string the name of the file to be stored with XML output
+ * @throws TIOException if the file cannot be written
+ */
+ public function saveToFile($file)
+ {
+ if(($fw=fopen($file,'w'))!==false)
+ {
+ fwrite($fw,$this->saveToString());
+ fclose($fw);
+ }
+ else
+ throw new TIOException('xmldocument_file_write_failed',$file);
+ }
+
+ /**
+ * Saves this XML document as an XML string
+ * @return string the XML string of this XML document
+ */
+ public function saveToString()
+ {
+ $version=empty($this->_version)?' version="1.0"':' version="'.$this->_version.'"';
+ $encoding=empty($this->_encoding)?'':' encoding="'.$this->_encoding.'"';
+ return "<?xml{$version}{$encoding}?>\n".$this->toString(0);
+ }
+
+ /**
+ * Recursively converts DOM XML nodes into TXmlElement
+ * @param DOMXmlNode the node to be converted
+ * @return TXmlElement the converted TXmlElement
+ */
+ private function buildElement($node)
+ {
+ $element=new TXmlElement($node->tagName);
+ $element->setValue($node->nodeValue);
+ foreach($node->attributes as $name=>$attr)
+ $element->getAttributes()->add($name,$attr->value);
+ foreach($node->childNodes as $child)
+ {
+ if($child instanceof DOMElement)
+ $element->getElements()->add($this->buildElement($child));
+ }
+ return $element;
+ }
+}
+
+
+/**
+ * TXmlElement class.
+ *
+ * TXmlElement represents an XML element node.
+ * You can obtain its tagname, attributes, text between the openning and closing
+ * tags via the TagName, Attributes, and Value properties, respectively.
+ * You can also retrieve its parent and child elements by Parent and Elements
+ * properties, respectively.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System
+ * @since 3.0
+ */
+class TXmlElementList extends TList
+{
+ /**
+ * @var TXmlElement owner of this list
+ */
+ private $_o;
+
+ /**
+ * Constructor.
+ * @param TXmlElement owner of this list
+ */
+ public function __construct(TXmlElement $owner)
+ {
+ parent::__construct();
+ $this->_o=$owner;
+ }
+
+ /**
+ * @return TXmlElement owner of this list
+ */
+ 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->getParent()!==null)
+ $item->getParent()->getElements()->remove($item);
+ $item->setParent($this->_o);
+ }
+
+ /**
+ * Overrides the parent implementation with customized processing of the removed item.
+ * @param mixed the removed item
+ */
+ protected function removedItem($item)
+ {
+ $item->setParent(null);
+ }
+
+ /**
+ * This method is invoked before adding an item to the map.
+ * If it returns true, the item will be added to the map, otherwise not.
+ * You can override this method to decide whether a specific can be added.
+ * @param mixed item to be added
+ * @return boolean whether the item can be added to the map
+ */
+ protected function canAddItem($item)
+ {
+ return ($item instanceof TXmlElement);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Exceptions/TErrorHandler.php b/framework/Exceptions/TErrorHandler.php
new file mode 100644
index 00000000..458d3169
--- /dev/null
+++ b/framework/Exceptions/TErrorHandler.php
@@ -0,0 +1,48 @@
+<?php
+
+class TErrorHandler extends TComponent implements IErrorHandler
+{
+ /**
+ * @var string module ID
+ */
+ private $_id;
+ /**
+ * @var boolean whether the module is initialized
+ */
+ private $_initialized=false;
+
+ /**
+ * 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)
+ {
+ $application->attachEventHandler('Error',array($this,'handle'));
+ $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;
+ }
+
+ public function handle($sender,$param)
+ { echo '...........................';
+ echo $param;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Exceptions/TException.php b/framework/Exceptions/TException.php
new file mode 100644
index 00000000..0c51e1f1
--- /dev/null
+++ b/framework/Exceptions/TException.php
@@ -0,0 +1,175 @@
+<?php
+/**
+ * Exception classes 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.Exceptions
+ */
+
+/**
+ * TException class
+ *
+ * TException is the base class for all PRADO exceptions.
+ * TException
+ * TSystemException
+ * TNullReferenceException
+ * TIndexOutOfRangeException
+ * TArithmeticException
+ * TInvalidValueException
+ * TInvalidTypeException
+ * TInvalidFormatException
+ * TInvalidOperationException
+ * TConfigurationException
+ * TSecurityException
+ * TIOException
+ * TDBException
+ * THttpException
+ * TNotSupportedException
+ * TApplicationException
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Exceptions
+ * @since 3.0
+ */
+class TException extends Exception
+{
+ private $_errorCode='';
+
+ public function __construct($errorCode)
+ {
+ $this->_errorCode=$errorCode;
+ $args=func_get_args();
+ $args[0]=$this->translateErrorCode($errorCode);
+ $str=call_user_func_array('sprintf',$args);
+ parent::__construct($str);
+ }
+
+ protected function translateErrorCode($key)
+ {
+ // $msgFile=dirname(__FILE__).'/messages.'.Prado::getLocale();
+ // if(!is_file($msgFile))
+ $msgFile=dirname(__FILE__).'/messages.en';
+ if(($entries=@file($msgFile))===false)
+ return $key;
+ else
+ {
+ foreach($entries as $entry)
+ {
+ @list($code,$message)=explode('=',$entry,2);
+ if(trim($code)===$key)
+ return trim($message);
+ }
+ return $key;
+ }
+ }
+
+ public function getErrorCode()
+ {
+ return $this->_errorCode;
+ }
+
+ public function setErrorCode($errorCode)
+ {
+ $this->_errorCode=$errorCode;
+ }
+
+ public function getErrorMessage()
+ {
+ return $this->getMessage();
+ }
+
+ public function setErrorMessage($msg)
+ {
+ $this->message=$msg;
+ }
+}
+
+class TSystemException extends TException
+{
+}
+
+class TApplicationException extends TException
+{
+}
+
+class TNullReferenceException extends TSystemException
+{
+}
+
+class TIndexOutOfRangeException extends TSystemException
+{
+}
+
+class TArithmeticException extends TSystemException
+{
+}
+
+class TInvalidOperationException extends TSystemException
+{
+}
+
+class TInvalidDataTypeException extends TSystemException
+{
+}
+
+class TInvalidDataValueException extends TSystemException
+{
+}
+
+class TInvalidDataFormatException extends TSystemException
+{
+}
+
+class TConfigurationException extends TSystemException
+{
+}
+
+class TIOException extends TException
+{
+}
+
+class TDBException extends TException
+{
+}
+
+class TSecurityException extends TException
+{
+}
+
+class THttpException extends TException
+{
+}
+
+class TNotSupportedException extends TException
+{
+}
+
+class TPhpErrorException extends TException
+{
+ public function __construct($errno,$errstr,$errfile,$errline)
+ {
+ static $errorTypes=array(
+ E_ERROR => "Error",
+ E_WARNING => "Warning",
+ E_PARSE => "Parsing Error",
+ E_NOTICE => "Notice",
+ E_CORE_ERROR => "Core Error",
+ E_CORE_WARNING => "Core Warning",
+ E_COMPILE_ERROR => "Compile Error",
+ E_COMPILE_WARNING => "Compile Warning",
+ E_USER_ERROR => "User Error",
+ E_USER_WARNING => "User Warning",
+ E_USER_NOTICE => "User Notice",
+ E_STRICT => "Runtime Notice"
+ );
+ $errorType=isset($errorTypes[$errno])?$errorTypes[$errno]:'Unknown Error';
+ parent::__construct("[$errorType] $errstr (@line $errline in file $errfile).");
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Exceptions/messages.en b/framework/Exceptions/messages.en
new file mode 100644
index 00000000..d949d88a
--- /dev/null
+++ b/framework/Exceptions/messages.en
@@ -0,0 +1,28 @@
+body_contents_not_allowed = %s: body contents are not allowed.
+control_id_not_unique = Control ID '%s' is not unique for control type '%s'.
+control_not_found = Unable to find a control with ID '%s'.
+control_not_in_form = Control '%s' is not enclosed within a server form.
+data_not_iterable = Data is not iterable. An array or an object implementing Traversable is required.
+event_not_defined = %s: event is not defined.
+expecting_closing_tag = Line %d: expecting closing tag %s.
+index_out_of_range = Index '%s' is out of range.
+invalid_accesskey = %s.AccessKey: only one character is allowed.
+invalid_control_id = Invalid control ID '%s' for control type '%s'.
+invalid_enum_value = Invalid value '%s' for enumeration type (%s)
+invalid_event_handler = Invalid event handler '%s' for event '%s'.
+invalid_expression = Invalid expression '%s': %s.
+invalid_statements = Invalid statements '%s': %s.
+invalid_subproperty = Invalid sub-property '%s'.
+invalid_style_value = %s.Style: only string is allowed.
+multiple_form_not_allowed = Multiple server forms are not allowed.
+must_be_component = %s must extend from TComponent.
+no_comments_in_property = Line %d: comments are not allowed in property values.
+property_not_defined = %s: property is not defined.
+property_read_only = %s: property is read-only.
+skinid_set_after_applied = %s: SkinID cannot be set after the skin is applied.
+skinid_set_after_preinit = %s: SkinID cannot be set after PreInit stage.
+stylesheet_applied = %s: StyleSheet skin is already applied.
+enabletheming_after_preinit = %s: EnableTheme cannot be set after PreInit stage.
+nonunique_template_directive = Line %d: at most one template directive is allowed.
+unexpected_closing_tag = Line %d: unexpected closing tag %s.
+unexpected_matching = Unexpected matching: %s. Please report this problem to PRADO developer team.
diff --git a/framework/IO/TTextWriter.php b/framework/IO/TTextWriter.php
new file mode 100644
index 00000000..17829acf
--- /dev/null
+++ b/framework/IO/TTextWriter.php
@@ -0,0 +1,19 @@
+<?php
+
+class TTextWriter extends TComponent
+{
+ public function flush()
+ {
+ }
+
+ public function write($str)
+ {
+ }
+
+ public function writeLine($str='')
+ {
+ $this->write($str."\n");
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Security/TAuthManager.php b/framework/Security/TAuthManager.php
new file mode 100644
index 00000000..c12ee245
--- /dev/null
+++ b/framework/Security/TAuthManager.php
@@ -0,0 +1,205 @@
+<?php
+
+class TAuthManager extends TComponent implements IModule
+{
+ const RETURN_URL_VAR='ReturnUrl';
+ /**
+ * @var TAuthorizationRuleCollection list of authorization rules
+ */
+ private $_authRules=null;
+ private $_guest='Guest';
+ private $_initialized=false;
+ private $_application;
+ private $_users=null;
+ private $_loginPage=null;
+ private $_skipAuthorization=false;
+
+ /**
+ * @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;
+ }
+
+ /**
+ * Initializes this module.
+ * This method is required by the IModule interface.
+ * @param IApplication Prado application, can be null
+ * @param TXmlElement configuration for this module, can be null
+ */
+ public function init($application,$config)
+ {
+ $this->_application=$application;
+ $application->attachEventHandler('Authentication',array($this,'doAuthentication'));
+ $application->attachEventHandler('EndRequest',array($this,'leave'));
+ $application->attachEventHandler('Authorization',array($this,'doAuthorization'));
+ $this->_initialized=true;
+ }
+
+ public function getGuestName()
+ {
+ return $this->_guest;
+ }
+
+ public function setGuestName($value)
+ {
+ $this->_guest=$value;
+ }
+
+ public function getUserManager()
+ {
+ if($this->_users instanceof TUserManager)
+ return $this->_users;
+ else
+ {
+ if(($users=$this->_application->getModule($this->_users))===null)
+ throw new TConfigurationException('authenticator_usermanager_inexistent',$this->_users);
+ if(!($users instanceof TUserManager))
+ throw new TConfigurationException('authenticator_usermanager_invalid',$this->_users);
+ $this->_users=$users;
+ return $users;
+ }
+ }
+
+ public function setUserManager($provider)
+ {
+ $this->_users=$provider;
+ }
+
+ public function getLoginPage()
+ {
+ return $this->_loginPage;
+ }
+
+ public function setLoginPage($pagePath)
+ {
+ $this->_loginPage=$pagePath;
+ }
+
+ public function doAuthentication($sender,$param)
+ {
+ $this->onAuthenticate($param);
+
+ $service=$this->_application->getService();
+ if(($service instanceof TPageService) && $service->isRequestingPage($this->getLoginPage()))
+ $this->_skipAuthorization=true;
+ }
+
+ public function doAuthorization($sender,$param)
+ {
+ if(!$this->_skipAuthorization)
+ {
+ $this->onAuthorize($param);
+ }
+ }
+
+ public function leave($sender,$param)
+ {
+ if($this->_application->getResponse()->getStatusCode()===401)
+ {
+ $service=$this->_application->getService();
+ if($service instanceof TPageService)
+ {
+ $returnUrl=$this->_application->getRequest()->getRequestUri();
+ $url=$service->constructUrl($this->getLoginPage(),array(self::RETURN_URL_VAR=>$returnUrl));
+ $this->_application->getResponse()->redirect($url);
+ }
+ }
+ }
+
+ public function onAuthenticate($param)
+ {
+ if($this->hasEventHandler('Authenticate'))
+ $this->raiseEvent('Authenticate',$this,$this->_application);
+ if($this->_application->getUser()!==null)
+ return;
+
+ if(($session=$this->_application->getSession())===null)
+ throw new TConfigurationException('authenticator_session_required');
+ $session->open();
+ if(($userManager=$this->getUserManager())===null)
+ throw new TConfigurationException('authenticator_usermanager_required');
+ $sessionInfo=$session->getItems()->itemAt($this->generateUserSessionKey());
+ $user=$userManager->getUser(null)->loadFromString($sessionInfo);
+ $this->_application->setUser($user);
+ }
+
+ public function onAuthorize($param)
+ {
+ if($this->hasEventHandler('Authenticate'))
+ $this->raiseEvent('Authorize',$this,$this->_application);
+ if($this->_authRules!==null && !$this->_authRules->isUserAllowed($this->_application->getUser(),$this->_application->getRequest()->getRequestType()))
+ {
+ $this->_application->getResponse()->setStatusCode(401);
+ $this->_application->completeRequest();
+ }
+ }
+
+ protected function generateUserSessionKey()
+ {
+ return md5($this->_application->getUniqueID().'prado:user');
+ }
+
+ public function updateSessionUser($user)
+ {
+ if(!$user->getIsGuest())
+ {
+ if(($session=$this->_application->getSession())===null)
+ throw new TConfigurationException('authenticator_session_required');
+ else
+ $session->getItems()->add($this->generateUserSessionKey(),$user->saveToString());
+ }
+ }
+
+ public function login($username,$password)
+ {
+ if(($userManager=$this->getUserManager())===null)
+ throw new TConfigurationException('authenticator_usermanager_required');
+ else
+ {
+ if($userManager->validateUser($username,$password))
+ {
+ $user=$userManager->getUser($username);
+ $this->updateSessionUser($user);
+ $this->_application->setUser($user);
+ return true;
+ }
+ else
+ return false;
+ }
+ }
+
+ public function logout()
+ {
+ if(($userManager=$this->getUserManager())===null)
+ throw new TConfigurationException('authenticator_usermanager_required');
+ else if(($session=$this->_application->getSession())===null)
+ throw new TConfigurationException('authenticator_session_required');
+ else
+ {
+ $userManager->logout($this->_application->getUser());
+ $session->destroy();
+ }
+ }
+ /**
+ * @return TAuthorizationRuleCollection list of authorization rules that may be applied
+ */
+
+ public function getAuthorizationRules()
+ {
+ if($this->_authRules===null)
+ $this->_authRules=new TAuthorizationRuleCollection;
+ return $this->_authRules;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Security/TAuthorizationRule.php b/framework/Security/TAuthorizationRule.php
new file mode 100644
index 00000000..2ee6de49
--- /dev/null
+++ b/framework/Security/TAuthorizationRule.php
@@ -0,0 +1,213 @@
+<?php
+/**
+ * TAuthorizationRule, TAuthorizationRuleCollection 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.Security
+ */
+/**
+ * TAuthorizationRule class
+ *
+ * TAuthorizationRule represents a single authorization rule.
+ * A rule is specified by an action (required), a list of users (optional),
+ * a list of roles (optional), and a verb (optional).
+ * Action can be either 'allow' or 'deny'.
+ * Guest (anonymous, unauthenticated) users are represented by question mark '?'.
+ * All users (including guest users) are represented by asterisk '*'.
+ * Users/roles are case-insensitive.
+ * Different users/roles are separated by comma ','.
+ * Verb can be either 'get' or 'post'. If it is absent, it means both.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Security
+ * @since 3.0
+ */
+class TAuthorizationRule extends TComponent
+{
+ /**
+ * @var string action, either 'allow' or 'deny'
+ */
+ private $_action;
+ /**
+ * @var array list of user IDs
+ */
+ private $_users;
+ /**
+ * @var array list of roles
+ */
+ private $_roles;
+ /**
+ * @var string verb, may be empty, 'get', or 'post'.
+ */
+ private $_verb;
+ /**
+ * @var boolean if this rule applies to everyone
+ */
+ private $_everyone;
+ /**
+ * @var boolean if this rule applies to guest user
+ */
+ private $_guest;
+
+ /**
+ * Constructor.
+ * @param string action, either 'deny' or 'allow'
+ * @param string a comma separated user list
+ * @param string a comma separated role list
+ * @param string verb, can be empty, 'get', or 'post'
+ */
+ public function __construct($action,$users,$roles,$verb='')
+ {
+ parent::__construct();
+ $action=strtolower(trim($action));
+ if($action==='allow' || $action==='deny')
+ $this->_action=$action;
+ else
+ throw new TInvalidDataValueException('authorizationrule_action_invalid',$action);
+ $this->_users=array();
+ $this->_roles=array();
+ $this->_everyone=false;
+ $this->_guest=false;
+ foreach(explode(',',$users) as $user)
+ {
+ if(($user=trim(strtolower($user)))!=='')
+ {
+ if($user==='*')
+ $this->_everyone=true;
+ else if($user==='?')
+ $this->_guest=true;
+ else
+ $this->_users[]=$user;
+ }
+ }
+ foreach(explode(',',$roles) as $role)
+ {
+ if(($role=trim(strtolower($role)))!=='')
+ $this->_roles[]=$role;
+ }
+ $verb=trim(strtolower($verb));
+ if($verb==='' || $verb==='get' || $verb==='post')
+ $this->_verb=$verb;
+ else
+ throw new TInvalidDataValueException('authorizationrule_verb_invalid',$verb);
+ }
+
+ /**
+ * @return string action, either 'allow' or 'deny'
+ */
+ public function getAction()
+ {
+ return $this->_action;
+ }
+
+ /**
+ * @return array list of user IDs
+ */
+ public function getUsers()
+ {
+ return $this->_users;
+ }
+
+ /**
+ * @return array list of roles
+ */
+ public function getRoles()
+ {
+ return $this->_roles;
+ }
+
+ /**
+ * @return string verb, may be empty, 'get', or 'post'.
+ */
+ public function getVerb()
+ {
+ return $this->_verb;
+ }
+
+ /**
+ * @return boolean if this rule applies to everyone
+ */
+ public function getGuestApplied()
+ {
+ return $this->_guest;
+ }
+
+ /**
+ * @var boolean if this rule applies to everyone
+ */
+ public function getEveryoneApplied()
+ {
+ return $this->_everyone;
+ }
+
+ /**
+ * @return integer 1 if the user is allowed, -1 if the user is denied, 0 if the rule does not apply to the user
+ */
+ public function isUserAllowed(IUser $user,$verb)
+ {
+ $decision=($this->_action==='allow')?1:-1;
+ if($this->_verb==='' || strcasecmp($verb,$this->_verb)===0)
+ {
+ if($this->_everyone || ($this->_guest && $user->getIsGuest()))
+ return $decision;
+ if(in_array(strtolower($user->getName()),$this->_users))
+ return $decision;
+ foreach($this->_roles as $role)
+ if($user->isInRole($role))
+ return $decision;
+ }
+ return 0;
+ }
+}
+
+
+/**
+ * TAuthorizationRuleCollection class.
+ * TAuthorizationRuleCollection represents a collection of authorization rules {@link TAuthorizationRule}.
+ * To check if a user is allowed, call {@link isUserAllowed}.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Security
+ * @since 3.0
+ */
+class TAuthorizationRuleCollection extends TList
+{
+ /**
+ * @param IUser the user to be authorized
+ * @param string verb, can be empty, 'post' or 'get'.
+ * @return boolean whether the user is allowed
+ */
+ public function isUserAllowed($user,$verb)
+ {
+ if($user instanceof IUser)
+ {
+ $verb=strtolower(trim($verb));
+ foreach($this as $rule)
+ {
+ if(($decision=$rule->isUserAllowed($user,$verb))!==0)
+ return ($decision>0);
+ }
+ return true;
+ }
+ else
+ return false;
+ }
+
+ /**
+ * Ensures that only instance of TAuthorizationRule is added to the collection.
+ * @param mixed item to be added to the collection
+ * @return boolean whether the item can be added to the collection
+ */
+ protected function canAddItem($item)
+ {
+ return ($item instanceof TAuthorizationRule);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Security/TMembershipManager.php b/framework/Security/TMembershipManager.php
new file mode 100644
index 00000000..a3c1ae55
--- /dev/null
+++ b/framework/Security/TMembershipManager.php
@@ -0,0 +1,109 @@
+<?php
+
+
+interface IMembershipUser
+{
+ public function getEmail();
+ public function setEmail($value);
+ public function getCreationDate();
+ public function setCreationDate($value);
+ public function getIsApproved();
+ public function setIsApproved($value);
+ public function getIsLockedOut();
+ public function setIsLockedOut($value);
+ public function getIsOnline();
+ public function setIsOnline($value);
+ public function getLastLoginDate();
+ public function setLastLoginDate($value);
+ public function getLastActivityDate();
+ public function setLastActivityDate($value);
+ public function getLastLockoutDate();
+ public function setLastLockoutDate($value);
+ public function getLastPasswordChangedDate();
+ public function setLastPasswordChangedDate($value);
+ public function getPasswordQuestion();
+ public function setPasswordQuestion($value);
+ public function getComment();
+ public function setComment($value);
+
+ public function update();
+ public function fetchPassword($passwordAnswer=null);
+ public function changePassword($username,$oldPassword,$newPassword);
+ public function changePasswordQuestionAndAnswer($username,$password,$newQuestion,$newAnswer);
+ public function resetPassword($passwordAnswer=null);
+}
+
+interface IUserManager
+{
+}
+
+
+
+class TMembershipUser extends TUser implements IMembershipUser
+{
+}
+
+interface IRoleProvider
+{
+ public function addUsersToRoles($users,$roles);
+ public function removeUsersFromRoles($users,$roles);
+ public function createRole($role);
+ public function deleteRole($role,$throwOnPopulatedRole);
+ public function getAllRoles();
+ public function getRolesForUser($user);
+ public function getUsersInRole($role);
+ public function isUserInRole($user,$role);
+ public function roleExists($role);
+}
+
+interface IMembershipProvider
+{
+ public function getApplicationName();
+ public function setApplicationName($value);
+
+ public function createUser($username,$password,$email,$question,$answer,$isApproved); // return $key or error status
+ public function deleteUser($username,$deleteAllRelatedData);
+ public function updateUser($user);
+
+ public function changePassword($username,$oldPassword,$newPassword);
+ public function changePasswordQuestionAndAnswer($username,$password,$newQuestion,$newAnswer);
+
+ public function encryptPassword($password);
+ public function decryptPassword($encodedPassword);
+ public function encodePassword($password,$format,$salt);
+ public function decodePassword($password,$format);
+ public function generateSalt();
+
+ public function findUsersByEmail($email,$pageIndex,$pageSize);
+ public function findUsersByName($email,$pageIndex,$pageSize);
+
+ public function getAllUsers($pageIndex,$pageSize);
+ public function getUser($username,$userkey,$userIsOnline);
+ public function getNumberOfUsersOnline(); //???
+ public function getUsernameByEmail($email);
+ public function getPassword($username,$answer);
+ public function resetPassword($username,$answer);
+ public function unlockUser($username);
+
+ public function validateUser($username,$password);
+
+ public function onValidatingPassword($param);
+
+ public function getEnablePasswordReset();
+ public function setEnablePasswordReset($value);
+ public function getEnablePasswordRetrieval();
+ public function setEnablePasswordRetrieval($value);
+ public function getMaxInvalidPasswordAttempts();
+ public function setMaxInvalidPasswordAttempts($value);
+ public function getUsernameFormat();
+ public function setUsernameFormat($value);
+ public function getPasswordFormat();
+ public function setPasswordFormat($value);
+ public function getRequiresQuestionAndAnswer();
+ public function setRequiresQuestionAndAnswer($value);
+ public function getRequiresUniqueEmail();
+ public function setRequiresUniqueEmail($value);
+}
+
+
+?> \ No newline at end of file
diff --git a/framework/Security/TStaticMembershipProvider.php b/framework/Security/TStaticMembershipProvider.php
new file mode 100644
index 00000000..857a8f26
--- /dev/null
+++ b/framework/Security/TStaticMembershipProvider.php
@@ -0,0 +1,324 @@
+using System;
+using System.Xml;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Configuration.Provider;
+using System.Web.Security;
+using System.Web.Hosting;
+using System.Web.Management;
+using System.Security.Permissions;
+using System.Web;
+
+public class ReadOnlyXmlMembershipProvider : MembershipProvider
+{
+ private Dictionary<string, MembershipUser> _Users;
+ private string _XmlFileName;
+
+ // MembershipProvider Properties
+ public override string ApplicationName
+ {
+ get { throw new NotSupportedException(); }
+ set { throw new NotSupportedException(); }
+ }
+
+ public override bool EnablePasswordRetrieval
+ {
+ get { return false; }
+ }
+
+ public override bool EnablePasswordReset
+ {
+ get { return false; }
+ }
+
+ public override int MaxInvalidPasswordAttempts
+ {
+ get { throw new NotSupportedException(); }
+ }
+
+ public override int MinRequiredNonAlphanumericCharacters
+ {
+ get { throw new NotSupportedException(); }
+ }
+
+ public override int MinRequiredPasswordLength
+ {
+ get { throw new NotSupportedException(); }
+ }
+
+ public override int PasswordAttemptWindow
+ {
+ get { throw new NotSupportedException(); }
+ }
+
+ public override MembershipPasswordFormat PasswordFormat
+ {
+ get { throw new NotSupportedException(); }
+ }
+
+ public override string PasswordStrengthRegularExpression
+ {
+ get { throw new NotSupportedException(); }
+ }
+
+ public override bool RequiresQuestionAndAnswer
+ {
+ get { throw new NotSupportedException(); }
+ }
+
+ public override bool RequiresUniqueEmail
+ {
+ get { throw new NotSupportedException(); }
+ }
+
+ // MembershipProvider Methods
+ public override void Initialize (string name,
+ NameValueCollection config)
+ {
+ // Verify that config isn't null
+ if (config == null)
+ throw new ArgumentNullException ("config");
+
+ // Assign the provider a default name if it doesn't have one
+ if (String.IsNullOrEmpty (name))
+ name = "ReadOnlyXmlMembershipProvider";
+
+ // Add a default "description" attribute to config if the
+ // attribute doesn't exist or is empty
+ if (string.IsNullOrEmpty (config["description"])) {
+ config.Remove ("description");
+ config.Add ("description",
+ "Read-only XML membership provider");
+ }
+
+ // Call the base class's Initialize method
+ base.Initialize(name, config);
+
+ // Initialize _XmlFileName and make sure the path
+ // is app-relative
+ string path = config["xmlFileName"];
+
+ if (String.IsNullOrEmpty (path))
+ path = "~/App_Data/Users.xml";
+
+ if (!VirtualPathUtility.IsAppRelative(path))
+ throw new ArgumentException
+ ("xmlFileName must be app-relative");
+
+ string fullyQualifiedPath = VirtualPathUtility.Combine
+ (VirtualPathUtility.AppendTrailingSlash
+ (HttpRuntime.AppDomainAppVirtualPath), path);
+
+ _XmlFileName = HostingEnvironment.MapPath(fullyQualifiedPath);
+ config.Remove ("xmlFileName");
+
+ // Make sure we have permission to read the XML data source and
+ // throw an exception if we don't
+ FileIOPermission permission =
+ new FileIOPermission(FileIOPermissionAccess.Read,
+ _XmlFileName);
+ permission.Demand();
+
+ // Throw an exception if unrecognized attributes remain
+ if (config.Count > 0) {
+ string attr = config.GetKey (0);
+ if (!String.IsNullOrEmpty (attr))
+ throw new ProviderException
+ ("Unrecognized attribute: " + attr);
+ }
+ }
+
+ public override bool ValidateUser(string username, string password)
+ {
+ // Validate input parameters
+ if (String.IsNullOrEmpty(username) ||
+ String.IsNullOrEmpty(password))
+ return false;
+
+ try
+ {
+ // Make sure the data source has been loaded
+ ReadMembershipDataStore();
+
+ // Validate the user name and password
+ MembershipUser user;
+ if (_Users.TryGetValue (username, out user))
+ {
+ if (user.Comment == password) // Case-sensitive
+ {
+ // NOTE: A read/write membership provider
+ // would update the user's LastLoginDate here.
+ // A fully featured provider would also fire
+ // an AuditMembershipAuthenticationSuccess
+ // Web event
+ return true;
+ }
+ }
+
+ // NOTE: A fully featured membership provider would
+ // fire an AuditMembershipAuthenticationFailure
+ // Web event here
+ return false;
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ }
+
+ public override MembershipUser GetUser(string username,
+ bool userIsOnline)
+ {
+ // Note: This implementation ignores userIsOnline
+
+ // Validate input parameters
+ if (String.IsNullOrEmpty(username))
+ return null;
+
+ // Make sure the data source has been loaded
+ ReadMembershipDataStore();
+
+ // Retrieve the user from the data source
+ MembershipUser user;
+ if (_Users.TryGetValue (username, out user))
+ return user;
+
+ return null;
+ }
+
+ public override MembershipUserCollection GetAllUsers(int pageIndex,
+ int pageSize, out int totalRecords)
+ {
+ // Note: This implementation ignores pageIndex and pageSize,
+ // and it doesn't sort the MembershipUser objects returned
+
+ // Make sure the data source has been loaded
+ ReadMembershipDataStore();
+
+ MembershipUserCollection users =
+ new MembershipUserCollection();
+
+ foreach (KeyValuePair<string, MembershipUser> pair in _Users)
+ users.Add(pair.Value);
+
+ totalRecords = users.Count;
+ return users;
+ }
+
+ public override int GetNumberOfUsersOnline()
+ {
+ throw new NotSupportedException();
+ }
+
+ public override bool ChangePassword(string username,
+ string oldPassword, string newPassword)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override bool
+ ChangePasswordQuestionAndAnswer(string username,
+ string password, string newPasswordQuestion,
+ string newPasswordAnswer)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override MembershipUser CreateUser(string username,
+ string password, string email, string passwordQuestion,
+ string passwordAnswer, bool isApproved, object providerUserKey,
+ out MembershipCreateStatus status)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override bool DeleteUser(string username,
+ bool deleteAllRelatedData)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override MembershipUserCollection
+ FindUsersByEmail(string emailToMatch, int pageIndex,
+ int pageSize, out int totalRecords)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override MembershipUserCollection
+ FindUsersByName(string usernameToMatch, int pageIndex,
+ int pageSize, out int totalRecords)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override string GetPassword(string username, string answer)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override MembershipUser GetUser(object providerUserKey,
+ bool userIsOnline)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override string GetUserNameByEmail(string email)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override string ResetPassword(string username,
+ string answer)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override bool UnlockUser(string userName)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override void UpdateUser(MembershipUser user)
+ {
+ throw new NotSupportedException();
+ }
+
+ // Helper method
+ private void ReadMembershipDataStore()
+ {
+ lock (this)
+ {
+ if (_Users == null)
+ {
+ _Users = new Dictionary<string, MembershipUser>
+ (16, StringComparer.InvariantCultureIgnoreCase);
+ XmlDocument doc = new XmlDocument();
+ doc.Load(_XmlFileName);
+ XmlNodeList nodes = doc.GetElementsByTagName("User");
+
+ foreach (XmlNode node in nodes)
+ {
+ MembershipUser user = new MembershipUser(
+ Name, // Provider name
+ node["UserName"].InnerText, // Username
+ null, // providerUserKey
+ node["EMail"].InnerText, // Email
+ String.Empty, // passwordQuestion
+ node["Password"].InnerText, // Comment
+ true, // isApproved
+ false, // isLockedOut
+ DateTime.Now, // creationDate
+ DateTime.Now, // lastLoginDate
+ DateTime.Now, // lastActivityDate
+ DateTime.Now, // lastPasswordChangedDate
+ new DateTime(1980, 1, 1) // lastLockoutDate
+ );
+
+ _Users.Add(user.UserName, user);
+ }
+ }
+ }
+ }
+}
+
diff --git a/framework/Security/TUserManager.php b/framework/Security/TUserManager.php
new file mode 100644
index 00000000..882c5d5c
--- /dev/null
+++ b/framework/Security/TUserManager.php
@@ -0,0 +1,192 @@
+<?php
+
+/**
+ * IUser interface.
+ *
+ * This interface must be implemented by user objects.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Security
+ * @since 3.0
+ */
+interface IUser
+{
+ public function getManager();
+ public function getName();
+ public function setName($value);
+ public function getIsGuest();
+ public function setIsGuest($value);
+ public function getRoles();
+ public function setRoles($value);
+ /**
+ * @param string role to be tested
+ * @return boolean whether the user is of this role
+ */
+ public function isInRole($role);
+ public function saveToString();
+ public function loadFromString($string);
+}
+
+class TUser extends TComponent implements IUser
+{
+ private $_manager;
+ private $_isGuest=false;
+ private $_name='';
+ private $_roles=array();
+
+ public function __construct($manager=null)
+ {
+ parent::__construct();
+ $this->_manager=$manager;
+ }
+
+ public function getManager()
+ {
+ return $this->_manager;
+ }
+
+ public function getName()
+ {
+ return $this->_name;
+ }
+
+ public function setName($value)
+ {
+ $this->_name=$value;
+ }
+
+ public function getIsGuest()
+ {
+ return $this->_isGuest;
+ }
+
+ public function setIsGuest($value)
+ {
+ $this->_isGuest=TPropertyValue::ensureBoolean($value);
+ if($this->_isGuest)
+ {
+ $this->_name='';
+ $this->_roles=array();
+ }
+ }
+
+ public function getRoles()
+ {
+ return $this->_roles;
+ }
+
+ public function setRoles($value)
+ {
+ if(is_array($value))
+ $this->_roles=$value;
+ else
+ {
+ $this->_roles=array();
+ foreach(explode(',',$value) as $role)
+ $this->_roles[]=trim($value);
+ }
+ }
+
+ public function isInRole($role)
+ {
+ return in_array($role,$this->_roles);
+ }
+
+ public function saveToString()
+ {
+ return serialize(array($this->_name,$this->_roles,$this->_isGuest));
+ }
+
+ public function loadFromString($data)
+ {
+ if(!empty($data))
+ {
+ $array=unserialize($data);
+ $this->_name=$array[0];
+ $this->_roles=$array[1];
+ $this->_isGuest=$array[2];
+ }
+ return $this;
+ }
+}
+
+
+class TUserManager extends TComponent implements IModule
+{
+ private $_id;
+ private $_users=array();
+ private $_guestName='Guest';
+ private $_passwordMode='MD5';
+
+ public function init($application,$config)
+ {
+ foreach($config->getElementsByTagName('user') as $node)
+ $this->_users[$node->getAttribute('name')]=$node->getAttribute('password');
+ }
+
+ public function getID()
+ {
+ return $this->_id;
+ }
+
+ public function setID($value)
+ {
+ $this->_id=$value;
+ }
+
+ public function getGuestName()
+ {
+ return $this->_guestName;
+ }
+
+ public function setGuestName($value)
+ {
+ $this->_guestName=$value;
+ }
+
+ public function getPasswordMode()
+ {
+ return $this->_passwordMode;
+ }
+
+ public function setPasswordMode($value)
+ {
+ $this->_passwordMode=TPropertyValue::ensureEnum($value,array('Clear','MD5','SHA1'));
+ }
+
+ public function validateUser($username,$password)
+ {
+ if($this->_passwordMode==='MD5')
+ $password=md5($password);
+ else if($this->_passwordMode==='SHA1')
+ $password=sha1($password);
+ return (isset($this->_users[$username]) && $this->_users[$username]===$password);
+ }
+
+ public function logout($user)
+ {
+ $user->setIsGuest(true);
+ $user->setName($this->getGuestName());
+ }
+
+ public function getUser($username=null)
+ {
+ if($username===null)
+ {
+ $user=new TUser($this);
+ $user->setIsGuest($username===null);
+ return $user;
+ }
+ else if(isset($this->_users[$username]))
+ {
+ $user=new TUser($this);
+ $user->setName($username);
+ return $user;
+ }
+ else
+ return null;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/TApplication.php b/framework/TApplication.php
new file mode 100644
index 00000000..973032e5
--- /dev/null
+++ b/framework/TApplication.php
@@ -0,0 +1,766 @@
+<?php
+/**
+ * TApplication 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
+ */
+
+/**
+ * Includes TPageService class (default service)
+ */
+require_once(PRADO_DIR.'/Web/Services/TPageService.php');
+
+/**
+ * TApplication class.
+ *
+ * TApplication coordinates modules and services, and serves as a configuration
+ * context for all Prado components.
+ *
+ * TApplication adopts a modular structure. A TApplication instance is a composition
+ * of multiple modules. A module is an instance of class implementing
+ * {@link IModule} interface. Each module accomplishes certain functionalities
+ * that are shared by all Prado components in an application.
+ * There are default modules and user-defined modules. The latter offers extreme
+ * flexibility of extending TApplication in a plug-and-play fashion.
+ * Modules cooperate with each other to serve a user request by following
+ * a sequence of lifecycles predefined in TApplication.
+ *
+ * TApplication dispatches each user request to a particular service which
+ * finishes the actual work for the request with the aid from the application
+ * modules.
+ *
+ * TApplication uses a configuration file to specify the settings of
+ * the application, the modules, the services, the parameters, and so on.
+ *
+ * Examples:
+ * <code>
+ * $application=new TApplication($configFile);
+ * $application->run();
+ * </code>
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System
+ * @since 3.0
+ */
+class TApplication extends TComponent implements IApplication
+{
+ /**
+ * Default service ID
+ */
+ const DEFAULT_SERVICE='page';
+ /**
+ * @var array list of events that define application lifecycles
+ */
+ private static $_steps=array(
+ 'BeginRequest',
+ 'Authentication',
+ 'PostAuthentication',
+ 'Authorization',
+ 'PostAuthorization',
+ 'LoadState',
+ 'PostLoadState',
+ 'PreRunService',
+ 'RunService',
+ 'PostRunService',
+ 'SaveState',
+ 'PostSaveState',
+ 'EndRequest'
+ );
+ /**
+ * @var array list of types that the named modules must be of
+ */
+ private static $_moduleTypes=array(
+ 'request'=>'THttpRequest',
+ 'response'=>'THttpResponse',
+ 'session'=>'THttpSession',
+ 'cache'=>'ICache',
+ 'error'=>'IErrorHandler'
+ );
+
+ /**
+ * @var string application ID
+ */
+ private $_id;
+ /**
+ * @var string unique application ID
+ */
+ private $_uniqueID;
+ /**
+ * @var boolean whether the request is completed
+ */
+ private $_requestCompleted=false;
+ /**
+ * @var integer application state
+ */
+ private $_step;
+ /**
+ * @var IService current service instance
+ */
+ private $_service;
+ /**
+ * @var array list of application modules
+ */
+ private $_modules;
+ /**
+ * @var TMap list of application parameters
+ */
+ private $_parameters;
+ /**
+ * @var string configuration file
+ */
+ private $_configFile;
+ /**
+ * @var string cache file
+ */
+ private $_cacheFile;
+ /**
+ * @var string user type
+ */
+ private $_userType='System.Security.TUser';
+ /**
+ * @var IUser user instance
+ */
+ private $_user=null;
+
+ /**
+ * Constructor.
+ * Loads application configuration and initializes application.
+ * If a cache is specified and present, it will be used instead of the configuration file.
+ * If the cache file is specified but is not present, the configuration file
+ * will be parsed and the result is saved in the cache file.
+ * @param string configuration file path (absolute or relative to current running script)
+ * @param string cache file path
+ */
+ public function __construct($configFile,$cacheFile='')
+ {
+ parent::__construct();
+ Prado::setApplication($this);
+ $this->_configFile=$configFile;
+ $this->_cacheFile=$cacheFile;
+ }
+
+ /**
+ * Executes the lifecycles of the application.
+ */
+ public function run()
+ {
+ try
+ {
+ $this->initApplication($this->_configFile,$this->_cacheFile);
+ $n=count(self::$_steps);
+ $this->_step=0;
+ $this->_requestCompleted=false;
+ while($this->_step<$n)
+ {
+ $method='on'.self::$_steps[$this->_step];
+ $this->$method(null);
+ if($this->_requestCompleted && $this->_step<$n-1)
+ $this->_step=$n-1;
+ else
+ $this->_step++;
+ }
+ }
+ catch(Exception $e)
+ {
+ $this->onError($e);
+ }
+ }
+
+ /**
+ * Completes current request processing.
+ * This method can be used to exit the application lifecycles after finishing
+ * the current cycle.
+ */
+ public function completeRequest()
+ {
+ $this->_requestCompleted=true;
+ }
+
+ /**
+ * @return string application ID
+ */
+ public function getID()
+ {
+ return $this->_id;
+ }
+
+ /**
+ * @param string application ID
+ */
+ public function setID($value)
+ {
+ $this->_id=$value;
+ }
+
+ /**
+ * @return string an ID that unique identifies this Prado application from the others
+ */
+ public function getUniqueID()
+ {
+ return $this->_uniqueID;
+ }
+
+ /**
+ * @return IService the currently requested service
+ */
+ public function getService()
+ {
+ return $this->_service;
+ }
+
+ /**
+ * Adds a module to application.
+ * Note, this method does not do module initialization.
+ * Also, if there is already a module with the same ID, an exception will be raised.
+ * @param string ID of the module
+ * @param IModule module object
+ * @throws TInvalidOperationException if a module with the same ID already exists
+ */
+ public function setModule($id,IModule $module)
+ {
+ if(isset($this->_modules[$id]))
+ throw new TInvalidOperationException('application_module_existing',$id);
+ else
+ $this->_modules[$id]=$module;
+ }
+
+ /**
+ * @return IModule the module with the specified ID, null if not found
+ */
+ public function getModule($id)
+ {
+ return isset($this->_modules[$id])?$this->_modules[$id]:null;
+ }
+
+ /**
+ * @return array list of loaded application modules, indexed by module IDs
+ */
+ public function getModules()
+ {
+ return $this->_modules;
+ }
+
+ /**
+ * @return TMap the list of application parameters
+ */
+ public function getParameters()
+ {
+ return $this->_parameters;
+ }
+
+ /**
+ * @return THttpRequest the request object
+ */
+ public function getRequest()
+ {
+ return isset($this->_modules['request'])?$this->_modules['request']:null;
+ }
+
+ /**
+ * @return THttpResponse the response object
+ */
+ public function getResponse()
+ {
+ return isset($this->_modules['response'])?$this->_modules['response']:null;
+ }
+
+ /**
+ * @return THttpSession the session object
+ */
+ public function getSession()
+ {
+ return isset($this->_modules['session'])?$this->_modules['session']:null;
+ }
+
+ /**
+ * @return ICache the cache object, null if not exists
+ */
+ public function getCache()
+ {
+ return isset($this->_modules['cache'])?$this->_modules['cache']:null;
+ }
+
+ /**
+ * @return IErrorHandler the error hanlder module
+ */
+ public function getErrorHandler()
+ {
+ return isset($this->_modules['error'])?$this->_modules['error']:null;
+ }
+
+ /**
+ * @return IRoleProvider provider for user auth management
+ */
+ public function getAuthManager()
+ {
+ return isset($this->_modules['auth'])?$this->_modules['auth']:null;
+ }
+
+ /**
+ * @return IUser the application user
+ */
+ public function getUser()
+ {
+ return $this->_user;
+ }
+
+ /**
+ * @param IUser the application user
+ */
+ public function setUser(IUser $user)
+ {
+ $this->_user=$user;
+ }
+
+ /**
+ * Loads configuration and initializes application.
+ * Configuration file will be read and parsed (if a valid cache version exists,
+ * it will be used instead). Then, modules are created and initialized;
+ * The requested service is created and initialized.
+ * @param string configuration file path (absolute or relative to current executing script)
+ * @param string cache file path, empty if no present or needed
+ * @throws TConfigurationException if config file is not given, or module is redefined of invalid type, or service not defined or of invalid type
+ */
+ protected function initApplication($configFile,$cacheFile)
+ {
+ if($cacheFile==='' || @filemtime($cacheFile)<filemtime($configFile))
+ {
+ $config=new TApplicationConfiguration;
+ if(empty($configFile))
+ throw new TConfigurationException('application_configfile_required');
+ $config->loadFromFile($configFile);
+ if($cacheFile!=='')
+ {
+ if(($fp=fopen($cacheFile,'wb'))!==false)
+ {
+ fputs($fp,Prado::serialize($config));
+ fclose($fp);
+ }
+ else
+ syslog(LOG_WARNING,'Prado application config cache file '.$cacheFile.' cannot be created.');
+ }
+ }
+ else
+ {
+ $config=Prado::unserialize(file_get_contents($cacheFile));
+ }
+
+ // generates unique ID by hashing the configuration file path
+ $this->_uniqueID=md5(realpath($configFile));
+
+ // set path aliases and using namespaces
+ foreach($config->getAliases() as $alias=>$path)
+ Prado::setPathOfAlias($alias,$path);
+ foreach($config->getUsings() as $using)
+ Prado::using($using);
+
+ // set application properties
+ foreach($config->getProperties() as $name=>$value)
+ $this->setPropertyByPath($name,$value);
+
+ // load parameters
+ $this->_parameters=new TMap;
+ foreach($config->getParameters() as $id=>$parameter)
+ {
+ if(is_string($parameter))
+ $this->_parameters->add($id,$parameter);
+ else
+ {
+ $component=Prado::createComponent($parameter[0]);
+ foreach($parameter[1] as $name=>$value)
+ $component->setPropertyByPath($name,$value);
+ $this->_parameters->add($id,$component);
+ }
+ }
+
+ // load and init modules specified in app config
+ $this->_modules=array();
+ foreach($config->getModules() as $id=>$moduleConfig)
+ {
+ if(isset($this->_modules[$id]))
+ throw new TConfigurationException('application_module_redefined',$id);
+ $module=Prado::createComponent($moduleConfig[0]);
+ if(isset(self::$_moduleTypes[$id]) && !($module instanceof self::$_moduleTypes[$id]))
+ throw new TConfigurationException('application_module_invalid',$id,self::$_moduleTypes[$id]);
+ $this->_modules[$id]=$module;
+ foreach($moduleConfig[1] as $name=>$value)
+ $module->setPropertyByPath($name,$value);
+ $module->init($this,$moduleConfig[2]);
+ }
+
+ if(($serviceID=$this->getRequest()->getServiceID())===null)
+ $serviceID=self::DEFAULT_SERVICE;
+ if(($serviceConfig=$config->getService($serviceID))!==null)
+ {
+ $service=Prado::createComponent($serviceConfig[0]);
+ if(!($service instanceof IService))
+ throw new TConfigurationException('application_service_invalid',$serviceID);
+ $this->_service=$service;
+ foreach($serviceConfig[1] as $name=>$value)
+ $service->setPropertyByPath($name,$value);
+ $service->init($this,$serviceConfig[2]);
+ $this->attachEventHandler('RunService',array($service,'run'));
+ }
+ else
+ throw new TConfigurationException('application_service_unknown',$serviceID);
+ }
+
+ /**
+ * Raises Error event.
+ * This method is invoked when an exception is raised during the lifecycles
+ * of the application.
+ * @param mixed event parameter
+ */
+ public function onError($param)
+ {
+ if($this->hasEventHandler('Error'))
+ $this->raiseEvent('Error',$this,$param);
+ else
+ echo $param;
+ }
+
+ /**
+ * Raises BeginRequest event.
+ * At the time when this method is invoked, application modules are loaded
+ * and initialized, user request is resolved and the corresponding service
+ * is loaded and initialized. The application is about to start processing
+ * the user request.
+ * @param mixed event parameter
+ */
+ public function onBeginRequest($param)
+ {
+ $this->raiseEvent('BeginRequest',$this,$param);
+ }
+
+ /**
+ * Raises Authentication event.
+ * This method is invoked when the user request needs to be authenticated.
+ * @param mixed event parameter
+ */
+ public function onAuthentication($param)
+ {
+ $this->raiseEvent('Authentication',$this,$param);
+ }
+
+ /**
+ * Raises PostAuthentication event.
+ * This method is invoked right after the user request is authenticated.
+ * @param mixed event parameter
+ */
+ public function onPostAuthentication($param)
+ {
+ $this->raiseEvent('PostAuthentication',$this,$param);
+ }
+
+ /**
+ * Raises Authorization event.
+ * This method is invoked when the user request needs to be authorized.
+ * @param mixed event parameter
+ */
+ public function onAuthorization($param)
+ {
+ $this->raiseEvent('Authorization',$this,$param);
+ }
+
+ /**
+ * Raises PostAuthorization event.
+ * This method is invoked right after the user request is authorized.
+ * @param mixed event parameter
+ */
+ public function onPostAuthorization($param)
+ {
+ $this->raiseEvent('PostAuthorization',$this,$param);
+ }
+
+ /**
+ * Raises LoadState event.
+ * This method is invoked when the application needs to load state (probably stored in session).
+ * @param mixed event parameter
+ */
+ public function onLoadState($param)
+ {
+ $this->raiseEvent('LoadState',$this,$param);
+ }
+
+ /**
+ * Raises PostLoadState event.
+ * This method is invoked right after the application state has been loaded.
+ * @param mixed event parameter
+ */
+ public function onPostLoadState($param)
+ {
+ $this->raiseEvent('PostLoadState',$this,$param);
+ }
+
+ /**
+ * Raises PreRunService event.
+ * This method is invoked right before the service is to be run.
+ * @param mixed event parameter
+ */
+ public function onPreRunService($param)
+ {
+ $this->raiseEvent('PreRunService',$this,$param);
+ }
+
+ /**
+ * Raises RunService event.
+ * This method is invoked when the service runs.
+ * @param mixed event parameter
+ */
+ public function onRunService($param)
+ {
+ $this->raiseEvent('RunService',$this,$param);
+ }
+
+ /**
+ * Raises PostRunService event.
+ * This method is invoked right after the servie is run.
+ * @param mixed event parameter
+ */
+ public function onPostRunService($param)
+ {
+ $this->raiseEvent('PostRunService',$this,$param);
+ }
+
+ /**
+ * Raises SaveState event.
+ * This method is invoked when the application needs to save state (probably stored in session).
+ * @param mixed event parameter
+ */
+ public function onSaveState($param)
+ {
+ $this->raiseEvent('SaveState',$this,$param);
+ }
+
+ /**
+ * Raises PostSaveState event.
+ * This method is invoked right after the application state has been saved.
+ * @param mixed event parameter
+ */
+ public function onPostSaveState($param)
+ {
+ $this->raiseEvent('PostSaveState',$this,$param);
+ }
+
+ /**
+ * Raises EndRequest event.
+ * This method is invoked when the application completes the processing of the request.
+ * @param mixed event parameter
+ */
+ public function onEndRequest($param)
+ {
+ $this->raiseEvent('EndRequest',$this,$param);
+ }
+}
+
+
+/**
+ * TApplicationConfiguration class.
+ *
+ * This class is used internally by TApplication to parse and represent application configuration.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System
+ * @since 3.0
+ */
+class TApplicationConfiguration extends TComponent
+{
+ /**
+ * @var array list of application initial property values, indexed by property names
+ */
+ private $_properties=array();
+ /**
+ * @var array list of namespaces to be used
+ */
+ private $_usings=array();
+ /**
+ * @var array list of path aliases, indexed by alias names
+ */
+ private $_aliases=array();
+ /**
+ * @var array list of module configurations
+ */
+ private $_modules=array(
+ 'request'=>array('THttpRequest',array(),null),
+ 'response'=>array('THttpResponse',array(),null),
+ 'error'=>array('TErrorHandler',array(),null)
+ );
+ /**
+ * @var array list of service configurations
+ */
+ private $_services=array(
+ 'page'=>array('TPageService',array(),null)
+ );
+ /**
+ * @var array list of parameters
+ */
+ private $_parameters=array();
+
+ /**
+ * Parses the application configuration file.
+ * @param string configuration file name
+ * @throws TConfigurationException if config file cannot be read or any parsing error is found
+ */
+ public function loadFromFile($fname)
+ {
+ if(!is_file($fname))
+ throw new TConfigurationException('application_configuration_inexistent',$fname);
+ $configPath=dirname($fname);
+ $dom=new TXmlDocument;
+ $dom->loadFromFile($fname);
+
+ // application properties
+ foreach($dom->getAttributes() as $name=>$value)
+ $this->_properties[$name]=$value;
+
+ // paths
+ if(($pathsNode=$dom->getElementByTagName('paths'))!==null)
+ {
+ foreach($pathsNode->getElementsByTagName('alias') as $aliasNode)
+ {
+ if(($id=$aliasNode->getAttribute('id'))!==null && ($path=$aliasNode->getAttribute('path'))!==null)
+ {
+ $path=str_replace('\\','/',$path);
+ if(preg_match('/^\\/|.:\\//',$path)) // if absolute path
+ $p=realpath($path);
+ else
+ $p=realpath($configPath.'/'.$path);
+ if($p===false || !is_dir($p))
+ throw new TConfigurationException('application_alias_path_invalid',$id,$path);
+ $this->_aliases[$id]=$p;
+ }
+ else
+ throw new TConfigurationException('application_alias_element_invalid');
+ }
+ foreach($pathsNode->getElementsByTagName('using') as $usingNode)
+ {
+ if(($namespace=$usingNode->getAttribute('namespace'))!==null)
+ $this->_usings[]=$namespace;
+ else
+ throw new TConfigurationException('application_using_element_invalid');
+ }
+ }
+
+ // application modules
+ if(($modulesNode=$dom->getElementByTagName('modules'))!==null)
+ {
+ foreach($modulesNode->getElementsByTagName('module') as $node)
+ {
+ $properties=$node->getAttributes();
+ if(($id=$properties->itemAt('id'))===null)
+ throw new TConfigurationException('application_module_element_invalid');
+ if(($type=$properties->remove('type'))===null && isset($this->_modules[$id]) && $this->_modules[$id][2]===null)
+ {
+ $type=$this->_modules[$id][0];
+ unset($this->_modules[$id]);
+ }
+ if($type===null)
+ throw new TConfigurationException('application_module_element_invalid');
+ if(isset($this->_modules[$id]))
+ throw new TConfigurationException('application_module_redefined',$id);
+ else
+ {
+ $node->setParent(null);
+ $this->_modules[$id]=array($type,$properties->toArray(),$node);
+ }
+ }
+ }
+
+ // services
+ if(($servicesNode=$dom->getElementByTagName('services'))!==null)
+ {
+ foreach($servicesNode->getElementsByTagName('service') as $node)
+ {
+ $properties=$node->getAttributes();
+ if(($id=$properties->itemAt('id'))===null)
+ throw new TConfigurationException('application_service_element_invalid');
+ if(($type=$properties->remove('type'))===null && isset($this->_services[$id]) && $this->_services[$id][2]===null)
+ {
+ $type=$this->_services[$id][0];
+ unset($this->_services[$id]);
+ }
+ if($type===null)
+ throw new TConfigurationException('application_service_element_invalid');
+ if(isset($this->_services[$id]))
+ throw new TConfigurationException('application_service_redefined',$id);
+ else
+ {
+ $node->setParent(null);
+ $this->_services[$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('application_parameter_element_invalid');
+ if(($type=$properties->remove('type'))===null)
+ $this->_parameters[$id]=$node->getValue();
+ else
+ $this->_parameters[$id]=array($type,$properties->toArray());
+ }
+ }
+ }
+
+ /**
+ * @return array list of application initial property values, indexed by property names
+ */
+ public function getProperties()
+ {
+ return $this->_properties;
+ }
+
+ /**
+ * @return array list of path aliases, indexed by alias names
+ */
+ public function getAliases()
+ {
+ return $this->_aliases;
+ }
+
+ /**
+ * @return array list of namespaces to be used
+ */
+ public function getUsings()
+ {
+ return $this->_usings;
+ }
+
+ /**
+ * @return array list of module configurations
+ */
+ public function getModules()
+ {
+ return $this->_modules;
+ }
+
+ /**
+ * @return array list of service configurations
+ */
+ public function getService($id)
+ {
+ return isset($this->_services[$id])?$this->_services[$id]:null;
+ }
+
+ /**
+ * @return array list of parameters
+ */
+ public function getParameters()
+ {
+ return $this->_parameters;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/TComponent.php b/framework/TComponent.php
new file mode 100644
index 00000000..36a06632
--- /dev/null
+++ b/framework/TComponent.php
@@ -0,0 +1,535 @@
+<?php
+/**
+ * TComponent, TList, TPropertyValue classes
+ *
+ * @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
+ */
+
+/**
+ * TComponent class
+ *
+ * TComponent is the base class for all PRADO components.
+ * TComponent implements the protocol of defining, using properties and events.
+ *
+ * A property is defined by a getter method, and/or a setter method.
+ * Properties can be accessed in the way like accessing normal object members.
+ * Reading or writing a property will cause the invocation of the corresponding
+ * getter or setter method, e.g.,
+ * <code>
+ * $a=$this->Text; // equivalent to $a=$this->getText();
+ * $this->Text='abc'; // equivalent to $this->setText('abc');
+ * </code>
+ * The signatures of getter and setter methods are as follows,
+ * <code>
+ * // getter, defines a readable property 'Text'
+ * function getText() { ... }
+ * // setter, defines a writable property 'Text', with $value being the value to be set to the property
+ * function setText($value) { ... }
+ * </code>
+ * Property names are case-insensitive. It is recommended that they are written
+ * in the format of concatenated words, with the first letter of each word
+ * capitalized (e.g. DisplayMode, ItemStyle).
+ *
+ * An event is defined by the presence of a method whose name is the event name prefixed with 'on'.
+ * The event name is case-insensitive.
+ * An event can be attached with one or several methods (called event handlers).
+ * An event can be raised by calling {@link raiseEvent} method, upon which
+ * the attached event handlers will be invoked automatically in the order they
+ * are attached to the event. Event handlers must have the following signature,
+ * <code>
+ * function eventHandlerFuncName($sender,$param) { ... }
+ * </code>
+ * where $sender refers to the object who is responsible for the raising of the event,
+ * and $param refers to a structure that may contain event-specific information.
+ * To raise an event (assuming named as 'Click') of a component, use
+ * <code>
+ * $component->raiseEvent('Click');
+ * </code>
+ * To attach an event handler to an event, use one of the following ways,
+ * <code>
+ * $component->Click=$callback; // or $component->Click->add($callback);
+ * $$component->attachEventHandler('Click',$callback);
+ * </code>
+ * The first two ways make use of the fact that $component->Click refers to
+ * the event handler list {@link TList} for the 'Click' event.
+ * The variable $callback contains the definition of the event handler that can
+ * be either a string referring to a global function name, or an array whose
+ * first element refers to an object and second element a method name/path that
+ * is reachable by the object, e.g.
+ * - 'buttonClicked' : buttonClicked($sender,$param);
+ * - array($object,'buttonClicked') : $object->buttonClicked($sender,$param);
+ * - array($object,'MainContent.SubmitButton.buttonClicked') :
+ * $object->MainContent->SubmitButton->buttonClicked($sender,$param);
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System
+ * @since 3.0
+ */
+class TComponent
+{
+ /**
+ * @var array event handler lists
+ */
+ private $_e=array();
+
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ }
+
+ /**
+ * Destructor.
+ */
+ public function __destruct()
+ {
+ }
+
+ /**
+ * Returns a property value or an event handler list by property or event name.
+ * Do not call this method. This is a PHP magic method that we override
+ * to allow using the following syntax to read a property:
+ * <code>
+ * $value=$component->PropertyName;
+ * </code>
+ * and to obtain the event handler list for an event,
+ * <code>
+ * $eventHandlerList=$component->EventName;
+ * </code>
+ * @param string the property name or the event name
+ * @return mixed the property value or the event handler list
+ * @throws TInvalidOperationException if the property/event is not defined.
+ */
+ public function __get($name)
+ {
+ $getter='get'.$name;
+ if(method_exists($this,$getter))
+ {
+ // getting a property
+ return $this->$getter();
+ }
+ else if(method_exists($this,'on'.$name))
+ {
+ // getting an event (handler list)
+ $name=strtolower($name);
+ if(!isset($this->_e[$name]))
+ $this->_e[$name]=new TList;
+ return $this->_e[$name];
+ }
+ else
+ {
+ throw new TInvalidOperationException('component_property_undefined',get_class($this),$name);
+ }
+ }
+
+ /**
+ * Sets value of a component property.
+ * Do not call this method. This is a PHP magic method that we override
+ * to allow using the following syntax to set a property or attach an event handler.
+ * <code>
+ * $this->PropertyName=$value;
+ * $this->EventName=$handler;
+ * </code>
+ * @param string the property name or event name
+ * @param mixed the property value or event handler
+ * @throws TInvalidOperationException If the property is not defined or read-only.
+ */
+ public function __set($name,$value)
+ {
+ $setter='set'.$name;
+ if(method_exists($this,$setter))
+ {
+ $this->$setter($value);
+ }
+ else if(method_exists($this,'on'.$name))
+ {
+ $this->attachEventHandler($name,$value);
+ }
+ else if(method_exists($this,'get'.$name))
+ {
+ throw new TInvalidOperationException('component_property_readonly',get_class($this),$name);
+ }
+ else
+ {
+ throw new TInvalidOperationException('component_property_undefined',get_class($this),$name);
+ }
+ }
+
+ /**
+ * Determines whether a property is defined.
+ * A property is defined if there is a getter or setter method
+ * defined in the class. Note, property names are case-insensitive.
+ * @param string the property name
+ * @return boolean whether the property is defined
+ */
+ final public function hasProperty($name)
+ {
+ return method_exists($this,'get'.$name) || method_exists($this,'set'.$name);
+ }
+
+ /**
+ * Determines whether a property can be read.
+ * A property can be read if the class has a getter method
+ * for the property name. Note, property name is case-insensitive.
+ * @param string the property name
+ * @return boolean whether the property can be read
+ */
+ final public function canGetProperty($name)
+ {
+ return method_exists($this,'get'.$name);
+ }
+
+ /**
+ * Determines whether a property can be set.
+ * A property can be written if the class has a setter method
+ * for the property name. Note, property name is case-insensitive.
+ * @param string the property name
+ * @return boolean whether the property can be written
+ */
+ final public function canSetProperty($name)
+ {
+ return method_exists($this,'set'.$name);
+ }
+
+ /**
+ * Evaluates a property path.
+ * A property path is a sequence of property names concatenated by '.' character.
+ * For example, 'Parent.Page' refers to the 'Page' property of the component's
+ * 'Parent' property value (which should be a component also).
+ * @param string property path
+ * @return mixed the property path value
+ */
+ public function getPropertyByPath($path)
+ {
+ $object=$this;
+ foreach(explode('.',$path) as $property)
+ $object=$object->$property;
+ return $object;
+ }
+
+ /**
+ * Sets a value to a property path.
+ * A property path is a sequence of property names concatenated by '.' character.
+ * For example, 'Parent.Page' refers to the 'Page' property of the component's
+ * 'Parent' property value (which should be a component also).
+ * @param string property path
+ * @param mixed the property path value
+ */
+ public function setPropertyByPath($path,$value)
+ {
+ $object=$this;
+ if(($pos=strrpos($path,'.'))===false)
+ $property=$path;
+ else
+ {
+ $object=$this->getPropertyByPath(substr($path,0,$pos));
+ $property=substr($path,$pos+1);
+ }
+ $object->$property=$value;
+ }
+
+ /**
+ * Determines whether an event is defined.
+ * An event is defined if the class has a method whose name is the event name prefixed with 'on'.
+ * Note, event name is case-insensitive.
+ * @param string the event name
+ * @return boolean
+ */
+ public function hasEvent($name)
+ {
+ return method_exists($this,'on'.$name);
+ }
+
+ /**
+ * @return boolean whether an event has been attached one or several handlers
+ */
+ public function hasEventHandler($name)
+ {
+ $name=strtolower($name);
+ return isset($this->_e[$name]) && $this->_e[$name]->getCount()>0;
+ }
+
+ /**
+ * Returns the list of attached event handlers for an event.
+ * @return TList list of attached event handlers for an event
+ * @throws TInvalidOperationException if the event is not defined
+ */
+ public function getEventHandlers($name)
+ {
+ if(method_exists($this,'on'.$name))
+ {
+ $name=strtolower($name);
+ if(!isset($this->_e[$name]))
+ $this->_e[$name]=new TList;
+ return $this->_e[$name];
+ }
+ else
+ throw new TInvalidOperationException('component_event_undefined',get_class($this),$name);
+ }
+
+ /**
+ * Attaches an event handler to an event.
+ *
+ * The handler must be a valid PHP callback, i.e., a string referring to
+ * a global function name, or an array containing two elements with
+ * the first element being an object and the second element a method name
+ * of the object. In Prado, you can also use method path to refer to
+ * an event handler. For example, array($object,'Parent.buttonClicked')
+ * uses a method path that refers to the method $object->Parent->buttonClicked(...).
+ *
+ * The event handler must be of the following signature,
+ * <code>
+ * function handlerName($sender,$param) {}
+ * </code>
+ * where $sender represents the object that raises the event,
+ * and $param is the event parameter.
+ *
+ * This is a convenient method to add an event handler.
+ * It is equivalent to {@link getEventHandlers}($name)->add($handler).
+ * For complete management of event handlers, use {@link getEventHandlers}
+ * to get the event handler list first, and then do various
+ * {@link TList} operations to append, insert or remove
+ * event handlers. You may also do these operations like
+ * getting and setting properties, e.g.,
+ * <code>
+ * $component->Click[]=array($object,'buttonClicked');
+ * $component->Click->addAt(0,array($object,'buttonClicked'));
+ * </code>
+ * which are equivalent to the following
+ * <code>
+ * $component->getEventHandlers('Click')->add(array($object,'buttonClicked'));
+ * $component->getEventHandlers('Click')->addAt(0,array($object,'buttonClicked'));
+ * </code>
+ *
+ * @param string the event name
+ * @param callback the event handler
+ * @throws TInvalidOperationException if the event does not exist
+ */
+ public function attachEventHandler($name,$handler)
+ {
+ $this->getEventHandlers($name)->add($handler);
+ }
+
+ /**
+ * Raises an event.
+ * This method represents the happening of an event and will
+ * invoke all attached event handlers for the event.
+ * @param string the event name
+ * @param mixed the event sender object
+ * @param TEventParameter the event parameter
+ * @throws TInvalidOperationException if the event is undefined
+ * @throws TInvalidDataValueException If an event handler is invalid
+ */
+ public function raiseEvent($name,$sender,$param)
+ {
+ $name=strtolower($name);
+ if(isset($this->_e[$name]))
+ {
+ foreach($this->_e[$name] as $handler)
+ {
+ if(is_string($handler))
+ {
+ call_user_func($handler,$sender,$param);
+ }
+ else if(is_callable($handler,true))
+ {
+ // an array: 0 - object, 1 - method name/path
+ list($object,$method)=$handler;
+ if(($pos=strrpos($method,'.'))!==false)
+ {
+ $object=$this->getPropertyByPath(substr($method,0,$pos));
+ $method=substr($method,$pos+1);
+ }
+ $object->$method($sender,$param);
+ }
+ else
+ throw new TInvalidDataValueException('component_event_handler_invalid',$handler);
+ }
+ }
+ else if(!$this->hasEvent($name))
+ throw new TInvalidOperationException('component_event_undefined',get_class($this),$name);
+ }
+
+ /**
+ * Evaluates a PHP expression in the context of this control.
+ * @return mixed the expression result
+ * @throws TInvalidOperationException if the expression is invalid
+ */
+ public function evaluateExpression($expression)
+ {
+ try
+ {
+ if(eval("\$result=$expression;")===false)
+ throw new Exception('');
+ return $result;
+ }
+ catch(Exception $e)
+ {
+ throw new TInvalidOperationException('component_expression_invalid',$expression,$e->getMessage());
+ }
+ }
+
+ /**
+ * Evaluates a list of PHP statements.
+ * @param string PHP statements
+ * @return string content echoed or printed by the PHP statements
+ * @throw TInvalidOperationException if the statements are invalid
+ */
+ public function evaluateStatements($statements)
+ {
+ try
+ {
+ ob_start();
+ if(eval($statements)===false)
+ throw new Exception('');
+ $content=ob_get_contents();
+ ob_end_clean();
+ return $content;
+ }
+ catch(Exception $e)
+ {
+ throw new TInvalidOperationException('component_statements_invalid',$statements,$e->getMessage());
+ }
+ }
+}
+
+/**
+ * TPropertyValue class
+ *
+ * TPropertyValue is a utility class that provides static methods
+ * to convert component property values to specific types.
+ *
+ * TPropertyValue is commonly used in component setter methods to ensure
+ * the new property value is of specific type.
+ * For example, a boolean-typed property setter method would be as follows,
+ * <code>
+ * function setPropertyName($value) {
+ * $value=TPropertyValue::ensureBoolean($value);
+ * // $value is now of boolean type
+ * }
+ * </code>
+ *
+ * Properties can be of the following types with specific type conversion rules:
+ * - string: a boolean value will be converted to 'true' or 'false'.
+ * - boolean: string 'true' (case-insensitive) will be converted to true,
+ * string 'false' (case-insensitive) will be converted to false.
+ * - integer
+ * - float
+ * - array
+ * - object
+ * - enum: enumerable type, represented by an array of strings.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System
+ * @since 3.0
+ */
+class TPropertyValue
+{
+ /**
+ * Converts a value to boolean type.
+ * Note, string 'true' (case-insensitive) will be converted to true,
+ * string 'false' (case-insensitive) will be converted to false.
+ * @param mixed the value to be converted.
+ * @return boolean
+ */
+ public static function ensureBoolean($value)
+ {
+ if (is_string($value))
+ return strcasecmp($value,'true')==0;
+ else
+ return (boolean)$value;
+ }
+
+ /**
+ * Converts a value to string type.
+ * Note, a boolean value will be converted to 'true' if it is true
+ * and 'false' if it is false.
+ * @param mixed the value to be converted.
+ * @return string
+ */
+ public static function ensureString($value)
+ {
+ if (is_bool($value))
+ return $value?'true':'false';
+ else
+ return (string)$value;
+ }
+
+ /**
+ * Converts a value to integer type.
+ * @param mixed the value to be converted.
+ * @return integer
+ */
+ public static function ensureInteger($value)
+ {
+ return (integer)$value;
+ }
+
+ /**
+ * Converts a value to float type.
+ * @param mixed the value to be converted.
+ * @return float
+ */
+ public static function ensureFloat($value)
+ {
+ return (float)$value;
+ }
+
+ /**
+ * Converts a value to array type.
+ * @param mixed the value to be converted.
+ * @return array
+ */
+ public static function ensureArray($value)
+ {
+ return (array)$value;
+ }
+
+ /**
+ * Converts a value to object type.
+ * @param mixed the value to be converted.
+ * @return object
+ */
+ public static function ensureObject($value)
+ {
+ return (object)$value;
+ }
+
+ /**
+ * Converts a value to enum type.
+ * @param mixed the value to be converted.
+ * @param array array of strings representing the enum type.
+ * @return string
+ * @throws TInvalidDataValueException if the original value is not in the string array.
+ */
+ public static function ensureEnum($value,$enum)
+ {
+ if(($index=array_search($value,$enum))!==false)
+ return $enum[$index];
+ else
+ throw new TInvalidDataValueException('propertyvalue_enumvalue_invalid',$value,implode(',',$enum));
+ }
+}
+
+/**
+ * TEventParameter class.
+ * TEventParameter is the base class for all event parameter classes.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System
+ * @since 3.0
+ */
+class TEventParameter extends TComponent
+{
+}
+
+?> \ No newline at end of file
diff --git a/framework/TODO.txt b/framework/TODO.txt
new file mode 100644
index 00000000..0bfaab26
--- /dev/null
+++ b/framework/TODO.txt
@@ -0,0 +1,306 @@
+
+think more about encoding/decoding
+<%@ MasterClass="Pages.MasterPage" %>
+THtmlTextWriter::addStyleAttribute(), also change how TStyle renders its content.
+
+
+callback
+
+http://www.xisc.com/index.php?page=homepage
+
+Features to be implemented later:
+- SmartNavigation: TForm, TPage
+- DefaultButton: TForm, TClientScriptManager
+
+Main Problems:
+1. How to solve viewstate ID mapping problems? What if a control has changed its ID before saving viewstate?
+5. What if in a getter, it needs to address a sibling or child control?
+6. learn more details about WebParts
+http://msdn.microsoft.com/library/default.asp?url=/library/en-us/odc_sp2003_ta/html/sharepoint_northwindwebparts.asp
+
+When a page is requested, asp.net 2.0 does the following
+- application creates the page instance by new operator
+- application invokes page.processrequest
+- page.frameworkinitialize is invoked
+- ... ControlBuilder.InitObject(page)
+- ... ... set SkinID
+- ... ... apply stylesheet skin
+- ... ... init simple properties (basic property types)
+- ... ... init complex proprerties (e.g. Font-Name), may need to call InitObject on Font
+- ... ... init data bindings
+- ... ... call BuildChildren if item implements IParserAccessor (Control implements it)
+- ... ... ... for each body item
+- ... ... ... ... get the control builder for the control class
+- ... ... ... ... call ControlBuilder.BuildObject
+- ... ... ... ... ... call InitObject
+- ... ... ... ... if it is user control (control with template)
+- ... ... ... ... ... call UserControl.InitializeAsUserControl(page)
+- ... ... ... ... ... ... call frameworkinitialize (won't call InitObject anymore)
+- ... ... ... ... call control.AddParsedSubObject(item)
+- ... ... init template properties (should be a property with template type, will use TemplateBuilder to build the object)
+- ... ... bind field to control (pass some fields from TemplateControl to the new control)
+
+How is a UserControl initialized if created dynamically?
+- ... InitializeAsUserControl invoked in OnInit (which will be caught up if the control is dynamically created)
+
+
+
+
+Findings:
+
+
+Control lifecycles:
+
+const CS_CONSTRUCTED=0; // after constructor
+const CS_FRAMEWORK_INITIALIZED=1; // after children created and properties set
+const CS_CHILDREN_INITIALIZED=2; // right before OnInit (also before applySkin, but after children OnInit)
+const CS_INITIALIZED=3; // right after OnInit
+const CS_STATE_LOADED=4; // right after loading state
+const CS_LOADED=5; // right after loaded
+const CS_PRERENDERED=6; // right after prerender
+
+stylesheet is applied before setting control properties (after skinID is set)
+skin is applied right before OnInit
+
+
+
+ControlBuilder is responsible to call AddParsedSubObject
+Its BuildChildren is
+
+My understanding of lifecycles:
+
+Page constructed (using new operator)
+Page.ProcessRequest
+ Page.FrameworkInitialize
+ Page.InitializeStyleSheet
+ Page.determinePostBackMode
+ Page.loadScrollPosition
+ Page.performPreInit
+ Page.initRecursive
+ Page.onInitComplete
+ Page.loadAllState
+ Page.processPostData
+ Page.onPreLoad
+ Page.loadRecursive
+ Page.processPostData
+ Page.raiseChangedEvents
+ Page.raisePostBackEvent
+ Page.onLoadComplete
+ Page.raiseCallBackEvent -> exit
+ Page.preRenderRecursive
+ Page.performPreRenderComplete
+ Page.saveAllState
+ Page.onSaveStateComplete
+ Page.renderControl
+ Page.unloadRecursive
+
+
+Possible solution for SkinID: setting SkinID will cause skin change immediately.
+With control hierarchy storing:
+
+
+Page constructed (template parsed but not instantiated)
+Page.loadAllState <---- page state totally restored
+Page.loadRecursive
+Page.processPostData
+Page.raiseChangedEvents
+Page.raisePostBackEvent or Page.raiseCallBackEvent
+Page.preRenderRecursive
+Page.saveAllState
+Page.renderControl
+Page.unloadRecursive
+
+
+
+Page constructed
+Page.frameworkInitialize (instantiating template)
+Page.initRecursive
+Page.saveAllState
+Page.renderControl
+Page.unloadRecursive
+
+
+
+
+edit distance.
+(UIUC) Jiawei Han: graph similarity mining
+
+module service?
+page service?
+
+
+index.php?page=path.to.page
+
+
+TODOs
+
+renderer??
+Dispose
+-->> viewstate id-based or index-based, structure keeping?? <<--
+HttpContext, url/path
+template path
+adapter
+parser
+
+
+class metadata attributes:
+e.g. the body content of a control can be parsed as child controls, or property values
+or list items
+
+Imagine a usecase (hmmm...could have problem....) How to specify two list properties for a single control?
+<com:TListBox>
+<ID>abc</ID>
+<Items>
+ <ListItem Value="yyy">xxx</ListItem>
+ <ListItem>yyy</ListItem>
+</Items>
+</com:TListBox>
+
+
+how to define a control with template??
+TemplateControl.LoadControl or LoadTemplate is used for controls that allows taking template contents for their attributes.
+
+
+UserControl! starting from the template instead of class
+
+
+
+idNotCalculated = 1;
+marked = 2;
+disableViewState = 4;
+controlsCreated = 8;
+invisible = 0x10;
+visibleDirty = 0x20;
+idNotRequired = 0x40;
+isNamingContainer = 0x80;
+creatingControls = 0x100;
+notVisibleOnPage = 0x200;
+themeApplied = 0x400;
+mustRenderID = 0x800;
+disableTheming = 0x1000;
+enableThemingSet = 0x2000;
+styleSheetApplied = 0x4000;
+controlAdapterResolved = 0x8000;
+designMode = 0x10000;
+designModeChecked = 0x20000;
+disableChildControlState = 0x40000;
+isWebControlDisabled = 0x80000;
+controlStateApplied = 0x100000;
+useGeneratedID = 0x200000;
+
+TODO:
+
+clean up ID, namingcontainer things:
+
+A control can be added into page hierarchy in the following ways
+- newly created in template
+ - manual ID
+ - need automatic ID
+- dynamically created in code
+ - manual ID
+ - need automatic ID
+- a previously removed control
+ - manual ID
+ - need automatic ID
+When a control changes its ID, the following things are necessary:
+- If it is a namingContainer
+ - all its descendant must update their unique ID
+- If it is a normal control, nothing needs to be done
+- In both cases, the namingContainer's nametable has to be updated
+When a control changes its page, ...?
+- All its children have to change the page too
+When a control changes its parent, ...?
+- This is like remvoedControl + addedControl.
+When a control changes its namingcontainer ...?
+- All its child controls have to change namingcontainer recursively
+ The old namingContainer nametable must also be changed recursively.
+When a control changes its templateControl...??
+- Nothing to be done.
+
+
+
+ControlState
+============
+Constructed
+Initialized
+ViewStateLoaded
+Loaded
+PreRendered
+
+
+
+asp.net 2.0 lifecycles
+========================
+Application: BeginRequest
+Application: PreAuthenticateRequest
+Application: AuthenticateRequest
+Application: PostAuthenticateRequest
+Application: PreAuthorizeRequest
+Application: AuthorizeRequest
+Application: PostAuthorizeRequest
+Application: PreResolveRequestCache
+Application: ResolveRequestCache
+Application: PostResolveRequestCache
+Application: PreMapRequestHandler
+Page: Construct
+Application: PostMapRequestHandler
+Application: PreAcquireRequestState
+Application: AcquireRequestState
+Application: PostAcquireRequestState
+Application: PreRequestHandlerExecute
+Page: AddParsedSubObject
+Page: CreateControlCollection
+Page: AddedControl
+Page: AddParsedSubObject
+Page: AddedControl
+Page: ResolveAdapter
+Page: DeterminePostBackMode
+Page: PreInit
+Control: ResolveAdapter
+Control: Init
+Control: TrackViewState
+Page: Init
+Page: TrackViewState
+Page: InitComplete
+Page: LoadPageStateFromPersistenceMedium
+Control: LoadViewState
+Page: EnsureChildControls
+Page: CreateChildControls
+Page: PreLoad
+Page: Load
+Control: DataBind
+Control: Load
+Page: EnsureChildControls
+Page: LoadComplete
+Page: EnsureChildControls
+Page: PreRender
+Control: EnsureChildControls
+Control: PreRender
+Page: PreRenderComplete
+Page: SaveViewState
+Control: SaveViewState
+Page: SaveViewState
+Control: SaveViewState
+Page: SavePageStateToPersistenceMedium
+Page: SaveStateComplete
+Page: CreateHtmlTextWriter
+Page: RenderControl
+Page: Render
+Page: RenderChildren
+Control: RenderControl
+Page: VerifyRenderingInServerForm
+Page: CreateHtmlTextWriter
+Control: Unload
+Control: Dispose
+Page: Unload
+Page: Dispose
+Application: PostRequestHandlerExecute
+Application: PreReleaseRequestState
+Application: ReleaseRequestState
+Application: PostReleaseRequestState
+Application: PreUpdateRequestCache
+Application: UpdateRequestCache
+Application: PostUpdateRequestCache
+Application: EndRequest
+Application: PreSendRequestHeaders
+Application: PreSendRequestContent \ No newline at end of file
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
diff --git a/framework/core.php b/framework/core.php
new file mode 100644
index 00000000..639122e1
--- /dev/null
+++ b/framework/core.php
@@ -0,0 +1,735 @@
+<?php
+/**
+ * Prado core interfaces and classes.
+ *
+ * This file contains and includes the definitions of prado core interfaces and classes.
+ *
+ * @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
+ */
+
+/**
+ * The framework installation path.
+ */
+define('PRADO_DIR',dirname(__FILE__));
+
+/**
+ * Includes TComponent definition
+ */
+require_once(PRADO_DIR.'/TComponent.php');
+/**
+ * Includes exception definitions
+ */
+require_once(PRADO_DIR.'/Exceptions/TException.php');
+/**
+ * Includes TList definition
+ */
+require_once(PRADO_DIR.'/Collections/TList.php');
+/**
+ * Includes TMap definition
+ */
+require_once(PRADO_DIR.'/Collections/TMap.php');
+/**
+ * Includes TXmlDocument, TXmlElement definition
+ */
+require_once(PRADO_DIR.'/Data/TXmlDocument.php');
+
+/**
+ * IApplication interface.
+ *
+ * This interface must be implemented by application classes.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System
+ * @since 3.0
+ */
+interface IApplication
+{
+ /**
+ * Defines Error event.
+ */
+ public function onError($param);
+ /**
+ * Defines BeginRequest event.
+ * @param mixed event parameter
+ */
+ public function onBeginRequest($param);
+ /**
+ * Defines Authentication event.
+ * @param mixed event parameter
+ */
+ public function onAuthentication($param);
+ /**
+ * Defines PostAuthentication event.
+ * @param mixed event parameter
+ */
+ public function onPostAuthentication($param);
+ /**
+ * Defines Authorization event.
+ * @param mixed event parameter
+ */
+ public function onAuthorization($param);
+ /**
+ * Defines PostAuthorization event.
+ * @param mixed event parameter
+ */
+ public function onPostAuthorization($param);
+ /**
+ * Defines LoadState event.
+ * @param mixed event parameter
+ */
+ public function onLoadState($param);
+ /**
+ * Defines PostLoadState event.
+ * @param mixed event parameter
+ */
+ public function onPostLoadState($param);
+ /**
+ * Defines PreRunService event.
+ * @param mixed event parameter
+ */
+ public function onPreRunService($param);
+ /**
+ * Defines RunService event.
+ * @param mixed event parameter
+ */
+ public function onRunService($param);
+ /**
+ * Defines PostRunService event.
+ * @param mixed event parameter
+ */
+ public function onPostRunService($param);
+ /**
+ * Defines SaveState event.
+ * @param mixed event parameter
+ */
+ public function onSaveState($param);
+ /**
+ * Defines PostSaveState event.
+ * @param mixed event parameter
+ */
+ public function onPostSaveState($param);
+ /**
+ * Defines EndRequest event.
+ * @param mixed event parameter
+ */
+ public function onEndRequest($param);
+ /**
+ * Runs the application.
+ */
+ public function run();
+ /**
+ * Completes and terminates the current request processing.
+ */
+ public function completeRequest();
+ /**
+ * @return string application ID
+ */
+ public function getID();
+ /**
+ * @param string application ID
+ */
+ public function setID($id);
+ /**
+ * @return string a unique ID that can uniquely identify the application from the others
+ */
+ public function getUniqueID();
+ /**
+ * @return IUser application user
+ */
+ public function getUser();
+ /**
+ * @param IUser application user
+ */
+ public function setUser(IUser $user);
+ /**
+ * @param string module ID
+ * @return IModule module corresponding to the ID, null if not found
+ */
+ public function getModule($id);
+ /**
+ * Adds a module into application.
+ * @param string module ID
+ * @param IModule module to be added
+ * @throws TInvalidOperationException if module with the same ID already exists
+ */
+ public function setModule($id,IModule $module);
+ /**
+ * @return array list of modules
+ */
+ public function getModules();
+ /**
+ * @return TMap list of parameters
+ */
+ public function getParameters();
+ /**
+ * @return IService the currently requested service
+ */
+ public function getService();
+ /**
+ * @return THttpRequest the current user request
+ */
+ public function getRequest();
+ /**
+ * @return THttpResponse the response to the request
+ */
+ public function getResponse();
+ /**
+ * @return THttpSession the user session
+ */
+ public function getSession();
+ /**
+ * @return ICache cache that is available to use
+ */
+ public function getCache();
+ /**
+ * @return IErrorHandler error handler
+ */
+ public function getErrorHandler();
+ /**
+ * @return IAuthManager the auth (authentication/authorization) manager
+ */
+ public function getAuthManager();
+}
+
+/**
+ * IModule interface.
+ *
+ * This interface must be implemented by application modules.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System
+ * @since 3.0
+ */
+interface IModule
+{
+ /**
+ * Initializes the module.
+ * @param IApplication the application object
+ * @param TXmlElement the configuration for the module
+ */
+ public function init($application,$configuration);
+ /**
+ * @return string ID of the module
+ */
+ public function getID();
+ /**
+ * @param string ID of the module
+ */
+ public function setID($id);
+}
+
+/**
+ * IService interface.
+ *
+ * This interface must be implemented by services.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System
+ * @since 3.0
+ */
+interface IService
+{
+ /**
+ * Initializes the service.
+ * @param IApplication the application object
+ * @param TXmlElement the configuration for the service
+ */
+ public function init($application,$configuration);
+ /**
+ * @return string ID of the service
+ */
+ public function getID();
+ /**
+ * @param string ID of the service
+ */
+ public function setID($id);
+ /**
+ * Runs the service.
+ */
+ public function run();
+}
+
+interface IErrorHandler
+{
+ public function handle($sender,$param);
+}
+
+/**
+ * ICache interface.
+ *
+ * This interface must be implemented by cache managers.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System
+ * @since 3.0
+ */
+interface ICache
+{
+ /**
+ * Retrieves a value from cache with a specified key.
+ * @return mixed the value stored in cache, false if the value is not in the cache or expired.
+ */
+ public function get($id);
+ /**
+ * Stores a value identified by a key into cache.
+ * If the cache already contains such a key, the existing value and
+ * expiration time will be replaced with the new ones.
+ *
+ * @param string the key identifying the value to be cached
+ * @param mixed the value to be cached
+ * @param integer the expiration time of the value,
+ * 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.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ public function set($id,$value,$expire=0);
+ /**
+ * Stores a value identified by a key into cache if the cache does not contain this key.
+ * Nothing will be done if the cache already contains the key.
+ * @param string the key identifying the value to be cached
+ * @param mixed the value to be cached
+ * @param integer the expiration time of the value,
+ * 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.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ public function add($id,$value,$expire=0);
+ /**
+ * Stores a value identified by a key into cache only if the cache contains this key.
+ * The existing value and expiration time will be overwritten with the new ones.
+ * @param string the key identifying the value to be cached
+ * @param mixed the value to be cached
+ * @param integer the expiration time of the value,
+ * 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.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ public function replace($id,$value,$expire=0);
+ /**
+ * Deletes a value with the specified key from cache
+ * @param string the key of the value to be deleted
+ * @return boolean if no error happens during deletion
+ */
+ public function delete($id);
+ /**
+ * Deletes all values from cache.
+ * Be careful of performing this operation if the cache is shared by multiple applications.
+ */
+ public function flush();
+}
+
+/**
+ * ITextWriter interface.
+ *
+ * This interface must be implemented by writers.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System
+ * @since 3.0
+ */
+interface ITextWriter
+{
+ /**
+ * Writes a string.
+ * @param string string to be written
+ */
+ public function write($str);
+ /**
+ * Flushes the content that has been written.
+ */
+ public function flush();
+}
+
+/**
+ * ITheme interface.
+ *
+ * This interface must be implemented by theme.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System
+ * @since 3.0
+ */
+interface ITheme
+{
+ /**
+ * Applies this theme to the specified control.
+ * @param TControl the control to be applied with this theme
+ */
+ public function apply($control);
+}
+
+/**
+ * ITemplate interface
+ *
+ * ITemplate specifies the interface for classes encapsulating
+ * parsed template structures.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System
+ * @since 3.0
+ */
+interface ITemplate
+{
+ /**
+ * 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
+ */
+ public function instantiateIn($parent);
+}
+
+/**
+ * PradoBase class.
+ *
+ * PradoBase implements a few fundamental static methods.
+ *
+ * To use the static methods, Use Prado as the class name rather than PradoBase.
+ * PradoBase is meant to serve as the base class of Prado. The latter might be
+ * rewritten for customization.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System
+ * @since 3.0
+ */
+class PradoBase
+{
+ /**
+ * File extension for Prado class files.
+ */
+ const CLASS_FILE_EXT='.php';
+ /**
+ * @var array list of path aliases
+ */
+ private static $_aliases=array('System'=>PRADO_DIR);
+ /**
+ * @var array list of namespaces currently in use
+ */
+ private static $_usings=array();
+ /**
+ * @var IApplication the application instance
+ */
+ private static $_application=null;
+
+ /**
+ * @return string the version of Prado framework
+ */
+ public static function getVersion()
+ {
+ return '3.0a';
+ }
+
+ /**
+ * PHP error handler.
+ * This method should be registered as PHP error handler using
+ * {@link set_error_handler}. The method throws an exception that
+ * contains the error information.
+ * @param integer the level of the error raised
+ * @param string the error message
+ * @param string the filename that the error was raised in
+ * @param integer the line number the error was raised at
+ */
+ public static function phpErrorHandler($errno,$errstr,$errfile,$errline)
+ {
+ if(error_reporting()!=0)
+ throw new TPhpErrorException($errno,$errstr,$errfile,$errline);
+ }
+
+ /**
+ * Default exception handler.
+ * This method should be registered as default exception handler using
+ * {@link set_exception_handler}. The method tries to use the errorhandler
+ * module of the Prado application to handle the exception.
+ * If the application or the module does not exist, it simply echoes the
+ * exception.
+ * @param Exception exception that is not caught
+ */
+ public static function exceptionHandler($exception)
+ {
+ if(self::$_application!==null && ($errorHandler=self::$_application->getErrorHandler())!==null)
+ {
+ $errorHandler->handle($exception);
+ }
+ else
+ {
+ echo $exception;
+ }
+ exit(1);
+ }
+
+ /**
+ * Stores the application instance in the class static member.
+ * This method helps implement a singleton pattern for TApplication.
+ * Repeated invocation of this method or the application constructor
+ * will cause the throw of an exception.
+ * This method should only be used by framework developers.
+ * @param IApplication the application instance
+ * @throws TInvalidOperationException if this method is invoked twice or more.
+ */
+ public static function setApplication(IApplication $app)
+ {
+ if(self::$_application!==null)
+ throw new TInvalidOperationException('prado_application_singleton_required');
+ self::$_application=$app;
+ }
+
+ /**
+ * @return IApplication the application singleton, null if the singleton has not be created yet.
+ */
+ public static function getApplication()
+ {
+ return self::$_application;
+ }
+
+ /**
+ * @return string the path of the framework
+ */
+ public static function getFrameworkPath()
+ {
+ return dirname(__FILE__);
+ }
+
+ /**
+ * Serializes a data.
+ * Original PHP serialize function has a bug that may not serialize
+ * properly an object.
+ * @param mixed data to be serialized
+ * @return string the serialized data
+ */
+ public static function serialize($data)
+ {
+ $arr[0]=$data;
+ return serialize($arr);
+ }
+
+ /**
+ * Unserializes a data.
+ * Original PHP unserialize function has a bug that may not unserialize
+ * properly an object.
+ * @param string data to be unserialized
+ * @return mixed unserialized data, null if unserialize failed
+ */
+ public static function unserialize($str)
+ {
+ $arr=unserialize($str);
+ return isset($arr[0])?$arr[0]:null;
+ }
+
+ /**
+ * Creates a component with the specified type.
+ * A component type can be either the component class name
+ * or a namespace referring to the path of the component class file.
+ * For example, 'TButton', 'System.Web.UI.WebControls.TButton' are both
+ * valid component type.
+ * @param string component type
+ * @return TComponent component instance of the specified type
+ * @throws TInvalidDataValueException if the component type is unknown
+ */
+ public static function createComponent($type)
+ {
+ if(class_exists($type,false))
+ return new $type;
+ if(($pos=strrpos($type,'.'))===false)
+ {
+ // a class name is supplied
+ $className=$type;
+ if(!class_exists($className,false))
+ {
+ require_once($className.self::CLASS_FILE_EXT);
+ }
+ if(class_exists($className,false))
+ return new $className;
+ else
+ throw new TInvalidDataValueException('prado_component_unknown',$type);
+ }
+ else
+ {
+ $className=substr($type,$pos+1);
+ if(($path=self::getPathOfNamespace($type))!==null)
+ {
+ // the class type is given in a namespace form
+ if(!class_exists($className,false))
+ {
+ require_once($path.self::CLASS_FILE_EXT);
+ }
+ if(class_exists($className,false))
+ return new $className;
+ }
+ throw new TInvalidDataValueException('prado_component_unknown',$type);
+ }
+ }
+
+ /**
+ * Uses a namespace.
+ * A namespace ending with an asterisk '*' refers to a directory, otherwise it represents a PHP file.
+ * If the namespace corresponds to a directory, the directory will be appended
+ * to the include path. If the namespace corresponds to a file, it will be included (require_once).
+ * @param string namespace to be used
+ * @throws TInvalidDataValueException if the namespace is invalid
+ */
+ public static function using($namespace)
+ {
+ if(!isset(self::$_usings[$namespace]))
+ {
+ if(($path=self::getPathOfNamespace($namespace,self::CLASS_FILE_EXT))===null)
+ throw new TInvalidDataValueException('prado_using_invalid',$namespace);
+ else
+ {
+ if($namespace[strlen($namespace)-1]==='*') // a file
+ {
+ if(is_dir($path))
+ {
+ self::$_usings[$namespace]=$path;
+ set_include_path(get_include_path().PATH_SEPARATOR.$path);
+ }
+ else
+ throw new TInvalidDataValueException('prado_using_invalid',$namespace);
+ }
+ else // a directory
+ {
+ if(is_file($path))
+ {
+ self::$_usings[$namespace]=$path;
+ require_once($path);
+ }
+ else
+ throw new TInvalidDataValueException('prado_using_invalid',$namespace);
+ }
+ }
+ }
+ }
+
+ /**
+ * Translates a namespace into a file path.
+ * The first segment of the namespace is considered as a path alias
+ * which is replaced with the actual path. The rest segments are
+ * subdirectory names appended to the aliased path.
+ * If the namespace ends with an asterisk '*', it represents a directory;
+ * Otherwise it represents a file whose extension name is specified by the second parameter (defaults to empty).
+ * Note, this method does not ensure the existence of the resulting file path.
+ * @param string namespace
+ * @param string extension to be appended if the namespace refers to a file
+ * @return string file path corresponding to the namespace, null if namespace is invalid
+ */
+ public static function getPathOfNamespace($namespace,$ext='')
+ {
+ if(isset(self::$_usings[$namespace]))
+ return self::$_usings[$namespace];
+ else
+ {
+ $segs=explode('.',$namespace);
+ $alias=array_shift($segs);
+ if(($file=array_pop($segs))!==null && ($root=self::getPathOfAlias($alias))!==null)
+ return rtrim($root.'/'.implode('/',$segs),'/').(($file==='*')?'':'/'.$file.$ext);
+ else
+ return null;
+ }
+ }
+
+ /**
+ * @param string alias to the path
+ * @return string the path corresponding to the alias, null if alias not defined.
+ */
+ public static function getPathOfAlias($alias)
+ {
+ if(isset(self::$_aliases[$alias]))
+ return self::$_aliases[$alias];
+ else
+ return null;
+ }
+
+ /**
+ * @param string alias to the path
+ * @param string the path corresponding to the alias
+ * @throws TInvalidOperationException if the alias is already defined
+ * @throws TInvalidDataValueException if the path is not a valid file path
+ */
+ public static function setPathOfAlias($alias,$path)
+ {
+ if(isset(self::$_aliases[$alias]))
+ throw new TInvalidOperationException('prado_alias_redefined',$alias);
+ else if(($rp=realpath($path))!==false && is_dir($rp))
+ self::$_aliases[$alias]=$rp;
+ else
+ throw new TInvalidDataValueException('prado_alias_invalid',$alias,$path);
+ }
+
+ /**
+ * Fatal error handler.
+ * This method is used in places where exceptions usually cannot be raised
+ * (e.g. magic methods).
+ * It displays the debug backtrace.
+ * @param string error message
+ */
+ function fatalError($msg)
+ {
+ echo '<h1>Fatal Error</h1>';
+ echo '<p>'.$msg.'</p>';
+ if(!function_exists('debug_backtrace'))
+ return;
+ echo '<h2>Debug Backtrace</h2>';
+ echo '<pre>';
+ $index=-1;
+ foreach(debug_backtrace() as $t)
+ {
+ $index++;
+ if($index==0) // hide the backtrace of this function
+ continue;
+ echo '#'.$index.' ';
+ if(isset($t['file']))
+ echo basename($t['file']) . ':' . $t['line'];
+ else
+ echo '<PHP inner-code>';
+ echo ' -- ';
+ if(isset($t['class']))
+ echo $t['class'] . $t['type'];
+ echo $t['function'];
+ if(isset($t['args']) && sizeof($t['args']) > 0)
+ echo '(...)';
+ else
+ echo '()';
+ echo "\n";
+ }
+ echo '</pre>';
+ exit(1);
+ }
+}
+
+/**
+ * Includes TErrorHandler class
+ */
+require_once(PRADO_DIR.'/Exceptions/TErrorHandler.php');
+/**
+ * Includes THttpRequest class
+ */
+require_once(PRADO_DIR.'/Web/THttpRequest.php'); // include TUser
+/**
+ * Includes THttpResponse class
+ */
+require_once(PRADO_DIR.'/Web/THttpResponse.php');
+/**
+ * Includes THttpSession class
+ */
+require_once(PRADO_DIR.'/Web/THttpSession.php');
+/**
+ * Includes TAuthorizationRule class
+ */
+require_once(PRADO_DIR.'/Security/TAuthorizationRule.php');
+
+?> \ No newline at end of file
diff --git a/framework/prado.php b/framework/prado.php
new file mode 100644
index 00000000..cfd10705
--- /dev/null
+++ b/framework/prado.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * Prado bootstrap file.
+ *
+ * This file must be included first in order to run prado applications.
+ *
+ * @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
+ */
+
+/**
+ * Includes the Prado core header file
+ */
+require_once(dirname(__FILE__).'/core.php');
+
+/**
+ * Defines Prado class if not defined.
+ */
+if(!class_exists('Prado',false))
+{
+ class Prado extends PradoBase
+ {
+ }
+}
+
+/**
+ * Defines __autoload function if not defined.
+ */
+if(!function_exists('__autoload'))
+{
+ function __autoload($className)
+ {
+ require_once($className.Prado::CLASS_FILE_EXT);
+ }
+}
+
+/**
+ * Sets up error handler to convert PHP errors into exceptions that can be caught.
+ */
+set_error_handler(array('Prado','phpErrorHandler'),error_reporting());
+
+/**
+ * Sets up handler to handle uncaught exceptions.
+ */
+set_exception_handler(array('Prado','exceptionHandler'));
+
+/**
+ * Includes TApplication class file
+ */
+require_once(dirname(__FILE__).'/TApplication.php');
+
+?> \ No newline at end of file