From ff32eed01f783ee33caeacb0f7315612f0994f8f Mon Sep 17 00:00:00 2001 From: xue <> Date: Sun, 8 Apr 2007 21:33:23 +0000 Subject: Added Day 2 tutorial. --- demos/blog-tutorial/protected/common/TopicList.tpl | 28 ++- .../protected/pages/Day1/CreateContact.page | 58 ++++-- .../blog-tutorial/protected/pages/Day1/Setup.page | 11 +- .../protected/pages/Day1/ShareLayout.page | 4 +- .../blog-tutorial/protected/pages/Day1/output.gif | Bin 15045 -> 13379 bytes .../protected/pages/Day2/CreateAR.page | 2 +- .../protected/pages/Day2/CreateDB.page | 12 +- demos/blog-tutorial/protected/pages/Day2/ER.gif | Bin 4919 -> 5172 bytes demos/blog-tutorial/protected/pages/Day2/ER.vsd | Bin 73216 -> 73216 bytes demos/blog-tutorial/protected/pages/Day3/Auth.page | 102 ++++++++++ .../protected/pages/Day3/CreateAdminUser.page | 148 +++++++++++++++ .../protected/pages/Day3/CreateEditUser.page | 191 +++++++++++++++++++ .../protected/pages/Day3/CreateLoginUser.page | 158 ++++++++++++++++ .../protected/pages/Day3/CreateNewUser.page | 206 +++++++++++++++++++++ .../protected/pages/Day3/Overview.page | 26 +++ .../protected/pages/Day3/directories.gif | Bin 0 -> 10329 bytes .../blog-tutorial/protected/pages/Day3/output.gif | Bin 0 -> 10006 bytes .../blog-tutorial/protected/pages/Day3/output2.gif | Bin 0 -> 9222 bytes .../blog-tutorial/protected/pages/Day3/output3.gif | Bin 0 -> 9464 bytes .../protected/pages/Requirements.page | 2 +- 20 files changed, 908 insertions(+), 40 deletions(-) create mode 100644 demos/blog-tutorial/protected/pages/Day3/Auth.page create mode 100644 demos/blog-tutorial/protected/pages/Day3/CreateAdminUser.page create mode 100644 demos/blog-tutorial/protected/pages/Day3/CreateEditUser.page create mode 100644 demos/blog-tutorial/protected/pages/Day3/CreateLoginUser.page create mode 100644 demos/blog-tutorial/protected/pages/Day3/CreateNewUser.page create mode 100644 demos/blog-tutorial/protected/pages/Day3/Overview.page create mode 100644 demos/blog-tutorial/protected/pages/Day3/directories.gif create mode 100644 demos/blog-tutorial/protected/pages/Day3/output.gif create mode 100644 demos/blog-tutorial/protected/pages/Day3/output2.gif create mode 100644 demos/blog-tutorial/protected/pages/Day3/output3.gif (limited to 'demos/blog-tutorial/protected') diff --git a/demos/blog-tutorial/protected/common/TopicList.tpl b/demos/blog-tutorial/protected/common/TopicList.tpl index b6174985..f66a2d0b 100644 --- a/demos/blog-tutorial/protected/common/TopicList.tpl +++ b/demos/blog-tutorial/protected/common/TopicList.tpl @@ -18,7 +18,7 @@
-We use template to organize the presentational layout of the feedback form. In the template, we use textboxes to collect user's name, email and feedback. And we use validators to ensure that the user provides all these information before submitting the feedback form. The whole template looks like the following: +We use template to organize the presentational layout of the feedback form. In the template, we use textboxes to collect user's name, email and feedback. And we use validators to ensure that the user provides all these information before submitting the feedback form. The whole template is as follows,
Please fill out the following form to let me know your feedback on my blog. Thanks!
+ +Your Name: +<com:TRequiredFieldValidator ControlToValidate="Name" + ErrorMessage="Please provide your name." + Display="Dynamic" /> +-The template looks very similar to a normal HTML page. The main difference is that the template contains a few <com:> tags. Each <com:> tag refers to a control whose properties are being initialized with name-value pairs in the tag. For example, the <com:TButton> refers to the TButton control which displays a button that users can click on to submit the feedback form. For complete template syntax, please refer to the Quickstart Tutorial. +As we can see that the template looks very similar to a normal HTML page. The main difference is that the template contains a few <com:> tags. Each <com:> tag refers to a control whose properties are being initialized with name-value pairs in the tag. For example, the <com:TButton> refers to the TButton control which displays a button that users can click on to submit the feedback form. For complete template syntax, please refer to the Quickstart Tutorial.
-Three controls are used here: +Below we summarize the controls that are used in the page template:
A further enhancement to this page is to show some confirmation message on the page after the user submits feedback. And possibly, the browser may be redirected to another page if the submission is successful. We will leave these tasks to our readers. diff --git a/demos/blog-tutorial/protected/pages/Day1/Setup.page b/demos/blog-tutorial/protected/pages/Day1/Setup.page index ee4744f5..0fe877bf 100644 --- a/demos/blog-tutorial/protected/pages/Day1/Setup.page +++ b/demos/blog-tutorial/protected/pages/Day1/Setup.page @@ -21,7 +21,7 @@ php path/to/prado-cli.php -c . Running the above command creates the following directories and files:
-We now have a skeleton PRADO application accessible via the URL http://hostname/blog/index.php which brings up a Web page showing "Welcome to PRADO". @@ -145,13 +145,16 @@ To change the location of the root page directory and change the name of homepag
diff --git a/demos/blog-tutorial/protected/pages/Day1/ShareLayout.page b/demos/blog-tutorial/protected/pages/Day1/ShareLayout.page
index 548cec1c..d3d1f553 100644
--- a/demos/blog-tutorial/protected/pages/Day1/ShareLayout.page
+++ b/demos/blog-tutorial/protected/pages/Day1/ShareLayout.page
@@ -17,7 +17,7 @@ It is also possible to share common layout via " />
+
For the moment, MainLayout only contains a simple header and a footer, as shown in the following. In future, we will add a side-bar to it. Readers are also encouraged to enhance the layout with other features.
@@ -38,7 +38,7 @@ For the moment, MainLayout only contains a simple header and a footer,
If we check the PostRecord class file, we should see the following content. diff --git a/demos/blog-tutorial/protected/pages/Day2/CreateDB.page b/demos/blog-tutorial/protected/pages/Day2/CreateDB.page index 291b20e4..eebda4c1 100644 --- a/demos/blog-tutorial/protected/pages/Day2/CreateDB.page +++ b/demos/blog-tutorial/protected/pages/Day2/CreateDB.page @@ -10,7 +10,7 @@ Most Web applications use database to keep data. Our blog system is not an excep For tutorial purpose, we have simplified the requirements of our blog system so that it only needs to deal with user and post data. We thus create two database tables, users and posts, as shown in the following entity-relationship (ER) diagram.
-
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);
+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: +
+ ++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. +
+ ++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: +
++We use TDataGrid to display the user accounts. Based on the above analysis, we configure the following four columns: +
+Complete page template is shown as follows:
+ ++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: +
+ ++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. +
+ ++Since AdminUser should only be accessible by administrators, we need to adjust the page configuration file protected/pages/users/config.xml accordingly. +
++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. +
+ ++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: +
++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. +
++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. +
+ ++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. +
+ ++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: +
++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. +
+ ++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. +
+ ++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. +
+ ++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. +
+ ++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. +
+ ++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: +
+ ++Based on the above analysis, we write the page template as follows: +
+ ++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. +
+ + ++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: +
+ ++In the above, calling save() will insert a new row in the users table. This intuitive feature is enabled by Active Record. +
+ ++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. +
+ ++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. +
+ ++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. +
+ ++After finishing this section, we shall expect to see the following directories and files: +
+ +