From 05869f23f798c9393e2bc6d310d56a97a11d1acd Mon Sep 17 00:00:00 2001 From: xue <> Date: Mon, 29 May 2006 02:05:19 +0000 Subject: Added blog demo (not done yet) --- demos/blog/index.php | 18 + demos/blog/protected/Common/BlogDataModule.php | 535 +++++++++++++++++++++ demos/blog/protected/Common/BlogErrors.php | 23 + demos/blog/protected/Common/BlogException.php | 14 + demos/blog/protected/Common/BlogPage.php | 26 + demos/blog/protected/Common/BlogUser.php | 38 ++ demos/blog/protected/Common/BlogUserManager.php | 56 +++ demos/blog/protected/Common/XListMenu.php | 127 +++++ demos/blog/protected/Common/messages.txt | 4 + demos/blog/protected/Common/schema.sql | 70 +++ demos/blog/protected/Data/Options.xml | 8 + demos/blog/protected/Layouts/MainLayout.php | 7 + demos/blog/protected/Layouts/MainLayout.tpl | 47 ++ demos/blog/protected/Pages/Admin/AdminMenu.php | 7 + demos/blog/protected/Pages/Admin/AdminMenu.tpl | 16 + demos/blog/protected/Pages/Admin/ConfigMan.page | 56 +++ demos/blog/protected/Pages/Admin/ConfigMan.php | 15 + demos/blog/protected/Pages/Admin/PostMan.page | 76 +++ demos/blog/protected/Pages/Admin/PostMan.php | 56 +++ demos/blog/protected/Pages/Admin/Settings.page | 4 + demos/blog/protected/Pages/Admin/UserMan.page | 95 ++++ demos/blog/protected/Pages/Admin/UserMan.php | 58 +++ demos/blog/protected/Pages/Admin/config.xml | 8 + demos/blog/protected/Pages/ErrorReport.page | 15 + demos/blog/protected/Pages/ErrorReport.php | 12 + demos/blog/protected/Pages/Posts/EditCategory.page | 36 ++ demos/blog/protected/Pages/Posts/EditCategory.php | 44 ++ demos/blog/protected/Pages/Posts/EditPost.page | 41 ++ demos/blog/protected/Pages/Posts/EditPost.php | 51 ++ demos/blog/protected/Pages/Posts/ListPost.page | 27 ++ demos/blog/protected/Pages/Posts/ListPost.php | 44 ++ demos/blog/protected/Pages/Posts/MyPost.page | 46 ++ demos/blog/protected/Pages/Posts/MyPost.php | 34 ++ demos/blog/protected/Pages/Posts/NewCategory.page | 36 ++ demos/blog/protected/Pages/Posts/NewCategory.php | 24 + demos/blog/protected/Pages/Posts/NewPost.page | 41 ++ demos/blog/protected/Pages/Posts/NewPost.php | 34 ++ demos/blog/protected/Pages/Posts/ViewPost.page | 113 +++++ demos/blog/protected/Pages/Posts/ViewPost.php | 73 +++ demos/blog/protected/Pages/Posts/config.xml | 7 + demos/blog/protected/Pages/Users/EditUser.page | 74 +++ demos/blog/protected/Pages/Users/EditUser.php | 43 ++ demos/blog/protected/Pages/Users/NewUser.page | 104 ++++ demos/blog/protected/Pages/Users/NewUser.php | 31 ++ demos/blog/protected/Pages/Users/ViewUser.page | 21 + demos/blog/protected/Pages/Users/ViewUser.php | 19 + demos/blog/protected/Pages/Users/config.xml | 7 + demos/blog/protected/Portlets/AccountPortlet.php | 14 + demos/blog/protected/Portlets/AccountPortlet.tpl | 20 + demos/blog/protected/Portlets/ArchivePortlet.php | 45 ++ demos/blog/protected/Portlets/ArchivePortlet.tpl | 15 + demos/blog/protected/Portlets/CategoryPortlet.php | 15 + demos/blog/protected/Portlets/CategoryPortlet.tpl | 24 + demos/blog/protected/Portlets/LoginPortlet.php | 22 + demos/blog/protected/Portlets/LoginPortlet.tpl | 36 ++ demos/blog/protected/Portlets/Portlet.php | 7 + demos/blog/protected/Portlets/SearchPortlet.php | 22 + demos/blog/protected/Portlets/SearchPortlet.tpl | 21 + demos/blog/protected/application.xml | 38 ++ demos/blog/sitemap.txt | 106 ++++ demos/blog/themes/Basic/style.css | 261 ++++++++++ 61 files changed, 2987 insertions(+) create mode 100644 demos/blog/index.php create mode 100644 demos/blog/protected/Common/BlogDataModule.php create mode 100644 demos/blog/protected/Common/BlogErrors.php create mode 100644 demos/blog/protected/Common/BlogException.php create mode 100644 demos/blog/protected/Common/BlogPage.php create mode 100644 demos/blog/protected/Common/BlogUser.php create mode 100644 demos/blog/protected/Common/BlogUserManager.php create mode 100644 demos/blog/protected/Common/XListMenu.php create mode 100644 demos/blog/protected/Common/messages.txt create mode 100644 demos/blog/protected/Common/schema.sql create mode 100644 demos/blog/protected/Data/Options.xml create mode 100644 demos/blog/protected/Layouts/MainLayout.php create mode 100644 demos/blog/protected/Layouts/MainLayout.tpl create mode 100644 demos/blog/protected/Pages/Admin/AdminMenu.php create mode 100644 demos/blog/protected/Pages/Admin/AdminMenu.tpl create mode 100644 demos/blog/protected/Pages/Admin/ConfigMan.page create mode 100644 demos/blog/protected/Pages/Admin/ConfigMan.php create mode 100644 demos/blog/protected/Pages/Admin/PostMan.page create mode 100644 demos/blog/protected/Pages/Admin/PostMan.php create mode 100644 demos/blog/protected/Pages/Admin/Settings.page create mode 100644 demos/blog/protected/Pages/Admin/UserMan.page create mode 100644 demos/blog/protected/Pages/Admin/UserMan.php create mode 100644 demos/blog/protected/Pages/Admin/config.xml create mode 100644 demos/blog/protected/Pages/ErrorReport.page create mode 100644 demos/blog/protected/Pages/ErrorReport.php create mode 100644 demos/blog/protected/Pages/Posts/EditCategory.page create mode 100644 demos/blog/protected/Pages/Posts/EditCategory.php create mode 100644 demos/blog/protected/Pages/Posts/EditPost.page create mode 100644 demos/blog/protected/Pages/Posts/EditPost.php create mode 100644 demos/blog/protected/Pages/Posts/ListPost.page create mode 100644 demos/blog/protected/Pages/Posts/ListPost.php create mode 100644 demos/blog/protected/Pages/Posts/MyPost.page create mode 100644 demos/blog/protected/Pages/Posts/MyPost.php create mode 100644 demos/blog/protected/Pages/Posts/NewCategory.page create mode 100644 demos/blog/protected/Pages/Posts/NewCategory.php create mode 100644 demos/blog/protected/Pages/Posts/NewPost.page create mode 100644 demos/blog/protected/Pages/Posts/NewPost.php create mode 100644 demos/blog/protected/Pages/Posts/ViewPost.page create mode 100644 demos/blog/protected/Pages/Posts/ViewPost.php create mode 100644 demos/blog/protected/Pages/Posts/config.xml create mode 100644 demos/blog/protected/Pages/Users/EditUser.page create mode 100644 demos/blog/protected/Pages/Users/EditUser.php create mode 100644 demos/blog/protected/Pages/Users/NewUser.page create mode 100644 demos/blog/protected/Pages/Users/NewUser.php create mode 100644 demos/blog/protected/Pages/Users/ViewUser.page create mode 100644 demos/blog/protected/Pages/Users/ViewUser.php create mode 100644 demos/blog/protected/Pages/Users/config.xml create mode 100644 demos/blog/protected/Portlets/AccountPortlet.php create mode 100644 demos/blog/protected/Portlets/AccountPortlet.tpl create mode 100644 demos/blog/protected/Portlets/ArchivePortlet.php create mode 100644 demos/blog/protected/Portlets/ArchivePortlet.tpl create mode 100644 demos/blog/protected/Portlets/CategoryPortlet.php create mode 100644 demos/blog/protected/Portlets/CategoryPortlet.tpl create mode 100644 demos/blog/protected/Portlets/LoginPortlet.php create mode 100644 demos/blog/protected/Portlets/LoginPortlet.tpl create mode 100644 demos/blog/protected/Portlets/Portlet.php create mode 100644 demos/blog/protected/Portlets/SearchPortlet.php create mode 100644 demos/blog/protected/Portlets/SearchPortlet.tpl create mode 100644 demos/blog/protected/application.xml create mode 100644 demos/blog/sitemap.txt create mode 100644 demos/blog/themes/Basic/style.css (limited to 'demos/blog') diff --git a/demos/blog/index.php b/demos/blog/index.php new file mode 100644 index 00000000..43c0b436 --- /dev/null +++ b/demos/blog/index.php @@ -0,0 +1,18 @@ +<?php + +$basePath=dirname(__FILE__); +$frameworkPath=$basePath.'/../../framework/prado.php'; +$assetsPath=$basePath.'/assets'; +$runtimePath=$basePath.'/protected/runtime'; + +if(!is_writable($assetsPath)) + die("Please make sure that the directory $assetsPath is writable by Web server process."); +if(!is_writable($runtimePath)) + die("Please make sure that the directory $runtimePath is writable by Web server process."); + +require_once($frameworkPath); + +$application=new TApplication; +$application->run(); + +?> \ No newline at end of file diff --git a/demos/blog/protected/Common/BlogDataModule.php b/demos/blog/protected/Common/BlogDataModule.php new file mode 100644 index 00000000..714743e7 --- /dev/null +++ b/demos/blog/protected/Common/BlogDataModule.php @@ -0,0 +1,535 @@ +<?php + +// post status: 0 - draft, 1 - published +// comment status: 0 - awaiting approval, 1 - published +class BlogDataModule extends TModule +{ + const DB_FILE_EXT='.db'; + const DEFAULT_DB_FILE='Application.Data.Blog'; + private $_db=null; + private $_dbFile=null; + + public function init($config) + { + $this->connectDatabase(); + } + + public function getDbFile() + { + if($this->_dbFile===null) + $this->_dbFile=Prado::getPathOfNamespace(self::DEFAULT_DB_FILE,self::DB_FILE_EXT); + return $this->_dbFile; + } + + public function setDbFile($value) + { + if(($this->_dbFile=Prado::getPathOfNamespace($value,self::DB_FILE_EXT))===null) + throw new BlogException('blogdatamodule_dbfile_invalid',$value); + } + + protected function createDatabase() + { + $schemaFile=dirname(__FILE__).'/schema.sql'; + $statements=explode(';',file_get_contents($schemaFile)); + foreach($statements as $statement) + { + if(trim($statement)!=='') + { + if(@sqlite_query($this->_db,$statement)===false) + throw new BlogException('blogdatamodule_createdatabase_failed',sqlite_error_string(sqlite_last_error($this->_db)),$statement); + } + } + } + + protected function connectDatabase() + { + $dbFile=$this->getDbFile(); + $newDb=!is_file($dbFile); + $error=''; + if(($this->_db=sqlite_open($dbFile,0666,$error))===false) + throw new BlogException('blogdatamodule_dbconnect_failed',$error); + if($newDb) + $this->createDatabase(); + } + + protected function generateModifier($filter,$orderBy,$limit) + { + $modifier=''; + if($filter!=='') + $modifier=' WHERE '.$filter; + if($orderBy!=='') + $modifier.=' ORDER BY '.$orderBy; + if($limit!=='') + $modifier.=' LIMIT '.$limit; + return $modifier; + } + + public function query($sql) + { + if(($result=@sqlite_query($this->_db,$sql))!==false) + return $result; + else + throw new BlogException('blogdatamodule_query_failed',sqlite_error_string(sqlite_last_error($this->_db)),$sql); + } + + protected function populateUserRecord($row) + { + $userRecord=new UserRecord; + $userRecord->ID=(integer)$row['id']; + $userRecord->Name=$row['name']; + $userRecord->FullName=$row['full_name']; + $userRecord->Role=(integer)$row['role']; + $userRecord->Password=$row['passwd']; + $userRecord->VerifyCode=$row['vcode']; + $userRecord->Email=$row['email']; + $userRecord->CreateTime=(integer)$row['reg_time']; + $userRecord->Status=(integer)$row['status']; + $userRecord->Website=$row['website']; + return $userRecord; + } + + public function queryUsers($filter='',$orderBy='',$limit='') + { + if($filter!=='') + $filter='WHERE '.$filter; + $sql="SELECT * FROM tblUsers $filter $orderBy $limit"; + $result=$this->query($sql); + $rows=sqlite_fetch_all($result,SQLITE_ASSOC); + $users=array(); + foreach($rows as $row) + $users[]=$this->populateUserRecord($row); + return $users; + } + + public function queryUserCount($filter) + { + if($filter!=='') + $filter='WHERE '.$filter; + $sql="SELECT COUNT(id) AS user_count FROM tblUsers $filter"; + $result=$this->query($sql); + if(($row=sqlite_fetch_array($result,SQLITE_ASSOC))!==false) + return $row['user_count']; + else + return 0; + } + + public function queryUserByID($id) + { + $sql="SELECT * FROM tblUsers WHERE id=$id"; + $result=$this->query($sql); + if(($row=sqlite_fetch_array($result,SQLITE_ASSOC))!==false) + return $this->populateUserRecord($row); + else + return null; + } + + public function queryUserByName($name) + { + $name=sqlite_escape_string($name); + $sql="SELECT * FROM tblUsers WHERE name='$name'"; + $result=$this->query($sql); + if(($row=sqlite_fetch_array($result,SQLITE_ASSOC))!==false) + return $this->populateUserRecord($row); + else + return null; + } + + public function insertUser($user) + { + $name=sqlite_escape_string($user->Name); + $fullName=sqlite_escape_string($user->FullName); + $passwd=sqlite_escape_string($user->Password); + $email=sqlite_escape_string($user->Email); + $website=sqlite_escape_string($user->Website); + $createTime=time(); + $sql="INSERT INTO tblUsers ". + "(name,full_name,role,passwd,email,reg_time,website) ". + "VALUES ('$name','$fullName',{$user->Role},'$passwd','$email',$createTime,'$website')"; + $this->query($sql); + $user->ID=sqlite_last_insert_rowid($this->_db); + } + + public function updateUser($user) + { + $name=sqlite_escape_string($user->Name); + $fullName=sqlite_escape_string($user->FullName); + $passwd=sqlite_escape_string($user->Password); + $email=sqlite_escape_string($user->Email); + $website=sqlite_escape_string($user->Website); + $sql="UPDATE tblUsers SET + name='$name', + full_name='$fullName', + role={$user->Role}, + passwd='$passwd', + vcode='{$user->VerifyCode}', + email='$email', + status={$user->Status}, + website='$website' + WHERE id={$user->ID}"; + $this->query($sql); + } + + public function deleteUser($id) + { + $this->query("DELETE FROM tblUsers WHERE id=$id"); + } + + protected function populatePostRecord($row) + { + $postRecord=new PostRecord; + $postRecord->ID=(integer)$row['id']; + $postRecord->AuthorID=(integer)$row['author_id']; + if($row['author_full_name']!=='') + $postRecord->AuthorName=$row['author_full_name']; + else + $postRecord->AuthorName=$row['author_name']; + $postRecord->CreateTime=(integer)$row['create_time']; + $postRecord->ModifyTime=(integer)$row['modify_time']; + $postRecord->Title=$row['title']; + $postRecord->Content=$row['content']; + $postRecord->Status=(integer)$row['status']; + $postRecord->CommentCount=(integer)$row['comment_count']; + return $postRecord; + } + + public function queryPosts($authorFilter,$timeFilter,$categoryFilter,$orderBy,$limit) + { + $filter=''; + if($authorFilter!=='') + $filter.=" AND $authorFilter"; + if($timeFilter!=='') + $filter.=" AND $timeFilter"; + if($categoryFilter!=='') + $filter.=" AND a.id IN (SELECT post_id AS id FROM tblPost2Category WHERE $categoryFilter)"; + $sql="SELECT a.id AS id, + a.author_id AS author_id, + b.name AS author_name, + b.full_name AS author_full_name, + a.create_time AS create_time, + a.modify_time AS modify_time, + a.title AS title, + a.content AS content, + a.status AS status, + a.comment_count AS comment_count + FROM tblPosts a, tblUsers b + WHERE a.author_id=b.id $filter $orderBy $limit"; + $result=$this->query($sql); + $rows=sqlite_fetch_all($result,SQLITE_ASSOC); + $posts=array(); + foreach($rows as $row) + $posts[]=$this->populatePostRecord($row); + return $posts; + } + + public function queryPostCount($authorFilter,$timeFilter,$categoryFilter) + { + $filter=''; + if($authorFilter!=='') + $filter.=" AND $authorFilter"; + if($timeFilter!=='') + $filter.=" AND $timeFilter"; + if($categoryFilter!=='') + $filter.=" AND a.id IN (SELECT post_id AS id FROM tblPost2Category WHERE $categoryFilter)"; + $sql="SELECT COUNT(a.id) AS post_count + FROM tblPosts a, tblUsers b + WHERE a.author_id=b.id $filter"; + $result=$this->query($sql); + if(($row=sqlite_fetch_array($result,SQLITE_ASSOC))!==false) + return $row['post_count']; + else + return 0; + } + + public function queryPostByID($id) + { + $sql="SELECT a.id AS id, + a.author_id AS author_id, + b.name AS author_name, + b.full_name AS author_full_name, + a.create_time AS create_time, + a.modify_time AS modify_time, + a.title AS title, + a.content AS content, + a.status AS status, + a.comment_count AS comment_count + FROM tblPosts a, tblUsers b + WHERE a.id=$id AND a.author_id=b.id"; + $result=$this->query($sql); + if(($row=sqlite_fetch_array($result,SQLITE_ASSOC))!==false) + return $this->populatePostRecord($row); + else + return null; + } + + public function insertPost($post,$catIDs) + { + $title=sqlite_escape_string($post->Title); + $content=sqlite_escape_string($post->Content); + $sql="INSERT INTO tblPosts + (author_id,create_time,title,content,status) + VALUES ({$post->AuthorID},{$post->CreateTime},'$title','$content',{$post->Status})"; + $this->query($sql); + $post->ID=sqlite_last_insert_rowid($this->_db); + foreach($catIDs as $catID) + $this->insertPostCategory($post->ID,$catID); + } + + public function updatePost($post,$newCatIDs=null) + { + if($newCatIDs!==null) + { + $cats=$this->queryCategoriesByPostID($post->ID); + $catIDs=array(); + foreach($cats as $cat) + $catIDs[]=$cat->ID; + $deleteIDs=array_diff($catIDs,$newCatIDs); + foreach($deleteIDs as $id) + $this->deletePostCategory($post->ID,$id); + $insertIDs=array_diff($newCatIDs,$catIDs); + foreach($insertIDs as $id) + $this->insertPostCategory($post->ID,$id); + } + + $title=sqlite_escape_string($post->Title); + $content=sqlite_escape_string($post->Content); + $sql="UPDATE tblPosts SET + modify_time={$post->ModifyTime}, + title='$title', + content='$content', + status={$post->Status} + WHERE id={$post->ID}"; + $this->query($sql); + } + + public function deletePost($id) + { + $cats=$this->queryCategoriesByPostID($id); + foreach($cats as $cat) + $this->deletePostCategory($id,$cat->ID); + $this->query("DELETE FROM tblComments WHERE post_id=$id"); + $this->query("DELETE FROM tblPosts WHERE id=$id"); + } + + protected function populateCommentRecord($row) + { + $commentRecord=new CommentRecord; + $commentRecord->ID=(integer)$row['id']; + $commentRecord->PostID=(integer)$row['post_id']; + $commentRecord->AuthorName=$row['author_name']; + $commentRecord->AuthorEmail=$row['author_email']; + $commentRecord->AuthorWebsite=$row['author_website']; + $commentRecord->AuthorIP=$row['author_ip']; + $commentRecord->CreateTime=(integer)$row['create_time']; + $commentRecord->Content=$row['content']; + $commentRecord->Status=(integer)$row['status']; + return $commentRecord; + } + + public function queryCommentsByPostID($id) + { + $sql="SELECT * FROM tblComments WHERE post_id=$id"; + $result=$this->query($sql); + $rows=sqlite_fetch_all($result,SQLITE_ASSOC); + $comments=array(); + foreach($rows as $row) + $comments[]=$this->populateCommentRecord($row); + return $comments; + } + + public function insertComment($comment) + { + $authorName=sqlite_escape_string($comment->AuthorName); + $authorEmail=sqlite_escape_string($comment->AuthorEmail); + $authorWebsite=sqlite_escape_string($comment->AuthorWebsite); + $content=sqlite_escape_string($comment->Content); + $sql="INSERT INTO tblComments + (post_id,author_name,author_email,author_website,author_ip,create_time,status,content) + VALUES ({$comment->PostID},'$authorName','$authorEmail','$authorWebsite','{$comment->AuthorIP}',{$comment->CreateTime},{$comment->Status},'$content')"; + $this->query($sql); + $comment->ID=sqlite_last_insert_rowid($this->_db); + $this->query("UPDATE tblPosts SET comment_count=comment_count+1 WHERE id={$comment->PostID}"); + } + + public function updateComment($comment) + { + $authorName=sqlite_escape_string($comment->AuthorName); + $authorEmail=sqlite_escape_string($comment->AuthorEmail); + $content=sqlite_escape_string($comment->Content); + $sql="UPDATE tblComments SET status={$comment->Status} WHERE id={$comment->ID}"; + $this->query($sql); + } + + public function deleteComment($id) + { + $result=$this->query("SELECT post_id FROM tblComments WHERE id=$id"); + if(($row=sqlite_fetch_array($result,SQLITE_ASSOC))!==false) + { + $postID=$row['post_id']; + $this->query("DELETE FROM tblComments WHERE id=$id"); + $this->query("UPDATE tblPosts SET comment_count=comment_count-1 WHERE id=$postID"); + } + } + + protected function populateCategoryRecord($row) + { + $catRecord=new CategoryRecord; + $catRecord->ID=(integer)$row['id']; + $catRecord->Name=$row['name']; + $catRecord->Description=$row['description']; + $catRecord->PostCount=$row['post_count']; + return $catRecord; + } + + public function queryCategories() + { + $sql="SELECT * FROM tblCategories"; + $result=$this->query($sql); + $rows=sqlite_fetch_all($result,SQLITE_ASSOC); + $cats=array(); + foreach($rows as $row) + $cats[]=$this->populateCategoryRecord($row); + return $cats; + } + + public function queryCategoriesByPostID($postID) + { + $sql="SELECT a.id AS id, + a.name AS name, + a.description AS description, + a.post_count AS post_count + FROM tblCategories a, tblPost2Category b + WHERE a.id=b.category_id AND b.post_id=$postID"; + $result=$this->query($sql); + $rows=sqlite_fetch_all($result,SQLITE_ASSOC); + $cats=array(); + foreach($rows as $row) + $cats[]=$this->populateCategoryRecord($row); + return $cats; + } + + public function queryCategoryByID($id) + { + $sql="SELECT * FROM tblCategories WHERE id=$id"; + $result=$this->query($sql); + if(($row=sqlite_fetch_array($result,SQLITE_ASSOC))!==false) + return $this->populateCategoryRecord($row); + else + return null; + } + + public function queryCategoryByName($name) + { + $name=sqlite_escape_string($name); + $sql="SELECT * FROM tblCategories WHERE name='$name'"; + $result=$this->query($sql); + if(($row=sqlite_fetch_array($result,SQLITE_ASSOC))!==false) + return $this->populateCategoryRecord($row); + else + return null; + } + + public function insertCategory($category) + { + $name=sqlite_escape_string($category->Name); + $description=sqlite_escape_string($category->Description); + $sql="INSERT INTO tblCategories + (name,description) + VALUES ('$name','$description')"; + $this->query($sql); + $category->ID=sqlite_last_insert_rowid($this->_db); + } + + public function updateCategory($category) + { + $name=sqlite_escape_string($category->Name); + $description=sqlite_escape_string($category->Description); + $sql="UPDATE tblCategories SET name='$name', description='$description', post_count={$category->PostCount} WHERE id={$category->ID}"; + $this->query($sql); + } + + public function deleteCategory($id) + { + $sql="DELETE FROM tblPost2Category WHERE category_id=$id"; + $this->query($sql); + $sql="DELETE FROM tblCategories WHERE id=$id"; + $this->query($sql); + } + + public function insertPostCategory($postID,$categoryID) + { + $sql="INSERT INTO tblPost2Category (post_id, category_id) VALUES ($postID, $categoryID)"; + $this->query($sql); + $sql="UPDATE tblCategories SET post_count=post_count+1 WHERE id=$categoryID"; + $this->query($sql); + } + + public function deletePostCategory($postID,$categoryID) + { + $sql="DELETE FROM tblPost2Category WHERE post_id=$postID AND category_id=$categoryID"; + if($this->query($sql)>0) + { + $sql="UPDATE tblCategories SET post_count=post_count-1 WHERE id=$categoryID"; + $this->query($sql); + } + } + + public function queryEarliestPostTime() + { + $sql="SELECT MIN(create_time) AS create_time FROM tblPosts"; + $result=$this->query($sql); + if(($row=sqlite_fetch_array($result,SQLITE_ASSOC))!==false) + return $row['create_time']; + else + return time(); + } +} + +class UserRecord +{ + public $ID; + public $Name; + public $FullName; + public $Role; + public $Password; + public $VerifyCode; + public $Email; + public $CreateTime; + public $Status; + public $Website; +} + +class PostRecord +{ + public $ID; + public $AuthorID; + public $AuthorName; + public $CreateTime; + public $ModifyTime; + public $Title; + public $Content; + public $Status; + public $CommentCount; +} + +class CommentRecord +{ + public $ID; + public $PostID; + public $AuthorName; + public $AuthorEmail; + public $AuthorWebsite; + public $AuthorIP; + public $CreateTime; + public $Status; + public $Content; +} + +class CategoryRecord +{ + public $ID; + public $Name; + public $Description; + public $PostCount; +} + +?> \ No newline at end of file diff --git a/demos/blog/protected/Common/BlogErrors.php b/demos/blog/protected/Common/BlogErrors.php new file mode 100644 index 00000000..501ec1c9 --- /dev/null +++ b/demos/blog/protected/Common/BlogErrors.php @@ -0,0 +1,23 @@ +<?php + +class BlogErrors +{ + const ERROR_UKNOWN=0; + const ERROR_POST_NOT_FOUND=1; + const ERROR_USER_NOT_FOUND=2; + const ERROR_PERMISSION_DENIED=3; + + private static $_errorMessages=array( + self::ERROR_UKNOWN=>'Unknown error.', + self::ERROR_POST_NOT_FOUND=>'The specified post cannot be found.', + self::ERROR_USER_NOT_FOUND=>'The specified user account cannot be found.', + self::ERROR_PERMISSION_DENIED=>'Sorry, you do not have permission to perform this action.', + ); + + public static function getMessage($errorCode) + { + return isset(self::$_errorMessages[$errorCode])?self::$_errorMessages[$errorCode]:self::$_errorMessages[0]; + } +} + +?> \ No newline at end of file diff --git a/demos/blog/protected/Common/BlogException.php b/demos/blog/protected/Common/BlogException.php new file mode 100644 index 00000000..ab8020d1 --- /dev/null +++ b/demos/blog/protected/Common/BlogException.php @@ -0,0 +1,14 @@ +<?php + +class BlogException extends TApplicationException +{ + /** + * @return string path to the error message file + */ + protected function getErrorMessageFile() + { + return dirname(__FILE__).'/messages.txt'; + } +} + +?> \ No newline at end of file diff --git a/demos/blog/protected/Common/BlogPage.php b/demos/blog/protected/Common/BlogPage.php new file mode 100644 index 00000000..f1634a80 --- /dev/null +++ b/demos/blog/protected/Common/BlogPage.php @@ -0,0 +1,26 @@ +<?php + +class BlogPage extends TPage +{ + public function getDataAccess() + { + return $this->getApplication()->getModule('data'); + } + + public function gotoDefaultPage() + { + $this->Response->redirect($this->Service->constructUrl($this->Service->DefaultPage)); + } + + public function gotoPage($pagePath,$getParameters=null) + { + $this->Response->redirect($this->Service->constructUrl($pagePath,$getParameters)); + } + + public function reportError($errorCode) + { + $this->gotoPage('ErrorReport',array('id'=>$errorCode)); + } +} + +?> \ No newline at end of file diff --git a/demos/blog/protected/Common/BlogUser.php b/demos/blog/protected/Common/BlogUser.php new file mode 100644 index 00000000..af49c8d7 --- /dev/null +++ b/demos/blog/protected/Common/BlogUser.php @@ -0,0 +1,38 @@ +<?php + +Prado::using('System.Security.TUser'); + +class BlogUser extends TUser +{ + private $_id; + + public function getID() + { + return $this->_id; + } + + public function setID($value) + { + $this->_id=$value; + } + + public function saveToString() + { + $a=array($this->_id,parent::saveToString()); + return serialize($a); + } + + public function loadFromString($data) + { + if(!empty($data)) + { + list($id,$str)=unserialize($data); + $this->_id=$id; + return parent::loadFromString($str); + } + else + return $this; + } +} + +?> \ No newline at end of file diff --git a/demos/blog/protected/Common/BlogUserManager.php b/demos/blog/protected/Common/BlogUserManager.php new file mode 100644 index 00000000..c3ddb80b --- /dev/null +++ b/demos/blog/protected/Common/BlogUserManager.php @@ -0,0 +1,56 @@ +<?php + +Prado::using('System.Security.IUserManager'); +Prado::using('Application.Common.BlogUser'); + +class BlogUserManager extends TModule implements IUserManager +{ + 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) + return new BlogUser($this); + else + { + $username=strtolower($username); + $db=$this->Application->getModule('data'); + if(($userRecord=$db->queryUserByName($username))!==null) + { + $user=new BlogUser($this); + $user->setID($userRecord->ID); + $user->setName($username); + $user->setIsGuest(false); + $user->setRoles($userRecord->Role===0?'user':'admin'); + return $user; + } + else + return null; + } + } + + /** + * 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) + { + $db=$this->Application->getModule('data'); + if(($userRecord=$db->queryUserByName($username))!==null) + return $userRecord->Password===md5($password); + else + return false; + } +} + +?> \ No newline at end of file diff --git a/demos/blog/protected/Common/XListMenu.php b/demos/blog/protected/Common/XListMenu.php new file mode 100644 index 00000000..f8223585 --- /dev/null +++ b/demos/blog/protected/Common/XListMenu.php @@ -0,0 +1,127 @@ +<?php +/** + * XListMenu and XListMenuItem class file + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2006 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Revision: $ $Date: $ + */ + +Prado::using('System.Web.UI.WebControls.TListControl'); + +/** + * XListMenu class + * + * XListMenu displays a list of hyperlinks that can be used for page menus. + * Menu items adjust their css class automatically according to the current + * page displayed. In particular, a menu item is considered as active if + * the URL it represents is for the page currently displayed. + * + * Usage of XListMenu is similar to PRADO list controls. Each list item has + * two extra properties: {@link XListMenuItem::setPagePath PagePath} and + * {@link XListMenuItem::setNavigateUrl NavigateUrl}. The former is used to + * determine if the item is active or not, while the latter specifies the + * URL for the item. If the latter is not specified, a URL to the page is + * generated automatically. + * + * In template, you may use the following tags to specify a menu: + * <code> + * <com:XListMenu ActiveCssClass="class1" InactiveCssClass="class2"> + * <com:XListMenuItem Text="Menu 1" PagePath="Page1" /> + * <com:XListMenuItem Text="Menu 2" PagePath="Page2" NavigateUrl="/page2" /> + * </com:XListMenu> + * </code> + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2006 PradoSoft + * @license http://www.pradosoft.com/license/ + */ +class XListMenu extends TListControl +{ + public function addParsedObject($object) + { + if($object instanceof XListMenuItem) + parent::addParsedObject($object); + } + + public function getActiveCssClass() + { + return $this->getViewState('ActiveCssClass',''); + } + + public function setActiveCssClass($value) + { + $this->setViewState('ActiveCssClass',$value,''); + } + + public function getInactiveCssClass() + { + return $this->getViewState('InactiveCssClass',''); + } + + public function setInactiveCssClass($value) + { + $this->setViewState('InactiveCssClass',$value,''); + } + + public function render($writer) + { + if(($activeClass=$this->getActiveCssClass())!=='') + $activeClass=' class="'.$activeClass.'"'; + if(($inactiveClass=$this->getInactiveCssClass())!=='') + $inactiveClass=' class="'.$inactiveClass.'"'; + $currentPagePath=$this->getPage()->getPagePath(); + $writer->write("<ul>\n"); + foreach($this->getItems() as $item) + { + $pagePath=$item->getPagePath(); + //if(strpos($currentPagePath.'.',$pagePath.'.')===0) + if($pagePath[strlen($pagePath)-1]==='*') + { + if(strpos($currentPagePath.'.',rtrim($pagePath,'*'))===0) + $cssClass=$activeClass; + else + $cssClass=$inactiveClass; + } + else + { + if($pagePath===$currentPagePath) + $cssClass=$activeClass; + else + $cssClass=$inactiveClass; + } + if(($url=$item->getNavigateUrl())==='') + $url=$this->getService()->constructUrl($pagePath); + $writer->write("<li><a href=\"$url\"$cssClass>".$item->getText()."</a></li>\n"); + } + $writer->write("</ul>"); + } +} + +class XListMenuItem extends TListItem +{ + public function getPagePath() + { + return $this->getValue(); + } + + public function setPagePath($value) + { + $this->setValue($value); + } + + public function getNavigateUrl() + { + return $this->hasAttribute('NavigateUrl')?$this->getAttribute('NavigateUrl'):''; + } + + public function setNavigateUrl($value) + { + $this->setAttribute('NavigateUrl',$value); + } +} + +?> \ No newline at end of file diff --git a/demos/blog/protected/Common/messages.txt b/demos/blog/protected/Common/messages.txt new file mode 100644 index 00000000..deb15ee3 --- /dev/null +++ b/demos/blog/protected/Common/messages.txt @@ -0,0 +1,4 @@ +blogdatamodule_dbconnect_failed = Unable to connect to database: {0} +blogdatamodule_dbfile_invalid = BlogDataModule.DbFile='{0}' is invalid. +blogdatamodule_createdatabase_failed = BlogDataModule failed to create database when executing SQL: {1}. Last SQL error is: {0}. +blogdatamodule_query_failed = Failed to execute SQL: {1}. Last SQL error is: {0}. \ No newline at end of file diff --git a/demos/blog/protected/Common/schema.sql b/demos/blog/protected/Common/schema.sql new file mode 100644 index 00000000..49f6f429 --- /dev/null +++ b/demos/blog/protected/Common/schema.sql @@ -0,0 +1,70 @@ +CREATE TABLE tblUsers ( + id INTEGER NOT NULL PRIMARY KEY, + name VARCHAR(128) NOT NULL UNIQUE, + full_name VARCHAR(128) DEFAULT '', + role INTEGER NOT NULL DEFAULT 0, /* 0: user, 1: admin */ + passwd VARCHAR(128) NOT NULL, + vcode VARCHAR(128) DEFAULT '', + email VARCHAR(128) NOT NULL, + reg_time INTEGER NOT NULL, + status INTEGER NOT NULL DEFAULT 0, /* 0: normal, 1: disabled, 2: pending approval */ + website VARCHAR(128) DEFAULT '' +); + +CREATE TABLE tblPosts ( + id INTEGER NOT NULL PRIMARY KEY, + author_id INTEGER NOT NULL, + create_time INTEGER NOT NULL, + modify_time INTEGER DEFAULT 0, + title VARCHAR(256) NOT NULL, + content TEXT NOT NULL, + status INTEGER NOT NULL DEFAULT 0, /* 0: published, 1: draft, 2: pending approval */ + comment_count INTEGER NOT NULL DEFAULT 0 +); + +CREATE TABLE tblComments ( + id INTEGER NOT NULL PRIMARY KEY, + post_id INTEGER NOT NULL, + author_name VARCHAR(64) NOT NULL, + author_email VARCHAR(128) NOT NULL, + author_website VARCHAR(128) DEFAULT '', + author_ip CHAR(16) NOT NULL, + create_time INTEGER NOT NULL, + status INTEGER NOT NULL DEFAULT 0, /* 0: published, 1: pending approval */ + content TEXT NOT NULL +); + +CREATE TABLE tblCategories ( + id INTEGER NOT NULL PRIMARY KEY, + name VARCHAR(128) NOT NULL UNIQUE, + description TEXT DEFAULT '', + post_count INTEGER NOT NULL DEFAULT 0 +); + +CREATE TABLE tblAttachments ( + id VARCHAR(128) NOT NULL PRIMARY KEY, + post_id INTEGER NOT NULL, + create_time INTEGER NOT NULL, + file_name VARCHAR(128) NOT NULL, + file_size INTEGER NOT NULL, + mime_type VARCHAR(32) NOT NULL DEFAULT 'text/html', + download_count INTEGER NOT NULL DEFAULT 0 +); + +CREATE TABLE tblPost2Category ( + post_id INTEGER NOT NULL, + category_id INTEGER NOT NULL, + PRIMARY KEY (post_id, category_id) +); + +INSERT INTO tblUsers (id,name,full_name,role,status,passwd,email,reg_time,website) + VALUES (1,'admin','Prado User',1,0,'4d688da592969d0a56b5accec3ce8554','admin@example.com',1148819681,'http://www.pradosoft.com'); + +INSERT INTO tblPosts (id,author_id,create_time,title,content,status) + VALUES (1,1,1148819691,'Welcome to Prado Weblog','Congratulations! You have successfully installed Prado Weblog. An administrator account has been created. Please login with <b>admin/prado</b> and update your password as soon as possible.',0); + +INSERT INTO tblCategories (name,description,post_count) + VALUES ('Miscellaneous','This category holds posts on any topic.',1); + +INSERT INTO tblPost2Category (post_id,category_id) + VALUES (1,1); diff --git a/demos/blog/protected/Data/Options.xml b/demos/blog/protected/Data/Options.xml new file mode 100644 index 00000000..02e51a98 --- /dev/null +++ b/demos/blog/protected/Data/Options.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> + +<parameters> + <parameter id="SiteTitle" value="Qiang's Blog" /> + <parameter id="SiteSubtitle" value="A PRADO-driven weblog" /> + <parameter id="SiteOwner" value="Qiang Xue" /> + <parameter id="AdminEmail" value="admin@example.com" /> +</parameters> \ No newline at end of file diff --git a/demos/blog/protected/Layouts/MainLayout.php b/demos/blog/protected/Layouts/MainLayout.php new file mode 100644 index 00000000..253d6c03 --- /dev/null +++ b/demos/blog/protected/Layouts/MainLayout.php @@ -0,0 +1,7 @@ +<?php + +class MainLayout extends TTemplateControl +{ +} + +?> \ No newline at end of file diff --git a/demos/blog/protected/Layouts/MainLayout.tpl b/demos/blog/protected/Layouts/MainLayout.tpl new file mode 100644 index 00000000..f171de7f --- /dev/null +++ b/demos/blog/protected/Layouts/MainLayout.tpl @@ -0,0 +1,47 @@ +<!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" xml:lang="en" > + +<com:THead Title=<%$ SiteName %> > +<meta http-equiv="Expires" content="Fri, Jan 01 1900 00:00:00 GMT"/> +<meta http-equiv="Pragma" content="no-cache"/> +<meta http-equiv="Cache-Control" content="no-cache"/> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> +<meta http-equiv="content-language" content="en"/> +</com:THead> + +<body> +<div id="page"> +<com:TForm> + +<div id="header"> +<h1 id="header-title"><%$ SiteTitle %></h1> +<h2 id="header-subtitle"><%$ SiteSubtitle %></h2> +</div><!-- end of header --> + +<div id="main"> +<com:TContentPlaceHolder ID="Main" /> +</div><!-- end of main --> + +<div id="sidebar"> + +<com:Application.Portlets.LoginPortlet Visible=<%= $this->User->IsGuest %>/> + +<com:Application.Portlets.AccountPortlet Visible=<%= !$this->User->IsGuest %>/> + +<com:Application.Portlets.SearchPortlet /> + +<com:Application.Portlets.CategoryPortlet /> + +<com:Application.Portlets.ArchivePortlet /> + +</div><!-- end of sidebar --> + +<div id="footer"> +Copyright © 2006 <%$ SiteOwner %>.<br/> +<%= Prado::poweredByPrado() %> +</div><!-- end of footer --> + +</com:TForm> +</div><!-- end of page --> +</body> +</html> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Admin/AdminMenu.php b/demos/blog/protected/Pages/Admin/AdminMenu.php new file mode 100644 index 00000000..40f40b88 --- /dev/null +++ b/demos/blog/protected/Pages/Admin/AdminMenu.php @@ -0,0 +1,7 @@ +<?php + +class AdminMenu extends TTemplateControl +{ +} + +?> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Admin/AdminMenu.tpl b/demos/blog/protected/Pages/Admin/AdminMenu.tpl new file mode 100644 index 00000000..596f3ed2 --- /dev/null +++ b/demos/blog/protected/Pages/Admin/AdminMenu.tpl @@ -0,0 +1,16 @@ +<div class="submenu"> +<com:XListMenu ActiveCssClass="submenu-active" InactiveCssClass="submenu-inactive"> + <com:XListMenuItem + Text="Posts" + PagePath="Admin.PostMan" + NavigateUrl=<%= $this->Service->constructUrl('Admin.PostMan') %> /> + <com:XListMenuItem + Text="Users" + PagePath="Admin.UserMan" + NavigateUrl=<%= $this->Service->constructUrl('Admin.UserMan') %> /> + <com:XListMenuItem + Text="Configurations" + PagePath="Admin.ConfigMan" + NavigateUrl=<%= $this->Service->constructUrl('Admin.ConfigMan') %> /> +</com:XListMenu> +</div> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Admin/ConfigMan.page b/demos/blog/protected/Pages/Admin/ConfigMan.page new file mode 100644 index 00000000..ad728284 --- /dev/null +++ b/demos/blog/protected/Pages/Admin/ConfigMan.page @@ -0,0 +1,56 @@ +<com:TContent ID="Main"> + +<h2>Administration Center</h2> + +<com:Application.Pages.Admin.AdminMenu /> + +<com:TPanel GroupingText="Site settings"> + +<span class="input-label">Title</span> +<br/> +<com:TTextBox ID="SiteTitle" /> +<br/> + +<span class="input-label">Subtitle</span> +<br/> +<com:TTextBox ID="SiteSubtitle" /> +<br/> + +<span class="input-label">Owner name</span> +<br/> +<com:TTextBox ID="SiteOwner" /> +<br/> + +<span class="input-label">Owner email</span> +<br/> +<com:TTextBox ID="AdminEmail" /> +<br/> + +<span class="input-label">Site theme</span> +<br/> +<com:TDropDownList ID="ThemeList" /> +<br/> + +</com:TPanel> + + +<com:TPanel GroupingText="Account settings"> + +<com:TCheckBox ID="MultipleUser" Text="Allow multiple users" /> +<br/> + +<com:TCheckBox ID="AccountApproval" Text="New accounts need approval" /> +<br/> + +</com:TPanel> + +<com:TPanel GroupingText="Post settings"> + +<com:TCheckBox ID="PostApproval" Text="New posts need approval" /> +<br/> + +</com:TPanel> + +<com:TLinkButton Text="Save" OnClick="saveButtonClicked" /> + +</com:TContent> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Admin/ConfigMan.php b/demos/blog/protected/Pages/Admin/ConfigMan.php new file mode 100644 index 00000000..dcbe1537 --- /dev/null +++ b/demos/blog/protected/Pages/Admin/ConfigMan.php @@ -0,0 +1,15 @@ +<?php + +class ConfigMan extends BlogPage +{ + public function onLoad($param) + { + parent::onLoad($param); + } + + public function saveButtonClicked($sender,$param) + { + } +} + +?> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Admin/PostMan.page b/demos/blog/protected/Pages/Admin/PostMan.page new file mode 100644 index 00000000..8ba8ef29 --- /dev/null +++ b/demos/blog/protected/Pages/Admin/PostMan.page @@ -0,0 +1,76 @@ +<com:TContent ID="Main"> + +<h2>Administration Center</h2> + +<com:Application.Pages.Admin.AdminMenu /> + +<com:TDataGrid ID="PostGrid" + AutoGenerateColumns="false" + DataKeyField="ID" + CssClass="grid" + HeaderStyle.CssClass="grid-header" + ItemStyle.CssClass="grid-row1" + SelectedItemStyle.CssClass="grid-row-selected" + AlternatingItemStyle.CssClass="grid-row2" + AllowPaging="true" + AllowCustomPaging="true" + PageSize="20" + PagerStyle.CssClass="grid-pager" + PagerStyle.Mode="Numeric" + OnPageIndexChanged="changePage" + OnPagerCreated="pagerCreated" + OnEditCommand="editItem" + OnUpdateCommand="saveItem" + OnCancelCommand="cancelItem" + > + <com:THyperLinkColumn + HeaderText="Title" + DataNavigateUrlField="ID" + DataNavigateUrlFormatString="#$this->Service->constructUrl('Posts.ViewPost',array('id'=>{0}))" + DataTextField="Title" + /> + <com:THyperLinkColumn + HeaderText="Author" + DataNavigateUrlField="AuthorID" + DataNavigateUrlFormatString="#$this->Service->constructUrl('Users.ViewUser',array('id'=>{0}))" + DataTextField="AuthorName" + /> + <com:TTemplateColumn + HeaderText="Status" + ItemStyle.HorizontalAlign="Center" + ItemStyle.Width="90px" > + <prop:ItemTemplate> + <%# + $this->Parent->DataItem->Status===0 ? + 'Published' : + ($this->Parent->DataItem->Status===1 ? 'Draft' : 'Pending') + %> + </prop:ItemTemplate> + <prop:EditItemTemplate> + <com:TDropDownList ID="PostStatus" SelectedValue=<%# $this->Parent->DataItem->Status %> > + <com:TListItem Value="0" Text="Published" /> + <com:TListItem Value="1" Text="Draft" /> + <com:TListItem Value="2" Text="Pending" /> + </com:TDropDownList> + </prop:EditItemTemplate> + </com:TTemplateColumn> + <com:TBoundColumn + HeaderText="Time" + ReadOnly="true" + DataField="CreateTime" + DataFormatString="#date('M j, Y',{0})" + ItemStyle.Wrap="false" + ItemStyle.Width="90px" + ItemStyle.HorizontalAlign="Center" + /> + <com:TEditCommandColumn + HeaderText="Command" + HeaderStyle.Width="80px" + UpdateText="Save" + ItemStyle.HorizontalAlign="Center" + ItemStyle.Wrap="false" + ItemStyle.Width="80px" + /> +</com:TDataGrid> + +</com:TContent> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Admin/PostMan.php b/demos/blog/protected/Pages/Admin/PostMan.php new file mode 100644 index 00000000..a99332eb --- /dev/null +++ b/demos/blog/protected/Pages/Admin/PostMan.php @@ -0,0 +1,56 @@ +<?php + +class PostMan extends BlogPage +{ + protected function bindData() + { + $offset=$this->PostGrid->CurrentPageIndex*$this->PostGrid->PageSize; + $limit=$this->PostGrid->PageSize; + $this->PostGrid->DataSource=$this->DataAccess->queryPosts('','','','ORDER BY a.status DESC, create_time DESC',"LIMIT $offset,$limit"); + $this->PostGrid->VirtualItemCount=$this->DataAccess->queryPostCount('','',''); + $this->PostGrid->dataBind(); + } + + public function onLoad($param) + { + parent::onLoad($param); + if(!$this->IsPostBack) + $this->bindData(); + } + + public function changePage($sender,$param) + { + $this->PostGrid->CurrentPageIndex=$param->NewPageIndex; + $this->bindData(); + } + + public function pagerCreated($sender,$param) + { + $param->Pager->Controls->insertAt(0,'Page: '); + } + + public function editItem($sender,$param) + { + $this->PostGrid->EditItemIndex=$param->Item->ItemIndex; + $this->bindData(); + } + + public function saveItem($sender,$param) + { + $item=$param->Item; + $postID=$this->PostGrid->DataKeys[$item->ItemIndex]; + $postRecord=$this->DataAccess->queryPostByID($postID); + $postRecord->Status=TPropertyValue::ensureInteger($item->Cells[2]->PostStatus->SelectedValue); + $this->DataAccess->updatePost($postRecord); + $this->PostGrid->EditItemIndex=-1; + $this->bindData(); + } + + public function cancelItem($sender,$param) + { + $this->PostGrid->EditItemIndex=-1; + $this->bindData(); + } +} + +?> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Admin/Settings.page b/demos/blog/protected/Pages/Admin/Settings.page new file mode 100644 index 00000000..48dfde96 --- /dev/null +++ b/demos/blog/protected/Pages/Admin/Settings.page @@ -0,0 +1,4 @@ +<com:TContent ID="main" > +Welcome, <com:TLabel Text=<%= $this->User->Name %> />! +This page contains site settings accessible only to site admin. +</com:TContent> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Admin/UserMan.page b/demos/blog/protected/Pages/Admin/UserMan.page new file mode 100644 index 00000000..a8b634c6 --- /dev/null +++ b/demos/blog/protected/Pages/Admin/UserMan.page @@ -0,0 +1,95 @@ +<com:TContent ID="Main"> + +<h2>Administration Center</h2> + +<com:Application.Pages.Admin.AdminMenu /> + +<com:TDataGrid ID="UserGrid" + AutoGenerateColumns="false" + DataKeyField="ID" + CssClass="grid" + HeaderStyle.CssClass="grid-header" + ItemStyle.CssClass="grid-row1" + SelectedItemStyle.CssClass="grid-row-selected" + AlternatingItemStyle.CssClass="grid-row2" + AllowPaging="true" + AllowCustomPaging="true" + PageSize="20" + PagerStyle.CssClass="grid-pager" + PagerStyle.Mode="Numeric" + OnPageIndexChanged="changePage" + OnPagerCreated="pagerCreated" + OnEditCommand="editItem" + OnUpdateCommand="saveItem" + OnCancelCommand="cancelItem" + > + <com:THyperLinkColumn + HeaderText="Name" + DataNavigateUrlField="ID" + DataNavigateUrlFormatString="#$this->Service->constructUrl('Users.ViewUser',array('id'=>{0}))" + DataTextField="Name" + /> + <com:TTemplateColumn + HeaderText="Role" + ItemStyle.HorizontalAlign="Center" + ItemStyle.Wrap="false" + ItemStyle.Width="7px" > + <prop:ItemTemplate> + <%# $this->Parent->DataItem->Role===0 ? 'User' : 'Admin' %> + </prop:ItemTemplate> + <prop:EditItemTemplate> + <com:TDropDownList ID="UserRole" SelectedValue=<%# $this->Parent->DataItem->Role %> > + <com:TListItem Value="0" Text="User" /> + <com:TListItem Value="1" Text="Admin" /> + </com:TDropDownList> + </prop:EditItemTemplate> + </com:TTemplateColumn> + <com:TTemplateColumn + HeaderText="Status" + ItemStyle.HorizontalAlign="Center" + ItemStyle.Wrap="false" + ItemStyle.Width="70px" > + <prop:ItemTemplate> + <%# + $this->Parent->DataItem->Status===0 ? + 'Normal' : + ($this->Parent->DataItem->Status===1 ? 'Disabled' : 'Pending') + %> + </prop:ItemTemplate> + <prop:EditItemTemplate> + <com:TDropDownList ID="UserStatus" SelectedValue=<%# $this->Parent->DataItem->Status %> > + <com:TListItem Value="0" Text="Normal" /> + <com:TListItem Value="1" Text="Disabled" /> + <com:TListItem Value="2" Text="Pending" /> + </com:TDropDownList> + </prop:EditItemTemplate> + </com:TTemplateColumn> + <com:TBoundColumn + HeaderText="Email" + DataField="Email" + ItemStyle.Wrap="false" + ItemStyle.Width="90px" + ReadOnly="true" > + <prop:DataFormatString># + '<a href="mailto:'.{0}.'">'.{0}.'</a>' + </prop:DataFormatString> + </com:TBoundColumn> + <com:TBoundColumn + HeaderText="Reg. Date" + DataField="CreateTime" + DataFormatString="#date('M j, Y',{0})" + ReadOnly="true" + ItemStyle.Wrap="false" + ItemStyle.Width="90px" + /> + <com:TEditCommandColumn + HeaderText="Command" + HeaderStyle.Width="80px" + UpdateText="Save" + ItemStyle.HorizontalAlign="Center" + ItemStyle.Wrap="false" + ItemStyle.Width="80px" + /> +</com:TDataGrid> + +</com:TContent> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Admin/UserMan.php b/demos/blog/protected/Pages/Admin/UserMan.php new file mode 100644 index 00000000..1cb62482 --- /dev/null +++ b/demos/blog/protected/Pages/Admin/UserMan.php @@ -0,0 +1,58 @@ +<?php + +class UserMan extends BlogPage +{ + protected function bindData() + { + $author=$this->User->ID; + $offset=$this->UserGrid->CurrentPageIndex*$this->UserGrid->PageSize; + $limit=$this->UserGrid->PageSize; + $this->UserGrid->DataSource=$this->DataAccess->queryUsers('','ORDER BY status DESC, name ASC',"LIMIT $offset,$limit"); + $this->UserGrid->VirtualItemCount=$this->DataAccess->queryUserCount(''); + $this->UserGrid->dataBind(); + } + + public function onLoad($param) + { + parent::onLoad($param); + if(!$this->IsPostBack) + $this->bindData(); + } + + public function changePage($sender,$param) + { + $this->UserGrid->CurrentPageIndex=$param->NewPageIndex; + $this->bindData(); + } + + public function pagerCreated($sender,$param) + { + $param->Pager->Controls->insertAt(0,'Page: '); + } + + public function editItem($sender,$param) + { + $this->UserGrid->EditItemIndex=$param->Item->ItemIndex; + $this->bindData(); + } + + public function saveItem($sender,$param) + { + $item=$param->Item; + $userID=$this->UserGrid->DataKeys[$item->ItemIndex]; + $userRecord=$this->DataAccess->queryUserByID($userID); + $userRecord->Role=TPropertyValue::ensureInteger($item->Cells[1]->UserRole->SelectedValue); + $userRecord->Status=TPropertyValue::ensureInteger($item->Cells[2]->UserStatus->SelectedValue); + $this->DataAccess->updateUser($userRecord); + $this->UserGrid->EditItemIndex=-1; + $this->bindData(); + } + + public function cancelItem($sender,$param) + { + $this->UserGrid->EditItemIndex=-1; + $this->bindData(); + } +} + +?> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Admin/config.xml b/demos/blog/protected/Pages/Admin/config.xml new file mode 100644 index 00000000..c99e5892 --- /dev/null +++ b/demos/blog/protected/Pages/Admin/config.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> + +<configuration> + <authorization> + <allow roles="admin" /> + <deny users="*" /> + </authorization> +</configuration> \ No newline at end of file diff --git a/demos/blog/protected/Pages/ErrorReport.page b/demos/blog/protected/Pages/ErrorReport.page new file mode 100644 index 00000000..a9b461d9 --- /dev/null +++ b/demos/blog/protected/Pages/ErrorReport.page @@ -0,0 +1,15 @@ +<com:TContent ID="Main"> + +<h2>Error</h2> + +<p> +<%= $this->ErrorMessage %> +</p> + +<p> +Please <a href="mailto:<%$ AdminEmail %>">report to us</a> +if you believe this error is caused by our system. Thanks! +</p> + + +</com:TContent> \ No newline at end of file diff --git a/demos/blog/protected/Pages/ErrorReport.php b/demos/blog/protected/Pages/ErrorReport.php new file mode 100644 index 00000000..3b24170f --- /dev/null +++ b/demos/blog/protected/Pages/ErrorReport.php @@ -0,0 +1,12 @@ +<?php + +class ErrorReport extends BlogPage +{ + public function getErrorMessage() + { + $id=TPropertyValue::ensureInteger($this->Request['id']); + return BlogErrors::getMessage($id); + } +} + +?> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Posts/EditCategory.page b/demos/blog/protected/Pages/Posts/EditCategory.page new file mode 100644 index 00000000..fdde2648 --- /dev/null +++ b/demos/blog/protected/Pages/Posts/EditCategory.page @@ -0,0 +1,36 @@ +<com:TContent ID="Main"> + +<h2>Update Post Category</h2> + +<span class="input-label">Category name</span> +<com:TRequiredFieldValidator + Display="Dynamic" + ControlToValidate="CategoryName" + ErrorMessage="...is required" + ValidationGroup="category" /> +<com:TCustomValidator + ControlToValidate="CategoryName" + ValidationGroup="category" + Display="Dynamic" + OnServerValidate="checkCategoryName" + Text="...must be unique" + ControlCssClass="inputerror" /> +<br/> +<com:TTextBox ID="CategoryName" Columns="50" MaxLength="128" /> +<br/> + +<span class="input-label">Description</span> +<br/> +<com:TTextBox + ID="CategoryDescription" + TextMode="MultiLine" + Columns="50" + Rows="5" /> +<br/> + +<com:TLinkButton + Text="Save" + OnClick="saveButtonClicked" + ValidationGroup="category" /> + +</com:TContent> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Posts/EditCategory.php b/demos/blog/protected/Pages/Posts/EditCategory.php new file mode 100644 index 00000000..fd2d0707 --- /dev/null +++ b/demos/blog/protected/Pages/Posts/EditCategory.php @@ -0,0 +1,44 @@ +<?php + +class EditCategory extends BlogPage +{ + public function getCurrentCategory() + { + $id=TPropertyValue::ensureInteger($this->Request['id']); + if(($cat=$this->DataAccess->queryCategoryByID($id))!==null) + return $cat; + else + throw new BlogException('xxx'); + } + + public function onLoad($param) + { + parent::onLoad($param); + if(!$this->IsPostBack) + { + $catRecord=$this->getCurrentCategory(); + $this->CategoryName->Text=$catRecord->Name; + $this->CategoryDescription->Text=$catRecord->Description; + } + } + + public function saveButtonClicked($sender,$param) + { + if($this->IsValid) + { + $categoryRecord=$this->getCurrentCategory(); + $categoryRecord->Name=$this->CategoryName->Text; + $categoryRecord->Description=$this->CategoryDescription->Text; + $this->DataAccess->updateCategory($categoryRecord); + $this->gotoPage('Posts.ListPost',array('cat'=>$categoryRecord->ID)); + } + } + + public function checkCategoryName($sender,$param) + { + $name=$this->CategoryName->Text; + $param->IsValid=$this->DataAccess->queryCategoryByName($name)===null; + } +} + +?> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Posts/EditPost.page b/demos/blog/protected/Pages/Posts/EditPost.page new file mode 100644 index 00000000..591f5945 --- /dev/null +++ b/demos/blog/protected/Pages/Posts/EditPost.page @@ -0,0 +1,41 @@ +<com:TContent ID="Main"> + +<h2>Update Post</h2> + +Title +<com:TRequiredFieldValidator +Display="Dynamic" + ControlToValidate="Title" + ErrorMessage="...is required" + ValidationGroup="post" /> +<br/> +<com:TTextBox ID="Title" Columns="70" MaxLength="256" /> +<br/> + +Content +<com:TRequiredFieldValidator +Display="Dynamic" + ControlToValidate="Content" + ErrorMessage="...is required" + ValidationGroup="post" /> +<br/> +<com:THtmlArea ID="Content" Width="450px" /> +<br/> + +Categories<br/> +<com:TListBox + ID="Categories" + SelectionMode="Multiple" + DataTextField="Name" + DataValueField="ID" /> +<br/> + +<com:TCheckBox ID="DraftMode" Text="in draft mode (the post will not be published)" /> +<br/> + +<com:TLinkButton + Text="Save" + OnClick="saveButtonClicked" + ValidationGroup="post" /> + +</com:TContent> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Posts/EditPost.php b/demos/blog/protected/Pages/Posts/EditPost.php new file mode 100644 index 00000000..57e92b1c --- /dev/null +++ b/demos/blog/protected/Pages/Posts/EditPost.php @@ -0,0 +1,51 @@ +<?php + +class EditPost extends BlogPage +{ + public function getCurrentPost() + { + $id=TPropertyValue::ensureInteger($this->Request['id']); + if(($post=$this->DataAccess->queryPostByID($id))!==null) + return $post; + else + throw new BlogException('xxx'); + } + + public function onLoad($param) + { + parent::onLoad($param); + if(!$this->IsPostBack) + { + $postRecord=$this->getCurrentPost(); + $this->Title->Text=$postRecord->Title; + $this->Content->Text=$postRecord->Content; + $this->DraftMode->Checked=$postRecord->Status===0; + $this->Categories->DataSource=$this->DataAccess->queryCategories(); + $this->Categories->dataBind(); + $cats=$this->DataAccess->queryCategoriesByPostID($postRecord->ID); + $catIDs=array(); + foreach($cats as $cat) + $catIDs[]=$cat->ID; + $this->Categories->SelectedValues=$catIDs; + } + } + + public function saveButtonClicked($sender,$param) + { + if($this->IsValid) + { + $postRecord=$this->getCurrentPost(); + $postRecord->Title=$this->Title->Text; + $postRecord->Content=$this->Content->Text; + $postRecord->Status=$this->DraftMode->Checked?0:1; + $postRecord->ModifyTime=time(); + $cats=array(); + foreach($this->Categories->SelectedValues as $value) + $cats[]=TPropertyValue::ensureInteger($value); + $this->DataAccess->updatePost($postRecord,$cats); + $this->gotoPage('Posts.ViewPost',array('id'=>$postRecord->ID)); + } + } +} + +?> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Posts/ListPost.page b/demos/blog/protected/Pages/Posts/ListPost.page new file mode 100644 index 00000000..15fc3d0c --- /dev/null +++ b/demos/blog/protected/Pages/Posts/ListPost.page @@ -0,0 +1,27 @@ +<com:TContent ID="Main"> + +<com:TRepeater ID="PostList" EnableViewState="false"> + <prop:ItemTemplate> +<div class="post"> +<div class="post-title"> +<%# $this->DataItem->Title %> +</div> +<div class="post-time"> +<%# date('l, F j, Y \a\t h:i:s a',$this->DataItem->CreateTime) %> +</div> +<div class="post-content"> +<%# $this->DataItem->Content %> +</div> +<div class="post-footer"> +posted by +<%# '<a href="' . $this->Service->constructUrl('Users.ViewUser',array('id'=>$this->DataItem->AuthorID)) . '">' . $this->DataItem->AuthorName . '</a>' %> +| +<%# '<a href="' . $this->Service->constructUrl('Posts.ViewPost',array('id'=>$this->DataItem->ID)) . '">PermaLink</a>' %> +| +<%# '<a href="' . $this->Service->constructUrl('Posts.ViewPost',array('id'=>$this->DataItem->ID)) . '#comments">Comments (' . $this->DataItem->CommentCount . ')</a>' %> +</div> +</div> + </prop:ItemTemplate> +</com:TRepeater> + +</com:TContent> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Posts/ListPost.php b/demos/blog/protected/Pages/Posts/ListPost.php new file mode 100644 index 00000000..6d56b543 --- /dev/null +++ b/demos/blog/protected/Pages/Posts/ListPost.php @@ -0,0 +1,44 @@ +<?php + +class ListPost extends BlogPage +{ + const DEFAULT_LIMIT=10; + + public function getPosts() + { + $timeFilter=''; + $catFilter=''; + if(($time=TPropertyValue::ensureInteger($this->Request['time']))>0) + { + $year=(integer)($time/100); + $month=$time%100; + $startTime=mktime(0,0,0,$month,1,$year); + if(++$month>12) + { + $month=1; + $year++; + } + $endTime=mktime(0,0,0,$month,1,$year); + $timeFilter="create_time>=$startTime AND create_time<$endTime"; + } + if(($catID=$this->Request['cat'])!==null) + { + $catID=TPropertyValue::ensureInteger($catID); + $catFilter="category_id=$catID"; + } + if(($offset=TPropertyValue::ensureInteger($this->Request['offset']))<=0) + $offset=0; + if(($limit=TPropertyValue::ensureInteger($this->Request['limit']))<=0) + $limit=self::DEFAULT_LIMIT; + return $this->DataAccess->queryPosts('',$timeFilter,$catFilter,'ORDER BY create_time DESC',"LIMIT $offset,$limit"); + } + + public function onLoad($param) + { + parent::onLoad($param); + $this->PostList->DataSource=$this->getPosts(); + $this->PostList->dataBind(); + } +} + +?> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Posts/MyPost.page b/demos/blog/protected/Pages/Posts/MyPost.page new file mode 100644 index 00000000..95a32ac9 --- /dev/null +++ b/demos/blog/protected/Pages/Posts/MyPost.page @@ -0,0 +1,46 @@ +<com:TContent ID="Main"> + +<h2>My Posts</h2> + +<com:TDataGrid ID="PostGrid" + AutoGenerateColumns="false" + CssClass="grid" + HeaderStyle.CssClass="grid-header" + ItemStyle.CssClass="grid-row1" + AlternatingItemStyle.CssClass="grid-row2" + AllowPaging="true" + AllowCustomPaging="true" + PageSize="20" + PagerStyle.CssClass="grid-pager" + PagerStyle.Mode="Numeric" + OnPageIndexChanged="changePage" + OnPagerCreated="pagerCreated" + > + <com:THyperLinkColumn + HeaderText="Title" + DataNavigateUrlField="ID" + DataNavigateUrlFormatString="#$this->Service->constructUrl('Posts.ViewPost',array('id'=>{0}))" + DataTextField="Title" + /> + <com:TBoundColumn + HeaderText="Status" + DataField="Status" + DataFormatString="#{0}?'Published':'Draft'" + ItemStyle.Width="70px" + /> + <com:TBoundColumn + HeaderText="Comments" + DataField="CommentCount" + ItemStyle.HorizontalAlign="Center" + ItemStyle.Width="80px" + /> + <com:TBoundColumn + HeaderText="Time" + DataField="CreateTime" + DataFormatString="#date('M j, Y',{0})" + ItemStyle.Wrap="false" + ItemStyle.Width="90px" + /> +</com:TDataGrid> + +</com:TContent> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Posts/MyPost.php b/demos/blog/protected/Pages/Posts/MyPost.php new file mode 100644 index 00000000..be03ca63 --- /dev/null +++ b/demos/blog/protected/Pages/Posts/MyPost.php @@ -0,0 +1,34 @@ +<?php + +class MyPost extends BlogPage +{ + protected function bindData() + { + $author=$this->User->ID; + $offset=$this->PostGrid->CurrentPageIndex*$this->PostGrid->PageSize; + $limit=$this->PostGrid->PageSize; + $this->PostGrid->DataSource=$this->DataAccess->queryPosts("author_id=$author",'','','ORDER BY a.status ASC, create_time DESC',"LIMIT $offset,$limit"); + $this->PostGrid->VirtualItemCount=$this->DataAccess->queryPostCount("author_id=$author",'',''); + $this->PostGrid->dataBind(); + } + + public function onLoad($param) + { + parent::onLoad($param); + if(!$this->IsPostBack) + $this->bindData(); + } + + public function changePage($sender,$param) + { + $this->PostGrid->CurrentPageIndex=$param->NewPageIndex; + $this->bindData(); + } + + public function pagerCreated($sender,$param) + { + $param->Pager->Controls->insertAt(0,'Page: '); + } +} + +?> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Posts/NewCategory.page b/demos/blog/protected/Pages/Posts/NewCategory.page new file mode 100644 index 00000000..92fe1468 --- /dev/null +++ b/demos/blog/protected/Pages/Posts/NewCategory.page @@ -0,0 +1,36 @@ +<com:TContent ID="Main"> + +<h2>New Post Category</h2> + +<span class="input-label">Category name</span> +<com:TRequiredFieldValidator + Display="Dynamic" + ControlToValidate="CategoryName" + ErrorMessage="...is required" + ValidationGroup="category" /> +<com:TCustomValidator + ControlToValidate="CategoryName" + ValidationGroup="category" + Display="Dynamic" + OnServerValidate="checkCategoryName" + Text="...must be unique" + ControlCssClass="inputerror" /> +<br/> +<com:TTextBox ID="CategoryName" Columns="50" MaxLength="128" /> +<br/> + +<span class="input-label">Description</span> +<br/> +<com:TTextBox + ID="CategoryDescription" + TextMode="MultiLine" + Columns="50" + Rows="5" /> +<br/> + +<com:TLinkButton + Text="Save" + OnClick="saveButtonClicked" + ValidationGroup="category" /> + +</com:TContent> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Posts/NewCategory.php b/demos/blog/protected/Pages/Posts/NewCategory.php new file mode 100644 index 00000000..d36f6af1 --- /dev/null +++ b/demos/blog/protected/Pages/Posts/NewCategory.php @@ -0,0 +1,24 @@ +<?php + +class NewCategory extends BlogPage +{ + public function saveButtonClicked($sender,$param) + { + if($this->IsValid) + { + $categoryRecord=new CategoryRecord; + $categoryRecord->Name=$this->CategoryName->Text; + $categoryRecord->Description=$this->CategoryDescription->Text; + $this->DataAccess->insertCategory($categoryRecord); + $this->gotoPage('Posts.ListPost',array('cat'=>$categoryRecord->ID)); + } + } + + public function checkCategoryName($sender,$param) + { + $name=$this->CategoryName->Text; + $param->IsValid=$this->DataAccess->queryCategoryByName($name)===null; + } +} + +?> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Posts/NewPost.page b/demos/blog/protected/Pages/Posts/NewPost.page new file mode 100644 index 00000000..a49188f6 --- /dev/null +++ b/demos/blog/protected/Pages/Posts/NewPost.page @@ -0,0 +1,41 @@ +<com:TContent ID="Main"> + +<h2>Write a New Post</h2> + +Title +<com:TRequiredFieldValidator +Display="Dynamic" + ControlToValidate="Title" + ErrorMessage="...is required" + ValidationGroup="post" /> +<br/> +<com:TTextBox ID="Title" Columns="70" MaxLength="256" /> +<br/> + +Content +<com:TRequiredFieldValidator +Display="Dynamic" + ControlToValidate="Content" + ErrorMessage="...is required" + ValidationGroup="post" /> +<br/> +<com:THtmlArea ID="Content" Width="450px" /> +<br/> + +Categories<br/> +<com:TListBox + ID="Categories" + SelectionMode="Multiple" + DataTextField="Name" + DataValueField="ID" /> +<br/> + +<com:TCheckBox ID="DraftMode" Text="in draft mode (the post will not be published)" /> +<br/> + +<com:TLinkButton + Text="Save" + OnClick="saveButtonClicked" + ValidationGroup="post" /> + +</com:TContent> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Posts/NewPost.php b/demos/blog/protected/Pages/Posts/NewPost.php new file mode 100644 index 00000000..055c7f92 --- /dev/null +++ b/demos/blog/protected/Pages/Posts/NewPost.php @@ -0,0 +1,34 @@ +<?php + +class NewPost extends BlogPage +{ + public function onLoad($param) + { + parent::onLoad($param); + if(!$this->IsPostBack) + { + $this->Categories->DataSource=$this->DataAccess->queryCategories(); + $this->Categories->dataBind(); + } + } + + public function saveButtonClicked($sender,$param) + { + if($this->IsValid) + { + $postRecord=new PostRecord; + $postRecord->Title=$this->Title->Text; + $postRecord->Content=$this->Content->Text; + $postRecord->Status=$this->DraftMode->Checked?0:1; + $postRecord->CreateTime=time(); + $postRecord->AuthorID=$this->User->ID; + $cats=array(); + foreach($this->Categories->SelectedValues as $value) + $cats[]=TPropertyValue::ensureInteger($value); + $this->DataAccess->insertPost($postRecord,$cats); + $this->gotoPage('Posts.ViewPost',array('id'=>$postRecord->ID)); + } + } +} + +?> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Posts/ViewPost.page b/demos/blog/protected/Pages/Posts/ViewPost.page new file mode 100644 index 00000000..4b233615 --- /dev/null +++ b/demos/blog/protected/Pages/Posts/ViewPost.page @@ -0,0 +1,113 @@ +<com:TContent ID="Main"> + +<div class="post"> +<div class="post-title"> +<%= $this->CurrentPost->Title %> +</div> +<div class="post-time"> +<%= date('l, F j, Y \a\t h:i:s a',$this->CurrentPost->CreateTime) %> +by +<%= '<a href="' . $this->Service->constructUrl('Users.ViewUser',array('id'=>$this->CurrentPost->AuthorID)) . '">' . $this->CurrentPost->AuthorName . '</a>' %> +<%= $this->CanEditPost ? '| <a href="' . $this->Service->constructUrl('Posts.EditPost',array('id'=>$this->CurrentPost->ID)) . '">Edit</a> | ' : ''; +%> +<com:TLinkButton + Text="Delete" + OnClick="deleteButtonClicked" + Visible=<%= $this->CanEditPost %> + Attributes.onclick="if(!confirm('Are you sure to delete this post? This will also delete all related comments.')) return false;" + /> +</div> +<div class="post-content"> +<%= $this->CurrentPost->Content %> +</div> +<div class="post-footer"> +<com:TRepeater ID="CategoryList" EnableViewState="false"> + <prop:ItemTemplate> + [ + <a href="<%# $this->Service->constructUrl('Posts.ListPost',array('cat'=>$this->DataItem->ID)) %>"><%# $this->DataItem->Name %></a> + ] + </prop:ItemTemplate> +</com:TRepeater> +</div> +</div> + +<div class="comments"> +<a name="comments"></a> +<h3>Comments</h3> + +<com:TRepeater ID="CommentList" OnItemCommand="repeaterItemCommand"> + <prop:ItemTemplate> +<div class="comment"> +<div class="comment-header"> +<com:TLinkButton + Text="Delete" + Attributes.onclick="if(!confirm('Are you sure to delete this comment?')) return false;" + CommandParameter=<%# $this->DataItem->ID %> + Visible=<%= $this->Page->CanEditPost %> Style="float:right"/> +<%# date('F j, Y \a\t h:i:s a',$this->DataItem->CreateTime) %> +by +<%# $this->DataItem->AuthorWebsite==='' ? + $this->DataItem->AuthorName : + '<a href="' . $this->DataItem->AuthorWebsite . '">' . $this->DataItem->AuthorName . '</a>' %> +</div> +<div class="comment-content"> +<%# $this->DataItem->Content %> +</div> +</div> + </prop:ItemTemplate> +</com:TRepeater> + +<h4>Leave your comment</h4> + +<span class="input-label">Name</span> +<com:TRequiredFieldValidator + ControlToValidate="CommentAuthor" + ValidationGroup="comment"" + Display="Dynamic" + Text="...is required" + ControlCssClass="inputerror" /> +<br/> +<com:TTextBox ID="CommentAuthor" /> +<br/> + +<span class="input-label">Email address</span> +<com:TRequiredFieldValidator + ControlToValidate="CommentEmail" + ValidationGroup="comment"" + Display="Dynamic" + Text="...is required" + ControlCssClass="inputerror" /> +<com:TEmailAddressValidator + ControlToValidate="CommentEmail" + ValidationGroup="comment" + Display="Dynamic" + Text="*" + ErrorMessage="You entered an invalid email address." + ControlCssClass="inputerror" /> +<br/> +<com:TTextBox ID="CommentEmail" /> +<br/> + +<span class="input-label">Personal website</span> +<br/> +<com:TTextBox ID="CommentWebsite" Columns="70"/> +<br/> + +<span class="input-label">Comment</span> +<com:TRequiredFieldValidator + ControlToValidate="CommentContent" + ValidationGroup="comment" + Display="Dynamic" + Text="...is required" + ControlCssClass="inputerror" /> +<br/> +<com:TTextBox ID="CommentContent" TextMode="MultiLine" Columns="55" Rows="10"/> +<br/> + +<com:TLinkButton + Text="Submit" + ValidationGroup="comment" + OnClick="submitCommentButtonClicked" /> + +</div> +</com:TContent> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Posts/ViewPost.php b/demos/blog/protected/Pages/Posts/ViewPost.php new file mode 100644 index 00000000..309bedc1 --- /dev/null +++ b/demos/blog/protected/Pages/Posts/ViewPost.php @@ -0,0 +1,73 @@ +<?php + +class ViewPost extends BlogPage +{ + private $_postID=null; + private $_post=null; + + public function getPostID() + { + if($this->_postID===null) + $this->_postID=TPropertyValue::ensureInteger($this->Request['id']); + return $this->_postID; + } + + public function getCurrentPost() + { + if($this->_post===null) + { + if(($this->_post=$this->DataAccess->queryPostByID($this->getPostID()))===null) + $this->reportError(BlogErrors::ERROR_POST_NOT_FOUND); + } + return $this->_post; + } + + public function getCanEditPost() + { + $user=$this->getUser(); + $authorID=$this->getCurrentPost()->AuthorID; + return $authorID===$user->getID() || $user->isInRole('admin'); + } + + public function onLoad($param) + { + parent::onLoad($param); + $this->CategoryList->DataSource=$this->DataAccess->queryCategoriesByPostID($this->getPostID()); + $this->CategoryList->dataBind(); + $this->CommentList->DataSource=$this->DataAccess->queryCommentsByPostID($this->getPostID()); + $this->CommentList->dataBind(); + } + + public function submitCommentButtonClicked($sender,$param) + { + if($this->IsValid) + { + $commentRecord=new CommentRecord; + $commentRecord->PostID=$this->CurrentPost->ID; + $commentRecord->AuthorName=$this->CommentAuthor->Text; + $commentRecord->AuthorEmail=$this->CommentEmail->Text; + $commentRecord->AuthorWebsite=$this->CommentWebsite->Text; + $commentRecord->AuthorIP=$this->Request->UserHostAddress; + $commentRecord->Content=$this->CommentContent->Text; + $commentRecord->CreateTime=time(); + $commentRecord->Status=0; + $this->DataAccess->insertComment($commentRecord); + $this->Response->reload(); + } + } + + public function deleteButtonClicked($sender,$param) + { + $this->DataAccess->deletePost($this->PostID); + $this->gotoDefaultPage(); + } + + public function repeaterItemCommand($sender,$param) + { + $id=TPropertyValue::ensureInteger($param->CommandParameter); + $this->DataAccess->deleteComment($id); + $this->Response->reload(); + } +} + +?> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Posts/config.xml b/demos/blog/protected/Pages/Posts/config.xml new file mode 100644 index 00000000..1c04e946 --- /dev/null +++ b/demos/blog/protected/Pages/Posts/config.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> + +<configuration> + <authorization> + <deny pages="EditPost,NewPost,MyPost" users="?" /> + </authorization> +</configuration> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Users/EditUser.page b/demos/blog/protected/Pages/Users/EditUser.page new file mode 100644 index 00000000..8c21fd50 --- /dev/null +++ b/demos/blog/protected/Pages/Users/EditUser.page @@ -0,0 +1,74 @@ +<com:TContent ID="Main"> + +<h2>Update Profile</h2> + +<com:TValidationSummary Display="Dynamic" ValidationGroup="user" /> + +<span class="input-label">Username</span> +<br/> +<com:TLabel ID="Username" /> + +<br/> + +<span class="input-label">Full name</span> +<br/> +<com:TTextBox ID="FullName" /> + +<br/> + +<span class="input-label">Password</span> +<br/> +<com:TTextBox ID="Password" TextMode="Password" /> +<com:TRegularExpressionValidator + ControlToValidate="Password" + ValidationGroup="user" + Display="Dynamic" + RegularExpression="[\w\.]{6,16}" + Text="*" + ErrorMessage="Your password must contain only letters, digits and underscores, and it must contain at least 6 and at most 16 characters." + ControlCssClass="inputerror" /> + +<br/> + +<span class="input-label">Re-type Password</span> +<br/> +<com:TTextBox ID="Password2" TextMode="Password" /> +<com:TCompareValidator + ControlToValidate="Password" + ControlToCompare="Password2" + ValidationGroup="user" + Display="Dynamic" + Text="*" + ErrorMessage="Your password entries did not match." + ControlCssClass="inputerror" /> + +<br/> + +<span class="input-label">Email Address</span> +<br/> +<com:TTextBox ID="Email" /> +<com:TRequiredFieldValidator + ControlToValidate="Email" + ValidationGroup="user" + Text="*" + ErrorMessage="Please provide your email address." + ControlCssClass="inputerror" /> +<com:TEmailAddressValidator + ControlToValidate="Email" + ValidationGroup="user" + Display="Dynamic" + Text="*" + ErrorMessage="You entered an invalid email address." + ControlCssClass="inputerror" /> + +<br/> + +<span class="input-label">Personal Website</span> +<br/> +<com:TTextBox ID="Website" AutoTrim="true" /> + +<br/> + +<com:TLinkButton Text="Save" ValidationGroup="user" OnClick="saveButtonClicked" /> + +</com:TContent> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Users/EditUser.php b/demos/blog/protected/Pages/Users/EditUser.php new file mode 100644 index 00000000..e3efcfd1 --- /dev/null +++ b/demos/blog/protected/Pages/Users/EditUser.php @@ -0,0 +1,43 @@ +<?php + +class EditUser extends BlogPage +{ + public function getCurrentUser() + { + if(($user=$this->DataAccess->queryUserByID($this->User->ID))!==null) + return $user; + else + throw new BlogException('xxx'); + } + + public function onLoad($param) + { + parent::onLoad($param); + if(!$this->IsPostBack) + { + $userRecord=$this->getCurrentUser(); + $this->Username->Text=$userRecord->Name; + $this->FullName->Text=$userRecord->FullName; + $this->Email->Text=$userRecord->Email; + $this->Website->Text=$userRecord->Website; + } + } + + public function saveButtonClicked($sender,$param) + { + if($this->IsValid) + { + $userRecord=$this->getCurrentUser(); + if($this->Password->Text!=='') + $userRecord->Password=md5($this->Password->Text); + $userRecord->FullName=$this->FullName->Text; + $userRecord->Email=$this->Email->Text; + $userRecord->Website=$this->Website->Text; + $this->DataAccess->updateUser($userRecord); + $authManager=$this->Application->getModule('auth'); + $this->gotoPage('Users.ViewUser',array('id'=>$userRecord->ID)); + } + } +} + +?> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Users/NewUser.page b/demos/blog/protected/Pages/Users/NewUser.page new file mode 100644 index 00000000..eba2dcec --- /dev/null +++ b/demos/blog/protected/Pages/Users/NewUser.page @@ -0,0 +1,104 @@ +<com:TContent ID="Main"> + +<h2>Create New Account</h2> + +<com:TValidationSummary Display="Dynamic" ValidationGroup="NewUser" /> + +<span class="input-label">Username</span> +<br/> +<com:TTextBox ID="Username" /> +<com:TRequiredFieldValidator + ControlToValidate="Username" + ValidationGroup="NewUser" + Display="Dynamic" + Text="*" + ErrorMessage="Please choose a username." + ControlCssClass="inputerror" /> +<com:TRegularExpressionValidator + ControlToValidate="Username" + ValidationGroup="NewUser" + Display="Dynamic" + RegularExpression="[\w]{3,16}" + Text="*" + ErrorMessage="Your username must contain only letters, digits and underscores, and it must contain at least 3 and at most 16 characters." + ControlCssClass="inputerror" /> +<com:TCustomValidator + ControlToValidate="Username" + ValidationGroup="NewUser" + Display="Dynamic" + OnServerValidate="checkUsername" + Text="*" + ErrorMessage="Sorry, your username is taken by someone else. Please choose another username." + ControlCssClass="inputerror" /> + +<br/> + +<span class="input-label">Full name</span> +<br/> +<com:TTextBox ID="FullName" /> + +<br/> + +<span class="input-label">Password</span> +<br/> +<com:TTextBox ID="Password" TextMode="Password" /> +<com:TRequiredFieldValidator + ControlToValidate="Password" + ValidationGroup="NewUser" + Display="Dynamic" + Text="*" + ErrorMessage="Please choose a password." + ControlCssClass="inputerror" /> +<com:TRegularExpressionValidator + ControlToValidate="Password" + ValidationGroup="NewUser" + Display="Dynamic" + RegularExpression="[\w\.]{6,16}" + Text="*" + ErrorMessage="Your password must contain only letters, digits and underscores, and it must contain at least 6 and at most 16 characters." + ControlCssClass="inputerror" /> + +<br/> + +<span class="input-label">Re-type Password</span> +<br/> +<com:TTextBox ID="Password2" TextMode="Password" /> +<com:TCompareValidator + ControlToValidate="Password" + ControlToCompare="Password2" + ValidationGroup="NewUser" + Display="Dynamic" + Text="*" + ErrorMessage="Your password entries did not match." + ControlCssClass="inputerror" /> + +<br/> + +<span class="input-label">Email Address</span> +<br/> +<com:TTextBox ID="Email" /> +<com:TRequiredFieldValidator + ControlToValidate="Email" + ValidationGroup="NewUser" + Text="*" + ErrorMessage="Please provide your email address." + ControlCssClass="inputerror" /> +<com:TEmailAddressValidator + ControlToValidate="Email" + ValidationGroup="NewUser" + Display="Dynamic" + Text="*" + ErrorMessage="You entered an invalid email address." + ControlCssClass="inputerror" /> + +<br/> + +<span class="input-label">Personal Website</span> +<br/> +<com:TTextBox ID="Website" AutoTrim="true" /> + +<br/> + +<com:TLinkButton Text="Register" ValidationGroup="NewUser" OnClick="createUser" /> + +</com:TContent> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Users/NewUser.php b/demos/blog/protected/Pages/Users/NewUser.php new file mode 100644 index 00000000..166abf66 --- /dev/null +++ b/demos/blog/protected/Pages/Users/NewUser.php @@ -0,0 +1,31 @@ +<?php + +class NewUser extends BlogPage +{ + public function checkUsername($sender,$param) + { + $username=$this->Username->Text; + $param->IsValid=$this->DataAccess->queryUserByName($username)===null; + } + + public function createUser($sender,$param) + { + if($this->IsValid) + { + $userRecord=new UserRecord; + $userRecord->Name=$this->Username->Text; + $userRecord->FullName=$this->FullName->Text; + $userRecord->Role=0; + $userRecord->Password=md5($this->Password->Text); + $userRecord->Email=$this->Email->Text; + $userRecord->CreateTime=time(); + $userRecord->Website=$this->Website->Text; + $this->DataAccess->insertUser($userRecord); + $authManager=$this->Application->getModule('auth'); + $authManager->login($this->Username->Text,$this->Password->Text); + $this->gotoDefaultPage(); + } + } +} + +?> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Users/ViewUser.page b/demos/blog/protected/Pages/Users/ViewUser.page new file mode 100644 index 00000000..2dba6b77 --- /dev/null +++ b/demos/blog/protected/Pages/Users/ViewUser.page @@ -0,0 +1,21 @@ +<com:TContent ID="Main"> + +<h2>User Profile</h2> + +Username: <%= $this->CurrentUser->Name %> +<br/> + +Full name: <%= $this->CurrentUser->FullName %> +<br/> + +Email: <%= $this->CurrentUser->Email %> +<br/> + +Privilege: <%= $this->CurrentUser->Role===0? 'User':'Administrator' %> +<br/> + +Personal website: <%= $this->CurrentUser->Website %> +<br/> + + +</com:TContent> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Users/ViewUser.php b/demos/blog/protected/Pages/Users/ViewUser.php new file mode 100644 index 00000000..3485f56b --- /dev/null +++ b/demos/blog/protected/Pages/Users/ViewUser.php @@ -0,0 +1,19 @@ +<?php + +class ViewUser extends BlogPage +{ + private $_currentUser=null; + + public function getCurrentUser() + { + if($this->_currentUser===null) + { + $id=TPropertyValue::ensureInteger($this->Request['id']); + if(($this->_currentUser=$this->DataAccess->queryUserByID($id))===null) + throw new BlogException('xxx'); + } + return $this->_currentUser; + } +} + +?> \ No newline at end of file diff --git a/demos/blog/protected/Pages/Users/config.xml b/demos/blog/protected/Pages/Users/config.xml new file mode 100644 index 00000000..df8e4ad1 --- /dev/null +++ b/demos/blog/protected/Pages/Users/config.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> + +<configuration> + <authorization> + <deny pages="EditUser" users="?" /> + </authorization> +</configuration> \ No newline at end of file diff --git a/demos/blog/protected/Portlets/AccountPortlet.php b/demos/blog/protected/Portlets/AccountPortlet.php new file mode 100644 index 00000000..0f0e60c6 --- /dev/null +++ b/demos/blog/protected/Portlets/AccountPortlet.php @@ -0,0 +1,14 @@ +<?php + +Prado::using('Application.Portlets.Portlet'); + +class AccountPortlet extends Portlet +{ + public function logout($sender,$param) + { + $this->Application->getModule('auth')->logout(); + $this->Response->reload(); + } +} + +?> \ No newline at end of file diff --git a/demos/blog/protected/Portlets/AccountPortlet.tpl b/demos/blog/protected/Portlets/AccountPortlet.tpl new file mode 100644 index 00000000..2a401f41 --- /dev/null +++ b/demos/blog/protected/Portlets/AccountPortlet.tpl @@ -0,0 +1,20 @@ +<div class="portlet"> + +<h2 class="portlet-title">Account</h2> + +<div class="portlet-content"> +Welcome, <b><%= $this->User->Name %></b>! +<ul> +<li><a href="<%= $this->Service->constructUrl('Posts.NewPost') %>">New post</a></li> +<li><a href="<%= $this->Service->constructUrl('Posts.MyPost') %>">My post</a></li> +<li><a href="<%= $this->Service->constructUrl('Users.ViewUser',array('id'=>$this->User->ID)) %>">Profile</a></li> +<%% +if($this->User->isInRole('admin')) + echo '<li><a href="'.$this->Service->constructUrl('Admin.PostMan').'">Admin</a></li>'; +%> +<li><com:TLinkButton Text="Logout" OnClick="logout" /></li> +</ul> + +</div><!-- end of portlet-content --> + +</div><!-- end of portlet --> diff --git a/demos/blog/protected/Portlets/ArchivePortlet.php b/demos/blog/protected/Portlets/ArchivePortlet.php new file mode 100644 index 00000000..a004c7a9 --- /dev/null +++ b/demos/blog/protected/Portlets/ArchivePortlet.php @@ -0,0 +1,45 @@ +<?php + +Prado::using('Application.Portlets.Portlet'); + +class ArchivePortlet extends Portlet +{ + private function makeMonthTime($timestamp) + { + $date=getdate($timestamp); + return mktime(0,0,0,$date['mon'],1,$date['year']); + } + + public function onLoad($param) + { + $currentTime=time(); + $startTime=$this->Application->getModule('data')->queryEarliestPostTime(); + if(empty($startTime)) // if no posts + $startTime=$currentTime; + + // obtain the timestamp for the initial month + $date=getdate($startTime); + $startTime=mktime(0,0,0,$date['mon'],1,$date['year']); + + $date=getdate($currentTime); + $month=$date['mon']; + $year=$date['year']; + + $timestamps=array(); + while(true) + { + if(($timestamp=mktime(0,0,0,$month,1,$year))<$startTime) + break; + $timestamps[]=$timestamp; + if(--$month===0) + { + $month=12; + $year--; + } + } + $this->MonthList->DataSource=$timestamps; + $this->MonthList->dataBind(); + } +} + +?> \ No newline at end of file diff --git a/demos/blog/protected/Portlets/ArchivePortlet.tpl b/demos/blog/protected/Portlets/ArchivePortlet.tpl new file mode 100644 index 00000000..c576e9f5 --- /dev/null +++ b/demos/blog/protected/Portlets/ArchivePortlet.tpl @@ -0,0 +1,15 @@ +<div class="portlet"> + +<h2 class="portlet-title">Archives</h2> + +<div class="portlet-content"> +<ul> +<com:TRepeater ID="MonthList" EnableViewState="false"> + <prop:ItemTemplate> + <li><a href="<%# $this->Service->constructUrl('Posts.ListPost',array('time'=>date('Ym',$this->DataItem))) %>"><%# date('F Y',$this->DataItem) %></a></li> + </prop:ItemTemplate> +</com:TRepeater> +</ul> +</div><!-- end of portlet-content --> + +</div><!-- end of portlet --> diff --git a/demos/blog/protected/Portlets/CategoryPortlet.php b/demos/blog/protected/Portlets/CategoryPortlet.php new file mode 100644 index 00000000..9c2033aa --- /dev/null +++ b/demos/blog/protected/Portlets/CategoryPortlet.php @@ -0,0 +1,15 @@ +<?php + +Prado::using('Application.Portlets.Portlet'); + +class CategoryPortlet extends Portlet +{ + public function onLoad($param) + { + parent::onLoad($param); + $this->CategoryList->DataSource=$this->Application->getModule('data')->queryCategories(); + $this->CategoryList->dataBind(); + } +} + +?> \ No newline at end of file diff --git a/demos/blog/protected/Portlets/CategoryPortlet.tpl b/demos/blog/protected/Portlets/CategoryPortlet.tpl new file mode 100644 index 00000000..acbd3bec --- /dev/null +++ b/demos/blog/protected/Portlets/CategoryPortlet.tpl @@ -0,0 +1,24 @@ +<div class="portlet"> + +<h2 class="portlet-title"> +Categories +<com:THyperLink + Text="[+]" + Tooltip="Create a new category" + NavigateUrl=<%= $this->Service->constructUrl('Posts.NewCategory') %> + Visible=<%= $this->User->isInRole('admin') %> /> +</h2> + +<div class="portlet-content"> +<ul> +<com:TRepeater ID="CategoryList" EnableViewState="false"> + <prop:ItemTemplate> + <li> + <a href="<%# $this->Service->constructUrl('Posts.ListPost',array('cat'=>$this->DataItem->ID)) %>"><%# $this->DataItem->Name . ' (' . $this->DataItem->PostCount . ')' %></a> + </li> + </prop:ItemTemplate> +</com:TRepeater> +</ul> +</div><!-- end of portlet-content --> + +</div><!-- end of portlet --> diff --git a/demos/blog/protected/Portlets/LoginPortlet.php b/demos/blog/protected/Portlets/LoginPortlet.php new file mode 100644 index 00000000..0085c17f --- /dev/null +++ b/demos/blog/protected/Portlets/LoginPortlet.php @@ -0,0 +1,22 @@ +<?php + +Prado::using('Application.Portlets.Portlet'); + +class LoginPortlet extends Portlet +{ + public function validateUser($sender,$param) + { + $authManager=$this->Application->getModule('auth'); + if(!$authManager->login($this->Username->Text,$this->Password->Text)) + $param->IsValid=false; + } + + public function loginButtonClicked($sender,$param) + { + if($this->Page->IsValid) + $this->Response->reload(); + //$this->Response->redirect($this->Application->getModule('auth')->getReturnUrl()); + } +} + +?> \ No newline at end of file diff --git a/demos/blog/protected/Portlets/LoginPortlet.tpl b/demos/blog/protected/Portlets/LoginPortlet.tpl new file mode 100644 index 00000000..6f8c5d4a --- /dev/null +++ b/demos/blog/protected/Portlets/LoginPortlet.tpl @@ -0,0 +1,36 @@ +<div class="portlet"> + +<h2 class="portlet-title">Login</h2> + +<com:TPanel CssClass="portlet-content" DefaultButton="LoginButton"> +Username +<com:TRequiredFieldValidator + ControlToValidate="Username" + ValidationGroup="login" + Text="...is required" + Display="Dynamic"/> +<br/> +<com:TTextBox ID="Username" /> +<br/> + +Password +<com:TCustomValidator + ControlToValidate="Password" + ValidationGroup="login" + Text="...is invalid" + Display="Dynamic" + OnServerValidate="validateUser" /> +<br/> +<com:TTextBox ID="Password" TextMode="Password" /> + +<br/> +<com:TLinkButton + ID="LoginButton" + Text="Login" + ValidationGroup="login" + OnClick="loginButtonClicked" /> +| <a href="<%= $this->Service->constructUrl('Users.NewUser') %>">Register</a> + +</com:TPanel><!-- end of portlet-content --> + +</div><!-- end of portlet --> diff --git a/demos/blog/protected/Portlets/Portlet.php b/demos/blog/protected/Portlets/Portlet.php new file mode 100644 index 00000000..4b1c80e9 --- /dev/null +++ b/demos/blog/protected/Portlets/Portlet.php @@ -0,0 +1,7 @@ +<?php + +class Portlet extends TTemplateControl +{ +} + +?> \ No newline at end of file diff --git a/demos/blog/protected/Portlets/SearchPortlet.php b/demos/blog/protected/Portlets/SearchPortlet.php new file mode 100644 index 00000000..1bad7f1c --- /dev/null +++ b/demos/blog/protected/Portlets/SearchPortlet.php @@ -0,0 +1,22 @@ +<?php + +Prado::using('Application.Portlets.Portlet'); + +class SearchPortlet extends Portlet +{ + public function onInit($param) + { + parent::onInit($param); + if(!$this->Page->IsPostBack && ($keyword=$this->Request['keyword'])!==null) + $this->Keyword->Text=$keyword; + } + + public function search($sender,$param) + { + $keyword=$this->Keyword->Text; + $url=$this->Service->constructUrl('SearchPost',array('keyword'=>$keyword)); + $this->Response->redirect($url); + } +} + +?> \ No newline at end of file diff --git a/demos/blog/protected/Portlets/SearchPortlet.tpl b/demos/blog/protected/Portlets/SearchPortlet.tpl new file mode 100644 index 00000000..f88fca7e --- /dev/null +++ b/demos/blog/protected/Portlets/SearchPortlet.tpl @@ -0,0 +1,21 @@ +<div class="portlet"> + +<h2 class="portlet-title">Search</h2> + +<com:TPanel CssClass="portlet-content" DefaultButton="SearchButton"> +Keyword +<com:TRequiredFieldValidator + ControlToValidate="Keyword" + ValidationGroup="search" + Text="...is required" + Display="Dynamic"/> +<br/> +<com:TTextBox ID="Keyword" /> +<com:TLinkButton + ID="SearchButton" + Text="Search" + ValidationGroup="search" + OnClick="search" /> +</com:TPanel><!-- end of portlet-content --> + +</div><!-- end of portlet --> diff --git a/demos/blog/protected/application.xml b/demos/blog/protected/application.xml new file mode 100644 index 00000000..9bca115c --- /dev/null +++ b/demos/blog/protected/application.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> + +<application id="personal" mode="Debug"> + <paths> + <using namespace="Application.Common.*" /> + </paths> + <!-- modules configured and loaded for all services --> + <modules> + <!-- Remove this comment mark to enable caching + <module id="cache" class="System.Caching.TSqliteCache" /> + --> + <!-- Remove this comment mark to enable PATH url format + <module id="request" class="THttpRequest" UrlFormat="Path" /> + --> + <!-- + <module id="session" class="THttpSession" /> + <module id="log" class="System.Util.TLogRouter"> + <route class="TBrowserLogRoute" /> + <route class="TFileLogRoute" Categories="System" Levels="Notice,Warning,Error,Alert,Fatal" /> + </module> + --> + <module class="System.Util.TParameterModule" ParameterFile="Application.Data.Options" /> + </modules> + <services> + <!-- page service --> + <service id="page" class="TPageService" BasePath="Application.Pages" DefaultPage="Posts.ListPost"> + <!-- modules configured and loaded when page service is requested --> + <modules> + <!-- user manager module --> + <module id="users" class="Application.Common.BlogUserManager" /> + <!-- auth manager module --> + <module id="auth" class="System.Security.TAuthManager" UserManager="users" LoginPage="Posts.ListPost" /> + <module id="data" class="Application.Common.BlogDataModule" /> + </modules> + <pages MasterClass="Application.Layouts.MainLayout" Theme="Basic" /> + </service> + </services> +</application> \ No newline at end of file diff --git a/demos/blog/sitemap.txt b/demos/blog/sitemap.txt new file mode 100644 index 00000000..8326855b --- /dev/null +++ b/demos/blog/sitemap.txt @@ -0,0 +1,106 @@ +Be careful about username case sensitivity!! + +Home : list of blogs filtered by a category or time range, with paging + +ViewBlog : read a single blog with all comments and a comment input form +NewBlog : create a new blog, with file attachment form and THtmlArea +EditBlog : edit an existing blog + +LoginUser : login page +NewUser : create a new user +EditUser : edit the current user + +Admin : whether allow multiple users, whether HTML is allowed (first user is always the admin) + +URL design: + +index.php?page=ListBlog×pan=123,456&limit=123,456 : list of latest blogs, equivalent to: +index.php?page=NewBlog +index.php?page=EditBlog&id=123 +index.php?page=ViewBlog&id=123 +index.php?page=NewUser +index.php?page=EditUser +index.php?page=ViewUser +index.php?page=Admin + + +Use Case 1: Add a post +1. Authorization check +2. display UI for adding post +3. input validation +4. add post to DB +5. display UI for post list + + +DB Logic needed: + +class Post extends DataObject +{ + public $xxx; +} + +class Comment extends DataObject +{ +} + +class UserProfile extends DataObject +{ + +} + +class DataObject extends TComponent +{ + protected static $mapping=array(); + + public function __construct($db) + { + } + + protected static function generateModifier($filter,$orderBy,$limit) + { + $modifier=''; + if($filter!=='') + $modifier=' WHERE '.$filter; + if($orderBy!=='') + $modifier.=' ORDER BY '.$orderBy; + if($limit!=='') + $modifier.=' LIMIT '.$limit; + return $modifier; + } + + public static function queryRow($filter='') + { + $modifier=self::generateModifier($filter,'',''); + } + + public static function query($filter='',$orderBy='',$limit='') + { + $modifier=self::generateModifier($filter,$orderBy,$limit); + } + + public function save() + { + } + + public function delete() + { + } +} + +public function queryUsers($filter='',$sortBy='',$limit='') +public function queryUser($id) +public function insertUser($user) +public function updateUser($user) +public function deleteUser($id) + +public function queryPosts($filter='',$sortBy='',$limit='') +public function queryPost($id) +public function insertPost($post) +public function updatePost($post) +public function deletePost($id) + +public function queryComments($filter='',$sortBy='',$limit='') +public function queryComment($id) +public function insertComment($comment) +public function updateComment($comment) +public function deleteComment($id) diff --git a/demos/blog/themes/Basic/style.css b/demos/blog/themes/Basic/style.css new file mode 100644 index 00000000..b8e9ca89 --- /dev/null +++ b/demos/blog/themes/Basic/style.css @@ -0,0 +1,261 @@ +html { + margin: 0; + padding: 0; +} + +body { + margin: 0; + padding: 0; + font-family: verdana, 'trebuchet ms', sans-serif; + font-size: 12px; + color: #333; + background: #36414d; + min-width:750px; +} + +form { + margin: 0; + padding: 0; +} + +a { + text-decoration: underline; +} + +a img { + border: 0px none; +} + +#page { + background:#fff; + margin:0 auto; + width:800px; +} + +#header { + background: #a3b8cc; + border-bottom: 1px solid silver; +} + +#header h1 { + padding:5px; + padding-left: 20px; + margin:0; + font-size: 16pt; +} + +#header h2 { + padding:5px; + padding-left: 20px; + margin:0; + font-size: 12pt; +} + +#main { + background:#fff; + float:left; + width:560px; + padding: 20px; +} + +#main h2 { + border-bottom: 1px solid silver; +} + +#sidebar { + background:#e6ecf2; + float:right; + width:200px; +} + +#sidebar ul { + margin-bottom:0; +} + +#sidebar h3, #sidebar p { + padding:0 10px 0 0; +} + +#footer { + background:#fff; + clear:both; + color: gray; + font-size:8pt; + text-align:center; + padding-top:25px; + padding-bottom:10px; +} + +.portlet { + margin: 10px; + border-bottom: 1px solid silver; + border-right: 1px solid silver; + background: #dae0e6; +} + +.portlet-title { + /* ie win (5, 5.5, 6) bugfix */ + p\osition: relative; + width: 100%; + w\idth: auto; + + margin: 0; + border-left: 5px solid #36414d; + border-bottom: 1px solid silver; + padding: 5px; + color: #fff; + background: #a3b8cc; + font-size: 8pt; + font-weight: bold; + line-height: 1; + text-transform: uppercase; +} + +.portlet-title a { + color: yellow; + text-decoration: none; + text-transform: none; +} + +.portlet-title a:hover { + color: red; +} + +.portlet-content { + margin: 0 0 10px 0; + border-top: 1px solid #cfd4d9; + padding: 10px 10px 0 10px; + font-size: 10px; +} + +.portlet-content ul { + margin: 0 15px 10px 15px; + padding: 0; + list-style: square; +} + +.portlet-content li { + color: #666; + margin-top: 3px; +} + +.portlet-content a { + color: #36414d; + text-decoration: none; +} + +.portlet-content a:hover { + color: red; +} + +.post { + margin-bottom: 15px; +} + +.post-title { + font-size: 14pt; + border-bottom: 1px silver solid; +} + +.post-time { + font-size: 8pt; + color: gray; +} + +.post-content { + margin-top: 10px; + margin-bottom: 10px; +} + +.post-footer { + text-align: right; + font-size: 8pt; +} + +.post-footer a { + color: #36414d; +} + +.comments { + border-top: 1px silver solid; +} + +.comments h3 { + font-size: 12pt; +} + +.comment { + margin-bottom: 10px; +} + +.comment-header { + background-color: #e6ecf2; + padding: 3px; + font-size: 8pt; +} + +.grid { + width: 100%; +} + +.grid td { + padding: 3px; +} + +.grid-header { + color: #fff; + background: #a3b8cc; +} + +.grid-row1 { + background: #dae0e6; +} + +.grid-row2 { + background: #e6ecf2; +} + +.grid-row-selected { + background: lightyellow; +} + +.grid-pager { + text-align: right; + color: silver; +} + +.grid-pager a { + color: green; + text-decoration: none; +} + +.submenu { + margin-bottom: 10px; + border-bottom: 5px solid #a3b8cc; + padding-right: 10px; + text-align: right; +} + +.submenu ul { + margin:0; + padding:0; + list-style:none; +} + +.submenu li { + display:inline; + margin:0; + padding:0; +} + +.submenu-active { + text-decoration: none; + background: #a3b8cc; + padding: 3px 5px 0 5px; +} + +.submenu-inactive { + text-decoration: none; + background: #dae0e6; + padding: 3px 5px 0 5px; +} \ No newline at end of file -- cgit v1.2.3