From 2570226fbac3e26b1e94896b50d1db4bc1aa3308 Mon Sep 17 00:00:00 2001 From: wei <> Date: Sun, 17 Dec 2006 22:20:50 +0000 Subject: Add TDataSourceConfig, TSqlMapConfig, TActiveRecordConfig --- .../protected/App_Code/Dao/BaseDao.php | 20 +++--- .../protected/App_Code/Dao/CategoryDao.php | 28 ++++---- .../protected/App_Code/Dao/ProjectDao.php | 46 ++++++------ .../protected/App_Code/Dao/ReportsDao.php | 27 ++++---- .../protected/App_Code/Dao/TimeEntryDao.php | 22 +++--- .../protected/App_Code/Dao/UserDao.php | 40 +++++------ .../time-tracker/protected/App_Code/DaoManager.php | 77 ++++----------------- .../protected/App_Data/DateTimeTypeHandler.php | 8 ++- .../protected/App_Data/SQLite/projects.xml | 28 ++++---- .../protected/App_Data/SQLite/time-entry.xml | 28 ++++---- .../protected/App_Data/SQLite/time-tracker.db | Bin 32768 -> 29696 bytes .../protected/App_Data/SQLite/time-tracker.db.bak | Bin 32768 -> 29696 bytes .../protected/App_Data/SQLite/users.xml | 24 +++---- .../App_Data/TimeTrackerUserTypeHandler.php | 4 +- .../protected/App_Data/sqlite-sqlmap.xml | 36 ++++------ demos/time-tracker/protected/application.xml | 31 +++------ .../protected/pages/TimeTracker/Login.page | 16 ----- 17 files changed, 177 insertions(+), 258 deletions(-) (limited to 'demos/time-tracker/protected') diff --git a/demos/time-tracker/protected/App_Code/Dao/BaseDao.php b/demos/time-tracker/protected/App_Code/Dao/BaseDao.php index cf27afc9..5edb6af7 100644 --- a/demos/time-tracker/protected/App_Code/Dao/BaseDao.php +++ b/demos/time-tracker/protected/App_Code/Dao/BaseDao.php @@ -21,24 +21,24 @@ class BaseDao { /** - * @var TSqlMapper sqlmap client. + * @var TSqlMapGateway sqlmap client. */ - private $_connection; - + private $_sqlmap; + /** - * @param TSqlMapper sqlmap client. + * @param TSqlMapGateway sqlmap client. */ - public function setConnection($connection) + public function setSqlMap($sqlmap) { - $this->_connection = $connection; + $this->_sqlmap = $sqlmap; } - + /** - * @return TSqlMapper sqlmap client. + * @return TSqlMapGateway sqlmap client. */ - protected function getConnection() + protected function getSqlMap() { - return $this->_connection; + return $this->_sqlmap; } } diff --git a/demos/time-tracker/protected/App_Code/Dao/CategoryDao.php b/demos/time-tracker/protected/App_Code/Dao/CategoryDao.php index cb1b6399..7476f036 100644 --- a/demos/time-tracker/protected/App_Code/Dao/CategoryDao.php +++ b/demos/time-tracker/protected/App_Code/Dao/CategoryDao.php @@ -4,48 +4,48 @@ class CategoryDao extends BaseDao { function addNewCategory($category) { - $sqlmap = $this->getConnection(); + $sqlmap = $this->getSqlMap(); $exists = $this->getCategoryByNameInProject( $category->Name, $category->ProjectID); if(!$exists) $sqlmap->insert('AddNewCategory', $category); - } - + } + function getCategoryByID($categoryID) { - $sqlmap = $this->getConnection(); + $sqlmap = $this->getSqlMap(); return $sqlmap->queryForObject('GetCategoryByID', $categoryID); } - + function getAllCategories() { - $sqlmap = $this->getConnection(); + $sqlmap = $this->getSqlMap(); return $sqlmap->queryForList('GetAllCategories'); } - + function deleteCategory($categoryID) { - $sqlmap = $this->getConnection(); + $sqlmap = $this->getSqlMap(); $sqlmap->delete('DeleteCategory', $categoryID); } - + function getCategoriesByProjectID($projectID) { - $sqlmap = $this->getConnection(); + $sqlmap = $this->getSqlMap(); return $sqlmap->queryForList('GetCategoriesByProjectID', $projectID); } - + function getCategoryByNameInProject($name, $projectID) { - $sqlmap = $this->getConnection(); + $sqlmap = $this->getSqlMap(); $param['project'] = $projectID; $param['category'] = $name; return $sqlmap->queryForObject('GetCategoryByNameInProject', $param); } - + function updateCategory($category) { - $sqlmap = $this->getConnection(); + $sqlmap = $this->getSqlMap(); $sqlmap->update('UpdateCategory', $category); } } diff --git a/demos/time-tracker/protected/App_Code/Dao/ProjectDao.php b/demos/time-tracker/protected/App_Code/Dao/ProjectDao.php index 3d3ced1d..9e8867bc 100644 --- a/demos/time-tracker/protected/App_Code/Dao/ProjectDao.php +++ b/demos/time-tracker/protected/App_Code/Dao/ProjectDao.php @@ -22,49 +22,49 @@ class ProjectDao extends BaseDao { public function projectNameExists($projectName) { - $sqlmap = $this->getConnection(); + $sqlmap = $this->getSqlMap(); return $sqlmap->queryForObject('ProjectNameExists', $projectName); } - + public function addNewProject($project) { - $sqlmap = $this->getConnection(); + $sqlmap = $this->getSqlMap(); $sqlmap->insert('CreateNewProject', $project); } - + public function getProjectByID($projectID) { - $sqlmap = $this->getConnection(); + $sqlmap = $this->getSqlMap(); return $sqlmap->queryForObject('GetProjectByID', $projectID); } - + public function deleteProject($projectID) { - $sqlmap = $this->getConnection(); + $sqlmap = $this->getSqlMap(); $sqlmap->update('DeleteProject',$projectID); } - + public function addUserToProject($projectID, $username) { - $sqlmap = $this->getConnection(); + $sqlmap = $this->getSqlMap(); $members = $this->getProjectMembers($projectID); if(!in_array($username, $members)) { $param['username'] = $username; $param['project'] = $projectID; $sqlmap->insert('AddUserToProject',$param); - } + } } - + public function getProjectMembers($projectID) { - $sqlmap = $this->getConnection(); + $sqlmap = $this->getSqlMap(); return $sqlmap->queryForList('GetProjectMembers', $projectID); } - + public function getAllProjects($sort='', $order='ASC') { - $sqlmap = $this->getConnection(); + $sqlmap = $this->getSqlMap(); if($sort === '') return $sqlmap->queryForList('GetAllProjects'); else @@ -72,32 +72,32 @@ class ProjectDao extends BaseDao $param['sort'] = $sort; $param['order'] = $order; return $sqlmap->queryForList('GetAllProjectsOrdered', $param); - } + } } - + public function getProjectsByManagerName($manager) { - $sqlmap = $this->getConnection(); + $sqlmap = $this->getSqlMap(); return $sqlmap->queryForList('GetProjectsByManagerName', $manager); } - + public function getProjectsByUserName($username) { - $sqlmap = $this->getConnection(); + $sqlmap = $this->getSqlMap(); return $sqlmap->queryForList('GetProjectsByUserName', $username); } - + public function removeUserFromProject($projectID, $username) { - $sqlmap = $this->getConnection(); + $sqlmap = $this->getSqlMap(); $param['username'] = $username; $param['project'] = $projectID; $sqlmap->delete('RemoveUserFromProject', $param); } - + public function updateProject($project) { - $sqlmap = $this->getConnection(); + $sqlmap = $this->getSqlMap(); $sqlmap->update('UpdateProject', $project); } } diff --git a/demos/time-tracker/protected/App_Code/Dao/ReportsDao.php b/demos/time-tracker/protected/App_Code/Dao/ReportsDao.php index 50005d06..3e5b6456 100644 --- a/demos/time-tracker/protected/App_Code/Dao/ReportsDao.php +++ b/demos/time-tracker/protected/App_Code/Dao/ReportsDao.php @@ -6,12 +6,12 @@ class ProjectReport extends TComponent public $EstimateHours = 0; public $EstimateCompletion = 0; public $Categories; - + public function __construct() { $this->Categories = new TList; } - + public function getActualHours() { $total = 0; @@ -26,21 +26,21 @@ class CategoryReport extends TComponent public $CategoryName = ''; public $EstimateHours = 0; public $members = array(); - + public function getActualHours() { $total = 0; foreach($this->members as $member) $total += $member['hours']; return $total; - } + } } class UserReport extends TComponent { public $Username; public $Projects = array(); - + public function getTotalHours() { $hours = 0; @@ -64,24 +64,23 @@ class ReportsDao extends BaseDao public function getTimeReportsByProjectIDs($projects) { $ids = implode(',', array_map('intval', $projects)); - $sqlmap = $this->getConnection(); - return $sqlmap->queryForList('GetTimeReportByProjectIDs', $ids); + $sqlmap = $this->getSqlMap(); + return $sqlmap->queryForList('GetTimeReportByProjectIDs', $ids); } - + public function getUserProjectTimeReports($users, $projects, $startDate, $endDate) { - $sqlmap = $this->getConnection(); - $driver = $sqlmap->openConnection(); + $sqlmap = $this->getSqlMap(); $ids = implode(',', array_map('intval', $projects)); - $usernames = implode(',', array_map(array($driver, 'quote'), $users)); - + $usernames = implode(',', array_map(array($sqlmap->getDbConnection(), 'quoteString'), $users)); + $param['projects'] = $ids; $param['members'] = $usernames; $param['startDate'] = intval($startDate); $param['endDate'] = intval($endDate); - + return $sqlmap->queryForList('GetTimeReportByUsername', $param); - } + } } ?> \ No newline at end of file diff --git a/demos/time-tracker/protected/App_Code/Dao/TimeEntryDao.php b/demos/time-tracker/protected/App_Code/Dao/TimeEntryDao.php index 7207ed47..4bd74eb5 100644 --- a/demos/time-tracker/protected/App_Code/Dao/TimeEntryDao.php +++ b/demos/time-tracker/protected/App_Code/Dao/TimeEntryDao.php @@ -4,33 +4,33 @@ class TimeEntryDao extends BaseDao { public function addNewTimeEntry($entry) { - $sqlmap = $this->getConnection(); - $sqlmap->insert('AddNewTimeEntry', $entry); + $sqlmap = $this->getSqlMap(); + $sqlmap->insert('AddNewTimeEntry', $entry); } - + public function getTimeEntryByID($entryID) { - $sqlmap = $this->getConnection(); - return $sqlmap->queryForObject('GetTimeEntryByID', $entryID); + $sqlmap = $this->getSqlMap(); + return $sqlmap->queryForObject('GetTimeEntryByID', $entryID); } - + public function deleteTimeEntry($entryID) { - $sqlmap = $this->getConnection(); + $sqlmap = $this->getSqlMap(); $sqlmap->delete('DeleteTimeEntry', $entryID); } - + public function getTimeEntriesInProject($username, $projectID) { - $sqlmap = $this->getConnection(); + $sqlmap = $this->getSqlMap(); $param['username'] = $username; $param['project'] = $projectID; return $sqlmap->queryForList('GetAllTimeEntriesByProjectIdAndUser', $param); } - + public function updateTimeEntry($entry) { - $sqlmap = $this->getConnection(); + $sqlmap = $this->getSqlMap(); $sqlmap->update('UpdateTimeEntry', $entry); } } diff --git a/demos/time-tracker/protected/App_Code/Dao/UserDao.php b/demos/time-tracker/protected/App_Code/Dao/UserDao.php index 28719d81..4bb23b3a 100644 --- a/demos/time-tracker/protected/App_Code/Dao/UserDao.php +++ b/demos/time-tracker/protected/App_Code/Dao/UserDao.php @@ -11,7 +11,7 @@ */ /** - * UserDao class list, create, find and delete users. + * 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. @@ -29,49 +29,49 @@ class UserDao extends BaseDao */ public function getUserByName($username) { - $sqlmap = $this->getConnection(); + $sqlmap = $this->getSqlMap(); return $sqlmap->queryForObject('GetUserByName', $username); } - + /** * @param string username * @return boolean true if username already exists, false otherwise. */ public function usernameExists($username) { - $sqlmap = $this->getConnection(); + $sqlmap = $this->getSqlMap(); return $sqlmap->queryForObject('UsernameExists', $username); } - + /** * @return array list of all enabled users. */ public function getAllUsers() { - $sqlmap = $this->getConnection(); + $sqlmap = $this->getSqlMap(); return $sqlmap->queryForList('GetAllUsers'); } - + /** * @param TimeTrackerUser new user details. * @param string new user password. */ public function addNewUser($user, $password) { - $sqlmap = $this->getConnection(); + $sqlmap = $this->getSqlMap(); $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 = $this->getSqlMap(); $sqlmap->delete('DeleteUserByName', $username); } @@ -82,7 +82,7 @@ class UserDao extends BaseDao */ public function updateUser($user,$password=null) { - $sqlmap = $this->getConnection(); + $sqlmap = $this->getSqlMap(); if($password !== null) { $param['user'] = $user; @@ -103,55 +103,55 @@ class UserDao extends BaseDao */ public function validateUser($username, $password) { - $sqlmap = $this->getConnection(); + $sqlmap = $this->getSqlMap(); $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 = $this->getSqlMap(); $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(); + $sqlmap = $this->getSqlMap(); $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(); + $sqlmap = $this->getSqlMap(); 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 = $this->getSqlMap(); $sqlmap->delete('DeleteUserRoles', $user); foreach($user->getRoles() as $role) { diff --git a/demos/time-tracker/protected/App_Code/DaoManager.php b/demos/time-tracker/protected/App_Code/DaoManager.php index 5e3eef56..bf25c555 100644 --- a/demos/time-tracker/protected/App_Code/DaoManager.php +++ b/demos/time-tracker/protected/App_Code/DaoManager.php @@ -9,10 +9,12 @@ * @version $Id$ * @package Demos */ - + +Prado::using('System.Data.SqlMap.TSqlMapConfig'); + /** * DaoManager class. - * + * * A Registry for Dao and an implementation of that type. * * @author Wei Zhuo @@ -20,57 +22,29 @@ * @package Demos * @since 3.1 */ -class DaoManager extends TModule +class DaoManager extends TSqlMapConfig { - /** - * @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) + public function init($xml) { - foreach($nodes as $node) + parent::init($xml); + foreach($xml->getElementsByTagName("dao") as $node) { - $id = $node->getAttribute('id'); - $class = $node->getAttribute('class'); - $this->_dao[$id] = array('class' => $class); + $this->_dao[$node->getAttribute('id')] = + array('class' => $node->getAttribute('class')); } } - + /** * @return array list of registered Daos */ @@ -78,7 +52,7 @@ class DaoManager extends TModule { return $this->_dao; } - + /** * Returns an implementation of a Dao type, implements the Registery * pattern. Multiple calls returns the same Dao instance. @@ -92,35 +66,14 @@ class DaoManager extends TModule if(!isset($this->_dao[$class]['instance'])) { $dao = Prado::createComponent($this->_dao[$class]['class']); - $dao->setConnection($this->getConnection()); - $this->_dao[$class]['instance'] = $dao; + $dao->setSqlMap($this->getClient()); + $this->_dao[$class]['instance'] = $dao; } return $this->_dao[$class]['instance']; } else throw new 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_Data/DateTimeTypeHandler.php b/demos/time-tracker/protected/App_Data/DateTimeTypeHandler.php index 17f3e14b..03af3d36 100644 --- a/demos/time-tracker/protected/App_Data/DateTimeTypeHandler.php +++ b/demos/time-tracker/protected/App_Data/DateTimeTypeHandler.php @@ -1,6 +1,6 @@ 10000) //strtotime doesn't like unix epoc time. + return intval($string); + return strtotime($string); } /** @@ -27,7 +29,7 @@ class DateTimeTypeHandler implements ITypeHandlerCallback { throw new TimeTrackerException('Not implemented'); } - + } ?> \ No newline at end of file diff --git a/demos/time-tracker/protected/App_Data/SQLite/projects.xml b/demos/time-tracker/protected/App_Data/SQLite/projects.xml index e5f52887..f28f7a47 100644 --- a/demos/time-tracker/protected/App_Data/SQLite/projects.xml +++ b/demos/time-tracker/protected/App_Data/SQLite/projects.xml @@ -21,8 +21,8 @@ INSERT INTO projects (Name, Description, CreationDate, EstimateDuration, CompletionDate, CreatorID, ManagerID) VALUES - (#Name#, #Description#, php('date', 'Y-m-d H:i:s'), #EstimateDuration#, - #CompletionDate, typeHandler=DateTime#, + (#Name#, #Description#, strftime('%s', 'now'), #EstimateDuration#, + #CompletionDate, typeHandler=DateTime#, #CreatorUserName#, #ManagerUserName#) select LAST_INSERT_ROWID() as value @@ -30,7 +30,7 @@ - SELECT + SELECT * - FROM + FROM time_entry WHERE EntryID = #value# @@ -66,7 +66,7 @@ diff --git a/demos/time-tracker/protected/App_Data/SQLite/time-tracker.db b/demos/time-tracker/protected/App_Data/SQLite/time-tracker.db index 124bbeba..6cafd20f 100644 Binary files a/demos/time-tracker/protected/App_Data/SQLite/time-tracker.db and b/demos/time-tracker/protected/App_Data/SQLite/time-tracker.db differ diff --git a/demos/time-tracker/protected/App_Data/SQLite/time-tracker.db.bak b/demos/time-tracker/protected/App_Data/SQLite/time-tracker.db.bak index 124bbeba..206b3e3c 100644 Binary files a/demos/time-tracker/protected/App_Data/SQLite/time-tracker.db.bak and b/demos/time-tracker/protected/App_Data/SQLite/time-tracker.db.bak differ diff --git a/demos/time-tracker/protected/App_Data/SQLite/users.xml b/demos/time-tracker/protected/App_Data/SQLite/users.xml index 74b33b2c..45e68c01 100644 --- a/demos/time-tracker/protected/App_Data/SQLite/users.xml +++ b/demos/time-tracker/protected/App_Data/SQLite/users.xml @@ -13,7 +13,7 @@ @@ -32,7 +32,7 @@ - + - INSERT INTO + INSERT INTO users (Username, Password, EmailAddress) VALUES (#user.Name#, #password#, #user.EmailAddress#) @@ -80,10 +80,10 @@ - INSERT INTO + INSERT INTO signon (SessionToken, Username, LastSignOnDate) VALUES - (#token#, #username#, php('date', 'Y-m-d H:i:s')) + (#token#, #username#, strftime('%s', 'now')) - UPDATE signon SET LastSignOnDate = php('date', 'Y-m-d H:i:s') + UPDATE signon SET LastSignOnDate = strftime('%s', 'now') WHERE SessionToken = #value# @@ -115,13 +115,13 @@ - UPDATE users + UPDATE users SET EmailAddress = #EmailAddress# WHERE Username = #Name# - UPDATE users + UPDATE users SET EmailAddress = #user.EmailAddress#, Password=#password# WHERE Username = #user.Name# diff --git a/demos/time-tracker/protected/App_Data/TimeTrackerUserTypeHandler.php b/demos/time-tracker/protected/App_Data/TimeTrackerUserTypeHandler.php index c2d5c4d6..54aba438 100644 --- a/demos/time-tracker/protected/App_Data/TimeTrackerUserTypeHandler.php +++ b/demos/time-tracker/protected/App_Data/TimeTrackerUserTypeHandler.php @@ -19,7 +19,7 @@ * @package Demos * @since 3.1 */ -class TimeTrackerUserTypeHandler implements ITypeHandlerCallback +class TimeTrackerUserTypeHandler extends TSqlMapTypeHandler { /** * Not implemented. @@ -34,7 +34,7 @@ class TimeTrackerUserTypeHandler implements ITypeHandlerCallback */ public function getResult($string) { - throw new TimeTrackerException('Not implemented'); + throw new TimeTrackerException('Not implemented'); } /** diff --git a/demos/time-tracker/protected/App_Data/sqlite-sqlmap.xml b/demos/time-tracker/protected/App_Data/sqlite-sqlmap.xml index 9590b506..3cbb846e 100644 --- a/demos/time-tracker/protected/App_Data/sqlite-sqlmap.xml +++ b/demos/time-tracker/protected/App_Data/sqlite-sqlmap.xml @@ -1,26 +1,16 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + \ No newline at end of file diff --git a/demos/time-tracker/protected/application.xml b/demos/time-tracker/protected/application.xml index 40619257..bae5f126 100644 --- a/demos/time-tracker/protected/application.xml +++ b/demos/time-tracker/protected/application.xml @@ -5,7 +5,7 @@ - + @@ -13,33 +13,24 @@ - - - - - - - + - + + + + - + \ No newline at end of file diff --git a/demos/time-tracker/protected/pages/TimeTracker/Login.page b/demos/time-tracker/protected/pages/TimeTracker/Login.page index 3022eaeb..442edc80 100644 --- a/demos/time-tracker/protected/pages/TimeTracker/Login.page +++ b/demos/time-tracker/protected/pages/TimeTracker/Login.page @@ -37,20 +37,4 @@ Create New User - - - - - OnClick="button1_Clicked" /> - <%= print echo "asd" %> - <%# print echo "asd" %> - <%[ asd ]%> - <%= as echo - - "asd" %> - <%# as echo - "asd" %> - <%= print echo "asd" %> - <%# print echo "asd" %> -- cgit v1.2.3