summaryrefslogtreecommitdiff
path: root/demos/blog-tutorial/samples
diff options
context:
space:
mode:
authorxue <>2007-06-26 15:59:16 +0000
committerxue <>2007-06-26 15:59:16 +0000
commitf92ca63a225d43e7ecca4ebe528c2a1e206a40f1 (patch)
tree0fb92aafab5fedc866c6cafd49433d72bf719114 /demos/blog-tutorial/samples
parentc31e5ab3ebd4ae1561cbdaeaa504d1095dcbbb0a (diff)
Completed day 4 for blog tutorial.
Diffstat (limited to 'demos/blog-tutorial/samples')
-rw-r--r--demos/blog-tutorial/samples/day3/blog/protected/pages/users/EditUser.php2
-rw-r--r--demos/blog-tutorial/samples/day3/blog/protected/pages/users/LoginUser.php2
-rw-r--r--demos/blog-tutorial/samples/day3/blog/protected/pages/users/NewUser.php2
-rw-r--r--demos/blog-tutorial/samples/day4/blog/index.php22
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/.htaccess1
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/BlogUser.php59
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/application.xml56
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/data/blog.dbbin0 -> 4096 bytes
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/database/PostRecord.php28
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/database/UserRecord.php28
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/layouts/MainLayout.php19
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/layouts/MainLayout.tpl40
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/pages/Contact.page47
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/pages/Contact.php30
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/pages/Home.page7
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/pages/posts/EditPost.page27
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/pages/posts/EditPost.php72
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/pages/posts/ListPost.page14
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/pages/posts/ListPost.php64
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/pages/posts/NewPost.page27
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/pages/posts/NewPost.php34
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/pages/posts/PostRenderer.php7
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/pages/posts/PostRenderer.tpl15
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/pages/posts/ReadPost.page25
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/pages/posts/ReadPost.php57
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/pages/posts/config.xml6
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/pages/users/AdminUser.page40
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/pages/users/AdminUser.php36
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/pages/users/EditUser.page61
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/pages/users/EditUser.php83
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/pages/users/LoginUser.page28
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/pages/users/LoginUser.php37
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/pages/users/NewUser.page73
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/pages/users/NewUser.php45
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/pages/users/config.xml7
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/schema.sql25
36 files changed, 1123 insertions, 3 deletions
diff --git a/demos/blog-tutorial/samples/day3/blog/protected/pages/users/EditUser.php b/demos/blog-tutorial/samples/day3/blog/protected/pages/users/EditUser.php
index cb69b9d5..81538c33 100644
--- a/demos/blog-tutorial/samples/day3/blog/protected/pages/users/EditUser.php
+++ b/demos/blog-tutorial/samples/day3/blog/protected/pages/users/EditUser.php
@@ -54,7 +54,7 @@ class EditUser extends TPage
$userRecord->save();
// redirects the browser to the homepage
- $this->Response->redirect($this->Service->constructUrl($this->Service->DefaultPage));
+ $this->Response->redirect($this->Service->DefaultPageUrl);
}
}
diff --git a/demos/blog-tutorial/samples/day3/blog/protected/pages/users/LoginUser.php b/demos/blog-tutorial/samples/day3/blog/protected/pages/users/LoginUser.php
index dff8f2f0..a0955490 100644
--- a/demos/blog-tutorial/samples/day3/blog/protected/pages/users/LoginUser.php
+++ b/demos/blog-tutorial/samples/day3/blog/protected/pages/users/LoginUser.php
@@ -28,7 +28,7 @@ class LoginUser extends TPage
// obtain the URL of the privileged page that the user wanted to visit originally
$url=$this->Application->getModule('auth')->ReturnUrl;
if(empty($url)) // the user accesses the login page directly
- $url=$this->Service->constructUrl($this->Service->DefaultPage);
+ $url=$this->Service->DefaultPageUrl;
$this->Response->redirect($url);
}
}
diff --git a/demos/blog-tutorial/samples/day3/blog/protected/pages/users/NewUser.php b/demos/blog-tutorial/samples/day3/blog/protected/pages/users/NewUser.php
index 005fb7d2..76e8cb88 100644
--- a/demos/blog-tutorial/samples/day3/blog/protected/pages/users/NewUser.php
+++ b/demos/blog-tutorial/samples/day3/blog/protected/pages/users/NewUser.php
@@ -37,7 +37,7 @@ class NewUser extends TPage
$userRecord->save();
// redirects the browser to the homepage
- $this->Response->redirect($this->Service->constructUrl($this->Service->DefaultPage));
+ $this->Response->redirect($this->Service->DefaultPageUrl);
}
}
}
diff --git a/demos/blog-tutorial/samples/day4/blog/index.php b/demos/blog-tutorial/samples/day4/blog/index.php
new file mode 100644
index 00000000..7ccffe49
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/index.php
@@ -0,0 +1,22 @@
+<?php
+
+// The following directory checks may be removed if performance is required
+$basePath=dirname(__FILE__);
+$frameworkPath=$basePath.'/../../../../../framework/prado.php';
+$assetsPath=$basePath.'/assets';
+$runtimePath=$basePath.'/protected/runtime';
+
+if(!is_file($frameworkPath))
+ die("Unable to find prado framework path $frameworkPath.");
+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-tutorial/samples/day4/blog/protected/.htaccess b/demos/blog-tutorial/samples/day4/blog/protected/.htaccess
new file mode 100644
index 00000000..3418e55a
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/.htaccess
@@ -0,0 +1 @@
+deny from all \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/BlogUser.php b/demos/blog-tutorial/samples/day4/blog/protected/BlogUser.php
new file mode 100644
index 00000000..6b9e0a23
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/BlogUser.php
@@ -0,0 +1,59 @@
+<?php
+
+// Include TDbUserManager.php file which defines TDbUser
+Prado::using('System.Security.TDbUserManager');
+
+/**
+ * BlogUser Class.
+ * BlogUser represents the user data that needs to be kept in session.
+ * Default implementation keeps username and role information.
+ */
+class BlogUser extends TDbUser
+{
+ /**
+ * Creates a BlogUser object based on the specified username.
+ * This method is required by TDbUser. It checks the database
+ * to see if the specified username is there. If so, a BlogUser
+ * object is created and initialized.
+ * @param string the specified username
+ * @return BlogUser the user object, null if username is invalid.
+ */
+ public function createUser($username)
+ {
+ // use UserRecord Active Record to look for the specified username
+ $userRecord=UserRecord::finder()->findByPk($username);
+ if($userRecord instanceof UserRecord) // if found
+ {
+ $user=new BlogUser($this->Manager);
+ $user->Name=$username; // set username
+ $user->Roles=($userRecord->role==1?'admin':'user'); // set role
+ $user->IsGuest=false; // the user is not a guest
+ return $user;
+ }
+ else
+ return null;
+ }
+
+ /**
+ * Checks if the specified (username, password) is valid.
+ * This method is required by TDbUser.
+ * @param string username
+ * @param string password
+ * @return boolean whether the username and password are valid.
+ */
+ public function validateUser($username,$password)
+ {
+ // use UserRecord Active Record to look for the (username, password) pair.
+ return UserRecord::finder()->findBy_username_AND_password($username,$password)!==null;
+ }
+
+ /**
+ * @return boolean whether this user is an administrator.
+ */
+ public function getIsAdmin()
+ {
+ return $this->isInRole('admin');
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/application.xml b/demos/blog-tutorial/samples/day4/blog/protected/application.xml
new file mode 100644
index 00000000..fd6781df
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/application.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<application id="blog" mode="Debug">
+ <paths>
+ <using namespace="Application.database.*" />
+ <using namespace="Application.common.*" />
+ </paths>
+
+ <!-- configurations for modules -->
+ <modules>
+ <!-- Remove this comment mark to enable caching
+ <module id="cache" class="System.Caching.TDbCache" />
+ -->
+
+ <!-- Remove this comment mark to enable PATH url format
+ <module id="request" class="THttpRequest" UrlFormat="Path" />
+ -->
+
+ <!-- Remove this comment mark to enable logging
+ <module id="log" class="System.Util.TLogRouter">
+ <route class="TBrowserLogRoute" Categories="System" />
+ </module>
+ -->
+ <module id="db" class="System.Data.TDataSourceConfig">
+ <database ConnectionString="sqlite:protected/data/blog.db" />
+ </module>
+
+ <module
+ class="System.Data.ActiveRecord.TActiveRecordConfig"
+ ConnectionID="db" />
+
+ <module id="auth"
+ class="System.Security.TAuthManager"
+ UserManager="users"
+ LoginPage="users.LoginUser" />
+
+ <module id="users"
+ class="System.Security.TDbUserManager"
+ UserClass="Application.BlogUser" />
+
+ </modules>
+
+ <!-- configuration for available services -->
+ <services>
+ <service id="page" class="TPageService" DefaultPage="posts.ListPost">
+ <pages MasterClass="Application.layouts.MainLayout" />
+ </service>
+ </services>
+
+ <!-- application parameters
+ <parameters>
+ <parameter id="param1" value="value1" />
+ <parameter id="param2" value="value2" />
+ </parameters>
+ -->
+</application> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/data/blog.db b/demos/blog-tutorial/samples/day4/blog/protected/data/blog.db
new file mode 100644
index 00000000..fa48526a
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/data/blog.db
Binary files differ
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/database/PostRecord.php b/demos/blog-tutorial/samples/day4/blog/protected/database/PostRecord.php
new file mode 100644
index 00000000..01f84437
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/database/PostRecord.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * Auto generated by prado-cli.php on 2007-04-07 10:44:20.
+ */
+class PostRecord extends TActiveRecord
+{
+ const TABLE='posts';
+
+ public $post_id;
+ public $author_id;
+ public $create_time;
+ public $title;
+ public $content;
+ public $status;
+
+ public $author;
+
+ protected static $RELATIONS=array
+ (
+ 'author' => array(self::BELONGS_TO, 'UserRecord'),
+ );
+
+ public static function finder($className=__CLASS__)
+ {
+ return parent::finder($className);
+ }
+}
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/database/UserRecord.php b/demos/blog-tutorial/samples/day4/blog/protected/database/UserRecord.php
new file mode 100644
index 00000000..18d5ebbe
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/database/UserRecord.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * Auto generated by prado-cli.php on 2007-04-07 10:44:25.
+ */
+class UserRecord extends TActiveRecord
+{
+ const TABLE='users';
+
+ public $username;
+ public $email;
+ public $password;
+ public $role;
+ public $first_name;
+ public $last_name;
+
+ public $posts=array();
+
+ protected static $RELATIONS=array
+ (
+ 'posts' => array(self::HAS_MANY, 'PostRecord'),
+ );
+
+ public static function finder($className=__CLASS__)
+ {
+ return parent::finder($className);
+ }
+}
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/layouts/MainLayout.php b/demos/blog-tutorial/samples/day4/blog/protected/layouts/MainLayout.php
new file mode 100644
index 00000000..46c1483d
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/layouts/MainLayout.php
@@ -0,0 +1,19 @@
+<?php
+
+class MainLayout extends TTemplateControl
+{
+ /**
+ * Logs out a user.
+ * This method responds to the "logout" button's OnClick event.
+ * @param mixed event sender
+ * @param mixed event parameter
+ */
+ public function logoutButtonClicked($sender,$param)
+ {
+ $this->Application->getModule('auth')->logout();
+ $url=$this->Service->constructUrl($this->Service->DefaultPage);
+ $this->Response->redirect($url);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/layouts/MainLayout.tpl b/demos/blog-tutorial/samples/day4/blog/protected/layouts/MainLayout.tpl
new file mode 100644
index 00000000..3f0a9a4f
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/layouts/MainLayout.tpl
@@ -0,0 +1,40 @@
+<html>
+<com:THead />
+<body>
+<com:TForm>
+
+<div id="header">
+<h1>My PRADO Blog</h1>
+</div>
+
+<div id="main">
+<com:TContentPlaceHolder ID="Main" />
+</div>
+
+<div id="footer">
+
+<a href="<%= $this->Service->DefaultPageUrl %>">Home</a>
+
+<com:THyperLink Text="New Post"
+ NavigateUrl="<%= $this->Service->constructUrl('posts.NewPost') %>"
+ Visible="<%= !$this->User->IsGuest %>" />
+
+<com:THyperLink Text="New User"
+ NavigateUrl="<%= $this->Service->constructUrl('users.NewUser') %>"
+ Visible="<%= $this->User->IsAdmin %>" />
+
+<com:THyperLink Text="Login"
+ NavigateUrl="<%= $this->Service->constructUrl('users.LoginUser') %>"
+ Visible="<%= $this->User->IsGuest %>" />
+
+<com:TLinkButton Text="Logout"
+ OnClick="logoutButtonClicked"
+ Visible="<%= !$this->User->IsGuest %>" />
+
+<br/>
+<%= PRADO::poweredByPrado() %>
+</div>
+
+</com:TForm>
+</body>
+</html> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/pages/Contact.page b/demos/blog-tutorial/samples/day4/blog/protected/pages/Contact.page
new file mode 100644
index 00000000..c36149ca
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/pages/Contact.page
@@ -0,0 +1,47 @@
+<%@ Title="My Blog - Contact" %>
+
+<com:TContent ID="Main">
+
+<h1>Contact</h1>
+<p>Please fill out the following form to let me know your feedback on my blog. Thanks!</p>
+
+<span>Your Name:</span>
+<com:TRequiredFieldValidator ControlToValidate="Name"
+ ErrorMessage="Please provide your name."
+ Display="Dynamic"
+ />
+<br/>
+<com:TTextBox ID="Name" />
+
+<br/>
+
+<span>Your Email:</span>
+<com:TRequiredFieldValidator ControlToValidate="Email"
+ ErrorMessage="Please provide your email address."
+ Display="Dynamic"
+ />
+<com:TEmailAddressValidator ControlToValidate="Email"
+ ErrorMessage="You entered an invalid email address."
+ Display="Dynamic"
+ />
+<br/>
+<com:TTextBox ID="Email" />
+
+<br/>
+
+<span>Feedback:</span>
+<com:TRequiredFieldValidator ControlToValidate="Feedback"
+ ErrorMessage="Please provide your feedback."
+ Display="Dynamic"
+ />
+<br/>
+<com:TTextBox ID="Feedback"
+ TextMode="MultiLine"
+ Rows="10"
+ Columns="40" />
+
+<br/>
+
+<com:TButton Text="Submit" OnClick="submitButtonClicked" />
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/pages/Contact.php b/demos/blog-tutorial/samples/day4/blog/protected/pages/Contact.php
new file mode 100644
index 00000000..b6ce575e
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/pages/Contact.php
@@ -0,0 +1,30 @@
+<?php
+
+class Contact extends TPage
+{
+ /**
+ * Event handler for the OnClick event of the submit button.
+ * @param TButton the button triggering the event
+ * @param TEventParameter event parameter (null here)
+ */
+ public function submitButtonClicked($sender, $param)
+ {
+ if ($this->IsValid) // check if input validation is successful
+ {
+ // obtain the user name, email, feedback from the textboxes
+ $name = $this->Name->Text;
+ $email = $this->Email->Text;
+ $feedback = $this->Feedback->Text;
+
+ // send an email to administrator with the above information
+ $this->mailFeedback($name, $email, $feedback);
+ }
+ }
+
+ protected function mailFeedback($name, $email, $feedback)
+ {
+ // implementation of sending the feedback email
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/pages/Home.page b/demos/blog-tutorial/samples/day4/blog/protected/pages/Home.page
new file mode 100644
index 00000000..7a9c4a7d
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/pages/Home.page
@@ -0,0 +1,7 @@
+<%@ Title="Welcome to PRADO" %>
+
+<com:TContent ID="Main">
+
+<h1>Welcome to PRADO!</h1>
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/EditPost.page b/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/EditPost.page
new file mode 100644
index 00000000..579d833e
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/EditPost.page
@@ -0,0 +1,27 @@
+<%@ Title="My Blog - Edit Post" %>
+
+<com:TContent ID="Main">
+
+<h1>Edit Post</h1>
+
+<span>Title:</span>
+<com:TRequiredFieldValidator
+ ControlToValidate="TitleEdit"
+ ErrorMessage="Please provide a title."
+ Display="Dynamic" />
+<br/>
+<com:TTextBox ID="TitleEdit" Columns="50" />
+
+<br/>
+<span>Content:</span>
+<com:TRequiredFieldValidator
+ ControlToValidate="ContentEdit"
+ ErrorMessage="Please provide content."
+ Display="Dynamic" />
+<br/>
+<com:THtmlArea ID="ContentEdit" />
+
+<br/>
+<com:TButton Text="Save" OnClick="saveButtonClicked" />
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/EditPost.php b/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/EditPost.php
new file mode 100644
index 00000000..e137b85e
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/EditPost.php
@@ -0,0 +1,72 @@
+<?php
+
+class EditPost extends TPage
+{
+ /**
+ * Initializes the inputs with existing post data.
+ * This method is invoked by the framework when the page is being initialized.
+ * @param mixed event parameter
+ */
+ public function onInit($param)
+ {
+ parent::onInit($param);
+ // Retrieves the existing user data. This is equivalent to:
+ // $postRecord=$this->getPost();
+ $postRecord=$this->Post;
+ // Authorization check: only the author or the administrator can edit the post
+ if($postRecord->author_id!==$this->User->Name && !$this->User->IsAdmin)
+ throw new THttpException(500,'You are not allowed to edit this post.');
+
+ if(!$this->IsPostBack) // if the page is initially requested
+ {
+ // Populates the input controls with the existing post data
+ $this->TitleEdit->Text=$postRecord->title;
+ $this->ContentEdit->Text=$postRecord->content;
+ }
+ }
+
+ /**
+ * Saves the post if all inputs are valid.
+ * This method responds to the OnClick event of the "Save" button.
+ * @param mixed event sender
+ * @param mixed event parameter
+ */
+ public function saveButtonClicked($sender,$param)
+ {
+ if($this->IsValid) // when all validations succeed
+ {
+ // Retrieves the existing user data. This is equivalent to:
+ // $postRecord=$this->getPost();
+ $postRecord=$this->Post;
+
+ // Fetches the input data
+ $postRecord->title=$this->TitleEdit->SafeText;
+ $postRecord->content=$this->ContentEdit->SafeText;
+
+ // saves to the database via Active Record mechanism
+ $postRecord->save();
+
+ // redirects the browser to the ReadPost page
+ $url=$this->Service->constructUrl('posts.ReadPost',array('id'=>$postRecord->post_id));
+ $this->Response->redirect($url);
+ }
+ }
+
+ /**
+ * Returns the post data to be editted.
+ * @return PostRecord the post data to be editted.
+ * @throws THttpException if the post data is not found.
+ */
+ protected function getPost()
+ {
+ // the ID of the post to be editted is passed via GET parameter 'id'
+ $postID=(int)$this->Request['id'];
+ // use Active Record to look for the specified post ID
+ $postRecord=PostRecord::finder()->findByPk($postID);
+ if($postRecord===null)
+ throw new THttpException(500,'Post is not found.');
+ return $postRecord;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/ListPost.page b/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/ListPost.page
new file mode 100644
index 00000000..e26bc2f5
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/ListPost.page
@@ -0,0 +1,14 @@
+<%@ Title="My Blog" %>
+
+<com:TContent ID="Main">
+
+<com:TRepeater ID="Repeater"
+ ItemRenderer="Application.pages.posts.PostRenderer"
+ AllowPaging="true"
+ AllowCustomPaging="true"
+ PageSize="5"
+ />
+
+<com:TPager ControlToPaginate="Repeater" OnPageIndexChanged="pageChanged" />
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/ListPost.php b/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/ListPost.php
new file mode 100644
index 00000000..7402dace
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/ListPost.php
@@ -0,0 +1,64 @@
+<?php
+
+class ListPost extends TPage
+{
+ /**
+ * Initializes the repeater.
+ * This method is invoked by the framework when initializing the page
+ * @param mixed event parameter
+ */
+ public function onInit($param)
+ {
+ parent::onInit($param);
+ if(!$this->IsPostBack) // if the page is requested the first time
+ {
+ // get the total number of posts available
+ $this->Repeater->VirtualItemCount=PostRecord::finder()->count();
+ // populates post data into the repeater
+ $this->populateData();
+ }
+ }
+
+ /**
+ * Event handler to the OnPageIndexChanged event of the pager.
+ * This method is invoked when the user clicks on a page button
+ * and thus changes the page of posts to display.
+ */
+ public function pageChanged($sender,$param)
+ {
+ // change the current page index to the new one
+ $this->Repeater->CurrentPageIndex=$param->NewPageIndex;
+ // re-populate data into the repeater
+ $this->populateData();
+ }
+
+ /**
+ * Determines which page of posts to be displayed and
+ * populates the repeater with the fetched data.
+ */
+ protected function populateData()
+ {
+ $offset=$this->Repeater->CurrentPageIndex*$this->Repeater->PageSize;
+ $limit=$this->Repeater->PageSize;
+ if($offset+$limit>$this->Repeater->VirtualItemCount)
+ $limit=$this->Repeater->VirtualItemCount-$offset;
+ $this->Repeater->DataSource=$this->getPosts($offset,$limit);
+ $this->Repeater->dataBind();
+ }
+
+ /**
+ * Fetches posts from database with offset and limit.
+ */
+ protected function getPosts($offset, $limit)
+ {
+ // Construts a query criteria
+ $criteria=new TActiveRecordCriteria;
+ $criteria->OrdersBy['create_time']='desc';
+ $criteria->Limit=$limit;
+ $criteria->Offset=$offset;
+ // query for the posts with the above criteria and with author information
+ return PostRecord::finder()->withAuthor()->findAll($criteria);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/NewPost.page b/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/NewPost.page
new file mode 100644
index 00000000..bd46dfbb
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/NewPost.page
@@ -0,0 +1,27 @@
+<%@ Title="My Blog - New Post" %>
+
+<com:TContent ID="Main">
+
+<h1>Create New Post</h1>
+
+<span>Title:</span>
+<com:TRequiredFieldValidator
+ ControlToValidate="TitleEdit"
+ ErrorMessage="Please provide a title."
+ Display="Dynamic" />
+<br/>
+<com:TTextBox ID="TitleEdit" Columns="50" />
+
+<br/>
+<span>Content:</span>
+<com:TRequiredFieldValidator
+ ControlToValidate="ContentEdit"
+ ErrorMessage="Please provide content."
+ Display="Dynamic" />
+<br/>
+<com:THtmlArea ID="ContentEdit" />
+
+<br/>
+<com:TButton Text="Create" OnClick="createButtonClicked" />
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/NewPost.php b/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/NewPost.php
new file mode 100644
index 00000000..a5e3ea4d
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/NewPost.php
@@ -0,0 +1,34 @@
+<?php
+
+class NewPost extends TPage
+{
+ /**
+ * Creates a new post if all inputs are valid.
+ * This method responds to the OnClick event of the "create" button.
+ * @param mixed event sender
+ * @param mixed event parameter
+ */
+ public function createButtonClicked($sender,$param)
+ {
+ if($this->IsValid) // when all validations succeed
+ {
+ // populates a PostRecord object with user inputs
+ $postRecord=new PostRecord;
+ // using SafeText instead of Text avoids Cross Site Scripting attack
+ $postRecord->title=$this->TitleEdit->SafeText;
+ $postRecord->content=$this->ContentEdit->SafeText;
+ $postRecord->author_id=$this->User->Name;
+ $postRecord->create_time=time();
+ $postRecord->status=0;
+
+ // saves to the database via Active Record mechanism
+ $postRecord->save();
+
+ // redirects the browser to the newly created post page
+ $url=$this->Service->constructUrl('posts.ReadPost',array('id'=>$postRecord->post_id));
+ $this->Response->redirect($url);
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/PostRenderer.php b/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/PostRenderer.php
new file mode 100644
index 00000000..cf0539a1
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/PostRenderer.php
@@ -0,0 +1,7 @@
+<?php
+
+class PostRenderer extends TRepeaterItemRenderer
+{
+}
+
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/PostRenderer.tpl b/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/PostRenderer.tpl
new file mode 100644
index 00000000..862df1ab
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/PostRenderer.tpl
@@ -0,0 +1,15 @@
+<h3>
+<com:THyperLink Text="<%# $this->Data->title %>"
+ NavigateUrl="<%# $this->Service->constructUrl('posts.ReadPost',array('id'=>$this->Data->post_id)) %>" />
+</h3>
+
+<p>
+Author:
+<com:TLiteral Text="<%# $this->Data->author->username %>" /><br/>
+Time:
+<com:TLiteral Text="<%# date('m/d/Y h:m:sa', $this->Data->create_time) %>" />
+</p>
+
+<p>
+<com:TLiteral Text="<%# $this->Data->content %>" />
+</p>
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/ReadPost.page b/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/ReadPost.page
new file mode 100644
index 00000000..60a8cf1e
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/ReadPost.page
@@ -0,0 +1,25 @@
+<com:TContent ID="Main">
+
+<h2>
+<com:TLiteral Text="<%= $this->Post->title %>" />
+</h2>
+
+<com:TControl Visible="<%= $this->canEdit() %>">
+ <a href="<%= $this->Service->constructUrl('posts.EditPost',array('id'=>$this->Post->post_id))%>">Edit</a> |
+ <com:TLinkButton Text="Delete"
+ OnClick="deletePost"
+ Attributes.onclick="javascript:if(!confirm('Are you sure?')) return false;" />
+</com:TControl>
+
+<p>
+Author:
+<com:TLiteral Text="<%= $this->Post->author->username %>" /><br/>
+Time:
+<com:TLiteral Text="<%= date('m/d/Y h:m:sa', $this->Post->create_time) %>" />
+</p>
+
+<p>
+<com:TLiteral Text="<%= $this->Post->content %>" />
+</p>
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/ReadPost.php b/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/ReadPost.php
new file mode 100644
index 00000000..2aeaa4d3
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/ReadPost.php
@@ -0,0 +1,57 @@
+<?php
+
+class ReadPost extends TPage
+{
+ private $_post;
+ /**
+ * Fetches the post data.
+ * This method is invoked by the framework when initializing the page
+ * @param mixed event parameter
+ */
+ public function onInit($param)
+ {
+ parent::onInit($param);
+ // post id is passed via the 'id' GET parameter
+ $postID=(int)$this->Request['id'];
+ // retrieves PostRecord with author information filled in
+ $this->_post=PostRecord::finder()->withAuthor()->findByPk($postID);
+ if($this->_post===null) // if post id is invalid
+ throw new THttpException(500,'Unable to find the specified post.');
+ // set the page title as the post title
+ $this->Title=$this->_post->title;
+ }
+
+ /**
+ * @return PostRecord the PostRecord currently being viewed
+ */
+ public function getPost()
+ {
+ return $this->_post;
+ }
+
+ /**
+ * Deletes the post currently being viewed
+ * This method is invoked when the user clicks on the "Delete" button
+ */
+ public function deletePost($sender,$param)
+ {
+ // only the author or the administrator can delete a post
+ if(!$this->canEdit())
+ throw new THttpException('You are not allowed to perform this action.');
+ // delete it from DB
+ $this->_post->delete();
+ // redirect the browser to the homepage
+ $this->Response->redirect($this->Service->DefaultPageUrl);
+ }
+
+ /**
+ * @return boolean whether the current user can edit/delete the post being viewed
+ */
+ public function canEdit()
+ {
+ // only the author or the administrator can edit/delete a post
+ return $this->User->Name===$this->Post->author_id || $this->User->IsAdmin;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/config.xml b/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/config.xml
new file mode 100644
index 00000000..64065ed5
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/config.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+ <authorization>
+ <deny pages="NewPost,EditPost" roles="?" />
+ </authorization>
+</configuration> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/pages/users/AdminUser.page b/demos/blog-tutorial/samples/day4/blog/protected/pages/users/AdminUser.page
new file mode 100644
index 00000000..af03b858
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/pages/users/AdminUser.page
@@ -0,0 +1,40 @@
+<%@ Title="My Blog - Manage User Accounts" %>
+
+<com:TContent ID="Main">
+
+<h1>Manage User Accounts</h1>
+
+<a href="<%= $this->Service->constructUrl('users.NewUser')%>">Create New User</a>
+<br/>
+
+<com:TDataGrid ID="UserGrid"
+ DataKeyField="username"
+ AutoGenerateColumns="false"
+ OnDeleteCommand="deleteButtonClicked">
+
+ <com:THyperLinkColumn
+ HeaderText="Username"
+ DataTextField="username"
+ DataNavigateUrlField="username">
+ <prop:DataNavigateUrlFormatString>#
+ $this->Service->constructUrl('users.EditUser',array('username'=>{0}))
+ </prop:DataNavigateUrlFormatString>
+ </com:THyperLinkColumn>
+
+ <com:TBoundColumn
+ HeaderText="Email"
+ DataField="email" />
+
+ <com:TCheckBoxColumn
+ HeaderText="Administrator"
+ DataField="role" />
+
+ <com:TButtonColumn
+ HeaderText="Command"
+ Text="Delete"
+ ButtonType="PushButton"
+ CommandName="delete" />
+
+</com:TDataGrid>
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/pages/users/AdminUser.php b/demos/blog-tutorial/samples/day4/blog/protected/pages/users/AdminUser.php
new file mode 100644
index 00000000..ad8f6e3d
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/pages/users/AdminUser.php
@@ -0,0 +1,36 @@
+<?php
+
+class AdminUser extends TPage
+{
+ /**
+ * Populates the datagrid with user lists.
+ * This method is invoked by the framework when initializing the page
+ * @param mixed event parameter
+ */
+ public function onInit($param)
+ {
+ parent::onInit($param);
+ // fetches all data account information
+ $this->UserGrid->DataSource=UserRecord::finder()->findAll();
+ // binds the data to interface components
+ $this->UserGrid->dataBind();
+ }
+
+ /**
+ * Deletes a specified user record.
+ * This method responds to the datagrid's OnDeleteCommand event.
+ * @param TDataGrid the event sender
+ * @param TDataGridCommandEventParameter the event parameter
+ */
+ public function deleteButtonClicked($sender,$param)
+ {
+ // obtains the datagrid item that contains the clicked delete button
+ $item=$param->Item;
+ // obtains the primary key corresponding to the datagrid item
+ $username=$this->UserGrid->DataKeys[$item->ItemIndex];
+ // deletes the user record with the specified username primary key
+ UserRecord::finder()->deleteByPk($username);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/pages/users/EditUser.page b/demos/blog-tutorial/samples/day4/blog/protected/pages/users/EditUser.page
new file mode 100644
index 00000000..8aa3670e
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/pages/users/EditUser.page
@@ -0,0 +1,61 @@
+<%@ Title="My Blog - Edit User" %>
+
+<com:TContent ID="Main">
+
+<h1>Edit User</h1>
+
+<span>Username:</span>
+<com:TLabel ID="Username" />
+
+<br/>
+<span>Password:</span>
+<br/>
+<com:TTextBox ID="Password" TextMode="Password" />
+
+<br/>
+<span>Re-type Password:</span>
+<com:TCompareValidator
+ ControlToValidate="Password"
+ ControlToCompare="Password2"
+ ErrorMessage="Your password entries did not match."
+ Display="Dynamic" />
+<br/>
+<com:TTextBox ID="Password2" TextMode="Password" />
+
+<br/>
+<span>Email Address:</span>
+<com:TRequiredFieldValidator
+ ControlToValidate="Email"
+ ErrorMessage="Please provide your email address."
+ Display="Dynamic" />
+<com:TEmailAddressValidator
+ ControlToValidate="Email"
+ ErrorMessage="You entered an invalid email address."
+ Display="Dynamic" />
+<br/>
+<com:TTextBox ID="Email" />
+
+<com:TControl Visible="<%= $this->User->IsAdmin %>">
+<br/>
+<span>Role:</span>
+<br/>
+<com:TDropDownList ID="Role">
+ <com:TListItem Text="Normal User" Value="0" />
+ <com:TListItem Text="Administrator" Value="1" />
+</com:TDropDownList>
+</com:TControl>
+
+<br/>
+<span>First Name:</span>
+<br/>
+<com:TTextBox ID="FirstName" />
+
+<br/>
+<span>Last Name:</span>
+<br/>
+<com:TTextBox ID="LastName" />
+
+<br/>
+<com:TButton Text="Save" OnClick="saveButtonClicked" />
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/pages/users/EditUser.php b/demos/blog-tutorial/samples/day4/blog/protected/pages/users/EditUser.php
new file mode 100644
index 00000000..81538c33
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/pages/users/EditUser.php
@@ -0,0 +1,83 @@
+<?php
+
+class EditUser extends TPage
+{
+ /**
+ * Initializes the inputs with existing user data.
+ * This method is invoked by the framework when the page is being initialized.
+ * @param mixed event parameter
+ */
+ public function onInit($param)
+ {
+ parent::onInit($param);
+ if(!$this->IsPostBack) // if the page is initially requested
+ {
+ // Retrieves the existing user data. This is equivalent to:
+ // $userRecord=$this->getUserRecord();
+ $userRecord=$this->UserRecord;
+
+ // Populates the input controls with the existing user data
+ $this->Username->Text=$userRecord->username;
+ $this->Email->Text=$userRecord->email;
+ $this->Role->SelectedValue=$userRecord->role;
+ $this->FirstName->Text=$userRecord->first_name;
+ $this->LastName->Text=$userRecord->last_name;
+ }
+ }
+
+ /**
+ * Saves the user account if all inputs are valid.
+ * This method responds to the OnClick event of the "save" button.
+ * @param mixed event sender
+ * @param mixed event parameter
+ */
+ public function saveButtonClicked($sender,$param)
+ {
+ if($this->IsValid) // when all validations succeed
+ {
+ // Retrieves the existing user data. This is equivalent to:
+ $userRecord=$this->UserRecord;
+
+ // Fetches the input data
+ $userRecord->username=$this->Username->Text;
+ // update password when the input is not empty
+ if(!empty($this->Password->Text))
+ $userRecord->password=$this->Password->Text;
+ $userRecord->email=$this->Email->Text;
+ // update the role if the current user is an administrator
+ if($this->User->IsAdmin)
+ $userRecord->role=(int)$this->Role->SelectedValue;
+ $userRecord->first_name=$this->FirstName->Text;
+ $userRecord->last_name=$this->LastName->Text;
+
+ // saves to the database via Active Record mechanism
+ $userRecord->save();
+
+ // redirects the browser to the homepage
+ $this->Response->redirect($this->Service->DefaultPageUrl);
+ }
+ }
+
+ /**
+ * Returns the user data to be editted.
+ * @return UserRecord the user data to be editted.
+ * @throws THttpException if the user data is not found.
+ */
+ protected function getUserRecord()
+ {
+ // the user to be editted is the currently logged-in user
+ $username=$this->User->Name;
+ // if the 'username' GET var is not empty and the current user
+ // is an administrator, we use the GET var value instead.
+ if($this->User->IsAdmin && $this->Request['username']!==null)
+ $username=$this->Request['username'];
+
+ // use Active Record to look for the specified username
+ $userRecord=UserRecord::finder()->findByPk($username);
+ if(!($userRecord instanceof UserRecord))
+ throw new THttpException(500,'Username is invalid.');
+ return $userRecord;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/pages/users/LoginUser.page b/demos/blog-tutorial/samples/day4/blog/protected/pages/users/LoginUser.page
new file mode 100644
index 00000000..f7fc7367
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/pages/users/LoginUser.page
@@ -0,0 +1,28 @@
+<%@ Title="My Blog - Login" %>
+
+<com:TContent ID="Main">
+
+<h1>Login</h1>
+
+<span>Username:</span>
+<com:TRequiredFieldValidator
+ ControlToValidate="Username"
+ ErrorMessage="Please provide your username."
+ Display="Dynamic" />
+<br/>
+<com:TTextBox ID="Username" />
+
+<br/>
+<span>Password:</span>
+<com:TCustomValidator
+ ControlToValidate="Password"
+ ErrorMessage="Your entered an invalid password."
+ Display="Dynamic"
+ OnServerValidate="validateUser" />
+<br/>
+<com:TTextBox ID="Password" TextMode="Password" />
+
+<br/>
+<com:TButton Text="Login" OnClick="loginButtonClicked" />
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/pages/users/LoginUser.php b/demos/blog-tutorial/samples/day4/blog/protected/pages/users/LoginUser.php
new file mode 100644
index 00000000..a0955490
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/pages/users/LoginUser.php
@@ -0,0 +1,37 @@
+<?php
+
+class LoginUser extends TPage
+{
+ /**
+ * Validates whether the username and password are correct.
+ * This method responds to the TCustomValidator's OnServerValidate event.
+ * @param mixed event sender
+ * @param mixed event parameter
+ */
+ public function validateUser($sender,$param)
+ {
+ $authManager=$this->Application->getModule('auth');
+ if(!$authManager->login($this->Username->Text,$this->Password->Text))
+ $param->IsValid=false; // tell the validator that validation fails
+ }
+
+ /**
+ * Redirects the user's browser to appropriate URL if login succeeds.
+ * This method responds to the login button's OnClick event.
+ * @param mixed event sender
+ * @param mixed event parameter
+ */
+ public function loginButtonClicked($sender,$param)
+ {
+ if($this->Page->IsValid) // all validations succeed
+ {
+ // obtain the URL of the privileged page that the user wanted to visit originally
+ $url=$this->Application->getModule('auth')->ReturnUrl;
+ if(empty($url)) // the user accesses the login page directly
+ $url=$this->Service->DefaultPageUrl;
+ $this->Response->redirect($url);
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/pages/users/NewUser.page b/demos/blog-tutorial/samples/day4/blog/protected/pages/users/NewUser.page
new file mode 100644
index 00000000..d1547a9a
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/pages/users/NewUser.page
@@ -0,0 +1,73 @@
+<%@ Title="My Blog - New User" %>
+
+<com:TContent ID="Main">
+
+<h1>Create New User</h1>
+
+<span>Username:</span>
+<com:TRequiredFieldValidator
+ ControlToValidate="Username"
+ ErrorMessage="Please provide a username."
+ Display="Dynamic" />
+<com:TCustomValidator
+ ControlToValidate="Username"
+ ErrorMessage="Sorry, your username is taken by someone else. Please choose another username."
+ OnServerValidate="checkUsername"
+ Display="Dynamic" />
+<br/>
+<com:TTextBox ID="Username" />
+
+<br/>
+<span>Password:</span>
+<com:TRequiredFieldValidator
+ ControlToValidate="Password"
+ ErrorMessage="Please provide a password."
+ Display="Dynamic" />
+<br/>
+<com:TTextBox ID="Password" TextMode="Password" />
+
+<br/>
+<span>Re-type Password:</span>
+<com:TCompareValidator
+ ControlToValidate="Password"
+ ControlToCompare="Password2"
+ ErrorMessage="Your password entries did not match."
+ Display="Dynamic" />
+<br/>
+<com:TTextBox ID="Password2" TextMode="Password" />
+
+<br/>
+<span>Email Address:</span>
+<com:TRequiredFieldValidator
+ ControlToValidate="Email"
+ ErrorMessage="Please provide your email address."
+ Display="Dynamic" />
+<com:TEmailAddressValidator
+ ControlToValidate="Email"
+ ErrorMessage="You entered an invalid email address."
+ Display="Dynamic" />
+<br/>
+<com:TTextBox ID="Email" />
+
+<br/>
+<span>Role:</span>
+<br/>
+<com:TDropDownList ID="Role">
+ <com:TListItem Text="Normal User" Value="0" />
+ <com:TListItem Text="Administrator" Value="1" />
+</com:TDropDownList>
+
+<br/>
+<span>First Name:</span>
+<br/>
+<com:TTextBox ID="FirstName" />
+
+<br/>
+<span>Last Name:</span>
+<br/>
+<com:TTextBox ID="LastName" />
+
+<br/>
+<com:TButton Text="Create" OnClick="createButtonClicked" />
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/pages/users/NewUser.php b/demos/blog-tutorial/samples/day4/blog/protected/pages/users/NewUser.php
new file mode 100644
index 00000000..76e8cb88
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/pages/users/NewUser.php
@@ -0,0 +1,45 @@
+<?php
+
+class NewUser extends TPage
+{
+ /**
+ * Checks whether the username exists in the database.
+ * This method responds to the OnServerValidate event of username's custom validator.
+ * @param mixed event sender
+ * @param mixed event parameter
+ */
+ public function checkUsername($sender,$param)
+ {
+ // valid if the username is not found in the database
+ $param->IsValid=UserRecord::finder()->findByPk($this->Username->Text)===null;
+ }
+
+ /**
+ * Creates a new user account if all inputs are valid.
+ * This method responds to the OnClick event of the "create" button.
+ * @param mixed event sender
+ * @param mixed event parameter
+ */
+ public function createButtonClicked($sender,$param)
+ {
+ if($this->IsValid) // when all validations succeed
+ {
+ // populates a UserRecord object with user inputs
+ $userRecord=new UserRecord;
+ $userRecord->username=$this->Username->Text;
+ $userRecord->password=$this->Password->Text;
+ $userRecord->email=$this->Email->Text;
+ $userRecord->role=(int)$this->Role->SelectedValue;
+ $userRecord->first_name=$this->FirstName->Text;
+ $userRecord->last_name=$this->LastName->Text;
+
+ // saves to the database via Active Record mechanism
+ $userRecord->save();
+
+ // redirects the browser to the homepage
+ $this->Response->redirect($this->Service->DefaultPageUrl);
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/pages/users/config.xml b/demos/blog-tutorial/samples/day4/blog/protected/pages/users/config.xml
new file mode 100644
index 00000000..56554441
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/pages/users/config.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+ <authorization>
+ <allow pages="NewUser,AdminUser" roles="admin" />
+ <deny users="?" />
+ </authorization>
+</configuration> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/schema.sql b/demos/blog-tutorial/samples/day4/blog/protected/schema.sql
new file mode 100644
index 00000000..89f7388e
--- /dev/null
+++ b/demos/blog-tutorial/samples/day4/blog/protected/schema.sql
@@ -0,0 +1,25 @@
+/* create users table */
+CREATE TABLE users (
+ username VARCHAR(128) NOT NULL PRIMARY KEY,
+ email VARCHAR(128) NOT NULL,
+ password VARCHAR(128) NOT NULL, /* in plain text */
+ role INTEGER NOT NULL, /* 0: normal user, 1: administrator */
+ first_name VARCHAR(128),
+ last_name VARCHAR(128)
+);
+
+/* create posts table */
+CREATE TABLE posts (
+ post_id INTEGER NOT NULL PRIMARY KEY,
+ author_id VARCHAR(128) NOT NULL
+ CONSTRAINT fk_author REFERENCES users(username),
+ create_time INTEGER NOT NULL, /* UNIX timestamp */
+ title VARCHAR(256) NOT NULL, /* title of the post */
+ content TEXT, /* post body */
+ status INTEGER NOT NULL /* 0: published; 1: draft; 2: pending; 2: denied */
+);
+
+/* insert some initial data records for testing */
+INSERT INTO users VALUES ('admin', 'admin@example.com', 'demo', 1, 'Qiang', 'Xue');
+INSERT INTO users VALUES ('demo', 'demo@example.com', 'demo', 0, 'Wei', 'Zhuo');
+INSERT INTO posts VALUES (NULL, 'admin', 1175708482, 'first post', 'this is my first post', 0);