summaryrefslogtreecommitdiff
path: root/demos/blog-tutorial/protected/pages/Day3
diff options
context:
space:
mode:
Diffstat (limited to 'demos/blog-tutorial/protected/pages/Day3')
-rw-r--r--demos/blog-tutorial/protected/pages/Day3/Auth.page102
-rw-r--r--demos/blog-tutorial/protected/pages/Day3/CreateAdminUser.page148
-rw-r--r--demos/blog-tutorial/protected/pages/Day3/CreateEditUser.page191
-rw-r--r--demos/blog-tutorial/protected/pages/Day3/CreateLoginUser.page158
-rw-r--r--demos/blog-tutorial/protected/pages/Day3/CreateNewUser.page206
-rw-r--r--demos/blog-tutorial/protected/pages/Day3/Overview.page26
-rw-r--r--demos/blog-tutorial/protected/pages/Day3/directories.gifbin0 -> 10329 bytes
-rw-r--r--demos/blog-tutorial/protected/pages/Day3/output.gifbin0 -> 10006 bytes
-rw-r--r--demos/blog-tutorial/protected/pages/Day3/output2.gifbin0 -> 9222 bytes
-rw-r--r--demos/blog-tutorial/protected/pages/Day3/output3.gifbin0 -> 9464 bytes
10 files changed, 831 insertions, 0 deletions
diff --git a/demos/blog-tutorial/protected/pages/Day3/Auth.page b/demos/blog-tutorial/protected/pages/Day3/Auth.page
new file mode 100644
index 00000000..82c667d7
--- /dev/null
+++ b/demos/blog-tutorial/protected/pages/Day3/Auth.page
@@ -0,0 +1,102 @@
+<com:TContent ID="Main">
+
+<h1>Authentication and Authorization</h1>
+
+<p>
+Before we set off to implement the user pages, we need to do some work to enable <a href="http://www.pradosoft.com/demos/quickstart/index.php?page=Advanced.Auth">authentication and authorization</a>.
+</p>
+
+<p>
+We add two new modules to the application configuration as follows:
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="xml">
+<modules>
+ ...TDataSourceConfig and TActiveRecordConfig modules...
+
+ <module id="auth"
+ class="System.Security.TAuthManager"
+ UserManager="users"
+ LoginPage="users.LoginUser" />
+
+ <module id="users"
+ class="System.Security.TDbUserManager"
+ UserClass="Application.BlogUser" />
+</modules>
+</com:TTextHighlighter>
+
+<p>
+The <a href="http://www.pradosoft.com/docs/classdoc/TAuthManager">TAuthManager</a> module manages the whole authentication and authorization workflow. It uses the <tt>users</tt> module as its user manager (see below). By specifying the <tt>LoginPage</tt> property, we inform the auth manager to redirect user's browser to the <tt>LoginUser</tt> page when an authorization fails. We will describe how to create <tt>LoginUser</tt> in the next subsection.
+</p>
+
+<p>
+The <tt>user</tt> module is of class <a href="http://www.pradosoft.com/docs/classdoc/TDbUserManager">TDbUserManager</a> which is responsible to verify the validity of a user and keep basic user data in the PHP session. The <tt>UserClass</tt> property is initialized as <tt>Application.BlogUser</tt>, which indicates the user manager would look for a <tt>BlogUser</tt> class under the directory <tt>protected</tt> (remember the alias <tt>Application</tt> refers to the <tt>protected</tt> directory) and use it to keep user's session data.
+</p>
+
+<p>
+As we will see in later sections, in controls and pages, we can use <tt>$this->User</tt> to obtain the <tt>BlogUser</tt> object which contains the information of the user currently accessing the system.
+</p>
+
+<p>
+Below is the implementation detail of <tt>BlogUser</tt>. Notice <a href="http://www.pradosoft.com/demos/quickstart/index.php?page=Database.ActiveRecord">Active Record</a> is used to perform DB query. For example, we use <tt>UserRecord::finder()->findByPk($username)</tt> to look for the primary key specified by <tt>$username</tt> in the <tt>users</tt> table.
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="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');
+ }
+}
+</com:TTextHighlighter>
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/protected/pages/Day3/CreateAdminUser.page b/demos/blog-tutorial/protected/pages/Day3/CreateAdminUser.page
new file mode 100644
index 00000000..36b43014
--- /dev/null
+++ b/demos/blog-tutorial/protected/pages/Day3/CreateAdminUser.page
@@ -0,0 +1,148 @@
+<com:TContent ID="Main">
+
+<h1>Creating <tt>AdminUser</tt> Page</h1>
+
+<p>
+The <tt>AdminUser</tt> page displays all user accounts in a list so that the administrator can perform some administrative work. For simplicity, the administrative work our blog system supports include editting a user account and deleting a user account.
+</p>
+
+<p>
+We will display the user list in a table. Each row of the table represents a single user account, and the following columns are to be displayed:
+</p>
+<ul>
+<li>Username - displays the usernames. In each cell a hyerplink is displayed which leads to the corresponding <a href="?page=Day3.CreateEditUser">EditUser</a> page.</li>
+<li>Email - displays the emails.</li>
+<li>Administrator - shows whether the user account is of the administrator role.</li>
+<li>Command - displays a column of "Delete" buttons. Clicking on any of them will lead to deletion of the corresponding user account.</li>
+</ul>
+
+
+<h2>Creating Page Template</h2>
+<p>
+We use <a href="http://www.pradosoft.com/demos/quickstart/?page=Controls.DataGrid">TDataGrid</a> to display the user accounts. Based on the above analysis, we configure the following four columns:
+</p>
+<ul>
+<li><a href="http://www.pradosoft.com/docs/classdoc/THyperLinkColumn">THyperLinkColumn</a> displays the username column. The URL is constructed according to the PHP expression specified in the <tt>DataNavigateUrlFormatString</tt> property.</li>
+<li><a href="http://www.pradosoft.com/docs/classdoc/TBoundColumn">TBoundColumn</a> displays the email column.</li>
+<li><a href="http://www.pradosoft.com/docs/classdoc/TCheckBoxColumn">TCheckBoxColumn</a> uses checkboxes to indicate whether a user account is of role administrator.</li>
+<li><a href="http://www.pradosoft.com/docs/classdoc/TButtonColumn">TButtonColumn</a> displays a column of "Delete" buttons.</li>
+</ul>
+
+<p>Complete page template is shown as follows:</p>
+
+<com:TTextHighlighter CssClass="source" Language="prado">
+&lt;%@ Title="My Blog - Manage User Accounts" %>
+
+&lt;com:TContent ID="Main">
+
+<h1>Manage User Accounts</h1>
+
+<a href="&lt;%= $this->Service->constructUrl('users.NewUser')%>">Create New User</a>
+<br/>
+
+&lt;com:TDataGrid ID="UserGrid"
+ DataKeyField="username"
+ AutoGenerateColumns="false"
+ OnDeleteCommand="deleteButtonClicked">
+
+ &lt;com:THyperLinkColumn
+ HeaderText="Username"
+ DataTextField="username"
+ DataNavigateUrlField="username">
+ &lt;prop:DataNavigateUrlFormatString>#
+ $this->Service->constructUrl('users.EditUser',array('username'=>{0}))
+ &lt;/prop:DataNavigateUrlFormatString>
+ &lt;/com:THyperLinkColumn>
+
+ &lt;com:TBoundColumn
+ HeaderText="Email"
+ DataField="email" />
+
+ &lt;com:TCheckBoxColumn
+ HeaderText="Administrator"
+ DataField="role" />
+
+ &lt;com:TButtonColumn
+ HeaderText="Command"
+ Text="Delete"
+ ButtonType="PushButton"
+ CommandName="delete" />
+
+&lt;/com:TDataGrid>
+
+&lt;/com:TContent>
+</com:TTextHighlighter>
+
+
+<h2>Creating Page Class</h2>
+
+<p>
+In the above page template, the datagrid's <tt>OnDeleteCommand</tt> event is ttached with the method <tt>deleteButtonClicked()</tt> which we shall implement in the page class. In addition, the datagrid needs to be populated with user accounts data when the page is initialized. Therefore, we write the page class as follows:
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="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);
+ }
+}
+</com:TTextHighlighter>
+
+<p>
+In the above, the <tt>deleteButtonClicked()</tt> method is invoked whenever a "Delete" button is clicked. To determine which row of the buttons is clicked, we check the <tt>Item.ItemIndex</tt> property of the event parameter. To further identify which user account is to be deleted, we retrieve the primary key (username) value via the datagrid's <tt>DataKeys</tt> property.
+</p>
+
+<com:TipBox>
+All <a href="http://www.pradosoft.com/docs/classdoc/TDataBoundControl">data-bound</a> controls have similar usage pattern. That is, set the <tt>DataSource</tt> property with the data and call <tt>dataBind()</tt> method to binds the data to the control's internal structure.
+</com:TipBox>
+
+
+<h2>Adding Permission Check</h2>
+<p>
+Since <tt>AdminUser</tt> should only be accessible by administrators, we need to adjust the page configuration file <tt>protected/pages/users/config.xml</tt> accordingly.
+</p>
+<com:TTextHighlighter CssClass="source" Language="xml">
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+ <authorization>
+ <allow pages="NewUser,AdminUser" roles="admin" />
+ <deny users="?" />
+ </authorization>
+</configuration>
+</com:TTextHighlighter>
+
+<h2>Testing</h2>
+<p>
+To test the <tt>AdminUser</tt> page, visit the URL <tt>http://hostname/blog/index.php?page=users.AdminUser</tt>. You may be required to login as an administrator first if you have not done so. We shall expect to see the following result.
+</p>
+
+<img src="<%~ output3.gif %>" class="output" />
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/protected/pages/Day3/CreateEditUser.page b/demos/blog-tutorial/protected/pages/Day3/CreateEditUser.page
new file mode 100644
index 00000000..64ac6798
--- /dev/null
+++ b/demos/blog-tutorial/protected/pages/Day3/CreateEditUser.page
@@ -0,0 +1,191 @@
+<com:TContent ID="Main">
+
+<h1>Creating <tt>EditUser</tt> Page</h1>
+
+<p>
+The <tt>EditUser</tt> page is very similar to the <a href="?page=Day3.CreateNewUser">NewUser</a>. The main difference is that when <tt>EditUser</tt> is initially requested, the input fields should be initialized with existing user information. Another slight difference is that <tt>EditUser</tt> can also be accessed by normal users.
+</p>
+
+<p>
+To determine which user account is to be editted, we use the following policy:
+</p>
+<ul>
+<li>If the current user is an administrator, he can edit any user account by specifying the account's username in a GET variable named 'username'. For example, <tt>http://hostname/blog/index.php?page=users.EditUser&username=demo</tt>.</li>
+<li>If the current user is an administrator and the URl does not contain 'username', the administrator himself's data is being updated.</li>
+<li>If the current user is a normal user, he can only edit his own account information, and he cannot modify his role data.</li>
+</ul>
+
+<h2>Creating Page Template</h2>
+<p>
+As you may have guessed, the page template <tt>EditUser</tt> is largely the same as that of <tt>NewUser</tt>. Besides the difference in page title and the caption of the submit button, there are three main differences.
+</p>
+<ul>
+<li>The "username" text box is replaced by a <a href="http://www.pradosoft.com/demos/quickstart/?page=Controls.Label">TLabel</a> control because we do not allow modifying username;</li>
+<li>The validator for the "password" input is removed. This is because if the user does not provide a password during editting, it means the user does not want to change the password.</li>
+<li>The "role" input is surrounded with <tt>TControl</tt> whose visibility is toggled according to the role of the currently logged-in user. If the user is not an administrator, the "role" input will not be displayed because normal users are not allowed to modify their roles.</li>
+</ul>
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="prado">
+&lt;%@ Title="My Blog - Edit User" %>
+
+&lt;com:TContent ID="Main">
+
+<h1>Edit User</h1>
+
+<span>Username:</span>
+&lt;com:TLabel ID="Username" />
+
+<br/>
+<span>Password:</span>
+<br/>
+&lt;com:TTextBox ID="Password" TextMode="Password" />
+
+<br/>
+<span>Re-type Password:</span>
+&lt;com:TCompareValidator
+ ControlToValidate="Password"
+ ControlToCompare="Password2"
+ ErrorMessage="Your password entries did not match."
+ Display="Dynamic" />
+<br/>
+&lt;com:TTextBox ID="Password2" TextMode="Password" />
+
+<br/>
+<span>Email Address:</span>
+&lt;com:TRequiredFieldValidator
+ ControlToValidate="Email"
+ ErrorMessage="Please provide your email address."
+ Display="Dynamic" />
+&lt;com:TEmailAddressValidator
+ ControlToValidate="Email"
+ ErrorMessage="You entered an invalid email address."
+ Display="Dynamic" />
+<br/>
+&lt;com:TTextBox ID="Email" />
+
+&lt;com:TControl Visible="&lt;%= $this->User->IsAdmin %>">
+<br/>
+<span>Role:</span>
+<br/>
+&lt;com:TDropDownList ID="Role">
+ &lt;com:TListItem Text="Normal User" Value="0" />
+ &lt;com:TListItem Text="Administrator" Value="1" />
+&lt;/com:TDropDownList>
+&lt;/com:TControl>
+
+<br/>
+<span>First Name:</span>
+<br/>
+&lt;com:TTextBox ID="FirstName" />
+
+<br/>
+<span>Last Name:</span>
+<br/>
+&lt;com:TTextBox ID="LastName" />
+
+<br/>
+&lt;com:TButton Text="Save" OnClick="saveButtonClicked" />
+
+&lt;/com:TContent>
+</com:TTextHighlighter>
+
+
+<h2>Creating Page Class</h2>
+
+<p>
+Based on the above description and template, we need to write a page class that initializes the inputs with the existing user information. In addition, the page class also needs to implement the <tt>saveButtonClicked()</tt> method which is attached to the "save" button's <tt>OnClick</tt> event.
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="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->constructUrl($this->Service->DefaultPage));
+ }
+ }
+
+ /**
+ * 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;
+ }
+}
+</com:TTextHighlighter>
+
+<com:TipBox>
+The <tt>onInit()</tt> method is invoked by PRADO during one of the <a href="http://www.pradosoft.com/demos/quickstart/?page=Fundamentals.Pages">page lifecycles</a>. Other commonly overriden lifecycle methods include <tt>onPreInit()</tt>, <tt>onLoad()</tt> and <tt>onPreRender()</tt>.
+</com:TipBox>
+
+<h2>Testing</h2>
+<p>
+To test the <tt>EditUser</tt> page, visit the URL <tt>http://hostname/blog/index.php?page=users.EditUser&username=demo</tt>. You may be required to login first if you have not done so. Try logging in with different accounts (e.g. admin/demo, demo/demo) and see how the page displays differently.
+</p>
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/protected/pages/Day3/CreateLoginUser.page b/demos/blog-tutorial/protected/pages/Day3/CreateLoginUser.page
new file mode 100644
index 00000000..61ce27b7
--- /dev/null
+++ b/demos/blog-tutorial/protected/pages/Day3/CreateLoginUser.page
@@ -0,0 +1,158 @@
+<com:TContent ID="Main">
+
+<h1>Creating <tt>LoginUser</tt> Page</h1>
+
+<p>
+The <tt>LoginUser</tt> page displays a login form and authenticates a user who tries to login. As described in <a href="?page=Day3.Auth">authentication and authorization</a>, the user's browser is automatically redirected to the <tt>LoginUser</tt> page when the user is attempting to access a privileged page, such as a user admin page.
+</p>
+
+<p>
+The workflow of <tt>LoginUser</tt> is very similar to the <a href="?page=Day1.CreateContact">Contact</a> page:
+</p>
+<ol>
+<li>When a user accesses the <tt>LoginUser</tt> page, a login form is displayed;</li>
+<li>The user fills in the username and password and clicks on the "login" button;</li>
+<li>The <tt>LoginUser</tt> receives the "login" event and triggers the authentication sequence;</li>
+<li>If the user enters correct username and password, the system assigns him a valid identity and redirects his browser to the desired privileged page; If not, a "password invalid" message is displayed.
+</ol>
+
+<h2>Creating Page Template</h2>
+
+<p>
+Below we show the template for <tt>LoginPage</tt>. As we see, the page mainly contains a text box for collecting username and a text box for password. The username input is required, which is ensured by the <tt>TRequiredFieldValidator</tt>. The correctness of the password input is ensured by the <a href="http://www.pradosoft.com/demos/quickstart/index.php?page=Controls.Validation">TCustomValidator</a> which invokes the page's <tt>validateUser()</tt> method when validation is performed. The page also has "login" button which invokes the page's <tt>loginButtonClicked()</tt> when it is clicked.
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="prado">
+&lt;%@ Title="My Blog - Login" %>
+
+&lt;com:TContent ID="Main">
+
+<h1>Login</h1>
+
+<span>Username:</span>
+&lt;com:TRequiredFieldValidator
+ ControlToValidate="Username"
+ ErrorMessage="Please provide your username."
+ Display="Dynamic" />
+<br/>
+&lt;com:TTextBox ID="Username" />
+
+<br/>
+<span>Password:</span>
+&lt;com:TCustomValidator
+ ControlToValidate="Password"
+ ErrorMessage="Your entered an invalid password."
+ Display="Dynamic"
+ OnServerValidate="validateUser" />
+<br/>
+&lt;com:TTextBox ID="Password" TextMode="Password" />
+
+<br/>
+&lt;com:TButton Text="Login" OnClick="loginButtonClicked" />
+
+&lt;/com:TContent>
+</com:TTextHighlighter>
+
+<h2>Creating Page Class</h2>
+
+<p>
+Like the <a href="?page=Day1.CreateContact">Contact</a> page, the <tt>LoginUser</tt> page also needs a class file which mainly contains the implementation of event handlers attached in the page template. Here, we need to implement two methods: <tt>validateUser()</tt> and <tt>loginButtonClicked()</tt>. In <tt>validateUser()</tt>, we use the <a href="?page=Day3.Auth">auth manager</a> to verify if the username and password are valid. If valid, the auth manager will automatically create a user session with appropriate user identity information.
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="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->constructUrl($this->Service->DefaultPage);
+ $this->Response->redirect($url);
+ }
+ }
+}
+</com:TTextHighlighter>
+
+
+<h2>Testing</h2>
+
+<p>
+So we have created the <tt>LoginUser</tt> page. We can test it by visiting the URL <tt>http://hostname/blog/index.php?page=users.LoginUser</tt>. Remember in the <a href="?page=Day2.CreateDB">Creating Database</a> subsection, we already created two user accounts (username/password): <tt>admin/demo</tt> and <tt>demo/demo</tt>. We can use them to test our login page.
+</p>
+
+<img src="<%~ output.gif %>" class="output"/>
+
+<h2>Adding Login/Logout Links to Master</h2>
+
+<p>
+To provide a direct way for users to login and logout, we modify the <tt>MainLayout</tt> master control a bit. In particular, we add a "login" hyperlink which links to the <tt>LoginUser</tt> page. We also add a "logout" link button which logs out a user when it is clicked.
+</p>
+
+<p>
+We modify the footer section of the <tt>MainLayout</tt>'s template as follows. The visibility of "login" and "logout" is determined according to user's status. If the user is not logged in yet, i.e., <tt>$this->User->IsGuest</tt> is true, the "login" link is visible while the "logout" link is not; and vice versa.
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="prado">
+<div id="footer">
+&lt;com:THyperLink Text="Login"
+ NavigateUrl="&lt;%= $this->Service->constructUrl('users.LoginUser') %>"
+ Visible="&lt;%= $this->User->IsGuest %>" />
+
+&lt;com:TLinkButton Text="Logout"
+ OnClick="logoutButtonClicked"
+ Visible="&lt;%= !$this->User->IsGuest %>" />
+
+<br/>
+&lt;%= PRADO::poweredByPrado() %>
+</div>
+</com:TTextHighlighter>
+
+<p>
+Since the "logout" button attaches its <tt>OnClick</tt> event with a method called <tt>logoutButtonClicked()</tt>, we need to modify the class file of <tt>MainLayout</tt> as well.
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="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);
+ }
+}
+</com:TTextHighlighter>
+
+<p>
+Now if we visit any page of our blog system, we should see either a link at the bottom of the page. The link displays "Login" if we have not logged in yet and "Logout" if we have logged in. If we click on "Logout", the browser will be redirected to the homepage and "Login" is displayed meaning we have logged out.
+</p>
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/protected/pages/Day3/CreateNewUser.page b/demos/blog-tutorial/protected/pages/Day3/CreateNewUser.page
new file mode 100644
index 00000000..d9ef684f
--- /dev/null
+++ b/demos/blog-tutorial/protected/pages/Day3/CreateNewUser.page
@@ -0,0 +1,206 @@
+<com:TContent ID="Main">
+
+<h1>Creating <tt>NewUser</tt> Page</h1>
+
+<p>
+The <tt>NewUser</tt> page is provided to the administrator user to create new a new user account. It needs to display a form that collects the information about the new user account. According to our <a href="?page=Day2.CreateDB">database definition</a>, we will need to collect the following information:
+</p>
+
+<ul>
+<li><tt>username</tt> - string, required and unique</li>
+<li><tt>email</tt> - string, required and unique</li>
+<li><tt>password</tt> - string, required</li>
+<li><tt>role</tt> - integer, required (either 0 or 1)</li>
+<li><tt>first_name</tt> - string, optional</li>
+<li><tt>last_name</tt> - string, optional</li>
+</ul>
+
+<h2>Creating Page Template</h2>
+<p>
+Based on the above analysis, we write the page template as follows:
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="prado">
+&lt;%@ Title="My Blog - New User" %>
+
+&lt;com:TContent ID="Main">
+
+<h1>Create New User</h1>
+
+<span>Username:</span>
+&lt;com:TRequiredFieldValidator
+ ControlToValidate="Username"
+ ErrorMessage="Please provide a username."
+ Display="Dynamic" />
+&lt;com:TCustomValidator
+ ControlToValidate="Username"
+ ErrorMessage="Sorry, your username is taken by someone else. Please choose another username."
+ OnServerValidate="checkUsername"
+ Display="Dynamic" />
+<br/>
+&lt;com:TTextBox ID="Username" />
+
+<br/>
+<span>Password:</span>
+&lt;com:TRequiredFieldValidator
+ ControlToValidate="Password"
+ ErrorMessage="Please provide a password."
+ Display="Dynamic" />
+<br/>
+&lt;com:TTextBox ID="Password" TextMode="Password" />
+
+<br/>
+<span>Re-type Password:</span>
+&lt;com:TCompareValidator
+ ControlToValidate="Password"
+ ControlToCompare="Password2"
+ ErrorMessage="Your password entries did not match."
+ Display="Dynamic" />
+<br/>
+&lt;com:TTextBox ID="Password2" TextMode="Password" />
+
+<br/>
+<span>Email Address:</span>
+&lt;com:TRequiredFieldValidator
+ ControlToValidate="Email"
+ ErrorMessage="Please provide your email address."
+ Display="Dynamic" />
+&lt;com:TEmailAddressValidator
+ ControlToValidate="Email"
+ ErrorMessage="You entered an invalid email address."
+ Display="Dynamic" />
+<br/>
+&lt;com:TTextBox ID="Email" />
+
+<br/>
+<span>Role:</span>
+<br/>
+&lt;com:TDropDownList ID="Role">
+ &lt;com:TListItem Text="Normal User" Value="0" />
+ &lt;com:TListItem Text="Administrator" Value="1" />
+&lt;/com:TDropDownList>
+
+<br/>
+<span>First Name:</span>
+<br/>
+&lt;com:TTextBox ID="FirstName" />
+
+<br/>
+<span>Last Name:</span>
+<br/>
+&lt;com:TTextBox ID="LastName" />
+
+<br/>
+&lt;com:TButton Text="Create" OnClick="createButtonClicked" />
+
+&lt;/com:TContent>
+</com:TTextHighlighter>
+
+<p>
+The template is not much different from the <tt>Contact</tt> template and the <tt>LoginUser</tt> page. It mainly consists of text boxes and validators. Some text boxes, such as username, are associated with two validators because of the multiple validation rules involved.
+</p>
+
+
+<h2>Creating Page Class</h2>
+
+<p>
+From the above page template, we see that we need to write a page class that implements the two event handlers: <tt>checkUsername()</tt> (attached to the first custom validator's <tt>OnServerValidate</tt> event) and <tt>createButtonClicked()</tt> (attached to the "create" button's <tt>OnClick</tt> event). Therefore, we write the page class as follows:
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="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->constructUrl($this->Service->DefaultPage));
+ }
+ }
+}
+</com:TTextHighlighter>
+
+<p>
+In the above, calling <tt>save()</tt> will insert a new row in the <tt>users</tt> table. This intuitive feature is enabled by <a href="http://www.pradosoft.com/demos/quickstart/?page=Database.ActiveRecord">Active Record</a>.
+</p>
+
+<com:NoteBox>
+For simplicity, usernames in our blog system are case-sensitive! In many practical systems, usernames may be required to be case-sensitive. So special handling needs to be made when creating a new user account as well as <a href="?page=Day3.Auth">performing authentication</a>. Also, the surrounding blanks in a username may need to be trimmed when creating a new account with it.
+</com:NoteBox>
+
+
+<h2>Testing</h2>
+<p>
+To test the <tt>NewUser</tt> page, visit the URL <tt>http://hostname/blog/index.php?page=users.NewUser</tt>. We shall see the following page output. Try enter different information into the form and see how the inputs are being validated. If all validation rules are satisfied, we shall expect the user account being created and the browser being redirected to the homepage.
+</p>
+
+<img src="<%~ output2.gif %>" class="output"/>
+
+
+<h2>Adding Permission Check</h2>
+<p>
+During testing, you may have asked: shouldn't the <tt>NewUser</tt> page be only accessible by the administrator user? Yes, this is called <a href="http://www.pradosoft.com/demos/quickstart/?page=Advanced.Auth">authorization</a>. We now describe how we add this permission check to the <tt>NewUser</tt> page.
+</p>
+
+<p>
+A straightforward way of performing permission check is in the page class where we check whether <tt>$this->User->IsAdmin</tt> is true, and if not we redirect the browser to the <tt>LoginUser</tt> page.
+</p>
+
+<p>
+PRADO offers a more systematic way of checking page access permissions. To do so, we need to use <a href="http://www.pradosoft.com/demos/quickstart/?page=Configurations.PageConfig">page configuration</a>. Create a file <tt>protected/pages/users/config.xml</tt> with the content as follows:
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="xml">
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+ <authorization>
+ <allow pages="NewUser" roles="admin" />
+ <deny users="?" />
+ </authorization>
+</configuration>
+</com:TTextHighlighter>
+
+<p>
+The page configuration contains authorization rules that apply to the pages under the directory <tt>protected/pages/users</tt>. The above configuration reads that the <tt>NewUser</tt> can be accessed by users of role <tt>admin</tt> (see <a href="?page=Day3.Auth">BlogUser.createUser()</a> for why the word "admin"), and deny anonymous access (<tt>users="?"</tt> means guest users) for all pages under the directory.
+</p>
+
+<p>
+Now if we visit the <tt>NewUser</tt> page as a guest, we will be redirected to the <tt>LoginUser</tt> page first. If our login is successful, we will be redirected back to the <tt>NewUser</tt> page.
+</p>
+
+<com:TipBox>
+Page configuration can contain more than authorization rules. For example, it can include <a href="http://www.pradosoft.com/demos/quickstart/?page=Fundamentals.Modules">modules</a> like we did in the <a href="?page=Day2.ConnectDB">application configuration</a>. For a PRADO application, each page directory can have a page configuration which applies to the pages in the same directory and all its subdirectories.
+</com:TipBox>
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/protected/pages/Day3/Overview.page b/demos/blog-tutorial/protected/pages/Day3/Overview.page
new file mode 100644
index 00000000..5a83e3a4
--- /dev/null
+++ b/demos/blog-tutorial/protected/pages/Day3/Overview.page
@@ -0,0 +1,26 @@
+<com:TContent ID="Main">
+
+<h1>User Management Overview</h1>
+
+<p>
+In this section, we create pages that are related with user management. In particular, we implement these required features: user login/logout, creating new user account and updating/deleting user accounts.
+</p>
+
+<p>
+According to the requirements, we need to create the following pages. To better organize our code, these user-related pages will be created under a new directory <tt>protected/pages/users</tt>.
+</p>
+
+<ul>
+ <li><tt>LoginUser</tt> displays a login form to login a user.</li>
+ <li><tt>NewUser</tt> creates a new user account.</li>
+ <li><tt>EditUser</tt> allows a registered user to update his profile.</li>
+ <li><tt>AdminUser</tt> allows the administrator to manage the user accounts, including setting permission level and deleting a user account.</li>
+</ul>
+
+<p>
+After finishing this section, we shall expect to see the following directories and files:
+</p>
+
+<img src="<%~ directories.gif %>" class="output" />
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/protected/pages/Day3/directories.gif b/demos/blog-tutorial/protected/pages/Day3/directories.gif
new file mode 100644
index 00000000..f59fda58
--- /dev/null
+++ b/demos/blog-tutorial/protected/pages/Day3/directories.gif
Binary files differ
diff --git a/demos/blog-tutorial/protected/pages/Day3/output.gif b/demos/blog-tutorial/protected/pages/Day3/output.gif
new file mode 100644
index 00000000..0d812dd0
--- /dev/null
+++ b/demos/blog-tutorial/protected/pages/Day3/output.gif
Binary files differ
diff --git a/demos/blog-tutorial/protected/pages/Day3/output2.gif b/demos/blog-tutorial/protected/pages/Day3/output2.gif
new file mode 100644
index 00000000..749255d6
--- /dev/null
+++ b/demos/blog-tutorial/protected/pages/Day3/output2.gif
Binary files differ
diff --git a/demos/blog-tutorial/protected/pages/Day3/output3.gif b/demos/blog-tutorial/protected/pages/Day3/output3.gif
new file mode 100644
index 00000000..a11ee653
--- /dev/null
+++ b/demos/blog-tutorial/protected/pages/Day3/output3.gif
Binary files differ