We use a SQLite 3 database to keep our data. We first convert the ER diagram into the following SQL statements and save them in the file protected/schema.sql.
@@ -20,8 +20,8 @@ We use a SQLite 3 database to keep our data. We first convert the ER diagram int
/* create users table */
CREATE TABLE users (
username VARCHAR(128) NOT NULL PRIMARY KEY,
- email VARCHAR(128) NOT NULL UNIQUE,
- password VARCHAR(128) NOT NULL, /* plain text password */
+ 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)
@@ -33,13 +33,13 @@ CREATE TABLE posts (
author VARCHAR(128) NOT NULL, /* references users.username */
create_time INTEGER NOT NULL, /* UNIX timestamp */
title VARCHAR(256) NOT NULL, /* title of the post */
- content TEXT NOT NULL /* content of the post */
+ 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');
+INSERT INTO posts VALUES (NULL, 'admin', 1175708482, 'first post', 'this is my first post', 0);
@@ -58,7 +58,7 @@ sqlite3 blog.db < ../schema.sql
The database has been created as protected/data/blog.db and we shall see the following directories and files:
-
It is required by SQLite that both the directory protected/data and the database file protected/data/blog.db be set writable by the Web server process.
diff --git a/demos/blog-tutorial/protected/pages/Day2/ER.gif b/demos/blog-tutorial/protected/pages/Day2/ER.gif
index 13e2d15b..90e4c1ea 100644
Binary files a/demos/blog-tutorial/protected/pages/Day2/ER.gif and b/demos/blog-tutorial/protected/pages/Day2/ER.gif differ
diff --git a/demos/blog-tutorial/protected/pages/Day2/ER.vsd b/demos/blog-tutorial/protected/pages/Day2/ER.vsd
index 2b59897a..95cf7f32 100644
Binary files a/demos/blog-tutorial/protected/pages/Day2/ER.vsd and b/demos/blog-tutorial/protected/pages/Day2/ER.vsd differ
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 @@
+
+
+Authentication and Authorization
+
+
+Before we set off to implement the user pages, we need to do some work to enable authentication and authorization.
+
+
+
+We add two new modules to the application configuration as follows:
+
+
+
+
+ ...TDataSourceConfig and TActiveRecordConfig modules...
+
+
+
+
+
+
+
+
+The TAuthManager module manages the whole authentication and authorization workflow. It uses the users module as its user manager (see below). By specifying the LoginPage property, we inform the auth manager to redirect user's browser to the LoginUser page when an authorization fails. We will describe how to create LoginUser in the next subsection.
+
+
+
+The user module is of class TDbUserManager which is responsible to verify the validity of a user and keep basic user data in the PHP session. The UserClass property is initialized as Application.BlogUser, which indicates the user manager would look for a BlogUser class under the directory protected (remember the alias Application refers to the protected directory) and use it to keep user's session data.
+
+
+
+As we will see in later sections, in controls and pages, we can use $this->User to obtain the BlogUser object which contains the information of the user currently accessing the system.
+
+
+
+Below is the implementation detail of BlogUser. Notice Active Record is used to perform DB query. For example, we use UserRecord::finder()->findByPk($username) to look for the primary key specified by $username in the users table.
+
+
+
+// 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/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 @@
+
+
+Creating AdminUser Page
+
+
+The AdminUser 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.
+
+
+
+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:
+
+
+- Username - displays the usernames. In each cell a hyerplink is displayed which leads to the corresponding EditUser page.
+- Email - displays the emails.
+- Administrator - shows whether the user account is of the administrator role.
+- Command - displays a column of "Delete" buttons. Clicking on any of them will lead to deletion of the corresponding user account.
+
+
+
+Creating Page Template
+
+We use TDataGrid to display the user accounts. Based on the above analysis, we configure the following four columns:
+
+
+- THyperLinkColumn displays the username column. The URL is constructed according to the PHP expression specified in the DataNavigateUrlFormatString property.
+- TBoundColumn displays the email column.
+- TCheckBoxColumn uses checkboxes to indicate whether a user account is of role administrator.
+- TButtonColumn displays a column of "Delete" buttons.
+
+
+Complete page template is shown as follows:
+
+
+<%@ Title="My Blog - Manage User Accounts" %>
+
+<com:TContent ID="Main">
+
+Manage User Accounts
+
+Create New User
+
+
+<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>
+
+
+
+Creating Page Class
+
+
+In the above page template, the datagrid's OnDeleteCommand event is ttached with the method deleteButtonClicked() 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:
+
+
+
+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);
+ }
+}
+
+
+
+In the above, the deleteButtonClicked() method is invoked whenever a "Delete" button is clicked. To determine which row of the buttons is clicked, we check the Item.ItemIndex 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 DataKeys property.
+
+
+
+All data-bound controls have similar usage pattern. That is, set the DataSource property with the data and call dataBind() method to binds the data to the control's internal structure.
+
+
+
+Adding Permission Check
+
+Since AdminUser should only be accessible by administrators, we need to adjust the page configuration file protected/pages/users/config.xml accordingly.
+
+
+
+
+
+
+
+
+
+
+
+Testing
+
+To test the AdminUser page, visit the URL http://hostname/blog/index.php?page=users.AdminUser. You may be required to login as an administrator first if you have not done so. We shall expect to see the following result.
+
+
+
+
+
\ 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 @@
+
+
+Creating EditUser Page
+
+
+The EditUser page is very similar to the NewUser. The main difference is that when EditUser is initially requested, the input fields should be initialized with existing user information. Another slight difference is that EditUser can also be accessed by normal users.
+
+
+
+To determine which user account is to be editted, we use the following policy:
+
+
+- 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, http://hostname/blog/index.php?page=users.EditUser&username=demo.
+- If the current user is an administrator and the URl does not contain 'username', the administrator himself's data is being updated.
+- If the current user is a normal user, he can only edit his own account information, and he cannot modify his role data.
+
+
+Creating Page Template
+
+As you may have guessed, the page template EditUser is largely the same as that of NewUser. Besides the difference in page title and the caption of the submit button, there are three main differences.
+
+
+- The "username" text box is replaced by a TLabel control because we do not allow modifying username;
+- 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.
+- The "role" input is surrounded with TControl 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.
+
+
+
+
+<%@ Title="My Blog - Edit User" %>
+
+<com:TContent ID="Main">
+
+Edit User
+
+Username:
+<com:TLabel ID="Username" />
+
+
+Password:
+
+<com:TTextBox ID="Password" TextMode="Password" />
+
+
+Re-type Password:
+<com:TCompareValidator
+ ControlToValidate="Password"
+ ControlToCompare="Password2"
+ ErrorMessage="Your password entries did not match."
+ Display="Dynamic" />
+
+<com:TTextBox ID="Password2" TextMode="Password" />
+
+
+Email Address:
+<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" />
+
+<com:TTextBox ID="Email" />
+
+<com:TControl Visible="<%= $this->User->IsAdmin %>">
+
+Role:
+
+<com:TDropDownList ID="Role">
+ <com:TListItem Text="Normal User" Value="0" />
+ <com:TListItem Text="Administrator" Value="1" />
+</com:TDropDownList>
+</com:TControl>
+
+
+First Name:
+
+<com:TTextBox ID="FirstName" />
+
+
+Last Name:
+
+<com:TTextBox ID="LastName" />
+
+
+<com:TButton Text="Save" OnClick="saveButtonClicked" />
+
+</com:TContent>
+
+
+
+Creating Page Class
+
+
+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 saveButtonClicked() method which is attached to the "save" button's OnClick event.
+
+
+
+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;
+ }
+}
+
+
+
+The onInit() method is invoked by PRADO during one of the page lifecycles. Other commonly overriden lifecycle methods include onPreInit(), onLoad() and onPreRender().
+
+
+Testing
+
+To test the EditUser page, visit the URL http://hostname/blog/index.php?page=users.EditUser&username=demo. 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.
+
+
+
\ 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 @@
+
+
+Creating LoginUser Page
+
+
+The LoginUser page displays a login form and authenticates a user who tries to login. As described in authentication and authorization, the user's browser is automatically redirected to the LoginUser page when the user is attempting to access a privileged page, such as a user admin page.
+
+
+
+The workflow of LoginUser is very similar to the Contact page:
+
+
+- When a user accesses the LoginUser page, a login form is displayed;
+- The user fills in the username and password and clicks on the "login" button;
+- The LoginUser receives the "login" event and triggers the authentication sequence;
+- 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.
+
+
+Creating Page Template
+
+
+Below we show the template for LoginPage. 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 TRequiredFieldValidator. The correctness of the password input is ensured by the TCustomValidator which invokes the page's validateUser() method when validation is performed. The page also has "login" button which invokes the page's loginButtonClicked() when it is clicked.
+
+
+
+<%@ Title="My Blog - Login" %>
+
+<com:TContent ID="Main">
+
+Login
+
+Username:
+<com:TRequiredFieldValidator
+ ControlToValidate="Username"
+ ErrorMessage="Please provide your username."
+ Display="Dynamic" />
+
+<com:TTextBox ID="Username" />
+
+
+Password:
+<com:TCustomValidator
+ ControlToValidate="Password"
+ ErrorMessage="Your entered an invalid password."
+ Display="Dynamic"
+ OnServerValidate="validateUser" />
+
+<com:TTextBox ID="Password" TextMode="Password" />
+
+
+<com:TButton Text="Login" OnClick="loginButtonClicked" />
+
+</com:TContent>
+
+
+Creating Page Class
+
+
+Like the Contact page, the LoginUser 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: validateUser() and loginButtonClicked(). In validateUser(), we use the auth manager 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.
+
+
+
+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);
+ }
+ }
+}
+
+
+
+Testing
+
+
+So we have created the LoginUser page. We can test it by visiting the URL http://hostname/blog/index.php?page=users.LoginUser. Remember in the Creating Database subsection, we already created two user accounts (username/password): admin/demo and demo/demo. We can use them to test our login page.
+
+
+
+
+Adding Login/Logout Links to Master
+
+
+To provide a direct way for users to login and logout, we modify the MainLayout master control a bit. In particular, we add a "login" hyperlink which links to the LoginUser page. We also add a "logout" link button which logs out a user when it is clicked.
+
+
+
+We modify the footer section of the MainLayout'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., $this->User->IsGuest is true, the "login" link is visible while the "logout" link is not; and vice versa.
+
+
+
+
+
+
+
+Since the "logout" button attaches its OnClick event with a method called logoutButtonClicked(), we need to modify the class file of MainLayout as well.
+
+
+
+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);
+ }
+}
+
+
+
+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.
+
+
+
\ 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 @@
+
+
+Creating NewUser Page
+
+
+The NewUser 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 database definition, we will need to collect the following information:
+
+
+
+- username - string, required and unique
+- email - string, required and unique
+- password - string, required
+- role - integer, required (either 0 or 1)
+- first_name - string, optional
+- last_name - string, optional
+
+
+Creating Page Template
+
+Based on the above analysis, we write the page template as follows:
+
+
+
+<%@ Title="My Blog - New User" %>
+
+<com:TContent ID="Main">
+
+Create New User
+
+Username:
+<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" />
+
+<com:TTextBox ID="Username" />
+
+
+Password:
+<com:TRequiredFieldValidator
+ ControlToValidate="Password"
+ ErrorMessage="Please provide a password."
+ Display="Dynamic" />
+
+<com:TTextBox ID="Password" TextMode="Password" />
+
+
+Re-type Password:
+<com:TCompareValidator
+ ControlToValidate="Password"
+ ControlToCompare="Password2"
+ ErrorMessage="Your password entries did not match."
+ Display="Dynamic" />
+
+<com:TTextBox ID="Password2" TextMode="Password" />
+
+
+Email Address:
+<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" />
+
+<com:TTextBox ID="Email" />
+
+
+Role:
+
+<com:TDropDownList ID="Role">
+ <com:TListItem Text="Normal User" Value="0" />
+ <com:TListItem Text="Administrator" Value="1" />
+</com:TDropDownList>
+
+
+First Name:
+
+<com:TTextBox ID="FirstName" />
+
+
+Last Name:
+
+<com:TTextBox ID="LastName" />
+
+
+<com:TButton Text="Create" OnClick="createButtonClicked" />
+
+</com:TContent>
+
+
+
+The template is not much different from the Contact template and the LoginUser 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.
+
+
+
+Creating Page Class
+
+
+From the above page template, we see that we need to write a page class that implements the two event handlers: checkUsername() (attached to the first custom validator's OnServerValidate event) and createButtonClicked() (attached to the "create" button's OnClick event). Therefore, we write the page class as follows:
+
+
+
+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));
+ }
+ }
+}
+
+
+
+In the above, calling save() will insert a new row in the users table. This intuitive feature is enabled by Active Record.
+
+
+
+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 performing authentication. Also, the surrounding blanks in a username may need to be trimmed when creating a new account with it.
+
+
+
+Testing
+
+To test the NewUser page, visit the URL http://hostname/blog/index.php?page=users.NewUser. 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.
+
+
+
+
+
+Adding Permission Check
+
+During testing, you may have asked: shouldn't the NewUser page be only accessible by the administrator user? Yes, this is called authorization. We now describe how we add this permission check to the NewUser page.
+
+
+
+A straightforward way of performing permission check is in the page class where we check whether $this->User->IsAdmin is true, and if not we redirect the browser to the LoginUser page.
+
+
+
+PRADO offers a more systematic way of checking page access permissions. To do so, we need to use page configuration. Create a file protected/pages/users/config.xml with the content as follows:
+
+
+
+
+
+
+
+
+
+
+
+
+
+The page configuration contains authorization rules that apply to the pages under the directory protected/pages/users. The above configuration reads that the NewUser can be accessed by users of role admin (see BlogUser.createUser() for why the word "admin"), and deny anonymous access (users="?" means guest users) for all pages under the directory.
+
+
+
+Now if we visit the NewUser page as a guest, we will be redirected to the LoginUser page first. If our login is successful, we will be redirected back to the NewUser page.
+
+
+
+Page configuration can contain more than authorization rules. For example, it can include modules like we did in the application configuration. 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.
+
+
+
\ 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 @@
+
+
+User Management Overview
+
+
+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.
+
+
+
+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 protected/pages/users.
+
+
+
+ - LoginUser displays a login form to login a user.
+ - NewUser creates a new user account.
+ - EditUser allows a registered user to update his profile.
+ - AdminUser allows the administrator to manage the user accounts, including setting permission level and deleting a user account.
+
+
+
+After finishing this section, we shall expect to see the following directories and files:
+
+
+
+
+
\ 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
Binary files /dev/null and b/demos/blog-tutorial/protected/pages/Day3/directories.gif 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
Binary files /dev/null and b/demos/blog-tutorial/protected/pages/Day3/output.gif 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
Binary files /dev/null and b/demos/blog-tutorial/protected/pages/Day3/output2.gif 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
Binary files /dev/null and b/demos/blog-tutorial/protected/pages/Day3/output3.gif differ
diff --git a/demos/blog-tutorial/protected/pages/Requirements.page b/demos/blog-tutorial/protected/pages/Requirements.page
index 497eba90..3b89750c 100644
--- a/demos/blog-tutorial/protected/pages/Requirements.page
+++ b/demos/blog-tutorial/protected/pages/Requirements.page
@@ -30,7 +30,7 @@ In general, the blog system should allow users to read blogs and authenticated u
System Maintenance
-- The system shall provide a mechanism to collect users' feedback.
+- The system shall be able to collect users' feedback.
- The system shall be flexible enough to allow adding new portlets in future.
- The system shall provide ease of using a different theme which changes the styles of the UI elements.
diff --git a/demos/blog-tutorial/samples/day1/blog/protected/layouts/MainLayout.tpl b/demos/blog-tutorial/samples/day1/blog/protected/layouts/MainLayout.tpl
index 5218b98d..6e264936 100644
--- a/demos/blog-tutorial/samples/day1/blog/protected/layouts/MainLayout.tpl
+++ b/demos/blog-tutorial/samples/day1/blog/protected/layouts/MainLayout.tpl
@@ -12,7 +12,7 @@
diff --git a/demos/blog-tutorial/samples/day2/blog/protected/data/blog.db b/demos/blog-tutorial/samples/day2/blog/protected/data/blog.db
index 37449fd3..46e82bfb 100644
Binary files a/demos/blog-tutorial/samples/day2/blog/protected/data/blog.db and b/demos/blog-tutorial/samples/day2/blog/protected/data/blog.db differ
diff --git a/demos/blog-tutorial/samples/day2/blog/protected/database/PostRecord.php b/demos/blog-tutorial/samples/day2/blog/protected/database/PostRecord.php
index 31451b30..a761286a 100644
--- a/demos/blog-tutorial/samples/day2/blog/protected/database/PostRecord.php
+++ b/demos/blog-tutorial/samples/day2/blog/protected/database/PostRecord.php
@@ -1,6 +1,6 @@
diff --git a/demos/blog-tutorial/samples/day2/blog/protected/schema.sql b/demos/blog-tutorial/samples/day2/blog/protected/schema.sql
index 085e47c3..d3189b40 100644
--- a/demos/blog-tutorial/samples/day2/blog/protected/schema.sql
+++ b/demos/blog-tutorial/samples/day2/blog/protected/schema.sql
@@ -1,8 +1,8 @@
/* create users table */
CREATE TABLE users (
username VARCHAR(128) NOT NULL PRIMARY KEY,
- email VARCHAR(128) NOT NULL UNIQUE,
- password VARCHAR(128) NOT NULL, /* plain text password */
+ 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)
@@ -14,10 +14,11 @@ CREATE TABLE posts (
author VARCHAR(128) NOT NULL, /* references users.username */
create_time INTEGER NOT NULL, /* UNIX timestamp */
title VARCHAR(256) NOT NULL, /* title of the post */
- content TEXT NOT NULL /* content of the post */
+ content TEXT NOT NULL, /* content of the post */
+ 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');
+INSERT INTO posts VALUES (NULL, 'admin', 1175708482, 'first post', 'this is my first post', 0);
diff --git a/demos/blog-tutorial/samples/day3/blog/index.php b/demos/blog-tutorial/samples/day3/blog/index.php
new file mode 100644
index 00000000..8132899e
--- /dev/null
+++ b/demos/blog-tutorial/samples/day3/blog/index.php
@@ -0,0 +1,23 @@
+run();
+
+?>
\ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day3/blog/protected/.htaccess b/demos/blog-tutorial/samples/day3/blog/protected/.htaccess
new file mode 100644
index 00000000..3418e55a
--- /dev/null
+++ b/demos/blog-tutorial/samples/day3/blog/protected/.htaccess
@@ -0,0 +1 @@
+deny from all
\ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day3/blog/protected/BlogUser.php b/demos/blog-tutorial/samples/day3/blog/protected/BlogUser.php
new file mode 100644
index 00000000..6b9e0a23
--- /dev/null
+++ b/demos/blog-tutorial/samples/day3/blog/protected/BlogUser.php
@@ -0,0 +1,59 @@
+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/day3/blog/protected/application.xml b/demos/blog-tutorial/samples/day3/blog/protected/application.xml
new file mode 100644
index 00000000..785f3608
--- /dev/null
+++ b/demos/blog-tutorial/samples/day3/blog/protected/application.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day3/blog/protected/data/blog.db b/demos/blog-tutorial/samples/day3/blog/protected/data/blog.db
new file mode 100644
index 00000000..37449fd3
Binary files /dev/null and b/demos/blog-tutorial/samples/day3/blog/protected/data/blog.db differ
diff --git a/demos/blog-tutorial/samples/day3/blog/protected/database/PostRecord.php b/demos/blog-tutorial/samples/day3/blog/protected/database/PostRecord.php
new file mode 100644
index 00000000..a761286a
--- /dev/null
+++ b/demos/blog-tutorial/samples/day3/blog/protected/database/PostRecord.php
@@ -0,0 +1,27 @@
+
\ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day3/blog/protected/database/UserRecord.php b/demos/blog-tutorial/samples/day3/blog/protected/database/UserRecord.php
new file mode 100644
index 00000000..87043894
--- /dev/null
+++ b/demos/blog-tutorial/samples/day3/blog/protected/database/UserRecord.php
@@ -0,0 +1,27 @@
+
\ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day3/blog/protected/layouts/MainLayout.php b/demos/blog-tutorial/samples/day3/blog/protected/layouts/MainLayout.php
new file mode 100644
index 00000000..46c1483d
--- /dev/null
+++ b/demos/blog-tutorial/samples/day3/blog/protected/layouts/MainLayout.php
@@ -0,0 +1,19 @@
+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/day3/blog/protected/layouts/MainLayout.tpl b/demos/blog-tutorial/samples/day3/blog/protected/layouts/MainLayout.tpl
new file mode 100644
index 00000000..89538ab2
--- /dev/null
+++ b/demos/blog-tutorial/samples/day3/blog/protected/layouts/MainLayout.tpl
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day3/blog/protected/pages/Contact.page b/demos/blog-tutorial/samples/day3/blog/protected/pages/Contact.page
new file mode 100644
index 00000000..c36149ca
--- /dev/null
+++ b/demos/blog-tutorial/samples/day3/blog/protected/pages/Contact.page
@@ -0,0 +1,47 @@
+<%@ Title="My Blog - Contact" %>
+
+
+
+Contact
+Please fill out the following form to let me know your feedback on my blog. Thanks!
+
+Your Name:
+
+
+
+
+
+
+Your Email:
+
+
+
+
+
+
+
+Feedback:
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day3/blog/protected/pages/Contact.php b/demos/blog-tutorial/samples/day3/blog/protected/pages/Contact.php
new file mode 100644
index 00000000..b6ce575e
--- /dev/null
+++ b/demos/blog-tutorial/samples/day3/blog/protected/pages/Contact.php
@@ -0,0 +1,30 @@
+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/day3/blog/protected/pages/Home.page b/demos/blog-tutorial/samples/day3/blog/protected/pages/Home.page
new file mode 100644
index 00000000..7a9c4a7d
--- /dev/null
+++ b/demos/blog-tutorial/samples/day3/blog/protected/pages/Home.page
@@ -0,0 +1,7 @@
+<%@ Title="Welcome to PRADO" %>
+
+
+
+Welcome to PRADO!
+
+
\ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day3/blog/protected/pages/users/AdminUser.page b/demos/blog-tutorial/samples/day3/blog/protected/pages/users/AdminUser.page
new file mode 100644
index 00000000..af03b858
--- /dev/null
+++ b/demos/blog-tutorial/samples/day3/blog/protected/pages/users/AdminUser.page
@@ -0,0 +1,40 @@
+<%@ Title="My Blog - Manage User Accounts" %>
+
+
+
+Manage User Accounts
+
+Create New User
+
+
+
+
+
+ #
+ $this->Service->constructUrl('users.EditUser',array('username'=>{0}))
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day3/blog/protected/pages/users/AdminUser.php b/demos/blog-tutorial/samples/day3/blog/protected/pages/users/AdminUser.php
new file mode 100644
index 00000000..ad8f6e3d
--- /dev/null
+++ b/demos/blog-tutorial/samples/day3/blog/protected/pages/users/AdminUser.php
@@ -0,0 +1,36 @@
+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/day3/blog/protected/pages/users/EditUser.page b/demos/blog-tutorial/samples/day3/blog/protected/pages/users/EditUser.page
new file mode 100644
index 00000000..8aa3670e
--- /dev/null
+++ b/demos/blog-tutorial/samples/day3/blog/protected/pages/users/EditUser.page
@@ -0,0 +1,61 @@
+<%@ Title="My Blog - Edit User" %>
+
+
+
+Edit User
+
+Username:
+
+
+
+Password:
+
+
+
+
+Re-type Password:
+
+
+
+
+
+Email Address:
+
+
+
+
+
+
+
+Role:
+
+
+
+
+
+
+
+
+First Name:
+
+
+
+
+Last Name:
+
+
+
+
+
+
+
\ No newline at end of file
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
new file mode 100644
index 00000000..cb69b9d5
--- /dev/null
+++ b/demos/blog-tutorial/samples/day3/blog/protected/pages/users/EditUser.php
@@ -0,0 +1,83 @@
+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;
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day3/blog/protected/pages/users/LoginUser.page b/demos/blog-tutorial/samples/day3/blog/protected/pages/users/LoginUser.page
new file mode 100644
index 00000000..f7fc7367
--- /dev/null
+++ b/demos/blog-tutorial/samples/day3/blog/protected/pages/users/LoginUser.page
@@ -0,0 +1,28 @@
+<%@ Title="My Blog - Login" %>
+
+
+
+Login
+
+Username:
+
+
+
+
+
+Password:
+
+
+
+
+
+
+
+
\ No newline at end of file
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
new file mode 100644
index 00000000..dff8f2f0
--- /dev/null
+++ b/demos/blog-tutorial/samples/day3/blog/protected/pages/users/LoginUser.php
@@ -0,0 +1,37 @@
+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);
+ }
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day3/blog/protected/pages/users/NewUser.page b/demos/blog-tutorial/samples/day3/blog/protected/pages/users/NewUser.page
new file mode 100644
index 00000000..d1547a9a
--- /dev/null
+++ b/demos/blog-tutorial/samples/day3/blog/protected/pages/users/NewUser.page
@@ -0,0 +1,73 @@
+<%@ Title="My Blog - New User" %>
+
+
+
+Create New User
+
+Username:
+
+
+
+
+
+
+Password:
+
+
+
+
+
+Re-type Password:
+
+
+
+
+
+Email Address:
+
+
+
+
+
+
+Role:
+
+
+
+
+
+
+
+First Name:
+
+
+
+
+Last Name:
+
+
+
+
+
+
+
\ No newline at end of file
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
new file mode 100644
index 00000000..005fb7d2
--- /dev/null
+++ b/demos/blog-tutorial/samples/day3/blog/protected/pages/users/NewUser.php
@@ -0,0 +1,45 @@
+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));
+ }
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day3/blog/protected/pages/users/config.xml b/demos/blog-tutorial/samples/day3/blog/protected/pages/users/config.xml
new file mode 100644
index 00000000..56554441
--- /dev/null
+++ b/demos/blog-tutorial/samples/day3/blog/protected/pages/users/config.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day3/blog/protected/schema.sql b/demos/blog-tutorial/samples/day3/blog/protected/schema.sql
new file mode 100644
index 00000000..d3189b40
--- /dev/null
+++ b/demos/blog-tutorial/samples/day3/blog/protected/schema.sql
@@ -0,0 +1,24 @@
+/* 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 VARCHAR(128) NOT NULL, /* references users.username */
+ create_time INTEGER NOT NULL, /* UNIX timestamp */
+ title VARCHAR(256) NOT NULL, /* title of the post */
+ content TEXT NOT NULL, /* content of the post */
+ 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);
diff --git a/demos/blog-tutorial/themes/PradoSoft/style.css b/demos/blog-tutorial/themes/PradoSoft/style.css
index ddfc7622..c9391df5 100644
--- a/demos/blog-tutorial/themes/PradoSoft/style.css
+++ b/demos/blog-tutorial/themes/PradoSoft/style.css
@@ -21,14 +21,14 @@ h1, h2, h3, h4
margin-bottom: 0;
}
-h1, h2
+h1
{
padding-bottom: 3px;
border-bottom: 1px solid #ccc;
}
h1 {
- font-size:13pt;
+ font-size:14pt;
}
h2 {
@@ -657,4 +657,14 @@ div.last-modified
{
border-left: 1px solid #ccc;
border-right: 1px solid #ccc;
+}
+
+img.output
+{
+ padding: 0.5em;
+ border-style:solid;
+ border-width:1px;
+ border-color:#eeeeee;
+ background-color:#ffffee;
+ margin: 0.2em;
}
\ No newline at end of file
--
cgit v1.2.3