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