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. --- .gitattributes | 32 ++++ 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 +- .../day1/blog/protected/layouts/MainLayout.tpl | 2 +- .../samples/day2/blog/protected/data/blog.db | Bin 5120 -> 4096 bytes .../day2/blog/protected/database/PostRecord.php | 4 +- .../day2/blog/protected/database/UserRecord.php | 2 +- .../day2/blog/protected/layouts/MainLayout.tpl | 2 +- .../samples/day2/blog/protected/schema.sql | 9 +- demos/blog-tutorial/samples/day3/blog/index.php | 23 +++ .../samples/day3/blog/protected/.htaccess | 1 + .../samples/day3/blog/protected/BlogUser.php | 59 ++++++ .../samples/day3/blog/protected/application.xml | 55 ++++++ .../samples/day3/blog/protected/data/blog.db | Bin 0 -> 5120 bytes .../day3/blog/protected/database/PostRecord.php | 27 +++ .../day3/blog/protected/database/UserRecord.php | 27 +++ .../day3/blog/protected/layouts/MainLayout.php | 19 ++ .../day3/blog/protected/layouts/MainLayout.tpl | 29 +++ .../samples/day3/blog/protected/pages/Contact.page | 47 +++++ .../samples/day3/blog/protected/pages/Contact.php | 30 +++ .../samples/day3/blog/protected/pages/Home.page | 7 + .../day3/blog/protected/pages/users/AdminUser.page | 40 ++++ .../day3/blog/protected/pages/users/AdminUser.php | 36 ++++ .../day3/blog/protected/pages/users/EditUser.page | 61 ++++++ .../day3/blog/protected/pages/users/EditUser.php | 83 +++++++++ .../day3/blog/protected/pages/users/LoginUser.page | 28 +++ .../day3/blog/protected/pages/users/LoginUser.php | 37 ++++ .../day3/blog/protected/pages/users/NewUser.page | 73 ++++++++ .../day3/blog/protected/pages/users/NewUser.php | 45 +++++ .../day3/blog/protected/pages/users/config.xml | 7 + .../samples/day3/blog/protected/schema.sql | 24 +++ demos/blog-tutorial/themes/PradoSoft/style.css | 14 +- 50 files changed, 1721 insertions(+), 50 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 create mode 100644 demos/blog-tutorial/samples/day3/blog/index.php create mode 100644 demos/blog-tutorial/samples/day3/blog/protected/.htaccess create mode 100644 demos/blog-tutorial/samples/day3/blog/protected/BlogUser.php create mode 100644 demos/blog-tutorial/samples/day3/blog/protected/application.xml create mode 100644 demos/blog-tutorial/samples/day3/blog/protected/data/blog.db create mode 100644 demos/blog-tutorial/samples/day3/blog/protected/database/PostRecord.php create mode 100644 demos/blog-tutorial/samples/day3/blog/protected/database/UserRecord.php create mode 100644 demos/blog-tutorial/samples/day3/blog/protected/layouts/MainLayout.php create mode 100644 demos/blog-tutorial/samples/day3/blog/protected/layouts/MainLayout.tpl create mode 100644 demos/blog-tutorial/samples/day3/blog/protected/pages/Contact.page create mode 100644 demos/blog-tutorial/samples/day3/blog/protected/pages/Contact.php create mode 100644 demos/blog-tutorial/samples/day3/blog/protected/pages/Home.page create mode 100644 demos/blog-tutorial/samples/day3/blog/protected/pages/users/AdminUser.page create mode 100644 demos/blog-tutorial/samples/day3/blog/protected/pages/users/AdminUser.php create mode 100644 demos/blog-tutorial/samples/day3/blog/protected/pages/users/EditUser.page create mode 100644 demos/blog-tutorial/samples/day3/blog/protected/pages/users/EditUser.php create mode 100644 demos/blog-tutorial/samples/day3/blog/protected/pages/users/LoginUser.page create mode 100644 demos/blog-tutorial/samples/day3/blog/protected/pages/users/LoginUser.php create mode 100644 demos/blog-tutorial/samples/day3/blog/protected/pages/users/NewUser.page create mode 100644 demos/blog-tutorial/samples/day3/blog/protected/pages/users/NewUser.php create mode 100644 demos/blog-tutorial/samples/day3/blog/protected/pages/users/config.xml create mode 100644 demos/blog-tutorial/samples/day3/blog/protected/schema.sql diff --git a/.gitattributes b/.gitattributes index 68e1848d..11a4c16b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -712,6 +712,16 @@ demos/blog-tutorial/protected/pages/Day2/ER.gif -text demos/blog-tutorial/protected/pages/Day2/ER.vsd -text demos/blog-tutorial/protected/pages/Day2/directories.gif -text demos/blog-tutorial/protected/pages/Day2/directories2.gif -text +demos/blog-tutorial/protected/pages/Day3/Auth.page -text +demos/blog-tutorial/protected/pages/Day3/CreateAdminUser.page -text +demos/blog-tutorial/protected/pages/Day3/CreateEditUser.page -text +demos/blog-tutorial/protected/pages/Day3/CreateLoginUser.page -text +demos/blog-tutorial/protected/pages/Day3/CreateNewUser.page -text +demos/blog-tutorial/protected/pages/Day3/Overview.page -text +demos/blog-tutorial/protected/pages/Day3/directories.gif -text +demos/blog-tutorial/protected/pages/Day3/output.gif -text +demos/blog-tutorial/protected/pages/Day3/output2.gif -text +demos/blog-tutorial/protected/pages/Day3/output3.gif -text demos/blog-tutorial/protected/pages/Overview.page -text demos/blog-tutorial/protected/pages/Requirements.page -text demos/blog-tutorial/samples/day1/blog/index.php -text @@ -734,6 +744,28 @@ demos/blog-tutorial/samples/day2/blog/protected/pages/Contact.page -text demos/blog-tutorial/samples/day2/blog/protected/pages/Contact.php -text demos/blog-tutorial/samples/day2/blog/protected/pages/Home.page -text demos/blog-tutorial/samples/day2/blog/protected/schema.sql -text +demos/blog-tutorial/samples/day3/blog/index.php -text +demos/blog-tutorial/samples/day3/blog/protected/.htaccess -text +demos/blog-tutorial/samples/day3/blog/protected/BlogUser.php -text +demos/blog-tutorial/samples/day3/blog/protected/application.xml -text +demos/blog-tutorial/samples/day3/blog/protected/data/blog.db -text +demos/blog-tutorial/samples/day3/blog/protected/database/PostRecord.php -text +demos/blog-tutorial/samples/day3/blog/protected/database/UserRecord.php -text +demos/blog-tutorial/samples/day3/blog/protected/layouts/MainLayout.php -text +demos/blog-tutorial/samples/day3/blog/protected/layouts/MainLayout.tpl -text +demos/blog-tutorial/samples/day3/blog/protected/pages/Contact.page -text +demos/blog-tutorial/samples/day3/blog/protected/pages/Contact.php -text +demos/blog-tutorial/samples/day3/blog/protected/pages/Home.page -text +demos/blog-tutorial/samples/day3/blog/protected/pages/users/AdminUser.page -text +demos/blog-tutorial/samples/day3/blog/protected/pages/users/AdminUser.php -text +demos/blog-tutorial/samples/day3/blog/protected/pages/users/EditUser.page -text +demos/blog-tutorial/samples/day3/blog/protected/pages/users/EditUser.php -text +demos/blog-tutorial/samples/day3/blog/protected/pages/users/LoginUser.page -text +demos/blog-tutorial/samples/day3/blog/protected/pages/users/LoginUser.php -text +demos/blog-tutorial/samples/day3/blog/protected/pages/users/NewUser.page -text +demos/blog-tutorial/samples/day3/blog/protected/pages/users/NewUser.php -text +demos/blog-tutorial/samples/day3/blog/protected/pages/users/config.xml -text +demos/blog-tutorial/samples/day3/blog/protected/schema.sql -text demos/blog-tutorial/themes/PradoSoft/arrowdown.gif -text demos/blog-tutorial/themes/PradoSoft/comment.gif -text demos/blog-tutorial/themes/PradoSoft/comment_add.gif -text 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 @@
-
Day 2: Working with Database - Part I
+
Day 2: Setting Up Database
-
Day 3: Working with Database - Part II
+
Day 3: Implementing User Management
-
Day 4: Authentication and Authorization
+
Day 4: Implementing Post Management
-
Day 5: Developing and Using Components
+
Day 5: Creating Portlets
+
  • Overview
  • +
  • Creating ReadPost Page
  • diff --git a/demos/blog-tutorial/protected/pages/Day1/CreateContact.page b/demos/blog-tutorial/protected/pages/Day1/CreateContact.page index 07adbe93..4daf43e3 100644 --- a/demos/blog-tutorial/protected/pages/Day1/CreateContact.page +++ b/demos/blog-tutorial/protected/pages/Day1/CreateContact.page @@ -14,7 +14,7 @@ The purpose of the Contact page is to collect feedback from Web users o To create the Contact page, we need two files under the pages directory: the page template file Contact.page and the page class file Contact.php.

    - + A page must have either a template file (extension .page) or a class file, or both: @@ -35,7 +35,7 @@ We first create the template file for the Contact page.

    -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,

    @@ -47,13 +47,40 @@ We use template to organize the presentational layout of the feedback form. In t <com:TForm> - ...textbox and validator for user's name... +

    Contact

    +

    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" /> +
    +<com:TTextBox ID="Name" /> - ...textbox and validators for user's email... +
    +Your Email: +<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" /> - ...textbox and validator for user's feedback content... +
    +Feedback: +<com:TRequiredFieldValidator ControlToValidate="Feedback" + ErrorMessage="Please provide your feedback." + Display="Dynamic" /> +
    +<com:TTextBox ID="Feedback" + TextMode="MultiLine" + Rows="10" + Columns="40" /> - <com:TButton Text="Submit" OnClick="submitButtonClicked" /> +
    +<com:TButton Text="Submit" OnClick="submitButtonClicked" /> </com:TForm> @@ -62,7 +89,7 @@ We use template to organize the presentational layout of the feedback form. In t

    -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.

    @@ -70,7 +97,7 @@ PRADO provides a control for every type of HTML input. For example, TTextBox controls, the template also uses many validator controls which ensure user's inputs satisfy specific validation rules. For example, to ensure a legitimate email address is provided, we use two validators to validate the "email" text box, as shown in the following:

    @@ -78,25 +105,24 @@ The following template shows the detail about "...textbox and validators for use <com:TRequiredFieldValidator ControlToValidate="Email" ErrorMessage="Please provide your email address." - Display="Dynamic" - /> + Display="Dynamic" /> <com:TEmailAddressValidator ControlToValidate="Email" ErrorMessage="You entered an invalid email address." - Display="Dynamic" - /> + Display="Dynamic" />
    <com:TTextBox ID="Email" />

    -Three controls are used here: +Below we summarize the controls that are used in the page template:

      -
    • TTextBox displays a textbox to allow user to enter his email address.
    • -
    • TRequiredFieldValidator ensures that the textbox is not empty when the feedback is submitted.
    • +
    • TForm displays an HTML form. Any input control must be enclosed within it. And most importantly, at most one TForm may appear in a page.
    • +
    • TTextBox displays a text box to collect user text input.
    • +
    • TRequiredFieldValidator ensures that the associated text box is not empty when the feedback is submitted.
    • TEmailAddressValidator ensures that the textbox contains a valid email address when the feedback is submitted.
    @@ -168,7 +194,7 @@ Page class name must be the same as the file name. This is also a requirement fo Our newly created Contact can be tested via the URL http://hostname/blog/index.php?page=Contact. If we click on the submit button without entering any information, we will see error messages appearing next to the corresponding textboxes. If we enter all required information, the method mailFeedback() will be invoked.

    - +

    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,

    </com:TForm> diff --git a/demos/blog-tutorial/protected/pages/Day1/output.gif b/demos/blog-tutorial/protected/pages/Day1/output.gif index f3ece514..9ad2bfb8 100644 Binary files a/demos/blog-tutorial/protected/pages/Day1/output.gif and b/demos/blog-tutorial/protected/pages/Day1/output.gif differ diff --git a/demos/blog-tutorial/protected/pages/Day2/CreateAR.page b/demos/blog-tutorial/protected/pages/Day2/CreateAR.page index d8b8b8ce..00ac1166 100644 --- a/demos/blog-tutorial/protected/pages/Day2/CreateAR.page +++ b/demos/blog-tutorial/protected/pages/Day2/CreateAR.page @@ -61,7 +61,7 @@ Here we used the " /> +

    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); @@ -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: +

    + + + +

    Creating Page Template

    +

    +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:

    + + +<%@ 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: +

    + + +

    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. +

    + +

    + + +<%@ 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: +

    +
      +
    1. When a user accesses the LoginUser page, a login form is displayed;
    2. +
    3. The user fills in the username and password and clicks on the "login" button;
    4. +
    5. The LoginUser receives the "login" event and triggers the authentication sequence;
    6. +
    7. 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: +

    + + + +

    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. +

    + + + +

    +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

    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