diff options
99 files changed, 3557 insertions, 86 deletions
diff --git a/.gitattributes b/.gitattributes index 93ce7505..6b709dfd 100644 --- a/.gitattributes +++ b/.gitattributes @@ -24,6 +24,67 @@ buildscripts/setup.php -text buildscripts/texbuilder/build.php -text buildscripts/texbuilder/pages.php -text buildscripts/texbuilder/prado3_quick_start.tex -text +demos/blog/index.php -text +demos/blog/protected/Common/BlogDataModule.php -text +demos/blog/protected/Common/BlogErrors.php -text +demos/blog/protected/Common/BlogException.php -text +demos/blog/protected/Common/BlogPage.php -text +demos/blog/protected/Common/BlogUser.php -text +demos/blog/protected/Common/BlogUserManager.php -text +demos/blog/protected/Common/XListMenu.php -text +demos/blog/protected/Common/messages.txt -text +demos/blog/protected/Common/schema.sql -text +demos/blog/protected/Data/Options.xml -text +demos/blog/protected/Layouts/MainLayout.php -text +demos/blog/protected/Layouts/MainLayout.tpl -text +demos/blog/protected/Pages/Admin/AdminMenu.php -text +demos/blog/protected/Pages/Admin/AdminMenu.tpl -text +demos/blog/protected/Pages/Admin/ConfigMan.page -text +demos/blog/protected/Pages/Admin/ConfigMan.php -text +demos/blog/protected/Pages/Admin/PostMan.page -text +demos/blog/protected/Pages/Admin/PostMan.php -text +demos/blog/protected/Pages/Admin/Settings.page -text +demos/blog/protected/Pages/Admin/UserMan.page -text +demos/blog/protected/Pages/Admin/UserMan.php -text +demos/blog/protected/Pages/Admin/config.xml -text +demos/blog/protected/Pages/ErrorReport.page -text +demos/blog/protected/Pages/ErrorReport.php -text +demos/blog/protected/Pages/Posts/EditCategory.page -text +demos/blog/protected/Pages/Posts/EditCategory.php -text +demos/blog/protected/Pages/Posts/EditPost.page -text +demos/blog/protected/Pages/Posts/EditPost.php -text +demos/blog/protected/Pages/Posts/ListPost.page -text +demos/blog/protected/Pages/Posts/ListPost.php -text +demos/blog/protected/Pages/Posts/MyPost.page -text +demos/blog/protected/Pages/Posts/MyPost.php -text +demos/blog/protected/Pages/Posts/NewCategory.page -text +demos/blog/protected/Pages/Posts/NewCategory.php -text +demos/blog/protected/Pages/Posts/NewPost.page -text +demos/blog/protected/Pages/Posts/NewPost.php -text +demos/blog/protected/Pages/Posts/ViewPost.page -text +demos/blog/protected/Pages/Posts/ViewPost.php -text +demos/blog/protected/Pages/Posts/config.xml -text +demos/blog/protected/Pages/Users/EditUser.page -text +demos/blog/protected/Pages/Users/EditUser.php -text +demos/blog/protected/Pages/Users/NewUser.page -text +demos/blog/protected/Pages/Users/NewUser.php -text +demos/blog/protected/Pages/Users/ViewUser.page -text +demos/blog/protected/Pages/Users/ViewUser.php -text +demos/blog/protected/Pages/Users/config.xml -text +demos/blog/protected/Portlets/AccountPortlet.php -text +demos/blog/protected/Portlets/AccountPortlet.tpl -text +demos/blog/protected/Portlets/ArchivePortlet.php -text +demos/blog/protected/Portlets/ArchivePortlet.tpl -text +demos/blog/protected/Portlets/CategoryPortlet.php -text +demos/blog/protected/Portlets/CategoryPortlet.tpl -text +demos/blog/protected/Portlets/LoginPortlet.php -text +demos/blog/protected/Portlets/LoginPortlet.tpl -text +demos/blog/protected/Portlets/Portlet.php -text +demos/blog/protected/Portlets/SearchPortlet.php -text +demos/blog/protected/Portlets/SearchPortlet.tpl -text +demos/blog/protected/application.xml -text +demos/blog/sitemap.txt -text +demos/blog/themes/Basic/style.css -text demos/composer/index.php -text demos/composer/index2.php -text demos/composer/protected/pages/ClassDefinition.php -text @@ -863,6 +924,7 @@ framework/Web/Javascripts/colorpicker/target_white.gif -text framework/Web/Javascripts/datepicker/calendar.png -text framework/Web/Javascripts/datepicker/datepicker.js -text framework/Web/Javascripts/datepicker/default.css -text +framework/Web/Javascripts/datepicker/spacer.gif -text framework/Web/Javascripts/effects/CHANGELOG -text framework/Web/Javascripts/effects/MIT-LICENSE -text framework/Web/Javascripts/effects/README -text @@ -1196,20 +1258,38 @@ tests/FunctionalTests/tickets.php -text tests/FunctionalTests/tickets/index.php -text tests/FunctionalTests/tickets/protected/pages/Layout.php -text tests/FunctionalTests/tickets/protected/pages/Layout.tpl -text +tests/FunctionalTests/tickets/protected/pages/TestHtmlArea.php -text +tests/FunctionalTests/tickets/protected/pages/Ticket121.page -text +tests/FunctionalTests/tickets/protected/pages/Ticket121.php -text +tests/FunctionalTests/tickets/protected/pages/Ticket163.page -text +tests/FunctionalTests/tickets/protected/pages/Ticket169.page -text +tests/FunctionalTests/tickets/protected/pages/Ticket191.page -text +tests/FunctionalTests/tickets/protected/pages/Ticket191.php -text tests/FunctionalTests/tickets/protected/pages/Ticket21.page -text tests/FunctionalTests/tickets/protected/pages/Ticket21.php -text tests/FunctionalTests/tickets/protected/pages/Ticket27.page -text +tests/FunctionalTests/tickets/protected/pages/Ticket28.page -text +tests/FunctionalTests/tickets/protected/pages/Ticket28.php -text tests/FunctionalTests/tickets/protected/pages/Ticket54.page -text tests/FunctionalTests/tickets/protected/pages/Ticket54Master.php -text tests/FunctionalTests/tickets/protected/pages/Ticket54Master.tpl -text tests/FunctionalTests/tickets/protected/pages/Ticket68.page -text tests/FunctionalTests/tickets/protected/pages/Ticket72.page -text tests/FunctionalTests/tickets/protected/pages/Ticket72.php -text +tests/FunctionalTests/tickets/protected/pages/Ticket93.page -text +tests/FunctionalTests/tickets/protected/pages/Ticket93.php -text tests/FunctionalTests/tickets/protected/pages/config.xml -text +tests/FunctionalTests/tickets/protected/pages/hotspot.jpg -text +tests/FunctionalTests/tickets/tests/Ticket121TestCase.php -text +tests/FunctionalTests/tickets/tests/Ticket163TestCase.php -text +tests/FunctionalTests/tickets/tests/Ticket169TestCase.php -text +tests/FunctionalTests/tickets/tests/Ticket191TestCase.php -text tests/FunctionalTests/tickets/tests/Ticket21TestCase.php -text tests/FunctionalTests/tickets/tests/Ticket27TestCase.php -text +tests/FunctionalTests/tickets/tests/Ticket28TestCase.php -text tests/FunctionalTests/tickets/tests/Ticket54TestCase.php -text tests/FunctionalTests/tickets/tests/Ticket72TestCase.php -text +tests/FunctionalTests/tickets/tests/Ticket93TestCase.php -text tests/FunctionalTests/validators.php -text tests/FunctionalTests/validators/index.php -text tests/FunctionalTests/validators/protected/pages/Button.page -text @@ -9,9 +9,12 @@ NEW: SQLMap (Wei) Version 3.0.1 June 1, 2006
==========================
+BUG: Ticket#28 - OnClick does not work with Safari/KHTML (Wei)
BUG: Ticket#37 - Changes of config files do not trigger cache update (Qiang)
BUG: Ticket#44 - THtmlArea (tiny_mce) not working on some systems (Qiang)
+BUG: Ticket#162 - Missing currency sign in TNumberFormat if Value is 0 (Wei)
BUG: Ticket#167 - TSecurityManager issues warning when trying to encrypt/decrypt strings (Qiang)
+BUG: Ticket#169 - Validation of subclass of THtmlArea/TDatePicker fails (Wei)
BUG: Ticket#179 - CGI incompatibility causing clientscripts.php failure (Qiang)
BUG: Ticket#181 - Unable to change Content-Type in response header if charset is not set (Qiang)
ENH: Ticket#150 - TDataGrid and TDataList now render table section tags (Qiang)
@@ -21,6 +24,8 @@ ENH: added search for quickstart tutorials (Wei) ENH: added support to property tags for template owner control (Qiang)
ENH: added Bulgarian requirement checker messages (StanProg)
ENH: added TTheme.BaseUrl and TTheme.BasePath property (Qiang)
+ENH: added TListControl.SelectedValues property (Qiang)
+ENH: added TThemeManager.AvailableThemes property (Qiang)
ENH: refactored TUserManager and TAuthManager so that they are easier to be extended (Qiang)
CHG: Ticket#151 - URL format is modified to handle empty GET values (Qiang)
CHG: Ticket#153 - TAssetManager now ignores .svn directories (Qiang)
diff --git a/buildscripts/jsbuilder/build.php b/buildscripts/jsbuilder/build.php index 33e5d133..50fb8b56 100644 --- a/buildscripts/jsbuilder/build.php +++ b/buildscripts/jsbuilder/build.php @@ -22,11 +22,11 @@ /**
* The root directory for storing all source js files
*/
-define('SOURCE_DIR',realpath(dirname(__FILE__).'/../../framework/Web/JavaScripts'));
+define('SOURCE_DIR',realpath(dirname(__FILE__).'/../../framework/Web/Javascripts'));
/**
* The directory for storing compressed js files
*/
-define('TARGET_DIR',realpath(dirname(__FILE__).'/../../framework/Web/JavaScripts/js'));
+define('TARGET_DIR',realpath(dirname(__FILE__).'/../../framework/Web/Javascripts/js'));
/**
* Location of the perl JS doc generator.
*/
@@ -148,20 +148,20 @@ $builds = 0; /**
* loop through all target files and build them one by one
*/
-foreach($libraries as $libFile => $sourceFiles)
+foreach($libraries as $jsFile => $sourceFiles)
{
- if(!empty($requestedLibs) && !in_array($libFile,$requestedLibs))
+ if(!empty($requestedLibs) && !in_array($jsFile,$requestedLibs))
continue;
- $libFile=TARGET_DIR.'/'.$libFile;
- echo "\nBuilding $libFile...\n";
+ $libFile=TARGET_DIR.'/'.$jsFile;
+ echo "\nBuilding $jsFile...\n";
$contents='';
- foreach($sourceFiles as $sourceFile)
+ foreach($sourceFiles as $sourceJsFile)
{
- $sourceFile=SOURCE_DIR.'/'.$sourceFile;
+ $sourceFile=SOURCE_DIR.'/'.$sourceJsFile;
if(!is_file($sourceFile))
echo "Source file not found: $sourceFile\n";
- echo "...adding $sourceFile\n";
+ echo "...adding $sourceJsFile\n";
$contents.=file_get_contents($sourceFile)."\n\n";
}
$tempFile=$libFile.'.tmp';
@@ -170,10 +170,11 @@ foreach($libraries as $libFile => $sourceFiles) $jsMin -> minify();
unset($jsMin);
@unlink($tempFile);
- echo "Saving file {$libFile}\n";
+ echo "Saving file {$jsFile}\n";
$builds++;
}
-if(isset($argv[1]) && preg_match('/doc*/', $argv[1]))
+
+if(isset($argv[1]) && preg_match('/(doc)+/', $argv[1]))
{
$files = "";
foreach($libraries as $lib)
@@ -189,4 +190,4 @@ else if($builds > 0) else
echo "No files to build.";
-?>
\ No newline at end of file +?>
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 diff --git a/demos/quickstart/protected/pages/Controls/Samples/TDataTypeValidator/Home.page b/demos/quickstart/protected/pages/Controls/Samples/TDataTypeValidator/Home.page index 783769ff..ac83f745 100644 --- a/demos/quickstart/protected/pages/Controls/Samples/TDataTypeValidator/Home.page +++ b/demos/quickstart/protected/pages/Controls/Samples/TDataTypeValidator/Home.page @@ -31,8 +31,8 @@ Data type validator with client-side validation disabled: ValidationGroup="Group2"
EnableClientScript="false"
ControlToValidate="TextBox2"
- DataType="Currency"
- Text="You must enter a currency." />
+ DataType="Integer"
+ Text="You must enter an integer." />
<com:TButton Text="Submit" ValidationGroup="Group2" />
</td>
</tr>
@@ -47,7 +47,8 @@ Validating a date input: ValidationGroup="Group3"
ControlToValidate="TextBox3"
DataType="Date"
- Text="You must enter a valid date." />
+ DateFormat="d/M/yyyy"
+ Text="You must enter a valid date (d/M/yyyy)." />
<com:TButton Text="Submit" ValidationGroup="Group3" />
</td>
</tr>
diff --git a/framework/I18N/core/NumberFormat.php b/framework/I18N/core/NumberFormat.php index d1184f86..8e715f15 100644 --- a/framework/I18N/core/NumberFormat.php +++ b/framework/I18N/core/NumberFormat.php @@ -122,7 +122,6 @@ class NumberFormat $string = (string)$number;
-
$decimal = $this->formatDecimal($string);
$integer = $this->formatInteger(abs($number));
@@ -130,9 +129,9 @@ class NumberFormat $result = $integer.$decimal;
else
$result = $integer;
-
+
//get the suffix
- if($number > 0)
+ if($number >= 0)
$suffix = $this->formatInfo->PositivePattern;
else if($number < 0)
$suffix = $this->formatInfo->NegativePattern;
diff --git a/framework/Security/TUser.php b/framework/Security/TUser.php index 7b785add..ee52ceaa 100644 --- a/framework/Security/TUser.php +++ b/framework/Security/TUser.php @@ -57,6 +57,7 @@ class TUser extends TComponent implements IUser public function __construct(IUserManager $manager)
{
$this->_manager=$manager;
+ $this->_name=$manager->getGuestName();
}
/**
diff --git a/framework/Web/Javascripts/colorpicker/colorpicker.js b/framework/Web/Javascripts/colorpicker/colorpicker.js index dc80f0c7..06cfb037 100644 --- a/framework/Web/Javascripts/colorpicker/colorpicker.js +++ b/framework/Web/Javascripts/colorpicker/colorpicker.js @@ -74,7 +74,7 @@ Object.extend(Prado.WebUI.TColorPicker.prototype, if(Prado.Browser().ie)
{
this.iePopUp = document.createElement('iframe');
- this.iePopUp.src = "";
+ this.iePopUp.src = Prado.WebUI.TColorPicker.UIImages['button.gif'];
this.iePopUp.style.position = "absolute"
this.iePopUp.scrolling="no"
this.iePopUp.frameBorder="0"
diff --git a/framework/Web/Javascripts/datepicker/datepicker.js b/framework/Web/Javascripts/datepicker/datepicker.js index 79763811..d5d9496c 100644 --- a/framework/Web/Javascripts/datepicker/datepicker.js +++ b/framework/Web/Javascripts/datepicker/datepicker.js @@ -81,11 +81,14 @@ Prado.WebUI.TDatePicker.prototype = Object.extend(this,options);
Event.observe(this.trigger, triggerEvent, this.show.bindEvent(this));
- this.create();
+
},
create : function()
{
+ if(typeof(this._calDiv) != "undefined")
+ return;
+
var div;
var table;
var tbody;
@@ -115,9 +118,10 @@ Prado.WebUI.TDatePicker.prototype = // Previous Month Button
td = document.createElement("td");
- var previousMonth = document.createElement("button");
- previousMonth.className = "prevMonthButton";
- previousMonth.appendChild(document.createTextNode("<<"));
+ var previousMonth = document.createElement("input");
+ previousMonth.className = "prevMonthButton button";
+ previousMonth.type = "button"
+ previousMonth.value = "<<";
td.appendChild(previousMonth);
tr.appendChild(td);
@@ -162,9 +166,10 @@ Prado.WebUI.TDatePicker.prototype = td = document.createElement("td");
- td.className = "nextMonthButton";
- var nextMonth = document.createElement("button");
- nextMonth.appendChild(document.createTextNode(">>"));
+ var nextMonth = document.createElement("input");
+ nextMonth.className = "nextMonthButton button";
+ nextMonth.type = "button";
+ nextMonth.value = ">>";
td.appendChild(nextMonth);
tr.appendChild(td);
@@ -178,6 +183,7 @@ Prado.WebUI.TDatePicker.prototype = var text;
table = document.createElement("table");
+ table.align="center";
table.className = "grid";
div.appendChild(table);
@@ -226,11 +232,12 @@ Prado.WebUI.TDatePicker.prototype = div.className = "calendarFooter";
this._calDiv.appendChild(div);
- var todayButton = document.createElement("button");
+ var todayButton = document.createElement("input");
+ todayButton.type="button";
todayButton.className = "todayButton";
var today = this.newDate();
var buttonText = today.SimpleFormat(this.Format,this);
- todayButton.appendChild(document.createTextNode(buttonText));
+ todayButton.value = buttonText;
div.appendChild(todayButton);
/*var clearButton = document.createElement("button");
@@ -243,7 +250,7 @@ Prado.WebUI.TDatePicker.prototype = if(Prado.Browser().ie)
{
this.iePopUp = document.createElement('iframe');
- this.iePopUp.src = "";
+ this.iePopUp.src = Prado.WebUI.TDatePicker.spacer;
this.iePopUp.style.position = "absolute"
this.iePopUp.scrolling="no"
this.iePopUp.frameBorder="0"
@@ -521,6 +528,8 @@ Prado.WebUI.TDatePicker.prototype = show : function()
{
+ this.create();
+
if(!this.showing)
{
var pos = Position.cumulativeOffset(this.control);
diff --git a/framework/Web/Javascripts/datepicker/default.css b/framework/Web/Javascripts/datepicker/default.css index e5d327e1..7e920f1b 100644 --- a/framework/Web/Javascripts/datepicker/default.css +++ b/framework/Web/Javascripts/datepicker/default.css @@ -21,6 +21,7 @@ margin-left: 1px;
}
+
.TDatePickerImageButton:hover
{
border-color: #ddd;
@@ -31,9 +32,9 @@ font-size: 11px;
}
-.TDatePicker .calendarHeader button
+.TDatePicker input.button
{
- font-size: 12px;
+ font-size: 11px;
width: 32px;
}
@@ -41,6 +42,7 @@ {
padding: 4px 0;
border: 1px solid white;
+ text-align: center;
}
.TDatePicker .hover
{
@@ -61,25 +63,36 @@ {
border: 1px solid white;
cursor: default;
- height: 23px;
+ height: 22px;
}
+
.TDatePicker th
{
width: 28px;
}
+
+.TDatePicker .calendarBody
+{
+ text-align: center;
+ width: 210px;
+ margin: 3px 6px;
+}
+
.TDatePicker .grid
{
border-spacing: 0px;
- margin: 3px;
}
+
.TDatePicker .calendarFooter
{
margin: 2px;
border-top: 1px solid #919EA9;
padding-top: 2px;
}
-.TDatePicker .calendarFooter button
+.TDatePicker .todayButton
{
- font-size: 12px;
+ font-size: 11px;
margin: 4px;
+ padding-left: 1em;
+ padding-right: 1em;
}
\ No newline at end of file diff --git a/framework/Web/Javascripts/datepicker/spacer.gif b/framework/Web/Javascripts/datepicker/spacer.gif Binary files differnew file mode 100755 index 00000000..fc256098 --- /dev/null +++ b/framework/Web/Javascripts/datepicker/spacer.gif diff --git a/framework/Web/Javascripts/extended/event.js b/framework/Web/Javascripts/extended/event.js index 29a8d5aa..4fd041d8 100644 --- a/framework/Web/Javascripts/extended/event.js +++ b/framework/Web/Javascripts/extended/event.js @@ -64,6 +64,7 @@ Object.extend(Event, * <tt>element</tt>. Only HTMLEvent and MouseEvent can be
* dispatched, keyboard events or UIEvent can not be dispatch
* via javascript consistently.
+ * For the "submit" event the submit() method is called.
* @param {Object} element id string or a DOM element.
* @param {String} event type to dispatch.
*/
@@ -86,12 +87,6 @@ Object.extend(Event, document.defaultView, 1, 0, 0, 0, 0, false,
false, false, false, 0, null);
}
- else
- {
- if(typeof(Logger) != "undefined")
- Logger.error("undefined event", type);
- return;
- }
element.dispatchEvent(event);
}
else if(document.createEventObject)
diff --git a/framework/Web/Javascripts/js/colorpicker.js b/framework/Web/Javascripts/js/colorpicker.js index 5bfab96d..47949239 100644 --- a/framework/Web/Javascripts/js/colorpicker.js +++ b/framework/Web/Javascripts/js/colorpicker.js @@ -34,7 +34,7 @@ Event.observe(this.button,"click",this._buttonOnClick);Event.observe(this.input, {var constructor=mode=="Basic"?"getBasicPickerContainer":"getFullPickerContainer" this.element=this[constructor](this.options['ID'],this.options['Palette']) document.body.appendChild(this.element);this.element.style.display="none";if(Prado.Browser().ie) -{this.iePopUp=document.createElement('iframe');this.iePopUp.src="";this.iePopUp.style.position="absolute" +{this.iePopUp=document.createElement('iframe');this.iePopUp.src=Prado.WebUI.TColorPicker.UIImages['button.gif'];this.iePopUp.style.position="absolute" this.iePopUp.scrolling="no" this.iePopUp.frameBorder="0" this.input.parentNode.appendChild(this.iePopUp);} diff --git a/framework/Web/Javascripts/js/datepicker.js b/framework/Web/Javascripts/js/datepicker.js index 6a28c061..a81bbeb4 100644 --- a/framework/Web/Javascripts/js/datepicker.js +++ b/framework/Web/Javascripts/js/datepicker.js @@ -8,16 +8,18 @@ Prado.WebUI.TDatePicker=Class.create();Object.extend(Prado.WebUI.TDatePicker,{ge {this.trigger=$(this.options.Trigger);var triggerEvent=this.options.TriggerEvent||"click";} else {this.trigger=this.control;var triggerEvent=this.options.TriggerEvent||"focus";} -Object.extend(this,options);Event.observe(this.trigger,triggerEvent,this.show.bindEvent(this));this.create();},create:function() -{var div;var table;var tbody;var tr;var td;this._calDiv=document.createElement("div");this._calDiv.className=this.ClassName;this._calDiv.style.display="none";this._calDiv.style.position="absolute" -div=document.createElement("div");div.className="calendarHeader";this._calDiv.appendChild(div);table=document.createElement("table");table.style.cellSpacing=0;div.appendChild(table);tbody=document.createElement("tbody");table.appendChild(tbody);tr=document.createElement("tr");tbody.appendChild(tr);td=document.createElement("td");var previousMonth=document.createElement("button");previousMonth.className="prevMonthButton";previousMonth.appendChild(document.createTextNode("<<"));td.appendChild(previousMonth);tr.appendChild(td);td=document.createElement("td");tr.appendChild(td);this._monthSelect=document.createElement("select");this._monthSelect.className="months";for(var i=0;i<this.MonthNames.length;i++){var opt=document.createElement("option");opt.innerHTML=this.MonthNames[i];opt.value=i;if(i==this.selectedDate.getMonth()){opt.selected=true;} +Object.extend(this,options);Event.observe(this.trigger,triggerEvent,this.show.bindEvent(this));},create:function() +{if(typeof(this._calDiv)!="undefined") +return;var div;var table;var tbody;var tr;var td;this._calDiv=document.createElement("div");this._calDiv.className=this.ClassName;this._calDiv.style.display="none";this._calDiv.style.position="absolute" +div=document.createElement("div");div.className="calendarHeader";this._calDiv.appendChild(div);table=document.createElement("table");table.style.cellSpacing=0;div.appendChild(table);tbody=document.createElement("tbody");table.appendChild(tbody);tr=document.createElement("tr");tbody.appendChild(tr);td=document.createElement("td");var previousMonth=document.createElement("input");previousMonth.className="prevMonthButton button";previousMonth.type="button" +previousMonth.value="<<";td.appendChild(previousMonth);tr.appendChild(td);td=document.createElement("td");tr.appendChild(td);this._monthSelect=document.createElement("select");this._monthSelect.className="months";for(var i=0;i<this.MonthNames.length;i++){var opt=document.createElement("option");opt.innerHTML=this.MonthNames[i];opt.value=i;if(i==this.selectedDate.getMonth()){opt.selected=true;} this._monthSelect.appendChild(opt);} td.appendChild(this._monthSelect);td=document.createElement("td");td.className="labelContainer";tr.appendChild(td);this._yearSelect=document.createElement("select");for(var i=this.FromYear;i<=this.UpToYear;++i){var opt=document.createElement("option");opt.innerHTML=i;opt.value=i;if(i==this.selectedDate.getFullYear()){opt.selected=false;} this._yearSelect.appendChild(opt);} -td.appendChild(this._yearSelect);td=document.createElement("td");td.className="nextMonthButton";var nextMonth=document.createElement("button");nextMonth.appendChild(document.createTextNode(">>"));td.appendChild(nextMonth);tr.appendChild(td);div=document.createElement("div");div.className="calendarBody";this._calDiv.appendChild(div);var calendarBody=div;var text;table=document.createElement("table");table.className="grid";div.appendChild(table);var thead=document.createElement("thead");table.appendChild(thead);tr=document.createElement("tr");thead.appendChild(tr);for(i=0;i<7;++i){td=document.createElement("th");text=document.createTextNode(this.ShortWeekDayNames[(i+this.FirstDayOfWeek)%7]);td.appendChild(text);td.className="weekDayHead";tr.appendChild(td);} +td.appendChild(this._yearSelect);td=document.createElement("td");var nextMonth=document.createElement("input");nextMonth.className="nextMonthButton button";nextMonth.type="button";nextMonth.value=">>";td.appendChild(nextMonth);tr.appendChild(td);div=document.createElement("div");div.className="calendarBody";this._calDiv.appendChild(div);var calendarBody=div;var text;table=document.createElement("table");table.align="center";table.className="grid";div.appendChild(table);var thead=document.createElement("thead");table.appendChild(thead);tr=document.createElement("tr");thead.appendChild(tr);for(i=0;i<7;++i){td=document.createElement("th");text=document.createTextNode(this.ShortWeekDayNames[(i+this.FirstDayOfWeek)%7]);td.appendChild(text);td.className="weekDayHead";tr.appendChild(td);} tbody=document.createElement("tbody");table.appendChild(tbody);for(week=0;week<6;++week){tr=document.createElement("tr");tbody.appendChild(tr);for(day=0;day<7;++day){td=document.createElement("td");td.className="calendarDate";text=document.createTextNode(String.fromCharCode(160));td.appendChild(text);tr.appendChild(td);var tmp=new Object();tmp.tag="DATE";tmp.value=-1;tmp.data=text;this.dateSlot[(week*7)+day]=tmp;Event.observe(td,"mouseover",this.hover.bindEvent(this));Event.observe(td,"mouseout",this.hover.bindEvent(this));}} -div=document.createElement("div");div.className="calendarFooter";this._calDiv.appendChild(div);var todayButton=document.createElement("button");todayButton.className="todayButton";var today=this.newDate();var buttonText=today.SimpleFormat(this.Format,this);todayButton.appendChild(document.createTextNode(buttonText));div.appendChild(todayButton);if(Prado.Browser().ie) -{this.iePopUp=document.createElement('iframe');this.iePopUp.src="";this.iePopUp.style.position="absolute" +div=document.createElement("div");div.className="calendarFooter";this._calDiv.appendChild(div);var todayButton=document.createElement("input");todayButton.type="button";todayButton.className="todayButton";var today=this.newDate();var buttonText=today.SimpleFormat(this.Format,this);todayButton.value=buttonText;div.appendChild(todayButton);if(Prado.Browser().ie) +{this.iePopUp=document.createElement('iframe');this.iePopUp.src=Prado.WebUI.TDatePicker.spacer;this.iePopUp.style.position="absolute" this.iePopUp.scrolling="no" this.iePopUp.frameBorder="0" document.body.appendChild(this.iePopUp);} @@ -87,7 +89,7 @@ this.onChange();},getElement:function() {var d=this.newDate(this.selectedDate);d.setMonth(month);this.setSelectedDate(d);},nextMonth:function() {this.setMonth(this.selectedDate.getMonth()+1);},prevMonth:function() {this.setMonth(this.selectedDate.getMonth()-1);},show:function() -{if(!this.showing) +{this.create();if(!this.showing) {var pos=Position.cumulativeOffset(this.control);if(this.options.InputMode=="TextBox") pos[1]+=this.control.offsetHeight;else {var dayList=Prado.WebUI.TDatePicker.getDayListControl(this.control);if(dayList) diff --git a/framework/Web/Javascripts/js/prado.js b/framework/Web/Javascripts/js/prado.js index c09cf64c..aa02e98e 100644 --- a/framework/Web/Javascripts/js/prado.js +++ b/framework/Web/Javascripts/js/prado.js @@ -129,9 +129,6 @@ return element.submit();if(document.createEvent) {var event=document.createEvent('HTMLEvents');event.initEvent(type,true,true);} else if(Event.isMouseEvent(type)) {var event=document.createEvent('MouseEvents');event.initMouseEvent(type,true,true,document.defaultView,1,0,0,0,0,false,false,false,false,0,null);} -else -{if(typeof(Logger)!="undefined") -Logger.error("undefined event",type);return;} element.dispatchEvent(event);} else if(document.createEventObject) {var evObj=document.createEventObject();element.fireEvent('on'+type,evObj);} diff --git a/framework/Web/Javascripts/prado/validation3.js b/framework/Web/Javascripts/prado/validation3.js index ad55f96b..6285dd52 100644 --- a/framework/Web/Javascripts/prado/validation3.js +++ b/framework/Web/Javascripts/prado/validation3.js @@ -763,7 +763,9 @@ Prado.WebUI.TBaseValidator.prototype = return value;
},
- /**
+ /**
+ * The ControlType property comes from TBaseValidator::getClientControlClass()
+ * Be sure to update the TBaseValidator::$_clientClass if new cases are added.
* @return mixed control value to validate
*/
getValidationValue : function(control)
diff --git a/framework/Web/UI/TThemeManager.php b/framework/Web/UI/TThemeManager.php index 6a908759..fcb20a81 100644 --- a/framework/Web/UI/TThemeManager.php +++ b/framework/Web/UI/TThemeManager.php @@ -79,6 +79,23 @@ class TThemeManager extends TModule }
/**
+ * @return array list of available theme names
+ */
+ public function getAvailableThemes()
+ {
+ $themes=array();
+ $basePath=$this->getBasePath();
+ $folder=@opendir($basePath);
+ while($file=@readdir($folder))
+ {
+ if($file!=='.' && $file!=='..' && $file!=='.svn' && is_dir($basePath.'/'.$file))
+ $themes[]=$file;
+ }
+ closedir($folder);
+ return $themes;
+ }
+
+ /**
* @return string the base path for all themes. It is returned as an absolute path.
* @throws TConfigurationException if base path is not set and "themes" directory does not exist.
*/
diff --git a/framework/Web/UI/WebControls/TBaseValidator.php b/framework/Web/UI/WebControls/TBaseValidator.php index ffdfd057..3c76db30 100644 --- a/framework/Web/UI/WebControls/TBaseValidator.php +++ b/framework/Web/UI/WebControls/TBaseValidator.php @@ -83,6 +83,12 @@ abstract class TBaseValidator extends TLabel implements IValidator * @var TClientSideValidatorOptions validator client-script options. */ private $_clientSide; + /** + * Controls for which the client-side validation3.js file needs to handle + * them specially. + * @var array list of control class names + */ + private static $_clientClass = array('THtmlArea', 'TDatePicker'); /** * Constructor. @@ -152,28 +158,44 @@ abstract class TBaseValidator extends TLabel implements IValidator $options['ValidationGroup'] = $this->getValidationGroup(); $options['ControlToValidate'] = $control->getClientID(); $options['ControlCssClass'] = $this->getControlCssClass(); - $options['ControlType'] = get_class($control); - + + $options['ControlType'] = $this->getClientControlClass($control); + if(!is_null($this->_clientSide)) $options = array_merge($options,$this->_clientSide->getOptions()->toArray()); - + return $options; } - + + /** + * Gets the Control type for client-side validation. If new cases exists in + * TBaseValidator::$_clientClass, be sure to update the corresponding + * "Javascript/validation3.js" file as well. + * @param TControl control to validate. + * @return string control type for client-side validation. + */ + private function getClientControlClass($control) + { + foreach(self::$_clientClass as $type) + if($control instanceof $type) + return $type; + return get_class($control); + } + /** * Gets the TClientSideValidatorOptions that allows modification of the client- - * side validator events. - * + * side validator events. + * * The client-side validator supports the following events. * # <tt>OnValidate</tt> -- raised before client-side validation is - * executed. + * executed. * # <tt>OnSuccess</tt> -- raised after client-side validation is completed * and is successfull, overrides default validator error messages updates. * # <tt>OnError</tt> -- raised after client-side validation is completed - * and failed, overrides default validator error message updates. - * + * and failed, overrides default validator error message updates. + * * You can attach custom javascript code to each of these events - * + * * @return TClientSideValidatorOptions javascript validator event options. */ public function getClientSide() @@ -182,7 +204,7 @@ abstract class TBaseValidator extends TLabel implements IValidator $this->_clientSide = $this->createClientSideOptions(); return $this->_clientSide; } - + /** * @return TClientSideValidatorOptions javascript validator event options. */ @@ -206,7 +228,7 @@ abstract class TBaseValidator extends TLabel implements IValidator if($this->getEnableClientScript() && !$scripts->isEndScriptRegistered($scriptKey)) { $manager['FormID'] = $formID; - $options = TJavaScript::encode($manager); + $options = TJavaScript::encode($manager); $scripts->registerPradoScript('validator'); $scripts->registerEndScript($scriptKey, "new Prado.ValidationManager({$options});"); } @@ -493,22 +515,22 @@ abstract class TBaseValidator extends TLabel implements IValidator /** * TClientSideValidatorOptions class. - * + * * Client-side validator events can be modified through the {@link * TBaseValidator::getClientSide ClientSide} property of a validator. The * subproperties of ClientSide are those of the TClientSideValidatorOptions * properties. The client-side validator supports the following events. - * + * * The <tt>OnValidate</tt> event is raise before the validator validation * functions are called. - * + * * The <tt>OnSuccess</tt> event is raised after the validator has successfully * validate the control. - * + * * The <tt>OnError</tt> event is raised after the validator fails validation. - * + * * See the quickstart documentation for further details. - * + * * @author Wei Zhuo <weizhuo[at]gmail[dot]com> * @version $Revision: $ $Date: $ * @package System.Web.UI.WebControls @@ -521,19 +543,19 @@ class TClientSideValidatorOptions extends TClientSideOptions */ public function getOnValidate() { - return $this->getOption('OnValidate'); + return $this->getOption('OnValidate'); } - + /** * Client-side OnValidate validator event is raise before the validators - * validation functions are called. + * validation functions are called. * @param string javascript code for client-side OnValidate event. */ public function setOnValidate($javascript) { $this->setFunction('OnValidate', $javascript); } - + /** * Client-side OnSuccess event is raise after validation is successfull. * This will override the default client-side validator behaviour. @@ -543,7 +565,7 @@ class TClientSideValidatorOptions extends TClientSideOptions { $this->setFunction('OnSuccess', $javascript); } - + /** * @return string javascript code for client-side OnSuccess event. */ @@ -551,7 +573,7 @@ class TClientSideValidatorOptions extends TClientSideOptions { return $this->getOption('OnSuccess'); } - + /** * Client-side OnError event is raised after validation failure. * This will override the default client-side validator behaviour. @@ -561,7 +583,7 @@ class TClientSideValidatorOptions extends TClientSideOptions { $this->setFunction('OnError', $javascript); } - + /** * @return string javascript code for client-side OnError event. */ @@ -569,7 +591,7 @@ class TClientSideValidatorOptions extends TClientSideOptions { return $this->getOption('OnError'); } - + /** * Ensure the string is a valid javascript function. If the string begins * with "javascript:" valid javascript function is assumed, otherwise the @@ -583,4 +605,4 @@ class TClientSideValidatorOptions extends TClientSideOptions } } -?>
\ No newline at end of file +?> diff --git a/framework/Web/UI/WebControls/TDatePicker.php b/framework/Web/UI/WebControls/TDatePicker.php index 59a71c90..a746437b 100644 --- a/framework/Web/UI/WebControls/TDatePicker.php +++ b/framework/Web/UI/WebControls/TDatePicker.php @@ -681,6 +681,18 @@ class TDatePicker extends TTextBox else
throw new TConfigurationException('datepicker_calendarstyle_invalid',$style);
}
+
+ /**
+ * Publish the spacer.gif for IE iframe source.
+ * @return string the URL for the spacer.gif.
+ */
+ protected function publishIFrameSpacer()
+ {
+ $cs = $this->getPage()->getClientScript();
+ $spacer = 'System.Web.Javascripts.datepicker.spacer';
+ if(($file = Prado::getPathOfNamespace($spacer,'.gif')) != null)
+ return $this->publishFilePath($file);
+ }
/**
* Add the client id to the input textbox, and register the client scripts.
@@ -696,18 +708,24 @@ class TDatePicker extends TTextBox /**
* Registers the javascript code to initialize the date picker.
- * Must use "Event.OnLoad" to initialize the date picker when the
- * full page is loaded, otherwise IE will throw an error.
*/
protected function registerCalendarClientScript()
{
if($this->getShowCalendar())
{
- $scripts = $this->getPage()->getClientScript();
- $scripts->registerPradoScript("datepicker");
+ $cs = $this->getPage()->getClientScript();
+ $cs->registerPradoScript("datepicker");
+
+ if(!$cs->isEndScriptRegistered('TDatePicker.spacer'))
+ {
+ $spacer = $this->publishIFrameSpacer();
+ $code = "Prado.WebUI.TDatePicker.spacer = '$spacer';";
+ $cs->registerEndScript('TDatePicker.spacer', $code);
+ }
+
$options = TJavaScript::encode($this->getDatePickerOptions());
- $code = "Event.OnLoad(function(){ new Prado.WebUI.TDatePicker($options); });";
- $scripts->registerEndScript("prado:".$this->getClientID(), $code);
+ $code = "new Prado.WebUI.TDatePicker($options);";
+ $cs->registerEndScript("prado:".$this->getClientID(), $code);
}
}
}
diff --git a/framework/Web/UI/WebControls/TListControl.php b/framework/Web/UI/WebControls/TListControl.php index 57d8563a..42f09aad 100644 --- a/framework/Web/UI/WebControls/TListControl.php +++ b/framework/Web/UI/WebControls/TListControl.php @@ -502,6 +502,45 @@ abstract class TListControl extends TDataBoundControl $this->_cachedSelectedValue=$value;
}
+
+ /**
+ * @return array list of the selected item values (strings)
+ */
+ public function getSelectedValues()
+ {
+ $values=array();
+ if($this->_items)
+ {
+ foreach($this->_items as $item)
+ {
+ if($item->getSelected())
+ $values[]=$item->getValue();
+ }
+ }
+ return $values;
+ }
+
+ /**
+ * @param array list of the selected item values
+ */
+ public function setSelectedValues($values)
+ {
+ if($this->_items)
+ {
+ $this->clearSelection();
+ $lookup=array();
+ foreach($this->_items as $item)
+ $lookup[$item->getValue()]=$item;
+ foreach($values as $value)
+ {
+ if(isset($lookup["$value"]))
+ $lookup["$value"]->setSelected(true);
+ else
+ throw new TInvalidDataValueException('listcontrol_selectedvalue_invalid',get_class($this),$value);
+ }
+ }
+ }
+
/**
* @return string selected value
*/
diff --git a/tests/FunctionalTests/tickets/protected/pages/TestHtmlArea.php b/tests/FunctionalTests/tickets/protected/pages/TestHtmlArea.php new file mode 100644 index 00000000..13a4189b --- /dev/null +++ b/tests/FunctionalTests/tickets/protected/pages/TestHtmlArea.php @@ -0,0 +1,8 @@ +<?php + +class TestHtmlArea extends THtmlArea +{ + +} + +?>
\ No newline at end of file diff --git a/tests/FunctionalTests/tickets/protected/pages/Ticket121.page b/tests/FunctionalTests/tickets/protected/pages/Ticket121.page new file mode 100644 index 00000000..9c9633d3 --- /dev/null +++ b/tests/FunctionalTests/tickets/protected/pages/Ticket121.page @@ -0,0 +1,16 @@ +<com:TContent ID="Content">
+
+<com:TImageButton
+ ImageUrl="http://www.pradosoft.com/images/powered.gif"
+ Text="Foo"
+ ValidationGroup="Foo"
+ OnClick="buttonClicked" />
+<com:TTextBox ID="FooTextBox" Text="sometext" />
+<com:TRequiredFieldValidator
+ ValidationGroup="Foo"
+ ControlToValidate="FooTextBox"
+ Text="Field required." />
+
+<com:TLabel ID="Result" />
+
+</com:TContent>
\ No newline at end of file diff --git a/tests/FunctionalTests/tickets/protected/pages/Ticket121.php b/tests/FunctionalTests/tickets/protected/pages/Ticket121.php new file mode 100644 index 00000000..1a625d83 --- /dev/null +++ b/tests/FunctionalTests/tickets/protected/pages/Ticket121.php @@ -0,0 +1,11 @@ +<?php
+
+class Ticket121 extends TPage
+{
+ public function buttonClicked($sender,$param)
+ {
+ $this->Result->Text="clicked at ({$param->X},{$param->Y})";
+ }
+}
+
+?>
\ No newline at end of file diff --git a/tests/FunctionalTests/tickets/protected/pages/Ticket163.page b/tests/FunctionalTests/tickets/protected/pages/Ticket163.page new file mode 100644 index 00000000..aa05e601 --- /dev/null +++ b/tests/FunctionalTests/tickets/protected/pages/Ticket163.page @@ -0,0 +1,9 @@ +<com:TContent ID="Content"> + <h1>Test Ticket #163</h1> + <h2>Missing currency sign in TNumberFormat if Value is 0</h2> + <com:TNumberFormat Type="currency" Culture="no" Currency="NOK" Value="100"/> + <hr /> + <com:TNumberFormat Type="currency" Culture="no" Currency="NOK" Value="0"/> + <hr /> + <com:TNumberFormat Type="currency" Culture="no" Currency="NOK" Value="-100"/> +</com:TContent>
\ No newline at end of file diff --git a/tests/FunctionalTests/tickets/protected/pages/Ticket169.page b/tests/FunctionalTests/tickets/protected/pages/Ticket169.page new file mode 100644 index 00000000..24c447d5 --- /dev/null +++ b/tests/FunctionalTests/tickets/protected/pages/Ticket169.page @@ -0,0 +1,8 @@ +<com:TContent ID="Content"> + <com:Application.pages.TestHtmlArea ID="test1"/> + <com:TRequiredFieldValidator + ID="validator1" + ControlToValidate="test1" + ErrorMessage="required" /> + <com:TButton Text="Click Me" /> +</com:TContent>
\ No newline at end of file diff --git a/tests/FunctionalTests/tickets/protected/pages/Ticket191.page b/tests/FunctionalTests/tickets/protected/pages/Ticket191.page new file mode 100644 index 00000000..2d673b8d --- /dev/null +++ b/tests/FunctionalTests/tickets/protected/pages/Ticket191.page @@ -0,0 +1,19 @@ +<com:TContent ID="Content">
+
+Global state:
+<com:TTextBox ID="TextBox2" />
+<com:TButton Text="Submit" OnClick="updateGlobal" />
+
+<br/>
+
+Your input:
+<com:TTextBox ID="TextBox" />
+<com:TButton Text="Submit" ValidationGroup="Foo" OnClick="buttonClicked" />
+<com:TCustomValidator
+ ValidationGroup="Foo"
+ ControlToValidate="TextBox"
+ Text="Your input must match the global state."
+ OnServerValidate="customValidation" />
+<br/>
+
+</com:TContent>
\ No newline at end of file diff --git a/tests/FunctionalTests/tickets/protected/pages/Ticket191.php b/tests/FunctionalTests/tickets/protected/pages/Ticket191.php new file mode 100644 index 00000000..61c2a253 --- /dev/null +++ b/tests/FunctionalTests/tickets/protected/pages/Ticket191.php @@ -0,0 +1,22 @@ +<?php
+
+class Ticket191 extends TPage
+{
+ public function buttonClicked($sender,$param)
+ {
+ if($this->IsValid)
+ $this->Application->clearGlobalState('ticket190');
+ }
+
+ public function customValidation($sender,$param)
+ {
+ $param->IsValid=$this->Application->getGlobalState('ticket190')===$this->TextBox->Text;
+ }
+
+ public function updateGlobal($sender,$param)
+ {
+ $this->Application->setGlobalState('ticket190',$this->TextBox2->Text);
+ }
+}
+
+?>
\ No newline at end of file diff --git a/tests/FunctionalTests/tickets/protected/pages/Ticket28.page b/tests/FunctionalTests/tickets/protected/pages/Ticket28.page new file mode 100644 index 00000000..056e9985 --- /dev/null +++ b/tests/FunctionalTests/tickets/protected/pages/Ticket28.page @@ -0,0 +1,6 @@ +<com:TContent ID="Content"> + + <h2>Safari LinkButton Test</h2> + <com:TLinkButton ID="linkButton1" Text="Click Me" OnClick="linkButton1_Clicked" /> + <com:TLabel ID="label1" Text="Label 1" /> +</com:TContent>
\ No newline at end of file diff --git a/tests/FunctionalTests/tickets/protected/pages/Ticket28.php b/tests/FunctionalTests/tickets/protected/pages/Ticket28.php new file mode 100644 index 00000000..7980119e --- /dev/null +++ b/tests/FunctionalTests/tickets/protected/pages/Ticket28.php @@ -0,0 +1,12 @@ +<?php + +class Ticket28 extends TPage +{ + function linkButton1_Clicked() + { + $this->label1->setText("Link Button 1 Clicked!"); + } +} + + +?>
\ No newline at end of file diff --git a/tests/FunctionalTests/tickets/protected/pages/Ticket93.page b/tests/FunctionalTests/tickets/protected/pages/Ticket93.page new file mode 100644 index 00000000..2217ab15 --- /dev/null +++ b/tests/FunctionalTests/tickets/protected/pages/Ticket93.page @@ -0,0 +1,61 @@ +<com:TContent ID="Content">
+
+<h2>ValidationGroups without any inputs with grouping</h2>
+<p>This test can not be tested using automation.</p>
+<com:TImageMap ImageUrl=<%~hotspot.jpg%> AlternateText="Navigate buttons" OnClick="buttonClicked" >
+
+<com:TRectangleHotSpot
+ hotspotmode="Navigate"
+ NavigateUrl="navigate1.htm"
+ alternatetext="Button 1"
+ top="30"
+ left="175"
+ bottom="110"
+ right="355" />
+
+<com:TRectangleHotSpot
+ hotspotmode="PostBack"
+ PostBackValue="test1"
+ CausesValidation="true"
+ ValidationGroup="Group1"
+ alternatetext="Button 2"
+ top="155"
+ left="175"
+ bottom="240"
+ right="355" />
+
+<com:TRectangleHotSpot
+ hotspotmode="PostBack"
+ PostBackValue="test2"
+ CausesValidation="true"
+ alternatetext="Button 3"
+ ValidationGroup="Group2"
+ top="285"
+ left="175"
+ bottom="365"
+ right="355" />
+
+</com:TImageMap>
+<div>
+<com:TTextBox ID="TextBox" />
+<com:TRequiredFieldValidator
+ ValidationGroup="Group1"
+ EnableClientScript="true"
+ ControlToValidate="TextBox"
+ Text="textbox 1 required" />
+
+<com:TTextBox ID="TextBox2" />
+<com:TRequiredFieldValidator
+ ValidationGroup="Group2"
+ EnableClientScript="false"
+ ControlToValidate="TextBox2"
+ Text="textbox 2 required" />
+
+<com:TRequiredFieldValidator
+ ValidationGroup=""
+ EnableClientScript="true"
+ ControlToValidate="TextBox2"
+ Text="No one is required" />
+</div>
+
+</com:TContent>
\ No newline at end of file diff --git a/tests/FunctionalTests/tickets/protected/pages/Ticket93.php b/tests/FunctionalTests/tickets/protected/pages/Ticket93.php new file mode 100644 index 00000000..dbdf2cab --- /dev/null +++ b/tests/FunctionalTests/tickets/protected/pages/Ticket93.php @@ -0,0 +1,14 @@ +<?php
+/*
+ * Created on 13/04/2006
+ */
+
+class Ticket93 extends TPage
+{
+ public function buttonClicked($sender,$param)
+ {
+ echo 'postback triggered with value '.$param->PostBackValue;
+ }
+}
+
+?>
diff --git a/tests/FunctionalTests/tickets/protected/pages/config.xml b/tests/FunctionalTests/tickets/protected/pages/config.xml index 83bb5791..48a0114c 100644 --- a/tests/FunctionalTests/tickets/protected/pages/config.xml +++ b/tests/FunctionalTests/tickets/protected/pages/config.xml @@ -1,5 +1,8 @@ <?xml version="1.0" encoding="utf-8"?>
<configuration>
+ <paths>
+ <using namespace="System.I18N.*" />
+ </paths>
<pages MasterClass="Application.pages.Layout" />
</configuration>
\ No newline at end of file diff --git a/tests/FunctionalTests/tickets/protected/pages/hotspot.jpg b/tests/FunctionalTests/tickets/protected/pages/hotspot.jpg Binary files differnew file mode 100644 index 00000000..3491813f --- /dev/null +++ b/tests/FunctionalTests/tickets/protected/pages/hotspot.jpg diff --git a/tests/FunctionalTests/tickets/tests/Ticket121TestCase.php b/tests/FunctionalTests/tickets/tests/Ticket121TestCase.php new file mode 100644 index 00000000..7453aeef --- /dev/null +++ b/tests/FunctionalTests/tickets/tests/Ticket121TestCase.php @@ -0,0 +1,19 @@ +<?php
+
+class Ticket121TestCase extends SeleniumTestCase
+{
+ function test()
+ {
+ $this->open('tickets/index.php?page=Ticket121');
+ $this->type("ctl0\$Content\$FooTextBox", "");
+ $this->verifyNotVisible('ctl0_Content_ctl1');
+ $this->click("//input[@type='image' and @id='ctl0_Content_ctl0']", "");
+ $this->verifyVisible('ctl0_Content_ctl1');
+ $this->type("ctl0\$Content\$FooTextBox", "content");
+ $this->clickAndWait("//input[@type='image' and @id='ctl0_Content_ctl0']", "");
+ $this->verifyNotVisible('ctl0_Content_ctl1');
+ $this->verifyTextPresent("clicked at", "");
+ }
+}
+
+?>
\ No newline at end of file diff --git a/tests/FunctionalTests/tickets/tests/Ticket163TestCase.php b/tests/FunctionalTests/tickets/tests/Ticket163TestCase.php new file mode 100644 index 00000000..cc78c466 --- /dev/null +++ b/tests/FunctionalTests/tickets/tests/Ticket163TestCase.php @@ -0,0 +1,14 @@ +<?php + +class Ticket163TestCase extends SeleniumTestCase +{ + function test() + { + $this->open('tickets/index.php?page=Ticket163'); + $this->assertTextPresent('kr 100,00'); + $this->assertTextPresent('kr 0,00'); + $this->assertTextPresent('-kr 100,00'); + } +} + +?>
\ No newline at end of file diff --git a/tests/FunctionalTests/tickets/tests/Ticket169TestCase.php b/tests/FunctionalTests/tickets/tests/Ticket169TestCase.php new file mode 100644 index 00000000..30bbc92d --- /dev/null +++ b/tests/FunctionalTests/tickets/tests/Ticket169TestCase.php @@ -0,0 +1,14 @@ +<?php + +class Ticket169TestCase extends SeleniumTestCase +{ + function test() + { + $this->open('tickets/index.php?page=Ticket169'); + $this->assertNotVisible('ctl0_Content_validator1'); + $this->click('ctl0_Content_ctl0'); + $this->assertVisible('ctl0_Content_validator1'); + } +} + +?>
\ No newline at end of file diff --git a/tests/FunctionalTests/tickets/tests/Ticket191TestCase.php b/tests/FunctionalTests/tickets/tests/Ticket191TestCase.php new file mode 100644 index 00000000..cc4c1571 --- /dev/null +++ b/tests/FunctionalTests/tickets/tests/Ticket191TestCase.php @@ -0,0 +1,16 @@ +<?php
+
+class Ticket191TestCase extends SeleniumTestCase
+{
+ function test()
+ {
+ $this->open('tickets/index.php?page=Ticket191');
+ $this->type("ctl0\$Content\$TextBox2", "test");
+ $this->clickAndWait("//input[@type='submit' and @name='ctl0\$Content\$ctl0']", "");
+ $this->type("ctl0\$Content\$TextBox", "test");
+ $this->clickAndWait("//input[@type='submit' and @name='ctl0\$Content\$ctl1']", "");
+ $this->verifyNotVisible('ctl0_Content_ctl2');
+ }
+}
+
+?>
\ No newline at end of file diff --git a/tests/FunctionalTests/tickets/tests/Ticket28TestCase.php b/tests/FunctionalTests/tickets/tests/Ticket28TestCase.php new file mode 100644 index 00000000..960dd2d8 --- /dev/null +++ b/tests/FunctionalTests/tickets/tests/Ticket28TestCase.php @@ -0,0 +1,15 @@ +<?php + +class Ticket28TestCase extends SeleniumTestCase +{ + + function test() + { + $this->open('tickets/index.php?page=Ticket28'); + $this->assertTextPresent('Label 1'); + $this->clickAndWait('link=Click Me'); + $this->assertTextPresent('Link Button 1 Clicked!'); + } +} + +?>
\ No newline at end of file diff --git a/tests/FunctionalTests/tickets/tests/Ticket93TestCase.php b/tests/FunctionalTests/tickets/tests/Ticket93TestCase.php new file mode 100644 index 00000000..6a286ffe --- /dev/null +++ b/tests/FunctionalTests/tickets/tests/Ticket93TestCase.php @@ -0,0 +1,16 @@ +<?php
+/*
+ * Created on 13/04/2006
+ *
+ */
+
+class Ticket93TestCase extends SeleniumTestCase
+{
+ function test()
+ {
+ $this->open('tickets/index.php?page=Ticket93');
+ $this->verifyTextPresent("ValidationGroups without any inputs with grouping");
+ }
+
+}
+?>
|