summaryrefslogtreecommitdiff
path: root/demos/time-tracker
diff options
context:
space:
mode:
authorwei <>2006-07-16 06:19:36 +0000
committerwei <>2006-07-16 06:19:36 +0000
commitc7d41e5bea4a5f96979a08da9cc9f79355edfe70 (patch)
tree8f21cba052c1eae7c7204ac272dd8c5e9d6fe110 /demos/time-tracker
parentaf68030fcf0c266300feb2c100149ecadef7d364 (diff)
Update Time Tracker demo.
Diffstat (limited to 'demos/time-tracker')
-rw-r--r--demos/time-tracker/protected/APP_CODE/BaseDao.php27
-rw-r--r--demos/time-tracker/protected/APP_CODE/DaoManager.php126
-rw-r--r--demos/time-tracker/protected/APP_CODE/Project.php18
-rw-r--r--demos/time-tracker/protected/APP_CODE/ProjectDao.php23
-rw-r--r--demos/time-tracker/protected/APP_CODE/TimeTrackerException.php19
-rw-r--r--demos/time-tracker/protected/APP_CODE/TimeTrackerUser.php51
-rw-r--r--demos/time-tracker/protected/APP_CODE/TimeTrackerUserTypeHandler.php54
-rw-r--r--demos/time-tracker/protected/APP_CODE/UserDao.php155
-rw-r--r--demos/time-tracker/protected/APP_CODE/UserManager.php68
-rw-r--r--demos/time-tracker/protected/APP_CODE/exceptions.txt6
-rw-r--r--demos/time-tracker/protected/App_Data/time_tracker.db (renamed from demos/time-tracker/protected/data/time_tracker.db)bin16384 -> 16384 bytes
-rw-r--r--demos/time-tracker/protected/pages/Docs/TopicList.tpl2
-rw-r--r--demos/time-tracker/protected/pages/Docs/WritingUnitTest.page43
-rw-r--r--demos/time-tracker/protected/pages/Docs/config.xml12
-rw-r--r--demos/time-tracker/protected/pages/Docs/db.pngbin26879 -> 26521 bytes
-rw-r--r--demos/time-tracker/protected/pages/TimeTracker/Login.page38
-rw-r--r--demos/time-tracker/protected/pages/TimeTracker/Login.php53
-rw-r--r--demos/time-tracker/protected/pages/TimeTracker/Logout.page0
-rw-r--r--demos/time-tracker/protected/pages/TimeTracker/Logout.php34
-rw-r--r--demos/time-tracker/protected/pages/TimeTracker/MainLayout.php7
-rw-r--r--demos/time-tracker/protected/pages/TimeTracker/MainLayout.tpl40
-rw-r--r--demos/time-tracker/protected/pages/TimeTracker/SiteMap.php8
-rw-r--r--demos/time-tracker/protected/pages/TimeTracker/SiteMap.tpl43
-rw-r--r--demos/time-tracker/protected/pages/TimeTracker/UserCreate.page65
-rw-r--r--demos/time-tracker/protected/pages/TimeTracker/UserCreate.php78
-rw-r--r--demos/time-tracker/protected/pages/TimeTracker/UserList.page3
-rw-r--r--demos/time-tracker/protected/pages/TimeTracker/config.xml24
-rw-r--r--demos/time-tracker/protected/pages/Welcome.page168
-rw-r--r--demos/time-tracker/tests/functional.php2
-rw-r--r--demos/time-tracker/tests/unit.php2
-rw-r--r--demos/time-tracker/tests/unit/AddUserToProjectTestCase.php35
-rw-r--r--demos/time-tracker/tests/unit/BaseTestCase.php35
-rw-r--r--demos/time-tracker/tests/unit/CreateNewProjectTestCase.php90
-rw-r--r--demos/time-tracker/tests/unit/ProjectDaoTestCase.php92
-rw-r--r--demos/time-tracker/tests/unit/ProjectTestCase.php6
-rw-r--r--demos/time-tracker/tests/unit/UserDaoTestCase.php283
-rw-r--r--demos/time-tracker/themes/TimeTracker/background.pngbin0 -> 4674 bytes
-rw-r--r--demos/time-tracker/themes/TimeTracker/site.css217
-rw-r--r--demos/time-tracker/themes/TimeTracker/tabs.pngbin0 -> 324 bytes
39 files changed, 1666 insertions, 261 deletions
diff --git a/demos/time-tracker/protected/APP_CODE/BaseDao.php b/demos/time-tracker/protected/APP_CODE/BaseDao.php
index f9146b59..63b91def 100644
--- a/demos/time-tracker/protected/APP_CODE/BaseDao.php
+++ b/demos/time-tracker/protected/APP_CODE/BaseDao.php
@@ -1,14 +1,41 @@
<?php
+/**
+ * Base DAO class file.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2005-2006 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Revision: $ $16/07/2006: $
+ * @package Demos
+ */
+/**
+ * Base DAO class.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ $16/07/2006: $
+ * @package Demos
+ * @since 3.1
+ */
class BaseDao
{
+ /**
+ * @var TSqlMapper sqlmap client.
+ */
private $_connection;
+ /**
+ * @param TSqlMapper sqlmap client.
+ */
public function setConnection($connection)
{
$this->_connection = $connection;
}
+ /**
+ * @return TSqlMapper sqlmap client.
+ */
protected function getConnection()
{
return $this->_connection;
diff --git a/demos/time-tracker/protected/APP_CODE/DaoManager.php b/demos/time-tracker/protected/APP_CODE/DaoManager.php
new file mode 100644
index 00000000..b8ac55af
--- /dev/null
+++ b/demos/time-tracker/protected/APP_CODE/DaoManager.php
@@ -0,0 +1,126 @@
+<?php
+/**
+ * DaoManager class file.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2005-2006 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Revision: $ $16/07/2006: $
+ * @package Demos
+ */
+
+/**
+ * DaoManager class.
+ *
+ * A Registry for Dao and an implementation of that type.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ $16/07/2006: $
+ * @package Demos
+ * @since 3.1
+ */
+class DaoManager extends TModule
+{
+ /**
+ * @var TSqlMapper sqlmap client
+ */
+ private $_connection;
+ /**
+ * @var boolean if the module has been initialized
+ */
+ private $_initialized=false;
+ /**
+ * @var array registered list of dao
+ */
+ private $_dao=array();
+ /**
+ * Initializes the module.
+ * This method is required by IModule and is invoked by application.
+ * It loads dao information from the module configuration.
+ * @param TXmlElement module configuration
+ */
+ public function init($config)
+ {
+ if($this->_connection === null)
+ throw new TimeTrackerException('daomanager_connection_required');
+ $app = $this->getApplication();
+ if(is_string($this->_connection))
+ {
+ if(($conn=$app->getModule($this->_connection)->getClient())===null)
+ throw new TimeTrackerException('daomanager_undefined_connection',$this->_connection);
+ if(!($conn instanceof TSqlMapper))
+ throw new TimeTrackerException('daomanager_invalid_connection', $this->_connection);
+ $this->_connection = $conn;
+ }
+ $this->includeDaoImplementation($config->getElementsByTagName('dao'));
+ $this->_initialized = true;
+ }
+
+ /**
+ * Register the dao type and implementation class names.
+ * @param array list of TXmlDocument nodes.
+ */
+ protected function includeDaoImplementation($nodes)
+ {
+ foreach($nodes as $node)
+ {
+ $id = $node->getAttribute('id');
+ $class = $node->getAttribute('class');
+ $this->_dao[$id] = array('class' => $class);
+ }
+ }
+
+ /**
+ * @return array list of registered Daos
+ */
+ public function getDaos()
+ {
+ return $this->_dao;
+ }
+
+ /**
+ * Returns an implementation of a Dao type, implements the Registery
+ * pattern. Multiple calls returns the same Dao instance.
+ * @param string Dao type to find.
+ * @return object instance of the Dao implementation.
+ */
+ public function getDao($class)
+ {
+ if(isset($this->_dao[$class]))
+ {
+ if(!isset($this->_dao[$class]['instance']))
+ {
+ $dao = Prado::createComponent($this->_dao[$class]['class']);
+ $dao->setConnection($this->getConnection());
+ $this->_dao[$class]['instance'] = $dao;
+ }
+ return $this->_dao[$class]['instance'];
+ }
+ else
+ throw TimeTrackerException('daomanager_undefined_dao', $class);
+ }
+
+ /**
+ * @return TSqlMapper sqlmap client instance
+ */
+ public function getConnection()
+ {
+ return $this->_connection;
+ }
+
+ /**
+ * Sets the connection for all Daos registered.
+ * @param string|TSqlMapper sqlmap client module id or TSqlMapper instance.
+ */
+ public function setConnection($client)
+ {
+ if($this->_initialized)
+ throw new TimeTrackerException('daomanager_unchangeable');
+ if(!is_string($client) && !($client instanceof TSqlMapper))
+ throw new TConfigurationException('daomanager_invalid_connection',$client);
+ $this->_connection = $client;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/time-tracker/protected/APP_CODE/Project.php b/demos/time-tracker/protected/APP_CODE/Project.php
index ad9f7d19..660fad04 100644
--- a/demos/time-tracker/protected/APP_CODE/Project.php
+++ b/demos/time-tracker/protected/APP_CODE/Project.php
@@ -1,5 +1,23 @@
<?php
+/**
+ * Project class file.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2005-2006 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Revision: $ $16/07/2006: $
+ * @package Demos
+ */
+/**
+ * Time Tracker Project class.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ $16/07/2006: $
+ * @package Demos
+ * @since 3.1
+ */
class Project
{
public $ActualDuration = 0;
diff --git a/demos/time-tracker/protected/APP_CODE/ProjectDao.php b/demos/time-tracker/protected/APP_CODE/ProjectDao.php
index 25a2845d..81902e0c 100644
--- a/demos/time-tracker/protected/APP_CODE/ProjectDao.php
+++ b/demos/time-tracker/protected/APP_CODE/ProjectDao.php
@@ -1,10 +1,26 @@
<?php
+/**
+ * Project DAO class file.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2005-2006 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Revision: $ $16/07/2006: $
+ * @package Demos
+ */
-Prado::using('Application.APP_CODE.BaseDao');
-
+/**
+ * Project DAO class.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ $16/07/2006: $
+ * @package Demos
+ * @since 3.1
+ */
class ProjectDao extends BaseDao
{
- public function createNewProject($project)
+/* public function createNewProject($project)
{
$sqlmap = $this->getConnection();
$creator = $sqlmap->queryForObject('GetUserByName', $project->CreatorUserName);
@@ -71,6 +87,7 @@ class ProjectDao extends BaseDao
return $sqlmap->insert('AddUserToProject', $param);
}
}
+*/
}
?> \ No newline at end of file
diff --git a/demos/time-tracker/protected/APP_CODE/TimeTrackerException.php b/demos/time-tracker/protected/APP_CODE/TimeTrackerException.php
index d715eefa..64b11405 100644
--- a/demos/time-tracker/protected/APP_CODE/TimeTrackerException.php
+++ b/demos/time-tracker/protected/APP_CODE/TimeTrackerException.php
@@ -1,5 +1,24 @@
<?php
+/**
+ * TimeTrackerException class file.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2005-2006 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Revision: $ $16/07/2006: $
+ * @package Demos
+ */
+/**
+ * Generic time tracker application exception. Exception messages are saved in
+ * "exceptions.txt"
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ $16/07/2006: $
+ * @package Demos
+ * @since 3.1
+ */
class TimeTrackerException extends TException
{
/**
diff --git a/demos/time-tracker/protected/APP_CODE/TimeTrackerUser.php b/demos/time-tracker/protected/APP_CODE/TimeTrackerUser.php
index 4b6987bd..99ac1209 100644
--- a/demos/time-tracker/protected/APP_CODE/TimeTrackerUser.php
+++ b/demos/time-tracker/protected/APP_CODE/TimeTrackerUser.php
@@ -1,31 +1,48 @@
<?php
+/**
+ * TimeTrackerUser class file.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2005-2006 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Revision: $ $16/07/2006: $
+ * @package Demos
+ */
+/**
+ * Import TUser and TUserManager
+ */
Prado::using('System.Security.TUser');
Prado::using('System.Security.TUserManager');
+/**
+ * User class for Time Tracker application.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ $16/07/2006: $
+ * @package Demos
+ * @since 3.1
+ */
class TimeTrackerUser extends TUser
{
- private $_ID;
+ private $_emailAddress;
- public function __construct()
+ /**
+ * @param string user email address
+ */
+ public function setEmailAddress($value)
{
- parent::__construct(new TUserManager());
+ $this->_emailAddress = $value;
}
-
- public function getID(){ return $this->_ID; }
- public function setID($value)
- {
- if(is_null($this->_ID))
- $this->_ID = $value;
- else
- throw new TimeTrackerUserException(
- 'timetracker_user_readonly_id');
- }
-}
-
-class TimeTrackerUserException extends TimeTrackerException
-{
+ /**
+ * @return string user email address
+ */
+ public function getEmailAddress()
+ {
+ return $this->_emailAddress;
+ }
}
?> \ No newline at end of file
diff --git a/demos/time-tracker/protected/APP_CODE/TimeTrackerUserTypeHandler.php b/demos/time-tracker/protected/APP_CODE/TimeTrackerUserTypeHandler.php
new file mode 100644
index 00000000..07c46acc
--- /dev/null
+++ b/demos/time-tracker/protected/APP_CODE/TimeTrackerUserTypeHandler.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * TimeTrackerUserTypeHandler class file.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2005-2006 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Revision: $ $16/07/2006: $
+ * @package Demos
+ */
+
+/**
+ * SQLMap type handler for TimeTrackerUser.
+ * The TimeTrackerUser requires an instance of IUserManager in constructor.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ $16/07/2006: $
+ * @package Demos
+ * @since 3.1
+ */
+class TimeTrackerUserTypeHandler implements ITypeHandlerCallback
+{
+ /**
+ * Not implemented.
+ */
+ public function getParameter($object)
+ {
+ throw new TimeTrackerException('Not implemented');
+ }
+
+ /**
+ * Not implemented.
+ */
+ public function getResult($string)
+ {
+ throw new TimeTrackerException('Not implemented');
+ }
+
+ /**
+ * Creates a new instance of TimeTrackerUser
+ * @param array result data
+ * @return TimeTrackerUser new user instance
+ */
+ public function createNewInstance($row=null)
+ {
+ $manager = Prado::getApplication()->getModule('users');
+ if(is_null($manager))
+ $manager = new UserManager();
+ return new TimeTrackerUser($manager);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/time-tracker/protected/APP_CODE/UserDao.php b/demos/time-tracker/protected/APP_CODE/UserDao.php
new file mode 100644
index 00000000..4dc39b2b
--- /dev/null
+++ b/demos/time-tracker/protected/APP_CODE/UserDao.php
@@ -0,0 +1,155 @@
+<?php
+/**
+ * User Dao class file.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2005-2006 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Revision: $ $16/07/2006: $
+ * @package Demos
+ */
+
+/**
+ * UserDao class list, create, find and delete users.
+ * In addition, it can validate username and password, and update
+ * the user roles. Furthermore, a unique new token can be generated,
+ * this token can be used to perform persistent cookie login.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ $16/07/2006: $
+ * @package Demos
+ * @since 3.1
+ */
+class UserDao extends BaseDao
+{
+ /**
+ * @param string username
+ * @return TimeTrackerUser find by user name, null if not found or disabled.
+ */
+ public function getUserByName($username)
+ {
+ $sqlmap = $this->getConnection();
+ return $sqlmap->queryForObject('GetUserByName', $username);
+ }
+
+ /**
+ * @return array list of all enabled users.
+ */
+ public function getAllUsers()
+ {
+ $sqlmap = $this->getConnection();
+ return $sqlmap->queryForList('GetAllUsers');
+ }
+
+ /**
+ * @param TimeTrackerUser new user details.
+ * @param string new user password.
+ */
+ public function addNewUser($user, $password)
+ {
+ $sqlmap = $this->getConnection();
+ $param['user'] = $user;
+ $param['password'] = md5($password);
+ $sqlmap->insert('AddNewUser', $param);
+ if(count($user->getRoles()) > 0)
+ $this->updateUserRoles($user);
+ }
+
+ /**
+ * @param string username to delete
+ */
+ public function deleteUserByName($username)
+ {
+ $sqlmap = $this->getConnection();
+ $sqlmap->delete('DeleteUserByName', $username);
+ }
+
+ /**
+ * Updates the user profile details, including user roles.
+ * @param TimeTrackerUser updated user details.
+ * @param string new user password, null to avoid updating password.
+ */
+ public function updateUser($user,$password=null)
+ {
+ $sqlmap = $this->getConnection();
+ if($password !== null)
+ {
+ $param['user'] = $user;
+ $param['password'] = md5($password);
+ $sqlmap->update('UpdateUserDetailsAndPassword', $param);
+ }
+ else
+ {
+ $sqlmap->update('UpdateUserDetails', $user);
+ }
+ $this->updateUserRoles($user);
+ }
+
+ /**
+ * @param string username to be validated
+ * @param string matching password
+ * @return boolean true if the username and password matches.
+ */
+ public function validateUser($username, $password)
+ {
+ $sqlmap = $this->getConnection();
+ $param['username'] = $username;
+ $param['password'] = md5($password);
+ return $sqlmap->queryForObject('ValidateUser', $param);
+ }
+
+ /**
+ * @param string unique persistent session token
+ * @return TimeTrackerUser user details if valid token, null otherwise.
+ */
+ public function validateSignon($token)
+ {
+ $sqlmap = $this->getConnection();
+ $sqlmap->update('UpdateSignon', $token);
+ return $sqlmap->queryForObject('ValidateAutoSignon', $token);
+ }
+
+ /**
+ * @param TimeTrackerUser user details to generate the token
+ * @return string unique persistent login token.
+ */
+ public function createSignonToken($user)
+ {
+ $sqlmap = $this->getConnection();
+ $param['username'] = $user->getName();
+ $param['token'] = md5(microtime().$param['username']);
+ $sqlmap->insert('RegisterAutoSignon', $param);
+ return $param['token'];
+ }
+
+ /**
+ * @param TimeTrackerUser deletes all signon token for given user, null to delete all
+ * tokens.
+ */
+ public function clearSignonTokens($user=null)
+ {
+ $sqlmap = $this->getConnection();
+ if($user !== null)
+ $sqlmap->delete('DeleteAutoSignon', $user->getName());
+ else
+ $sqlmap->delete('DeleteAllSignon');
+ }
+
+ /**
+ * @param TimeTrackerUser user details for updating the assigned roles.
+ */
+ public function updateUserRoles($user)
+ {
+ $sqlmap = $this->getConnection();
+ $sqlmap->delete('DeleteUserRoles', $user);
+ foreach($user->getRoles() as $role)
+ {
+ $param['username'] = $user->getName();
+ $param['role'] = $role;
+ $sqlmap->update('AddUserRole', $param);
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/time-tracker/protected/APP_CODE/UserManager.php b/demos/time-tracker/protected/APP_CODE/UserManager.php
new file mode 100644
index 00000000..1327dc3c
--- /dev/null
+++ b/demos/time-tracker/protected/APP_CODE/UserManager.php
@@ -0,0 +1,68 @@
+<?php
+/**
+ * UserManager class file.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2005-2006 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Revision: $ $16/07/2006: $
+ * @package Demos
+ */
+
+/**
+ * User manager module class for time tracker application.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ $16/07/2006: $
+ * @package Demos
+ * @since 3.1
+ */
+class UserManager extends TModule implements IUserManager
+{
+ /**
+ * @return string name for a guest user.
+ */
+ public function getGuestName()
+ {
+ return 'Guest';
+ }
+
+ /**
+ * Returns a user instance given the user name.
+ * @param string user name, null if it is a guest.
+ * @return TUser the user instance, null if the specified username is not in the user database.
+ */
+ public function getUser($username=null)
+ {
+ if($username===null)
+ {
+ $user=new TUser($this);
+ $user->setIsGuest(true);
+ return $user;
+ }
+ else
+ {
+ $daos = $this->getApplication()->getModule('daos');
+ $userDao = $daos->getDao('UserDao');
+ $user = $userDao->getUserByName($username);
+ $user->setIsGuest(false);
+ return $user;
+ }
+ }
+
+ /**
+ * Validates if the username and password are correct.
+ * @param string user name
+ * @param string password
+ * @return boolean true if validation is successful, false otherwise.
+ */
+ public function validateUser($username,$password)
+ {
+ $daos = $this->getApplication()->getModule('daos');
+ $userDao = $daos->getDao('UserDao');
+ return $userDao->validateUser($username, $password);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/time-tracker/protected/APP_CODE/exceptions.txt b/demos/time-tracker/protected/APP_CODE/exceptions.txt
index e948f4d0..6568cc72 100644
--- a/demos/time-tracker/protected/APP_CODE/exceptions.txt
+++ b/demos/time-tracker/protected/APP_CODE/exceptions.txt
@@ -1,3 +1,7 @@
timetracker_user_readonly_id = Time tracker user ID is read-only.
invalid_creator_and_manager = Unable to find time tracker usernames '{1}' and '{2}' for project '{0}'.
-project_exists = Project '{0}' already exists. \ No newline at end of file
+project_exists = Project '{0}' already exists.
+daomanager_connection_required = An TSqlMapper connection is required by Dao Manager.
+daomanager_undefined_connection = Connection '{0}' for Dao Manager is undefined.
+daomanager_invalid_connection = Connection '{0}' does not appear to ba a TSqlMapper in Dao Manager.
+daomanager_undefined_dao = Dao class '{0}' is not registered. \ No newline at end of file
diff --git a/demos/time-tracker/protected/data/time_tracker.db b/demos/time-tracker/protected/App_Data/time_tracker.db
index 03fe9156..03fe9156 100644
--- a/demos/time-tracker/protected/data/time_tracker.db
+++ b/demos/time-tracker/protected/App_Data/time_tracker.db
Binary files differ
diff --git a/demos/time-tracker/protected/pages/Docs/TopicList.tpl b/demos/time-tracker/protected/pages/Docs/TopicList.tpl
index 5fa2adb5..53243578 100644
--- a/demos/time-tracker/protected/pages/Docs/TopicList.tpl
+++ b/demos/time-tracker/protected/pages/Docs/TopicList.tpl
@@ -14,7 +14,7 @@
<div>Testing Business Code</div>
<ul>
- <li><a href="?page=Docs.CreateBusinessCode">Create Business Code</a></li>
+ <li><a href="?page=Docs.DatabaseDesign">Database Design</a></li>
<li><a href="?page=Docs.UsingSQLMap">Using SQLMap Data Mapper</a></li>
<li><a href="?page=Docs.UserClassAndExceptions">User Class and Exceptions</a></li>
<li><a href="?page=Docs.MoreTests">More Tests</a></li>
diff --git a/demos/time-tracker/protected/pages/Docs/WritingUnitTest.page b/demos/time-tracker/protected/pages/Docs/WritingUnitTest.page
index 32c7bc79..77bdcbe6 100644
--- a/demos/time-tracker/protected/pages/Docs/WritingUnitTest.page
+++ b/demos/time-tracker/protected/pages/Docs/WritingUnitTest.page
@@ -1,13 +1,19 @@
<com:TContent ID="body">
<h1>Writing a Unit Test</h1>
+<p>Before we begin to write our business logic and code, we shall
+proceed with the path of <a href="http://tdd.com">test driven development</a> (TDD), or at least take
+some part of that process.</p>
+
<p>Unit testing is a useful tool when we want to start to test
our individual business logic classes.
- The <tt>tests/unit</tt> directory will be used to hold the unit test cases and <tt>tests/functional</tt> directory
-to hold the function test cases.
+ The <tt>tests/unit</tt> directory will be used to hold the unit test
+ cases and <tt>tests/functional</tt> directory
+ to hold the function test cases.
</p>
<h2>Write a unit test case</h2>
-<p>We will start be writing a very simple unit test case.</p>
+<p>We will start be writing a very simple unit test case. Notice
+that we are writing the test case <b>first</b>.</p>
<com:TTextHighlighter Language="php" CssClass="source">
&lt;?php
class ProjectTestCase extends UnitTestCase
@@ -29,14 +35,16 @@ directory.</p>
<img src="<%~ unit_test1.png %>" class="figure"/>
<div class="caption"><b>Figure 1:</b> Unit test runner</div>
</p>
-<p>Clicking on the <tt>ProjectTestCase.php</tt> like, you should see
+<p>Clicking on the <tt>ProjectTestCase.php</tt> link, you should see
<img src="<%~ unit_test2.png %>" class="figure"/>
<div class="caption"><b>Figure 2:</b> Unit test failure</div>
</p>
<h2>Smallest step to make the test pass.</h2>
-<p>Obviously, we need create the class <tt>Project</tt>, so lets define the class.</p>
+<p>Since we only wrote the test case and nothing else we expected
+that the test case will fail at some point. Obviously, we need create
+a class <tt>Project</tt>, so lets define the <tt>Project</tt> class.</p>
<com:TTextHighlighter Language="php" CssClass="source">
&lt;?php
class Project
@@ -44,15 +52,11 @@ class Project
}
?&gt;
</com:TTextHighlighter>
-<p>Save the above code as <tt>time-tracker/protected/pages/APP_CODE/Project.php</tt>.
- Where the <tt>APP_CODE</tt> directory will contain most of your business logic code for the Time Tracker application.</p>
-<p>We also need to add the following line in our test case so as to include the <tt>Project</tt> class file when running the tests.</p>
-
-<p class="note">
-The statement <tt>Prado::using('Application.APP_CODE.Project')</tt> basically
-loads the <tt>Project.php</tt> class file. It assumes that a class name <tt>Project</tt> has filename <tt>Project.php</tt>.
-For futher details regarding <tt>Prado::using</tt> can be found in <a href="http://www.pradosoft.com/demos/quickstart/index.php?page=Fundamentals.Components#704">Prado Namespaces</a> documentation.
-</p>
+<p>We save the above code as <tt>time-tracker/protected/pages/APP_CODE/Project.php</tt>.
+ Where the <tt>APP_CODE</tt> directory will contain most of the business logic code
+ for the Time Tracker application.</p>
+<p>Now, we also need to add the following line in our test case so as to
+include the <tt>Project</tt> class file when running the tests.</p>
<com:TTextHighlighter Language="php" CssClass="source">
&lt;?php
@@ -63,11 +67,20 @@ class ProjectTestCase extends UnitTestCase
}
?&gt;
</com:TTextHighlighter>
+
+<div class="info"><b>Info:</b>
+The statement <tt>Prado::using('Application.APP_CODE.Project')</tt> basically
+loads the <tt>Project.php</tt> class file. It assumes that a class name <tt>Project</tt> has filename <tt>Project.php</tt>.
+For futher details regarding <tt>Prado::using</tt> can be found in <a href="http://www.pradosoft.com/demos/quickstart/index.php?page=Fundamentals.Components#704">Prado Namespaces</a> documentation.
+</div>
+
<p>Run the unit test runner again, we see that the test has passed.
<img src="<%~ unit_test3.png %>" class="figure"/>
<div class="caption"><b>Figure 3:</b> Unit test success</div>
</p>
<p>
-Later on, we shall write more test cases. See the SimpleTest documentation for detailed tutorial on writing test cases.</p>
+Later on, we shall write more test cases. See the
+<a href="http://www.lastcraft.com/simple_test.php">SimpleTest documentation</a>
+for detailed tutorial on writing test cases.</p>
</com:TContent> \ No newline at end of file
diff --git a/demos/time-tracker/protected/pages/Docs/config.xml b/demos/time-tracker/protected/pages/Docs/config.xml
index da4d3bfc..e8fdc3fe 100644
--- a/demos/time-tracker/protected/pages/Docs/config.xml
+++ b/demos/time-tracker/protected/pages/Docs/config.xml
@@ -1,8 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
- <paths>
- <alias id="Pages" path="." />
- </paths>
- <pages MasterClass="Application.pages.Docs.Layout" />
+
+ <modules>
+ <module id="theme"
+ class="System.Web.UI.TThemeManager"
+ BasePath="Quickstart.themes"
+ BaseUrl="../quickstart/themes" />
+ </modules>
+ <pages MasterClass="Application.pages.Docs.Layout" Theme="PradoSoft"/>
</configuration> \ No newline at end of file
diff --git a/demos/time-tracker/protected/pages/Docs/db.png b/demos/time-tracker/protected/pages/Docs/db.png
index f2209ef4..efdcc1e5 100644
--- a/demos/time-tracker/protected/pages/Docs/db.png
+++ b/demos/time-tracker/protected/pages/Docs/db.png
Binary files differ
diff --git a/demos/time-tracker/protected/pages/TimeTracker/Login.page b/demos/time-tracker/protected/pages/TimeTracker/Login.page
new file mode 100644
index 00000000..dbc16de1
--- /dev/null
+++ b/demos/time-tracker/protected/pages/TimeTracker/Login.page
@@ -0,0 +1,38 @@
+<%@ Title="My Company - Time Tracker - Site Logon" %>
+<com:TContent ID="Main">
+<h2>Time Tracker Website Login</h2>
+
+<fieldset class="login"><legend>User Login</legend>
+ <div class="username">
+ <com:TLabel ForControl="username" Text="User Name:" />
+ <span class="required">*</span>
+ <com:TTextBox ID="username" />
+ <com:TRequiredFieldValidator
+ ControlToValidate="username"
+ ErrorMessage="Please enter your username."
+ ControlCssClass="required-field" />
+ </div>
+ <div class="password">
+ <com:TLabel ForControl="password" Text="Password:" />
+ <span class="required">*</span>
+ <com:TTextBox ID="password" TextMode="Password" />
+ <com:TRequiredFieldValidator
+ ControlToValidate="password"
+ ErrorMessage="Please enter your password."
+ ControlCssClass="required-field" />
+ </div>
+ <div class="remember">
+ <com:TCheckBox ID="remember" Text="Remember me next time" />
+ </div>
+ <com:TCustomValidator
+ ControlToValidate="password"
+ Display="Dynamic"
+ Text="Your login attempt was not successful. Please try again."
+ OnServerValidate="validateUser" />
+ <div class="signin">
+ <com:TButton Text="Log In" OnClick="doLogin" />
+ </div>
+ <div class="create">
+ <a href="?page=TimeTracker.UserCreate">Create New User</a>
+ </fieldset>
+ </com:TContent> \ No newline at end of file
diff --git a/demos/time-tracker/protected/pages/TimeTracker/Login.php b/demos/time-tracker/protected/pages/TimeTracker/Login.php
new file mode 100644
index 00000000..376953a5
--- /dev/null
+++ b/demos/time-tracker/protected/pages/TimeTracker/Login.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ * Login Page class file.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2005-2006 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Revision: $ $16/07/2006: $
+ * @package Demos
+ */
+
+/**
+ * Login page class.
+ *
+ * Validate the user credentials and redirect to requested page
+ * if successful.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ $16/07/2006: $
+ * @package Demos
+ * @since 3.1
+ */
+class Login extends TPage
+{
+ /**
+ * Validates the username and password.
+ * @param TControl custom validator that created the event.
+ * @param TServerValidateEventParameter validation parameters.
+ */
+ public function validateUser($sender, $param)
+ {
+ $authManager=$this->Application->getModule('auth');
+ if(!$authManager->login($this->username->Text,$this->password->Text))
+ $param->IsValid=false;;
+ }
+
+ /**
+ * Redirect to the requested page if login is successful.
+ * @param TControl button control that created the event.
+ * @param TEventParameter event parameters.
+ */
+ public function doLogin($sender, $param)
+ {
+ if($this->Page->IsValid)
+ {
+ $auth = $this->Application->getModule('auth');
+ $this->Response->redirect($auth->getReturnUrl());
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/time-tracker/protected/pages/TimeTracker/Logout.page b/demos/time-tracker/protected/pages/TimeTracker/Logout.page
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/demos/time-tracker/protected/pages/TimeTracker/Logout.page
diff --git a/demos/time-tracker/protected/pages/TimeTracker/Logout.php b/demos/time-tracker/protected/pages/TimeTracker/Logout.php
new file mode 100644
index 00000000..08fdfaf6
--- /dev/null
+++ b/demos/time-tracker/protected/pages/TimeTracker/Logout.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Logout class file.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2005-2006 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Revision: $ $16/07/2006: $
+ * @package Demos
+ */
+
+/**
+ * Logout page class.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ $16/07/2006: $
+ * @package Demos
+ * @since 3.1
+ */
+class Logout extends TPage
+{
+ /**
+ * Logs out the current user and redirect to default page.
+ */
+ function onLoad($param)
+ {
+ $this->Application->getModule('auth')->logout();
+ $url = $this->Service->constructUrl($this->Service->DefaultPage);
+ $this->Response->redirect($url);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/time-tracker/protected/pages/TimeTracker/MainLayout.php b/demos/time-tracker/protected/pages/TimeTracker/MainLayout.php
new file mode 100644
index 00000000..253d6c03
--- /dev/null
+++ b/demos/time-tracker/protected/pages/TimeTracker/MainLayout.php
@@ -0,0 +1,7 @@
+<?php
+
+class MainLayout extends TTemplateControl
+{
+}
+
+?> \ No newline at end of file
diff --git a/demos/time-tracker/protected/pages/TimeTracker/MainLayout.tpl b/demos/time-tracker/protected/pages/TimeTracker/MainLayout.tpl
new file mode 100644
index 00000000..2d8bad44
--- /dev/null
+++ b/demos/time-tracker/protected/pages/TimeTracker/MainLayout.tpl
@@ -0,0 +1,40 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<com:THead Title="My Company - Time Tracker - Log" />
+<body>
+
+<com:TForm>
+
+<h1 class="heading">
+ <a href="index.php">My Company
+ <span class="subheading">Time Tracker</span>
+ </a>
+</h1>
+<div class="minheading">
+<h2 class="login">
+ <com:TLabel CssClass="name" Text="Welcome <%= $this->User->Name %>" />
+ <com:THyperLink
+ Text="Login"
+ NavigateUrl=<%= $this->Service->constructUrl('TimeTracker.Login') %>
+ Visible=<%= $this->User->getIsGuest() %> />
+ <com:THyperLink
+ Text="Logout"
+ NavigateUrl=<%= $this->Service->constructUrl('TimeTracker.Logout') %>
+ Visible=<%= !$this->User->getIsGuest() %> />
+</h2>
+<h2 class="help"><a href="?page=Welcome">Help</a></h2>
+<h2 class="guide"><a href="?page=Docs.Home">Implementation Guide</a></h2>
+</div>
+
+<com:Application.pages.TimeTracker.SiteMap
+ Visible=<%= !$this->User->getIsGuest() %> />
+
+<div class="main">
+<com:TContentPlaceHolder ID="Main" />
+</div>
+
+</com:TForm>
+
+</body>
+</html> \ No newline at end of file
diff --git a/demos/time-tracker/protected/pages/TimeTracker/SiteMap.php b/demos/time-tracker/protected/pages/TimeTracker/SiteMap.php
new file mode 100644
index 00000000..0b71eb68
--- /dev/null
+++ b/demos/time-tracker/protected/pages/TimeTracker/SiteMap.php
@@ -0,0 +1,8 @@
+<?php
+
+class SiteMap extends TTemplateControl
+{
+
+}
+
+?> \ No newline at end of file
diff --git a/demos/time-tracker/protected/pages/TimeTracker/SiteMap.tpl b/demos/time-tracker/protected/pages/TimeTracker/SiteMap.tpl
new file mode 100644
index 00000000..48187b52
--- /dev/null
+++ b/demos/time-tracker/protected/pages/TimeTracker/SiteMap.tpl
@@ -0,0 +1,43 @@
+<com:TPanel CssClass="sitemap" Visible="true">
+<ul class="level1">
+ <li class="active"><a class="menuitem" href="?page=TimeTracker.TimeEntry">Log</a>
+ </li>
+ <li><span class="menuitem">Reports</span>
+ <ul class="level2">
+ <li><a href="?page=TimeTracker.ReportProject">Project Reports</a></li>
+ <li><a href="?page=TimeTracker.ReportResource">Resources Report</a></li>
+ </ul>
+ </li>
+ <li>
+ <span class="menuitem">Projects</span>
+ <ul class="level2">
+ <li><a href="?page=TimeTracker.ProjectDetails">Create New Project</a></li>
+ <li><a href="?page=TimeTracker.ProjectList">List Projects</a></li>
+ </ul>
+ </li>
+ <li>
+ <span class="menuitem">Adminstration</span>
+ <ul class="level2">
+ <li><a href="?page=TimeTracker.UserCreate">Create New User</a></li>
+ <li><a href="?page=TimeTracker.UserList">List Users</a></li>
+ </ul>
+ </li>
+</ul>
+<com:TClientScript PradoScripts="prado">
+ Event.OnLoad(function()
+ {
+ menuitems = $$(".menuitem");
+ menuitems.each(function(el)
+ {
+ Event.observe(el, "mouseover", function(ev)
+ {
+ menuitems.each(function(item)
+ {
+ Element.removeClassName(item.parentNode, "active");
+ });
+ Element.addClassName(Event.element(ev).parentNode, "active");
+ });
+ });
+ });
+</com:TClientScript>
+</com:TPanel> \ No newline at end of file
diff --git a/demos/time-tracker/protected/pages/TimeTracker/UserCreate.page b/demos/time-tracker/protected/pages/TimeTracker/UserCreate.page
new file mode 100644
index 00000000..fda7ba9b
--- /dev/null
+++ b/demos/time-tracker/protected/pages/TimeTracker/UserCreate.page
@@ -0,0 +1,65 @@
+<com:TContent ID="Main">
+<h2>Create New User</h2>
+
+<fieldset class="signup"><legend>User Details</legend>
+ <h4>Sign Up for Your New Account</h4>
+ <div class="username">
+ <com:TLabel ForControl="username" Text="User Name:" />
+ <span class="required">*</span>
+ <com:TTextBox ID="username" />
+ <com:TRequiredFieldValidator
+ ControlToValidate="username"
+ Display="Dynamic"
+ ErrorMessage="Please enter your username."
+ ControlCssClass="required-field" />
+ <com:TCustomValidator
+ ControlToValidate="username"
+ Display="Dynamic"
+ OnServerValidate="checkUsername" />
+ </div>
+ <div class="password">
+ <com:TLabel ForControl="password" Text="Password:" />
+ <span class="required">*</span>
+ <com:TTextBox ID="password" TextMode="Password" />
+ <com:TRequiredFieldValidator
+ ControlToValidate="password"
+ Display="Dynamic"
+ ErrorMessage="Please enter your password (6 or more characters)."
+ ControlCssClass="required-field" />
+ <com:TRegularExpressionValidator
+ ControlToValidate="password"
+ Display="Dynamic"
+ RegularExpression="\w{6,}"
+ ErrorMessage="Please enter 6 or more characters."
+ ControlCssClass="required-field" />
+ </div>
+ <div class="password">
+ <com:TLabel ForControl="password2" Text="Confirm Password:" />
+ <span class="required">*</span>
+ <com:TTextBox ID="password2" TextMode="Password" />
+ <com:TCompareValidator
+ ControlToValidate="password"
+ ControlToCompare="password2"
+ ErrorMessage="The Password and Confirmation Password must match."
+ ControlCssClass="required-field" />
+ </div>
+ <div class="email">
+ <com:TLabel ForControl="email" Text="E-Mail Address:" />
+ <span class="required">*</span>
+ <com:TTextBox ID="email" Style="width:20em"/>
+ <com:TRequiredFieldValidator
+ ControlToValidate="email"
+ Display="Dynamic"
+ ErrorMessage="Please enter your E-Mail address."
+ ControlCssClass="required-field" />
+ <com:TEmailAddressValidator
+ ControlToValidate="email"
+ Display="Dynamic"
+ ErrorMessage="E-Mail address does not seem to be valid."
+ ControlCssClass="required-field" /> </div>
+ <div class="create">
+ <com:TButton Text="Create User" OnClick="createNewUser" />
+ </div>
+</fieldset>
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/time-tracker/protected/pages/TimeTracker/UserCreate.php b/demos/time-tracker/protected/pages/TimeTracker/UserCreate.php
new file mode 100644
index 00000000..b337bfca
--- /dev/null
+++ b/demos/time-tracker/protected/pages/TimeTracker/UserCreate.php
@@ -0,0 +1,78 @@
+<?php
+/**
+ * UserCreate page class file.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2005-2006 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Revision: $ $16/07/2006: $
+ * @package Demos
+ */
+
+/**
+ * Create new user page class. Validate that the usernames are unique
+ * and set the new user credentials as the current application credentials.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ $16/07/2006: $
+ * @package Demos
+ * @since 3.1
+ */
+class UserCreate extends TPage
+{
+ /**
+ * Verify that the username is not taken.
+ * @param TControl custom validator that created the event.
+ * @param TServerValidateEventParameter validation parameters.
+ */
+ public function checkUsername($sender, $param)
+ {
+ $userDao = $this->Application->Modules['daos']->getDao('UserDao');
+ $user = $userDao->getUserByName($this->username->Text);
+ if(!is_null($user))
+ {
+ $param->IsValid = false;
+ $sender->ErrorMessage =
+ "The user name is already taken, try '{$user->Name}01'";
+ }
+ }
+
+ /**
+ * Create a new user if all data entered are valid.
+ * The default user roles are obtained from "config.xml". The new user
+ * details is saved to the database and the new credentials are used as the
+ * application user. The user is redirected to the requested page.
+ * @param TControl button control that created the event.
+ * @param TEventParameter event parameters.
+ */
+ public function createNewUser($sender, $param)
+ {
+ if($this->IsValid)
+ {
+ $newUser = new TimeTrackerUser($this->User->Manager);
+ $newUser->EmailAddress = $this->email->Text;
+ $newUser->Name = $this->username->Text;
+ $newUser->IsGuest = false;
+ $newUser->Roles = $this->Application->Parameters['NewUserRoles'];
+
+ //save the user
+ $userDao = $this->Application->Modules['daos']->getDao('UserDao');
+ $userDao->addNewUser($newUser, $this->password->Text);
+
+ //update the user
+ $auth = $this->Application->getModule('auth');
+ $auth->updateSessionUser($newUser);
+ $this->Application->User = $newUser;
+
+ //return to requested page
+ $this->Response->redirect($auth->getReturnUrl());
+
+ //goto default page.
+ //$url = $this->Service->constructUrl($this->Service->DefaultPage);
+ //$this->Response->redirect($url);
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/time-tracker/protected/pages/TimeTracker/UserList.page b/demos/time-tracker/protected/pages/TimeTracker/UserList.page
new file mode 100644
index 00000000..48b2bbc7
--- /dev/null
+++ b/demos/time-tracker/protected/pages/TimeTracker/UserList.page
@@ -0,0 +1,3 @@
+<com:TContent ID="Main">
+<h1>List Users</h1>
+</com:TContent> \ No newline at end of file
diff --git a/demos/time-tracker/protected/pages/TimeTracker/config.xml b/demos/time-tracker/protected/pages/TimeTracker/config.xml
new file mode 100644
index 00000000..dac6465d
--- /dev/null
+++ b/demos/time-tracker/protected/pages/TimeTracker/config.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<configuration>
+
+ <modules>
+ <!-- user manager module -->
+ <module id="users" class="Application.App_Code.UserManager" />
+ <!-- auth manager module -->
+ <module id="auth" class="System.Security.TAuthManager"
+ UserManager="users" LoginPage="TimeTracker.Login" />
+ </modules>
+
+ <authorization>
+ <allow roles="admin" />
+ <allow pages="UserCreate,Logout,Login" users="*" />
+ <deny users="*" />
+ </authorization>
+
+ <pages MasterClass="Application.pages.TimeTracker.MainLayout" Theme="TimeTracker" />
+
+ <parameters>
+ <parameter id="NewUserRoles" value="admin,manager,consultant" />
+ </parameters>
+</configuration> \ No newline at end of file
diff --git a/demos/time-tracker/protected/pages/Welcome.page b/demos/time-tracker/protected/pages/Welcome.page
new file mode 100644
index 00000000..65ada02d
--- /dev/null
+++ b/demos/time-tracker/protected/pages/Welcome.page
@@ -0,0 +1,168 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" >
+<head>
+ <title>Welcome</title>
+ <style type="text/css">
+ table {
+ border-collapse:collapse;
+ }
+
+ td, th {
+ border:1px solid black;
+ padding: 10px;
+ }
+
+ th {
+ text-align:left;
+ }
+ </style>
+</head>
+<body>
+ <h1>Time Tracker Starter Kit</h1>
+ <p>
+ Welcome to your new <strong>Time Tracker</strong> sample application. The key features are:</p>
+ <ul>
+ <li><strong>Projects.</strong> Define project information like due dates, hours to complete,
+ project resources, and more.</li>
+ <li><strong>Track Time.</strong> Track time spent each day by category and project.</li>
+ <li><strong>Reports.</strong> Generate progress and team resource reports across multiple projects.</li>
+ </ul>
+ <p>
+ The <a href="index.php">Time Tracker site is ready to run!</a> No changes are needed.</p>
+
+ <hr />
+
+ <h2>Site Members and Roles</h2>
+ <p>
+ Your Time Tracker Web site allows visitors to register as members and then log in.
+ Members have specific privileges defined by roles such as administrator or guest.
+ Each Time Tracker Web site defines site-specific roles.
+ The following table describes what features are available to visitors in different roles.</p>
+ <table>
+ <tr>
+ <th>Visitor</th>
+ <th>Privileges</th>
+ <th>Default Login</th>
+ </tr>
+ <tr>
+ <td>
+ Not logged in</td>
+ <td>
+ No privileges.</td>
+ <td></td>
+ </tr>
+ <tr>
+ <td>Logged in</td>
+ <td>
+ No privileges. All members must be associated at least with the role <strong>Consultant</strong>.</td>
+ <td>N/A</td>
+ </tr>
+ <tr>
+ <td>Logged in as <br />
+ <strong>Consultant</strong></td>
+ <td>May log time entries only.</td>
+ <td>username: <tt>consultant</tt><br />
+ password: <tt>consultant</tt></td>
+ </tr>
+ <tr>
+ <td>
+ Logged in as<br />
+ <strong>Project Manager</strong></td>
+ <td>
+ May additionally edit all projects and view reports.</td>
+ <td>username: <tt>manager</tt><br />
+ password: <tt>manager</tt></td>
+ </tr>
+ <tr>
+ <td>
+ Logged in as<br />
+ <strong>Project Administrator</strong></td>
+ <td>
+ May additionally view the list of all users.</td>
+ <td>username: <tt>admin</tt><br />
+ password: <tt>admin</tt></td>
+ </tr>
+ </table>
+ <p>
+ Be sure to create a user name for yourself and assign yourself to a role (such as administrator) that can manage the site.</p>
+ <p>
+ Visitors can register by clicking the <strong>Create new </strong>user link on the home page.
+ New members are activated automatically, and are assigned to a role as specified in the <tt>application.xml</tt> file.
+ You can manage users (for example, assign them to a role) when login as administrator.
+ For details, see <a href="#AppendixA">Appendix A</a>.</p>
+
+ <hr />
+
+ <h2>Projects and Time Entries</h2>
+ <h3>To add a project and categories</h3>
+ <ol>
+ <li>Log in to the site as a member in the role <strong>Project Manager</strong> or <strong>Project Administrator</strong>.</li>
+ <li>Click the <strong>Projects</strong> tab and then click <strong>Create New Project</strong>.</li>
+ <li>Specify a project name, project manager, estimated complete date, estimated duration, and description.</li>
+ <li>Under <strong>Specify Project Members</strong>, select a resource. You must select at least one resource. </li>
+ <li>Click <strong>Save</strong>. A category pane is displayed on the right.</li>
+ <li>Specify a category name, category abbreviation, and duration.</li>
+ <li>Click <strong>Add</strong>. The new category is displayed in the categories list. </li>
+ <li>Repeat steps 6 and 7 to create additional categories.</li>
+ </ol>
+ <h3>Log a Time Entry</h3>
+ <ol>
+ <li>Log in to the site as a consultant (member in the role <strong>Consultant</strong>).</li>
+ <li>Click the <strong>Log</strong> tab.</li>
+ <li>Under <strong>Log your hours</strong>, choose a project and a category and fill in the day, hours, and description.</li>
+ <li>Make sure the correct consultant is selected in the <strong>Time Sheet For</strong> list. </li>
+ <li>Click <strong>Add Entry</strong>.</li>
+ </ol>
+
+ <hr />
+
+ <h2>Reports</h2>
+ <h3>To create a project report</h3>
+ <ol>
+ <li>Log in to the site as a member in the role <strong>Project Manager</strong> or <strong>Project Administrator</strong>.</li>
+ <li>Click the <strong>Reports</strong> tab and then click <strong>Project Reports</strong>. </li>
+ <li>Under <strong>Select a project</strong>,<strong> </strong>choose one or more projects.</li>
+ <li>Click <strong>Generate Report</strong>.</li>
+ </ol>
+ <h3>To create a resource report</h3>
+ <ol>
+ <li>Log in to the site as a member in the role <strong>Project Manager</strong> or <strong>Project Administrator</strong>.</li>
+ <li>Click the <strong>Reports</strong> tab and then click <strong>Resources Report</strong>. </li>
+ <li>Select one or more projects, select one or more resources, and then specify a date range. </li>
+ <li>Click <strong>Generate Report</strong>.</li>
+ </ol>
+
+ <hr />
+
+ <a name="AppendixA" />
+ <h2>Appendix A - Manually Managing Members and Roles</h2>
+ <p>
+ Your Time Tracker Web site allows visitors to register as members.
+ Members have specific privileges defined by a role you assign to them.
+ A special administrative role has rights to perform all functions in the site.</p>
+ <p>
+ To create a user (member):</p>
+ <ol>
+ <li>Login as administrator, click <strong>Create New User</strong>.
+ </li>
+ <li>...</li>
+ </ol>
+ <p>
+ To modify an existing member's role:</p>
+ <ol>
+ <li>Login as administrator, click <strong>List Users</strong>.
+ </li>
+ <li>...</li>
+ </ol>
+
+ <hr />
+
+ <h2>Appendix B - Publishing Your Site</h2>
+ <p>
+ When you are ready to share the Web site with others, you can copy it to your Web server.
+ You need to know the File Transfer Protocol (FTP) address of your server, and if required, the user name and password assigned to you.</p>
+ <ol>
+ <li>...</li>
+ </ol>
+</body>
+</html>
diff --git a/demos/time-tracker/tests/functional.php b/demos/time-tracker/tests/functional.php
index c216ada8..888b29db 100644
--- a/demos/time-tracker/tests/functional.php
+++ b/demos/time-tracker/tests/functional.php
@@ -1,6 +1,6 @@
<?php
-include_once '../../prado-trunk\tests\test_tools\functional_tests.php';
+include_once '../../../tests/test_tools/functional_tests.php';
$test_cases = dirname(__FILE__)."/functional";
diff --git a/demos/time-tracker/tests/unit.php b/demos/time-tracker/tests/unit.php
index a920b205..20d56432 100644
--- a/demos/time-tracker/tests/unit.php
+++ b/demos/time-tracker/tests/unit.php
@@ -1,6 +1,6 @@
<?php
-include_once '../../prado-trunk/tests/test_tools/unit_tests.php';
+include_once '../../../tests/test_tools/unit_tests.php';
$app_directory = "../protected";
$test_cases = dirname(__FILE__)."/unit";
diff --git a/demos/time-tracker/tests/unit/AddUserToProjectTestCase.php b/demos/time-tracker/tests/unit/AddUserToProjectTestCase.php
deleted file mode 100644
index 36defc57..00000000
--- a/demos/time-tracker/tests/unit/AddUserToProjectTestCase.php
+++ /dev/null
@@ -1,35 +0,0 @@
-<?php
-
-require_once(dirname(__FILE__).'/ProjectDaoTestCase.php');
-
-class AddUserToProjectTestCase extends ProjectDaoTestCase
-{
- function testCanAddNewUserToProject()
- {
- $project = $this->createNewTestProject();
-
- $user = new TimeTrackerUser();
- $user->ID = 3;
- $user->Name = "test user 1";
-
- if(($conn = $this->connection) instanceof MockTSqlMapper)
- {
- $this->setupMockConnectionFor($project);
- $conn->setReturnReference('queryForObject', $user, array('GetUserByName', $user->Name));
- $conn->setReturnValue('queryForList', array(), array('GetProjectMembers', $project));
-
- $param['project'] = $project;
- $param['user'] = $user;
-
- $conn->setReturnValue('insert', true, array('AddNewUserToProject', $param));
-
- $conn->expectAtLeastOnce('insert');
- $conn->expectAtLeastOnce('queryForList');
- }
-
- $this->assertTrue($this->dao->createNewProject($project));
- $this->assertTrue($this->dao->addUserToProject($project, $user));
- }
-}
-
-?> \ No newline at end of file
diff --git a/demos/time-tracker/tests/unit/BaseTestCase.php b/demos/time-tracker/tests/unit/BaseTestCase.php
new file mode 100644
index 00000000..8ce3cca8
--- /dev/null
+++ b/demos/time-tracker/tests/unit/BaseTestCase.php
@@ -0,0 +1,35 @@
+<?php
+
+class BaseTestCase extends UnitTestCase
+{
+ protected $sqlmap;
+
+ function setup()
+ {
+ $app = Prado::getApplication();
+ $this->sqlmap = $app->getModule('daos')->getConnection();
+ }
+
+ function flushDatabase()
+ {
+ $conn = $this->sqlmap->openConnection();
+ $file = Prado::getPathOfNamespace('Application.App_Data.mysql-reset','.sql');
+ if(is_file($file))
+ $this->runScript($conn, $file);
+ else
+ throw new Exception('unable to find script file '.$file);
+ }
+
+ protected function runScript($connection, $script)
+ {
+ $sql = file_get_contents($script);
+ $lines = explode(';', $sql);
+ foreach($lines as $line)
+ {
+ $line = trim($line);
+ if(strlen($line) > 0)
+ $connection->execute($line);
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/demos/time-tracker/tests/unit/CreateNewProjectTestCase.php b/demos/time-tracker/tests/unit/CreateNewProjectTestCase.php
deleted file mode 100644
index 0aa67405..00000000
--- a/demos/time-tracker/tests/unit/CreateNewProjectTestCase.php
+++ /dev/null
@@ -1,90 +0,0 @@
-<?php
-
-require_once(dirname(__FILE__).'/ProjectDaoTestCase.php');
-
-class CreateNewProjectTestCase extends ProjectDaoTestCase
-{
- function testProjectDaoCanCreateNewProject()
- {
- $project = $this->createNewTestProject();
-
- if(($conn = $this->connection) instanceof MockTSqlMapper)
- {
- $this->setupMockConnectionFor($project);
- $conn->expectMinimumCallCount('queryForObject', 3);
- $conn->expectAtLeastOnce('insert');
- }
-
- $this->assertProjectCreated($project);
- }
-
- function testProjectExistsException()
- {
- $project = $this->createNewTestProject();
-
- if(($conn = $this->connection) instanceof MockTSqlMapper)
- {
- //make the project exist
- $conn->setReturnValue('queryForObject',
- $project, array('GetProjectByName', 'Project 1'));
- $this->setupMockConnectionFor($project);
- }
-
- try
- {
- $this->assertProjectCreated($project);
- $this->fail();
- }
- catch(TimeTrackerException $e)
- {
- $this->pass();
- }
- }
- function testProjectCustomerNotExistsException()
- {
- $project = $this->createNewTestProject();
-
- if(($conn = $this->connection) instanceof MockTSqlMapper)
- {
- //customer does not exist
- $conn->setReturnValue('queryForObject',
- null, array('GetUserByName', 'Customer A'));
- $this->setupMockConnectionFor($project);
- }
-
- try
- {
- $this->assertProjectCreated($project);
- $this->fail();
- }
- catch(TimeTrackerException $e)
- {
- $this->pass();
- }
- }
-
- function testProjectManagerNotExistsException()
- {
- $project = $this->createNewTestProject();
-
- if(($conn = $this->connection) instanceof MockTSqlMapper)
- {
- //manager does not exist
- $conn->setReturnValue('queryForObject',
- null, array('GetUserByName', 'Manager A'));
- $this->setupMockConnectionFor($project);
- }
-
- try
- {
- $this->assertProjectCreated($project);
- $this->fail();
- }
- catch(TimeTrackerException $e)
- {
- $this->pass();
- }
- }
-}
-
-?> \ No newline at end of file
diff --git a/demos/time-tracker/tests/unit/ProjectDaoTestCase.php b/demos/time-tracker/tests/unit/ProjectDaoTestCase.php
deleted file mode 100644
index bc576630..00000000
--- a/demos/time-tracker/tests/unit/ProjectDaoTestCase.php
+++ /dev/null
@@ -1,92 +0,0 @@
-<?php
-
-//formerly PradoDaoTestCase.php
-
-Prado::using('Application.APP_CODE.*');
-Prado::using('System.DataAccess.SQLMap.TSqlMapper');
-
-Mock::generate('TSqlMapper');
-
-class ProjectDaoTestCase extends UnitTestCase
-{
- protected $dao;
- protected $connection;
-
- function setup()
- {
- $this->dao= new ProjectDao();
- $this->connection = new MockTSqlMapper($this);
- $this->dao->setConnection($this->connection);
- }
-
-/*
- //Simple test case, will not detect project existanc
- //This case will clash with the more complete test case below.
- function testProjectDaoCanCreateNewProject()
- {
- $project = new Project();
- $project->Name = "Project 1";
-
- if(($conn = $this->connection) instanceof MockTSqlMapper)
- {
- $conn->expectOnce('insert', array('CreateNewProject', $project));
- $conn->setReturnValue('insert', true);
-
- $conn->expectOnce('queryForObject', array('GetProjectByID', 1));
- $conn->setReturnReference('queryForObject', $project);
- }
-
- $this->assertTrue($this->dao->createNewProject($project));
- $this->assertEqual($this->dao->getProjectByID(1), $project);
- }
-*/
- function setupMockConnectionFor($project)
- {
- $customer = new TimeTrackerUser();
- $customer->ID = 1;
- $customer->Name = "Customer A";
-
- $manager = new TimeTrackerUser();
- $manager->ID = 2;
- $manager->Name = "Manager A";
-
- $conn = $this->connection;
-
- //return the customer and manager
- $conn->setReturnValue('queryForObject',
- $customer, array('GetUserByName', 'Customer A'));
- $conn->setReturnValue('queryForObject',
- $manager, array('GetUserByName', 'Manager A'));
-
- //project does not exist
- $conn->setReturnValue('queryForObject',
- null, array('GetProjectByName', 'Project 1'));
-
- $param['project'] = $project;
- $param['creator'] = $customer->ID;
- $param['manager'] = $manager->ID;
-
- $conn->setReturnValue('insert',
- true, array('CreateNewProject', $param));
- $conn->setReturnReference('queryForObject',
- $project, array('GetProjectByID', 1));
- }
-
- function createNewTestProject()
- {
- $project = new Project();
- $project->Name = "Project 1";
- $project->CreatorUserName = "Customer A";
- $project->ManagerUserName = "Manager A";
-
- return $project;
- }
-
- function assertProjectCreated($project)
- {
- $this->assertTrue($this->dao->createNewProject($project));
- $this->assertEqual($this->dao->getProjectByID(1), $project);
- }
-
-}
-?> \ No newline at end of file
diff --git a/demos/time-tracker/tests/unit/ProjectTestCase.php b/demos/time-tracker/tests/unit/ProjectTestCase.php
index a61c4f77..59f612bd 100644
--- a/demos/time-tracker/tests/unit/ProjectTestCase.php
+++ b/demos/time-tracker/tests/unit/ProjectTestCase.php
@@ -3,13 +3,17 @@
//import Project class.
Prado::using('Application.APP_CODE.Project');
-class ProjectTestCase extends UnitTestCase
+require_once(dirname(__FILE__).'/BaseTestCase.php');
+
+class ProjectTestCase extends BaseTestCase
{
function testProjectClassExists()
{
$project = new Project();
$this->pass();
}
+
+
}
?> \ No newline at end of file
diff --git a/demos/time-tracker/tests/unit/UserDaoTestCase.php b/demos/time-tracker/tests/unit/UserDaoTestCase.php
new file mode 100644
index 00000000..d1f3c728
--- /dev/null
+++ b/demos/time-tracker/tests/unit/UserDaoTestCase.php
@@ -0,0 +1,283 @@
+<?php
+
+require_once(dirname(__FILE__).'/BaseTestCase.php');
+
+class UserDaoTestCase extends BaseTestCase
+{
+ protected $userDao;
+
+ function setup()
+ {
+ parent::setup();
+ $app = Prado::getApplication();
+ $this->userDao = $app->getModule('daos')->getDao('UserDao');
+ }
+
+ function assertIsAdmin($user)
+ {
+ if(!$user)
+ return $this->fail();
+ $this->assertEqual($user->getName(), 'admin');
+ $this->assertEqual($user->getEmailAddress(), 'admin@pradosoft.com');
+ }
+
+ function assertSameUser($user1, $user2)
+ {
+ if(is_null($user1) || is_null($user2))
+ return $this->fail();
+
+ $this->assertEqual($user1->getName(), $user2->getName());
+ $this->assertEqual($user1->getEmailAddress(), $user2->getEmailAddress());
+ }
+
+ function assertIsAdminRole($user)
+ {
+ if(is_null($user))
+ return $this->fail();
+
+ $this->assertTrue($user->isInRole('admin'));
+ }
+
+ function assertIsManagerRole($user)
+ {
+ if(is_null($user))
+ return $this->fail();
+
+ $this->assertTrue($user->isInRole('manager'));
+ }
+
+ function assertIsConsultantRole($user)
+ {
+ if(is_null($user))
+ return $this->fail();
+
+ $this->assertTrue($user->isInRole('consultant'));
+ }
+
+ function assertNotConsultantRole($user)
+ {
+ if(is_null($user))
+ return $this->fail();
+
+ $this->assertFalse($user->isInRole('consultant'));
+ }
+
+ function testGetUserByName()
+ {
+ $user = $this->userDao->getUserByName('admin');
+ $this->assertNotNull($user);
+ $this->assertIsAdmin($user);
+ }
+
+ function testGetNonExistentUser()
+ {
+ $user = $this->userDao->getUserByName('none');
+ $this->assertNull($user);
+ }
+
+ function testGetUsers()
+ {
+ $users = $this->userDao->getAllUsers();
+ $this->assertEqual(count($users), 3);
+ }
+
+ function testUserLogon()
+ {
+ $success = $this->userDao->validateUser('admin', 'admin');
+ $this->assertTrue($success);
+ }
+
+ function testBadLogin()
+ {
+ $success = $this->userDao->validateUser('admin', 'hahah');
+ $this->assertFalse($success);
+ }
+
+
+ function testAddNewUser()
+ {
+ $user = new TimeTrackerUser(new UserManager());
+ $user->Name = "user1";
+ $user->EmailAddress = 'user1@pradosoft.com';
+
+ $this->userDao->addNewUser($user, 'password');
+
+ $check = $this->userDao->getUserByName($user->Name);
+
+ $this->assertSameUser($check, $user);
+ $this->flushDatabase();
+ }
+
+ function testDeleteUserByName()
+ {
+ $this->userDao->deleteUserByName('admin');
+
+ $admin = $this->userDao->getUserByName('admin');
+ $this->assertNull($admin);
+
+ $users = $this->userDao->getAllUsers();
+ $this->assertEqual(count($users), 2);
+
+ $this->flushDatabase();
+ }
+
+ function testAutoSignon()
+ {
+ $user = new TimeTrackerUser(new UserManager());
+ $user->Name = "admin";
+
+ $token = $this->userDao->createSignonToken($user);
+
+ $check = $this->userDao->validateSignon($token);
+
+ $this->assertIsAdmin($check);
+
+ $this->flushDatabase();
+ }
+
+
+ function testBadAutoSignon()
+ {
+ $user = new TimeTrackerUser(new UserManager());
+ $user->Name = "admin";
+
+ $token = $this->userDao->createSignonToken($user);
+
+ $check = $this->userDao->validateSignon('adasd');
+ $this->assertNull($check);
+
+ $this->flushDatabase();
+ }
+
+ function testAdminRoles()
+ {
+ $user = $this->userDao->getUserByName('admin');
+ $this->assertIsAdminRole($user);
+ $this->assertIsManagerRole($user);
+ $this->assertIsConsultantRole($user);
+ }
+
+ function testSetUserRoles()
+ {
+ $user = new TimeTrackerUser(new UserManager());
+ $user->Name = "user1";
+ $user->EmailAddress = 'user1@pradosoft.com';
+ $user->Roles = array("manager", "consultant");
+
+ $this->userDao->addNewUser($user, 'password');
+ $check = $this->userDao->getUserByName('user1');
+
+ $this->assertIsManagerRole($check);
+ $this->assertIsConsultantRole($check);
+
+ $this->flushDatabase();
+ }
+
+ function testSetUserRoleNoNullUser()
+ {
+ $user = new TimeTrackerUser(new UserManager());
+ $user->Name = "user1";
+ $user->EmailAddress = 'user1@pradosoft.com';
+ $user->Roles = array("manager", "consultant");
+
+ try
+ {
+ $this->userDao->updateUserRoles($user);
+ $this->fail();
+ }
+ catch(TDataMapperException $e)
+ {
+ $this->pass();
+ }
+
+ $check = $this->sqlmap->queryForObject('GetUserByName', 'user1');
+ $this->assertNull($check);
+ }
+
+ function testUpdateUser()
+ {
+ $user = $this->userDao->getUserByName('admin');
+ $user->EmailAddress = 'something@pradosoft.com';
+ $user->Roles = array('manager', 'admin');
+
+ $this->userDao->updateUser($user);
+
+ $check = $this->userDao->getUserByName('admin');
+ $this->assertIsAdminRole($check);
+ $this->assertIsManagerRole($check);
+ $this->assertNotConsultantRole($check);
+
+ $this->flushDatabase();
+ }
+
+ function testUpdateUserPassword()
+ {
+ $user = $this->userDao->getUserByName('admin');
+ $user->EmailAddress = 'something@pradosoft.com';
+ $user->Roles = array('manager', 'admin');
+
+ $pass = 'newpasword';
+
+ $this->userDao->updateUser($user, $pass);
+
+ $success = $this->userDao->validateUser('admin', $pass);
+
+ $this->assertTrue($success);
+
+ $this->flushDatabase();
+ }
+
+ function testClearSignonTokens()
+ {
+ $user = new TimeTrackerUser(new UserManager());
+ $user->Name = "admin";
+
+ $token1 = $this->userDao->createSignonToken($user);
+ sleep(1);
+ $token2 = $this->userDao->createSignonToken($user);
+ $this->assertNotEqual($token1, $token2);
+
+ $check1 = $this->userDao->validateSignon($token1);
+ $check2 = $this->userDao->validateSignon($token2);
+
+ $this->assertIsAdmin($check1);
+ $this->assertIsAdmin($check2);
+
+ $this->userDao->clearSignonTokens($user);
+
+ $check3 = $this->userDao->validateSignon($token1);
+ $check4 = $this->userDao->validateSignon($token2);
+
+ $this->assertNull($check3);
+ $this->assertNull($check4);
+ }
+
+ function testClearAllSigonTokens()
+ {
+ $user1 = new TimeTrackerUser(new UserManager());
+ $user1->Name = "admin";
+
+ $user2 = new TimeTrackerUser(new UserManager());
+ $user2->Name = "manager";
+
+ $token1 = $this->userDao->createSignonToken($user1);
+ $token2 = $this->userDao->createSignonToken($user2);
+
+ $check1 = $this->userDao->validateSignon($token1);
+ $check2 = $this->userDao->validateSignon($token2);
+
+ $this->assertIsAdmin($check1);
+ $this->assertNotNull($check2);
+ $this->assertEqual($check2->Name, $user2->Name);
+
+ $this->userDao->clearSignonTokens();
+
+ $check3 = $this->userDao->validateSignon($token1);
+ $check4 = $this->userDao->validateSignon($token2);
+
+ $this->assertNull($check3);
+ $this->assertNull($check4);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/time-tracker/themes/TimeTracker/background.png b/demos/time-tracker/themes/TimeTracker/background.png
new file mode 100644
index 00000000..ad339daf
--- /dev/null
+++ b/demos/time-tracker/themes/TimeTracker/background.png
Binary files differ
diff --git a/demos/time-tracker/themes/TimeTracker/site.css b/demos/time-tracker/themes/TimeTracker/site.css
new file mode 100644
index 00000000..f426243e
--- /dev/null
+++ b/demos/time-tracker/themes/TimeTracker/site.css
@@ -0,0 +1,217 @@
+html
+{
+ background-color: #eef;
+}
+body
+{
+ background: url(background.png) repeat-x top;
+ margin: 0;
+ padding: 2em;
+ font-family: Tahoma, Arial, Helvetica, sans-serif;
+}
+
+h1, h2, h3, h4
+{
+ color: #6495ED;
+}
+
+div.main
+{
+
+ background-color: White;
+ padding: 30px 30px 200px 30px;
+ margin-top: 1.8em;
+ -moz-border-radius: 15px 15px 0 0;
+}
+
+h1.heading, h1 a
+{
+ color: white;
+ text-decoration: none;
+ margin: -1.2em 0 1em 0;
+ font-size: 1.25em;
+}
+
+h1.heading, h1 a:hover
+{
+ color: white;
+}
+
+h1 .subheading
+{
+ display: block;
+ font-size: 0.96em;
+ color: #E0FFFF;
+}
+
+.minheading
+{
+ text-align: right;
+ margin-top: -3.5em;
+}
+.minheading h2
+{
+ font-size: 0.9em;
+ display: inline;
+ padding-left: 1em;
+}
+
+.minheading .name
+{
+ color: white;
+ margin-right: 1em;
+}
+
+.minheading a
+{
+ color: white;
+}
+
+.minheading a:hover
+{
+ color: Yellow;
+}
+
+a
+{
+ color: #f61;
+}
+
+a:hover
+{
+ color: red;
+}
+
+/** menu **/
+
+.sitemap
+{
+ text-align: center;
+ margin-top: 1.5em;
+ margin-bottom: -1.5em;
+}
+
+ul.level1
+{
+
+}
+
+ul.level1, ul.level2
+{
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+ul.level1 li
+{
+ display: inline;
+}
+ul.level1 li .menuitem
+{
+ padding: 0.3em 20px 0.3em 20px;
+ background-color: #B6CEF7;
+ cursor: pointer;
+ background-image: url(tabs.png);
+ background-repeat: repeat-x;
+ color: #f71;
+ font-weight: bold;
+ letter-spacing: 2px;
+}
+
+ul.level1 li.active .menuitem
+{
+ background-color: White;
+ background-image: none;
+}
+
+ul.level2
+{
+ display: none;
+}
+
+li.active ul.level2
+{
+ float: left;
+ display: block;
+ width: 95%;
+ margin: 1em 0 0 0;
+ padding-bottom: 1em;
+}
+
+li.active ul.level2 li a
+{
+ margin-left: 1em;
+ color: #f61;
+ text-decoration: none;
+ border-bottom:1px dashed #f61;
+}
+
+li.active ul.level2 li a:hover
+{
+ color: Red;
+}
+
+
+/** forms **/
+
+fieldset.login, fieldset.signup
+{
+ margin: 0;
+ border: 1px solid #6495ED;
+ padding: 1.5em;
+}
+
+fieldset legend
+{
+ font-weight: bold;
+ color: #4169E1;
+ padding: 0.5em;
+}
+
+fieldset.login label, fieldset.signup label
+{
+ float: left;
+ color: #708090;
+ width: 120px;
+ text-align: right;
+}
+
+fieldset.signup label
+{
+ width: 9em;
+}
+
+fieldset span.required
+{
+ font-size: 0.85em;
+ font-weight: bold;
+ color: Red;
+}
+
+fieldset.login div, fieldset.signup div
+{
+ padding: 0.25em;
+}
+
+fieldset.login .remember label
+{
+ float:none;
+ margin-left: 0.4em;
+}
+fieldset.login .remember input
+{
+ margin-left: 133px;
+}
+
+fieldset.login .signin input
+{
+ padding: 0.1em 2em;
+ margin-left: 133px;
+}
+
+fieldset.signup .create
+{
+ margin: 1em;
+ padding-left: 9em;
+} \ No newline at end of file
diff --git a/demos/time-tracker/themes/TimeTracker/tabs.png b/demos/time-tracker/themes/TimeTracker/tabs.png
new file mode 100644
index 00000000..7f1ca0bb
--- /dev/null
+++ b/demos/time-tracker/themes/TimeTracker/tabs.png
Binary files differ