summaryrefslogtreecommitdiff
path: root/demos/blog-tutorial
diff options
context:
space:
mode:
authorxue <>2007-06-29 17:41:20 +0000
committerxue <>2007-06-29 17:41:20 +0000
commit4a9dd5c8513ed96d1e0cf43e370b170dc38fb502 (patch)
treef41bbf25ac5bbc8d0b656c3e7c087e40b09b38b1 /demos/blog-tutorial
parent2d266a7994c57e9a13cccecacf8940e11263614f (diff)
finished blog-tutorial.
Diffstat (limited to 'demos/blog-tutorial')
-rw-r--r--demos/blog-tutorial/protected/common/TopicList.tpl30
-rw-r--r--demos/blog-tutorial/protected/pages/Day1/ShareLayout.page6
-rw-r--r--demos/blog-tutorial/protected/pages/Day4/CreateListPost.page2
-rw-r--r--demos/blog-tutorial/protected/pages/Day4/CreateNewPost.page3
-rw-r--r--demos/blog-tutorial/protected/pages/Day4/Overview.page2
-rw-r--r--demos/blog-tutorial/protected/pages/Day5/ErrorLogging.page159
-rw-r--r--demos/blog-tutorial/protected/pages/Day5/Performance.page67
-rw-r--r--demos/blog-tutorial/protected/pages/Day5/Summary.page36
-rw-r--r--demos/blog-tutorial/protected/pages/Day5/UseTheme.page138
-rw-r--r--demos/blog-tutorial/protected/pages/Day5/output.gifbin0 -> 4282 bytes
-rw-r--r--demos/blog-tutorial/protected/pages/Day5/output2.gifbin0 -> 7798 bytes
-rw-r--r--demos/blog-tutorial/protected/pages/Day5/output3.gifbin0 -> 5190 bytes
-rw-r--r--demos/blog-tutorial/protected/pages/Overview.page6
-rw-r--r--demos/blog-tutorial/samples/day1/blog/protected/layouts/MainLayout.tpl10
-rw-r--r--demos/blog-tutorial/samples/day2/blog/protected/layouts/MainLayout.tpl10
-rw-r--r--demos/blog-tutorial/samples/day3/blog/protected/layouts/MainLayout.tpl14
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/application.xml1
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/layouts/MainLayout.tpl5
-rw-r--r--demos/blog-tutorial/samples/day4/blog/protected/pages/posts/PostRenderer.tpl2
-rw-r--r--demos/blog-tutorial/samples/day5/blog/index.php22
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/.htaccess1
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/BlogErrorHandler.php40
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/BlogException.php7
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/BlogUser.php59
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/application.xml50
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/data/blog.dbbin0 -> 4096 bytes
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/database/PostRecord.php28
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/database/UserRecord.php28
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/error.html20
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/layouts/MainLayout.php19
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/layouts/MainLayout.tpl43
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/pages/Contact.page47
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/pages/Contact.php30
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/pages/Home.page7
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/pages/posts/EditPost.page27
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/pages/posts/EditPost.php72
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/pages/posts/ListPost.page14
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/pages/posts/ListPost.php64
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/pages/posts/NewPost.page27
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/pages/posts/NewPost.php34
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/pages/posts/PostRenderer.php7
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/pages/posts/PostRenderer.tpl17
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/pages/posts/ReadPost.page25
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/pages/posts/ReadPost.php59
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/pages/posts/config.xml6
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/pages/users/AdminUser.page40
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/pages/users/AdminUser.php36
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/pages/users/EditUser.page61
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/pages/users/EditUser.php83
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/pages/users/LoginUser.page28
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/pages/users/LoginUser.php37
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/pages/users/NewUser.page73
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/pages/users/NewUser.php45
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/pages/users/config.xml7
-rw-r--r--demos/blog-tutorial/samples/day5/blog/protected/schema.sql25
-rw-r--r--demos/blog-tutorial/samples/day5/blog/themes/Basic/button.skin1
-rw-r--r--demos/blog-tutorial/samples/day5/blog/themes/Basic/style.css37
57 files changed, 1667 insertions, 50 deletions
diff --git a/demos/blog-tutorial/protected/common/TopicList.tpl b/demos/blog-tutorial/protected/common/TopicList.tpl
index 05fe3277..a98181dc 100644
--- a/demos/blog-tutorial/protected/common/TopicList.tpl
+++ b/demos/blog-tutorial/protected/common/TopicList.tpl
@@ -50,32 +50,12 @@
</div>
<div class="topic">
-<div>Day 5: Creating Portlets</div>
+<div>Day 5: Refactoring and Deployment</div>
<ul>
- <li><a href="?page=">Overview</a></li>
-</div>
-
-<div class="topic">
-<div>Day 6: Customization and Refactoring</div>
-<ul>
- <li><a href="?page=">Using Themes and Skins</a></li>
- <li><a href="?page=">Customizing Error Handling</a></li>
- <li><a href="?page=">Logging</a></li>
-</ul>
-</div>
-
-<div class="topic">
-<div>Day 7: Performance Tuneup and Deployment</div>
-<ul>
- <li><a href="?page=">Caching</a></li>
-</ul>
-</div>
-
-<div class="topic">
-<div>Summary</div>
-<ul>
- <li><a href="?page=">Development Process</a></li>
- <li><a href="?page=">Future Work</a></li>
+ <li><a href="?page=Day5.UseTheme">Using Themes and Skins</a></li>
+ <li><a href="?page=Day5.ErrorLogging">Error Handling and Logging</a></li>
+ <li><a href="?page=Day5.Performance">Performance Tuneup</a></li>
+ <li><a href="?page=Day5.Summary">Summary</a></li>
</ul>
</div>
diff --git a/demos/blog-tutorial/protected/pages/Day1/ShareLayout.page b/demos/blog-tutorial/protected/pages/Day1/ShareLayout.page
index d6c4d29f..9cd0410b 100644
--- a/demos/blog-tutorial/protected/pages/Day1/ShareLayout.page
+++ b/demos/blog-tutorial/protected/pages/Day1/ShareLayout.page
@@ -28,6 +28,7 @@ For the moment, <tt>MainLayout</tt> only contains a simple header and a footer,
&lt;com:THead />
<body>
&lt;com:TForm>
+<div id="page">
<div id="header">
<h1>My PRADO Blog</h1>
@@ -41,6 +42,7 @@ For the moment, <tt>MainLayout</tt> only contains a simple header and a footer,
&lt;%= PRADO::poweredByPrado() %>
</div>
+</div>
&lt;/com:TForm>
</body>
</html>
@@ -113,7 +115,7 @@ Besides <tt>&lt;com:TContent&gt;</tt>, we also see another new tag <tt>&lt;%@ %&
</p>
<p>
-By setting <tt>MasterClass</tt> property as <tt>Application.layouts.MainLayout</tt>, we instruct the <tt>Contact</tt> page to use <tt>MainLayout</tt> as its master. Here, we are using the <a href="http://www.pradosoft.com/demos/quickstart/?page=Fundamentals.Components">namespace format</a> to refer to the <tt>MainLayout</tt> class.
+By setting <tt>MasterClass</tt> property as <tt>Application.layouts.MainLayout</tt>, we instruct the <tt>Contact</tt> page to use <tt>MainLayout</tt> as its master. Here, we are using the <a href="http://www.pradosoft.com/demos/quickstart/?page=Fundamentals.Components">namespace format</a> to refer to the <tt>MainLayout</tt> class.
</p>
<com:InfoBox>
@@ -140,7 +142,7 @@ class Contact extends TPage
parent::onPreInit($param);
$this->MasterClass='Path.To.NewLayout';
}
-
+
// ...
}
?>
diff --git a/demos/blog-tutorial/protected/pages/Day4/CreateListPost.page b/demos/blog-tutorial/protected/pages/Day4/CreateListPost.page
index e25c0609..1dada650 100644
--- a/demos/blog-tutorial/protected/pages/Day4/CreateListPost.page
+++ b/demos/blog-tutorial/protected/pages/Day4/CreateListPost.page
@@ -146,6 +146,7 @@ The expression <tt>$this->Data</tt> refers to the data item passed to the repeat
</p>
<com:TTextHighlighter CssClass="source" Language="prado">
+<div class="post-box">
<h3>
&lt;com:THyperLink Text="&lt;%# $this->Data->title %>"
NavigateUrl="&lt;%# $this->Service->constructUrl('posts.ReadPost',array('id'=>$this->Data->post_id)) %>" />
@@ -161,6 +162,7 @@ Time:
<p>
&lt;com:TLiteral Text="&lt;%# $this->Data->content %>" />
</p>
+</div>
</com:TTextHighlighter>
<h3>Creating Renderer Class</h3>
diff --git a/demos/blog-tutorial/protected/pages/Day4/CreateNewPost.page b/demos/blog-tutorial/protected/pages/Day4/CreateNewPost.page
index 5011c346..9020c517 100644
--- a/demos/blog-tutorial/protected/pages/Day4/CreateNewPost.page
+++ b/demos/blog-tutorial/protected/pages/Day4/CreateNewPost.page
@@ -25,7 +25,8 @@ As the number of our pages expands, we would like to modify <tt>MainLayout</tt>
<com:TTextHighlighter CssClass="source" Language="prado">
<div id="footer">
-<a href="&lt;%= $this->Service->DefaultPageUrl %>">Home</a>
+&lt;com:THyperLink Text="Home"
+ NavigateUrl="&lt;%= $this->Service->DefaultPageUrl %>" />
&lt;com:THyperLink Text="New Post"
NavigateUrl="&lt;%= $this->Service->constructUrl('posts.NewPost') %>"
diff --git a/demos/blog-tutorial/protected/pages/Day4/Overview.page b/demos/blog-tutorial/protected/pages/Day4/Overview.page
index cbff07c5..74e691e3 100644
--- a/demos/blog-tutorial/protected/pages/Day4/Overview.page
+++ b/demos/blog-tutorial/protected/pages/Day4/Overview.page
@@ -21,6 +21,6 @@ According to the requirements, we need to create the following pages organized u
After finishing this section, we shall expect to see the following directories and files:
</p>
-<img src="%~ directories.gif %>" class="output" />
+<img src="<%~ directories.gif %>" class="output" />
</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/protected/pages/Day5/ErrorLogging.page b/demos/blog-tutorial/protected/pages/Day5/ErrorLogging.page
new file mode 100644
index 00000000..52f7ef54
--- /dev/null
+++ b/demos/blog-tutorial/protected/pages/Day5/ErrorLogging.page
@@ -0,0 +1,159 @@
+<com:TContent ID="Main">
+
+<h1>Error Handling and Logging</h1>
+
+<p>
+If we try to access the URL <tt>http://hostname/blog/index.php?page=EditPost&id=100</tt>, we will see the following error page because the post with ID 100 does not exist in our blog system yet. We would like to customize this error page so that it looks more consistent with the other blog pages in layout. We also want to log this kind of errors to study user behaviors. In this section, we will accomplish these two tasks.
+</p>
+
+<img src="<%~ output2.gif %>" class="output" />
+
+<com:InfoBox>
+An important task in a Web application is <a href="http://www.pradosoft.com/demos/quickstart/?page=Advanced.Error">error handling</a> which is often associated <a href="http://www.pradosoft.com/demos/quickstart/?page=Advanced.Logging">logging</a>. There are two types of errors that may occur in a PRADO application: those caused by developers and those by end-users. The former should be resolved before the application is put into production, while the latter are usually within the initial design scope and should be handled nicely (e.g. log the error and display a special page instructing the end-user what to do next.) PRADO implements a very flexible yet powerful framework for error handling and logging.
+</com:InfoBox>
+
+
+<h2>Customizing Error Handling</h2>
+
+<p>
+PRADO implicitly loads a <tt>TErrorHandler</tt> module to handle errors. We would like to customize this module so that our blog system can display a customized page for errors caused by end-users. We thus modify the application configuration as follows:
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="xml">
+......
+<modules>
+ ......
+ <module class="Application.BlogErrorHandler" />
+ ......
+</modules>
+......
+</com:TTextHighlighter>
+
+<p>
+The class <tt>BlogErrorHandler</tt> as specified in the above is a new error handler module that we will create next. It extends and replaces the default <tt>TErrorHandler</tt> module.
+</p>
+
+<p>
+We create a file named <tt>protected/BlogErrorHandler.php</tt> as follows. The class <tt>BlogErrorHandler</tt> overrides two methods of <tt>TErrorHandler</tt>:
+</p>
+<ul>
+ <li><tt>getErrorTemplate()</tt> - this method returns the template string used for displaying a particular user error.</li>
+ <li><tt>handleExternalError()</tt> - this method is invoked when a user error occurs and displays the error.</li>
+</ul>
+<com:TTextHighlighter CssClass="source" Language="php">
+Prado::using('System.Exceptions.TErrorHandler');
+Prado::using('Application.BlogException');
+
+class BlogErrorHandler extends TErrorHandler
+{
+ /**
+ * Retrieves the template used for displaying external exceptions.
+ * This method overrides the parent implementation.
+ */
+ protected function getErrorTemplate($statusCode,$exception)
+ {
+ // use our own template for BlogException
+ if($exception instanceof BlogException)
+ {
+ // get the path of the error template file: protected/error.html
+ $templateFile=Prado::getPathOfNamespace('Application.error','.html');
+ return file_get_contents($templateFile);
+ }
+ else // otherwise use the template defined by PRADO
+ return parent::getErrorTemplate($statusCode,$exception);
+ }
+
+ /**
+ * Handles external error caused by end-users.
+ * This method overrides the parent implementation.
+ * It is invoked by PRADO when an external exception is thrown.
+ */
+ protected function handleExternalError($statusCode,$exception)
+ {
+ // log the error (only for BlogException)
+ if($exception instanceof BlogException)
+ Prado::log($exception->getErrorMessage(),TLogger::ERROR,'BlogApplication');
+ // call parent implementation to display the error
+ parent::handleExternalError($statusCode,$exception);
+ }
+}
+</com:TTextHighlighter>
+
+<p>
+In the above, we specify that when a <tt>BlogException</tt> is thrown, we use a new template <tt>protected/error.html</tt> to display the error. Therefore, we need to create the <tt>BlogException</tt> class and replace all the occurances of <tt>THttpException</tt> in our code (such as <a href="?page=Day3.CreateEditUser">EditUser</a> and <a href="?page=Day4.CreateReadPost">ReadPost</a> pages). We also need to create the error template <tt>protected/error.html</tt>. The <tt>BlogException</tt> class extends <tt>THttpException</tt> and is empty. The class file is saved as <tt>protected/BlogException.php</tt>.
+
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="php">
+class BlogException extends THttpException
+{
+}
+</com:TTextHighlighter>
+
+<p>
+Below is the content in our error template <tt>protected/error.html</tt>. Note, the template is not a PRADO template because it only recognizes very limited number of tokens, such as <tt>%%ErrorMessage%%</tt>, <tt>%%ServerAdmin%%</tt>.
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="xml">
+<html>
+<head>
+<title>%%ErrorMessage%%</title>
+</head>
+<body>
+<div id="page">
+<div id="header">
+<h1>My PRADO Blog</h1>
+</div>
+<div id="main">
+<p style="color:red">%%ErrorMessage%%</p>
+<p>
+The above error happened when the server was processing your request.
+</p>
+<p>
+If you think this is a server error, please contact the <a href="mailto:%%ServerAdmin%%">webmaster</a>.
+</p>
+</div>
+</body>
+</html>
+</com:TTextHighlighter>
+
+
+<h2>Logging Errors</h2>
+
+<p>
+In the <tt>handleExternalError()</tt> method of <tt>BlogErrorHandler</tt>, we invoke <tt>Prado::log()</tt> to log the error if it is of type <tt>BlogException</tt>. The error is logged in memory. To save the logs into permanent medium such as file or database, we need to enable appropriate error logging routes. This is done in the application configuration as follows:
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="xml">
+......
+<modules>
+ ......
+ <module id="log" class="System.Util.TLogRouter">
+ <route class="TFileLogRoute" Categories="BlogApplication" />
+ </module>
+ ......
+</modules>
+......
+</com:TTextHighlighter>
+
+<p>
+In the above, we add a log route that saves logs into a file. We also specify the category filter as <tt>BlogApplication</tt> such that only the log messages of the selected categories are saved. This helps reduce the size of the log file and also improves its readability.
+</p>
+
+<h2>Testing</h2>
+<p>
+To see how our blog systems reponds to an invalid user request, we test the URL <tt>http://hostname/blog/index.php?page=posts.ReadPost&id=100</tt>. We shall see the following error page which is different from what we saw earlier on.
+</p>
+
+<img src="<%~ output3.gif %>" class="output" />
+
+<p>
+If we search under the directory <tt>protected/runtime</tt>, we will find a file named <tt>prado.log</tt>. This is the log file that we just configured to save the error messages. The file may contain contents like the following,
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="text">
+Jun 28 22:15:27 [Error] [BlogApplication] Unable to find the specified post.
+Jun 29 08:42:57 [Error] [BlogApplication] Unable to find the specified post.
+</com:TTextHighlighter>
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/protected/pages/Day5/Performance.page b/demos/blog-tutorial/protected/pages/Day5/Performance.page
new file mode 100644
index 00000000..cbaae7e4
--- /dev/null
+++ b/demos/blog-tutorial/protected/pages/Day5/Performance.page
@@ -0,0 +1,67 @@
+<com:TContent ID="Main">
+
+<h1>Performance Tuneup</h1>
+
+<p>
+Before we deploy our blog system, we would like to tuneup the system performance.
+</p>
+
+<h2>Changing Application Mode</h2>
+
+<p>
+A PRADO application may be configured to run in different mode. By default, it runs in debug mode which generates a lot of log messages and in case of errors, it displays full call stack of the error places. Such behavior is preferrable during development, but not so if the system is in production. To change the application mode from <tt>Debug</tt> to <tt>Normal</tt> (meaning production mode), we modify the application configuration as follows:
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="xml">
+<?xml version="1.0" encoding="utf-8"?>
+<application id="blog" mode="Normal">
+ ......
+</application>
+</com:TTextHighlighter>
+
+<h2>Enabling Caching</h2>
+
+<p>
+There are a lot of parsing work involved in a PRADO application: configuration XMLs, templates, theme skins, etc. For every user request, PRADO needs to redo the parsing. To save this effort, we can enable caching. To do so, we modify the application configuration as follows,
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="xml">
+......
+<modules>
+ ......
+ <module id="cache" class="System.Caching.TDbCache" />
+ ......
+</modules>
+......
+</com:TTextHighlighter>
+
+<p>
+Now after accessing any page in our blog system, we shall be able to find a file named <tt>sqlite3.cache</tt>. This is the database file that keeps the parsed page templates, configurations, etc.
+</p>
+
+<com:InfoBox>
+The cache module we just enabled uses database as persistent cache medium. PRADO also has other cache modules using faster cache medium, such as <tt>TMemCache</tt>, <tt>TAPCCache</tt>. They require installation of the corresponding PHP extensions.
+</com:InfoBox>
+
+
+<h2>Using <tt>pradolite.php</tt></h2>
+
+<p>
+Running a PRADO page involves including tens of PHP files, which could be very time-consuming. These files also carry a lot of comments for generating user-friendly API documentation. In order to reduce this cost, we modify <tt>index.php</tt> and replace the inclusion of <tt>prado.php</tt> with <tt>pradolite.php</tt>. The latter is a big file generated by merging necessary PRADO code files and stripping out comments. We thus modify <tt>index.php</tt> as follows,
+</p>
+
+<h2>Other Techniques</h2>
+
+<p>
+There are other techniques to further improve the performance of a PRADO application. According to our experience, one of the bottlenecks in a Web application is the database tier. The database queries often take a long time to complete, which greatly degrades the response time of a page request. Caching is the main solution for this problem. The cache module enabled in our application configuration can also be used for this purpose.
+</p>
+
+<p>
+For a page that is relatively stable yet frequently accessed, <a href="http://www.pradosoft.com/demos/quickstart/?page=Controls.OutputCache">output caching</a> should be considered. Output caching caches the HTML output of selected portions of a page. This may improve the performance of the cached pages significantly.
+</p>
+
+<p>
+Server caching techniques are proven to be very effective in improving the performance of PRADO applications. For example, we have observed that by using Zend Optimizer, the RPS (request per second) of a PRADO application can be increased by more than ten times. Of course, this is at the cost of stale output, while PRADO's caching techniques always ensure the correctness of the output.
+</p>
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/protected/pages/Day5/Summary.page b/demos/blog-tutorial/protected/pages/Day5/Summary.page
new file mode 100644
index 00000000..3ea350eb
--- /dev/null
+++ b/demos/blog-tutorial/protected/pages/Day5/Summary.page
@@ -0,0 +1,36 @@
+<com:TContent ID="Main">
+
+<h1>Summary</h1>
+
+<p>
+We finally can deploy our blog system. To do so, we only need to copy the whole <tt>blog</tt> directory to the target Web directory. We may need to modify <tt>index.php</tt> so that it can locate the correct path where the PRADO framework is installed.
+</p>
+
+<p>
+So we have completed our blog system. The process may seem not trivial because it takes us nearly five days to reach here. However, as we stated at the beginning, the main purpose of this tutorial is to help PRADO developers get familiar with the commonly used PRADO techniques. The tutorial is not meant to finish a blog system in five minutes and then we learn nothing.
+</p>
+
+<p>
+In summary, developing a DB-driven PRADO application involves the following steps:
+</p>
+<ol>
+ <li>Design and create database</li>
+ <li>Create initial application layout using <tt>prado-cli</tt></li>
+ <li>Set up error handling process to deal with end-user errors</li>
+ <li>Create and set up themes</li>
+ <li>Design and create master classes to share common page layouts</li>
+ <li>Create database classes and set up database connections</li>
+ <li>Design and create various pages</li>
+ <li>Test and tune up performance</li>
+ <li>Deploy the application</li>
+</ol>
+
+<p>
+Unlike the order in our tutorial, error handling and creating theme are done earlier in the above process. This is because they often require global change in class code and template. For example, we need to replace <tt>THttpException</tt> with <tt>BlogException</tt> in our tutorial. If we define stylesheet classes earlier, we could more easily use them when creating page templates.
+</p>
+
+<p>
+As a final tip, try to think in object-oriented way during the design and implementation. Use class inheritance and composition wisely, and you will find the whole project is easier to be developed in parallel by multiple developers. The code also has more chance to be reused so that future projects can be finished much faster.
+</p>
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/protected/pages/Day5/UseTheme.page b/demos/blog-tutorial/protected/pages/Day5/UseTheme.page
new file mode 100644
index 00000000..21bc9d81
--- /dev/null
+++ b/demos/blog-tutorial/protected/pages/Day5/UseTheme.page
@@ -0,0 +1,138 @@
+<com:TContent ID="Main">
+
+<h1>Using Themes and Skins</h1>
+
+<p>
+PRADO has intrinsic support for <a href="http://www.pradosoft.com/demos/quickstart/?page=Advanced.Themes">themes</a>. By using themes, we can better separate logic and presentation, and we can also change the overall appearance of our blog system more easily.
+</p>
+
+<h2>Creating Theme</h2>
+
+<p>
+We first create a new directory <tt>themes</tt>. This is the parent directory of all themes for a particular PRADO application. Any subdirectory under this directory will become a theme whose name is the subdirectory name.
+</p>
+
+<p>
+To create a theme named <tt>Basic</tt>, we create a subdirectory <tt>theme/Basic</tt>. Under this directory, we may place theme-dependent stylesheet files, Javascript files, images, and skin files.
+</p>
+
+<com:InfoBox>
+The <tt>themes</tt> directory must be Web-accessible, like the <tt>assets</tt> directory. Do not place sensitive data files under this directory. You can change the name or location of this directory by configuring the <a href="http://www.pradosoft.com/docs/classdoc/TThemeManager">TThemeManager</a> module in the application configuration.
+</com:InfoBox>
+
+
+<h3>Creating Stylesheet File</h2>
+
+<p>
+Under the <tt>themes/Basic</tt> directory, we create a CSS stylesheet file named <tt>style.css</tt>. When a page uses this theme, PRADO will automatically import this stylesheet to the page. The same occurs for Javascript files.
+</p>
+
+<p>
+The CSS file is shown as follows.
+</p>
+
+<com:TTextHighlighter CssClass="source">
+body {
+ font-family: verdana, 'trebuchet ms', sans-serif;
+ font-size: 10pt;
+ background: white;
+}
+#page {
+ margin: 0 auto 0 auto;
+ width: 600px;
+}
+#footer {
+ text-align: center;
+ margin-top: 10px;
+ padding: 10px;
+ border-top: 1px solid silver;
+}
+.post-box {
+ margin-bottom: 10px;
+ padding: 5px;
+}
+.post-box h3 {
+ padding: 5px;
+ font-size: 13pt;
+ background: lightgray;
+}
+.post-box a {
+ color: black;
+ text-decoration: none;
+}
+.post-box a:hover {
+ color: red;
+}
+</com:TTextHighlighter>
+
+
+<h3>Creating Skin File</h2>
+
+<p>
+We use skins to initialize the properties of PRADO controls. Skins are stored as skin files (suffix name <tt>.skin</tt>) under a theme directory. Each skin file can contain multiple skins for one or several types of control.
+</p>
+
+<p>
+As a test, we will try to create a skin that changes the background color of the link buttons in the page footer. We create a file named <tt>button.skin</tt> under the theme directory <tt>themes/Basic</tt>.
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="prado">
+&lt;com:THyperLink SkinID="MainMenu" BackColor="lightgreen" />
+</com:TTextHighlighter>
+
+<p>
+The <tt>button.skin</tt> skin file contains only one skin for <tt>THyperLink</tt> controls whose <tt>SkinID</tt> property is <tt>MainMenu</tt>. The skin sets the background color of the control to be light-green.
+</p>
+
+<p>
+Accordingly, we need to modify <tt>protected/common/MainLayout.tpl</tt> so that the link buttons in the footer use <tt>MainMenu</tt> as their <tt>SkinID</tt>.
+</p>
+<com:TTextHighlighter CssClass="source" Language="prado">
+......
+<div id="footer">
+......
+&lt;com:THyperLink Text="Home" SkinID="MainMenu"
+ NavigateUrl="&lt;%= $this->Service->DefaultPageUrl %>" />
+
+&lt;com:THyperLink Text="New Post" SkinID="MainMenu"
+ NavigateUrl="&lt;%= $this->Service->constructUrl('posts.NewPost') %>"
+ Visible="&lt;%= !$this->User->IsGuest %>" />
+......
+</div>
+......
+</com:TTextHighlighter>
+
+<com:InfoBox>
+The syntax for skin files is very similar to that of PRADO templates. Each <tt>&lt;com:&gt;</tt> tag defines a skin for a particular type of control. PRADO automatically aggregates all skin files in a theme and applies them when a themed page is being rendered.
+</com:InfoBox>
+
+
+<h2>Using Theme</h2>
+
+<p>
+To use the theme we just created, we modify the application configuration as follows. As we see, the <tt>Theme</tt> property for all pages is set as <tt>Basic</tt>, the name of the theme we created.
+</p>
+
+<com:TTextHighlighter CssClass="source" Language="xml">
+......
+ <services>
+ <service id="page" class="TPageService" DefaultPage="posts.ListPost">
+ <pages MasterClass="Application.layouts.MainLayout" Theme="Basic" />
+ </service>
+ </services>
+......
+</com:TTextHighlighter>
+
+<com:InfoBox>
+It is possible to specify different themes for different pages, and this can be done either in application/page configurations or programmatically (note <tt>Theme</tt> a page property). For the latter, it is must be done in <tt>onPreInit()</tt> method of the page because PRADO applies a theme to a page early in the page lifecycle.
+</com:InfoBox>
+
+
+<h2>Testing</h2>
+<p>
+To see how our blog pages look like, visit the URL <tt>http://hostname/blog/index.php</tt>. We shall see the font, layout, borders are changed in the page. Also, the link buttons in the footer have light-green background.
+</p>
+
+<img src="<%~ output.gif %>" class="output" />
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/protected/pages/Day5/output.gif b/demos/blog-tutorial/protected/pages/Day5/output.gif
new file mode 100644
index 00000000..67bd18a3
--- /dev/null
+++ b/demos/blog-tutorial/protected/pages/Day5/output.gif
Binary files differ
diff --git a/demos/blog-tutorial/protected/pages/Day5/output2.gif b/demos/blog-tutorial/protected/pages/Day5/output2.gif
new file mode 100644
index 00000000..16c81704
--- /dev/null
+++ b/demos/blog-tutorial/protected/pages/Day5/output2.gif
Binary files differ
diff --git a/demos/blog-tutorial/protected/pages/Day5/output3.gif b/demos/blog-tutorial/protected/pages/Day5/output3.gif
new file mode 100644
index 00000000..6879bbdf
--- /dev/null
+++ b/demos/blog-tutorial/protected/pages/Day5/output3.gif
Binary files differ
diff --git a/demos/blog-tutorial/protected/pages/Overview.page b/demos/blog-tutorial/protected/pages/Overview.page
index 9dbf6dc0..bc895a33 100644
--- a/demos/blog-tutorial/protected/pages/Overview.page
+++ b/demos/blog-tutorial/protected/pages/Overview.page
@@ -2,16 +2,12 @@
<h1>Welcome to the PRADO Blog Tutorial</h1>
-<com:NoteBox>
-This tutorial is NOT completed yet. Please let us know you suggestions about this tutorial on <a href="http://www.pradosoft.com/forum/index.php?board=5.0">our forum</a>. Thanks!
-</com:NoteBox>
-
<p>
The purpose of this tutorial is to provide new PRADO users a step-by-step guidance on how to develop a blog system using PRADO. Readers of this tutorial are not required to have prior knowledge about PRADO. However, readers should have basic knowledge of object-oriented programming (OOP) and database programming. For a more definitive guidance about PRADO, readers may refer to the <a href="http://www.pradosoft.com/demos/quickstart/">Quickstart Tutorial</a>.
</p>
<p>
-This tutorial is organized in a day-by-day fashion. Each day new PRADO concepts and techniques are introduced, and new features are added to the blog system. At the end, we will complete a simple blog application that meets a list of <a href="?page=Requirements">initial requirements</a>.
+This tutorial is organized in a day-by-day fashion. Each day new PRADO concepts and techniques are introduced, and new features of the blog system are implemented. At the end, we will complete a simple blog application that meets a list of <a href="?page=Requirements">initial requirements</a>.
</p>
<p>
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 6e264936..97cafb04 100644
--- a/demos/blog-tutorial/samples/day1/blog/protected/layouts/MainLayout.tpl
+++ b/demos/blog-tutorial/samples/day1/blog/protected/layouts/MainLayout.tpl
@@ -2,19 +2,21 @@
<com:THead />
<body>
<com:TForm>
-
+<div id="page">
+
<div id="header">
<h1>My PRADO Blog</h1>
</div>
-
+
<div id="main">
<com:TContentPlaceHolder ID="Main" />
</div>
-
+
<div id="footer">
<%= PRADO::poweredByPrado() %>
</div>
-
+
+</div>
</com:TForm>
</body>
</html> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day2/blog/protected/layouts/MainLayout.tpl b/demos/blog-tutorial/samples/day2/blog/protected/layouts/MainLayout.tpl
index 6e264936..97cafb04 100644
--- a/demos/blog-tutorial/samples/day2/blog/protected/layouts/MainLayout.tpl
+++ b/demos/blog-tutorial/samples/day2/blog/protected/layouts/MainLayout.tpl
@@ -2,19 +2,21 @@
<com:THead />
<body>
<com:TForm>
-
+<div id="page">
+
<div id="header">
<h1>My PRADO Blog</h1>
</div>
-
+
<div id="main">
<com:TContentPlaceHolder ID="Main" />
</div>
-
+
<div id="footer">
<%= PRADO::poweredByPrado() %>
</div>
-
+
+</div>
</com:TForm>
</body>
</html> \ 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
index 89538ab2..a50f78fb 100644
--- a/demos/blog-tutorial/samples/day3/blog/protected/layouts/MainLayout.tpl
+++ b/demos/blog-tutorial/samples/day3/blog/protected/layouts/MainLayout.tpl
@@ -2,28 +2,30 @@
<com:THead />
<body>
<com:TForm>
-
+<div id="page">
+
<div id="header">
<h1>My PRADO Blog</h1>
</div>
-
+
<div id="main">
<com:TContentPlaceHolder ID="Main" />
</div>
-
+
<div id="footer">
-<com:THyperLink Text="Login"
+<com:THyperLink Text="Login"
NavigateUrl="<%= $this->Service->constructUrl('users.LoginUser') %>"
Visible="<%= $this->User->IsGuest %>" />
-<com:TLinkButton Text="Logout"
+<com:TLinkButton Text="Logout"
OnClick="logoutButtonClicked"
Visible="<%= !$this->User->IsGuest %>" />
<br/>
<%= PRADO::poweredByPrado() %>
</div>
-
+
+</div>
</com:TForm>
</body>
</html> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/application.xml b/demos/blog-tutorial/samples/day4/blog/protected/application.xml
index fd6781df..68ed9609 100644
--- a/demos/blog-tutorial/samples/day4/blog/protected/application.xml
+++ b/demos/blog-tutorial/samples/day4/blog/protected/application.xml
@@ -3,7 +3,6 @@
<application id="blog" mode="Debug">
<paths>
<using namespace="Application.database.*" />
- <using namespace="Application.common.*" />
</paths>
<!-- configurations for modules -->
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/layouts/MainLayout.tpl b/demos/blog-tutorial/samples/day4/blog/protected/layouts/MainLayout.tpl
index 3f0a9a4f..cc4a34c1 100644
--- a/demos/blog-tutorial/samples/day4/blog/protected/layouts/MainLayout.tpl
+++ b/demos/blog-tutorial/samples/day4/blog/protected/layouts/MainLayout.tpl
@@ -2,6 +2,7 @@
<com:THead />
<body>
<com:TForm>
+<div id="page">
<div id="header">
<h1>My PRADO Blog</h1>
@@ -13,7 +14,8 @@
<div id="footer">
-<a href="<%= $this->Service->DefaultPageUrl %>">Home</a>
+<com:THyperLink Text="Home"
+ NavigateUrl="<%= $this->Service->DefaultPageUrl %>" />
<com:THyperLink Text="New Post"
NavigateUrl="<%= $this->Service->constructUrl('posts.NewPost') %>"
@@ -35,6 +37,7 @@
<%= PRADO::poweredByPrado() %>
</div>
+</div>
</com:TForm>
</body>
</html> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/PostRenderer.tpl b/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/PostRenderer.tpl
index 862df1ab..8f4a0502 100644
--- a/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/PostRenderer.tpl
+++ b/demos/blog-tutorial/samples/day4/blog/protected/pages/posts/PostRenderer.tpl
@@ -1,3 +1,4 @@
+<div class="post-box">
<h3>
<com:THyperLink Text="<%# $this->Data->title %>"
NavigateUrl="<%# $this->Service->constructUrl('posts.ReadPost',array('id'=>$this->Data->post_id)) %>" />
@@ -13,3 +14,4 @@ Time:
<p>
<com:TLiteral Text="<%# $this->Data->content %>" />
</p>
+</div> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/index.php b/demos/blog-tutorial/samples/day5/blog/index.php
new file mode 100644
index 00000000..7ccffe49
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/index.php
@@ -0,0 +1,22 @@
+<?php
+
+// The following directory checks may be removed if performance is required
+$basePath=dirname(__FILE__);
+$frameworkPath=$basePath.'/../../../../../framework/prado.php';
+$assetsPath=$basePath.'/assets';
+$runtimePath=$basePath.'/protected/runtime';
+
+if(!is_file($frameworkPath))
+ die("Unable to find prado framework path $frameworkPath.");
+if(!is_writable($assetsPath))
+ die("Please make sure that the directory $assetsPath is writable by Web server process.");
+if(!is_writable($runtimePath))
+ die("Please make sure that the directory $runtimePath is writable by Web server process.");
+
+
+require_once($frameworkPath);
+
+$application=new TApplication;
+$application->run();
+
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/.htaccess b/demos/blog-tutorial/samples/day5/blog/protected/.htaccess
new file mode 100644
index 00000000..3418e55a
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/.htaccess
@@ -0,0 +1 @@
+deny from all \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/BlogErrorHandler.php b/demos/blog-tutorial/samples/day5/blog/protected/BlogErrorHandler.php
new file mode 100644
index 00000000..56b71f8a
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/BlogErrorHandler.php
@@ -0,0 +1,40 @@
+<?php
+
+Prado::using('System.Exceptions.TErrorHandler');
+Prado::using('Application.BlogException');
+
+class BlogErrorHandler extends TErrorHandler
+{
+ /**
+ * Retrieves the template used for displaying external exceptions.
+ * This method overrides the parent implementation.
+ */
+ protected function getErrorTemplate($statusCode,$exception)
+ {
+ // use our own template for BlogException
+ if($exception instanceof BlogException)
+ {
+ // get the path of the error template file: protected/error.html
+ $templateFile=Prado::getPathOfNamespace('Application.error','.html');
+ return file_get_contents($templateFile);
+ }
+ else // otherwise use the template defined by PRADO
+ return parent::getErrorTemplate($statusCode,$exception);
+ }
+
+ /**
+ * Handles external error caused by end-users.
+ * This method overrides the parent implementation.
+ * It is invoked by PRADO when an external exception is thrown.
+ */
+ protected function handleExternalError($statusCode,$exception)
+ {
+ // log the error (only for BlogException)
+ if($exception instanceof BlogException)
+ Prado::log($exception->getErrorMessage(),TLogger::ERROR,'BlogApplication');
+ // call parent implementation to display the error
+ parent::handleExternalError($statusCode,$exception);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/BlogException.php b/demos/blog-tutorial/samples/day5/blog/protected/BlogException.php
new file mode 100644
index 00000000..fd17fc84
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/BlogException.php
@@ -0,0 +1,7 @@
+<?php
+
+class BlogException extends THttpException
+{
+}
+
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/BlogUser.php b/demos/blog-tutorial/samples/day5/blog/protected/BlogUser.php
new file mode 100644
index 00000000..6b9e0a23
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/BlogUser.php
@@ -0,0 +1,59 @@
+<?php
+
+// Include TDbUserManager.php file which defines TDbUser
+Prado::using('System.Security.TDbUserManager');
+
+/**
+ * BlogUser Class.
+ * BlogUser represents the user data that needs to be kept in session.
+ * Default implementation keeps username and role information.
+ */
+class BlogUser extends TDbUser
+{
+ /**
+ * Creates a BlogUser object based on the specified username.
+ * This method is required by TDbUser. It checks the database
+ * to see if the specified username is there. If so, a BlogUser
+ * object is created and initialized.
+ * @param string the specified username
+ * @return BlogUser the user object, null if username is invalid.
+ */
+ public function createUser($username)
+ {
+ // use UserRecord Active Record to look for the specified username
+ $userRecord=UserRecord::finder()->findByPk($username);
+ if($userRecord instanceof UserRecord) // if found
+ {
+ $user=new BlogUser($this->Manager);
+ $user->Name=$username; // set username
+ $user->Roles=($userRecord->role==1?'admin':'user'); // set role
+ $user->IsGuest=false; // the user is not a guest
+ return $user;
+ }
+ else
+ return null;
+ }
+
+ /**
+ * Checks if the specified (username, password) is valid.
+ * This method is required by TDbUser.
+ * @param string username
+ * @param string password
+ * @return boolean whether the username and password are valid.
+ */
+ public function validateUser($username,$password)
+ {
+ // use UserRecord Active Record to look for the (username, password) pair.
+ return UserRecord::finder()->findBy_username_AND_password($username,$password)!==null;
+ }
+
+ /**
+ * @return boolean whether this user is an administrator.
+ */
+ public function getIsAdmin()
+ {
+ return $this->isInRole('admin');
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/application.xml b/demos/blog-tutorial/samples/day5/blog/protected/application.xml
new file mode 100644
index 00000000..cd996cb6
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/application.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<application id="blog" mode="Normal">
+ <paths>
+ <using namespace="Application.database.*" />
+ </paths>
+
+ <!-- configurations for modules -->
+ <modules>
+ <module id="cache" class="System.Caching.TDbCache" />
+
+ <module class="Application.BlogErrorHandler" />
+
+ <module id="log" class="System.Util.TLogRouter">
+ <route class="TFileLogRoute" Categories="BlogApplication" />
+ </module>
+
+ <module id="db" class="System.Data.TDataSourceConfig">
+ <database ConnectionString="sqlite:protected/data/blog.db" />
+ </module>
+
+ <module
+ class="System.Data.ActiveRecord.TActiveRecordConfig"
+ ConnectionID="db" />
+
+ <module id="auth"
+ class="System.Security.TAuthManager"
+ UserManager="users"
+ LoginPage="users.LoginUser" />
+
+ <module id="users"
+ class="System.Security.TDbUserManager"
+ UserClass="Application.BlogUser" />
+
+ </modules>
+
+ <!-- configuration for available services -->
+ <services>
+ <service id="page" class="TPageService" DefaultPage="posts.ListPost">
+ <pages MasterClass="Application.layouts.MainLayout" Theme="Basic" />
+ </service>
+ </services>
+
+ <!-- application parameters
+ <parameters>
+ <parameter id="param1" value="value1" />
+ <parameter id="param2" value="value2" />
+ </parameters>
+ -->
+</application> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/data/blog.db b/demos/blog-tutorial/samples/day5/blog/protected/data/blog.db
new file mode 100644
index 00000000..fa48526a
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/data/blog.db
Binary files differ
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/database/PostRecord.php b/demos/blog-tutorial/samples/day5/blog/protected/database/PostRecord.php
new file mode 100644
index 00000000..01f84437
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/database/PostRecord.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * Auto generated by prado-cli.php on 2007-04-07 10:44:20.
+ */
+class PostRecord extends TActiveRecord
+{
+ const TABLE='posts';
+
+ public $post_id;
+ public $author_id;
+ public $create_time;
+ public $title;
+ public $content;
+ public $status;
+
+ public $author;
+
+ protected static $RELATIONS=array
+ (
+ 'author' => array(self::BELONGS_TO, 'UserRecord'),
+ );
+
+ public static function finder($className=__CLASS__)
+ {
+ return parent::finder($className);
+ }
+}
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/database/UserRecord.php b/demos/blog-tutorial/samples/day5/blog/protected/database/UserRecord.php
new file mode 100644
index 00000000..18d5ebbe
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/database/UserRecord.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * Auto generated by prado-cli.php on 2007-04-07 10:44:25.
+ */
+class UserRecord extends TActiveRecord
+{
+ const TABLE='users';
+
+ public $username;
+ public $email;
+ public $password;
+ public $role;
+ public $first_name;
+ public $last_name;
+
+ public $posts=array();
+
+ protected static $RELATIONS=array
+ (
+ 'posts' => array(self::HAS_MANY, 'PostRecord'),
+ );
+
+ public static function finder($className=__CLASS__)
+ {
+ return parent::finder($className);
+ }
+}
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/error.html b/demos/blog-tutorial/samples/day5/blog/protected/error.html
new file mode 100644
index 00000000..caf2cdfc
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/error.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<title>%%ErrorMessage%%</title>
+</head>
+<body>
+<div id="page">
+<div id="header">
+<h1>My PRADO Blog</h1>
+</div>
+<div id="main">
+<p style="color:red">%%ErrorMessage%%</p>
+<p>
+The above error happened when the server was processing your request.
+</p>
+<p>
+If you think this is a server error, please contact the <a href="mailto:%%ServerAdmin%%">webmaster</a>.
+</p>
+</div>
+</body>
+</html> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/layouts/MainLayout.php b/demos/blog-tutorial/samples/day5/blog/protected/layouts/MainLayout.php
new file mode 100644
index 00000000..46c1483d
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/layouts/MainLayout.php
@@ -0,0 +1,19 @@
+<?php
+
+class MainLayout extends TTemplateControl
+{
+ /**
+ * Logs out a user.
+ * This method responds to the "logout" button's OnClick event.
+ * @param mixed event sender
+ * @param mixed event parameter
+ */
+ public function logoutButtonClicked($sender,$param)
+ {
+ $this->Application->getModule('auth')->logout();
+ $url=$this->Service->constructUrl($this->Service->DefaultPage);
+ $this->Response->redirect($url);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/layouts/MainLayout.tpl b/demos/blog-tutorial/samples/day5/blog/protected/layouts/MainLayout.tpl
new file mode 100644
index 00000000..34723f77
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/layouts/MainLayout.tpl
@@ -0,0 +1,43 @@
+<html>
+<com:THead />
+<body>
+<com:TForm>
+<div id="page">
+
+<div id="header">
+<h1>My PRADO Blog</h1>
+</div>
+
+<div id="main">
+<com:TContentPlaceHolder ID="Main" />
+</div>
+
+<div id="footer">
+
+<com:THyperLink Text="Home" SkinID="MainMenu"
+ NavigateUrl="<%= $this->Service->DefaultPageUrl %>" />
+
+<com:THyperLink Text="New Post" SkinID="MainMenu"
+ NavigateUrl="<%= $this->Service->constructUrl('posts.NewPost') %>"
+ Visible="<%= !$this->User->IsGuest %>" />
+
+<com:THyperLink Text="New User" SkinID="MainMenu"
+ NavigateUrl="<%= $this->Service->constructUrl('users.NewUser') %>"
+ Visible="<%= $this->User->IsAdmin %>" />
+
+<com:THyperLink Text="Login" SkinID="MainMenu"
+ NavigateUrl="<%= $this->Service->constructUrl('users.LoginUser') %>"
+ Visible="<%= $this->User->IsGuest %>" />
+
+<com:TLinkButton Text="Logout" SkinID="MainMenu"
+ OnClick="logoutButtonClicked"
+ Visible="<%= !$this->User->IsGuest %>" />
+
+<br/>
+<%= PRADO::poweredByPrado() %>
+</div>
+
+</div>
+</com:TForm>
+</body>
+</html> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/pages/Contact.page b/demos/blog-tutorial/samples/day5/blog/protected/pages/Contact.page
new file mode 100644
index 00000000..c36149ca
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/pages/Contact.page
@@ -0,0 +1,47 @@
+<%@ Title="My Blog - Contact" %>
+
+<com:TContent ID="Main">
+
+<h1>Contact</h1>
+<p>Please fill out the following form to let me know your feedback on my blog. Thanks!</p>
+
+<span>Your Name:</span>
+<com:TRequiredFieldValidator ControlToValidate="Name"
+ ErrorMessage="Please provide your name."
+ Display="Dynamic"
+ />
+<br/>
+<com:TTextBox ID="Name" />
+
+<br/>
+
+<span>Your Email:</span>
+<com:TRequiredFieldValidator ControlToValidate="Email"
+ ErrorMessage="Please provide your email address."
+ Display="Dynamic"
+ />
+<com:TEmailAddressValidator ControlToValidate="Email"
+ ErrorMessage="You entered an invalid email address."
+ Display="Dynamic"
+ />
+<br/>
+<com:TTextBox ID="Email" />
+
+<br/>
+
+<span>Feedback:</span>
+<com:TRequiredFieldValidator ControlToValidate="Feedback"
+ ErrorMessage="Please provide your feedback."
+ Display="Dynamic"
+ />
+<br/>
+<com:TTextBox ID="Feedback"
+ TextMode="MultiLine"
+ Rows="10"
+ Columns="40" />
+
+<br/>
+
+<com:TButton Text="Submit" OnClick="submitButtonClicked" />
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/pages/Contact.php b/demos/blog-tutorial/samples/day5/blog/protected/pages/Contact.php
new file mode 100644
index 00000000..b6ce575e
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/pages/Contact.php
@@ -0,0 +1,30 @@
+<?php
+
+class Contact extends TPage
+{
+ /**
+ * Event handler for the OnClick event of the submit button.
+ * @param TButton the button triggering the event
+ * @param TEventParameter event parameter (null here)
+ */
+ public function submitButtonClicked($sender, $param)
+ {
+ if ($this->IsValid) // check if input validation is successful
+ {
+ // obtain the user name, email, feedback from the textboxes
+ $name = $this->Name->Text;
+ $email = $this->Email->Text;
+ $feedback = $this->Feedback->Text;
+
+ // send an email to administrator with the above information
+ $this->mailFeedback($name, $email, $feedback);
+ }
+ }
+
+ protected function mailFeedback($name, $email, $feedback)
+ {
+ // implementation of sending the feedback email
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/pages/Home.page b/demos/blog-tutorial/samples/day5/blog/protected/pages/Home.page
new file mode 100644
index 00000000..7a9c4a7d
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/pages/Home.page
@@ -0,0 +1,7 @@
+<%@ Title="Welcome to PRADO" %>
+
+<com:TContent ID="Main">
+
+<h1>Welcome to PRADO!</h1>
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/EditPost.page b/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/EditPost.page
new file mode 100644
index 00000000..579d833e
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/EditPost.page
@@ -0,0 +1,27 @@
+<%@ Title="My Blog - Edit Post" %>
+
+<com:TContent ID="Main">
+
+<h1>Edit Post</h1>
+
+<span>Title:</span>
+<com:TRequiredFieldValidator
+ ControlToValidate="TitleEdit"
+ ErrorMessage="Please provide a title."
+ Display="Dynamic" />
+<br/>
+<com:TTextBox ID="TitleEdit" Columns="50" />
+
+<br/>
+<span>Content:</span>
+<com:TRequiredFieldValidator
+ ControlToValidate="ContentEdit"
+ ErrorMessage="Please provide content."
+ Display="Dynamic" />
+<br/>
+<com:THtmlArea ID="ContentEdit" />
+
+<br/>
+<com:TButton Text="Save" OnClick="saveButtonClicked" />
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/EditPost.php b/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/EditPost.php
new file mode 100644
index 00000000..e137b85e
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/EditPost.php
@@ -0,0 +1,72 @@
+<?php
+
+class EditPost extends TPage
+{
+ /**
+ * Initializes the inputs with existing post data.
+ * This method is invoked by the framework when the page is being initialized.
+ * @param mixed event parameter
+ */
+ public function onInit($param)
+ {
+ parent::onInit($param);
+ // Retrieves the existing user data. This is equivalent to:
+ // $postRecord=$this->getPost();
+ $postRecord=$this->Post;
+ // Authorization check: only the author or the administrator can edit the post
+ if($postRecord->author_id!==$this->User->Name && !$this->User->IsAdmin)
+ throw new THttpException(500,'You are not allowed to edit this post.');
+
+ if(!$this->IsPostBack) // if the page is initially requested
+ {
+ // Populates the input controls with the existing post data
+ $this->TitleEdit->Text=$postRecord->title;
+ $this->ContentEdit->Text=$postRecord->content;
+ }
+ }
+
+ /**
+ * Saves the post if all inputs are valid.
+ * This method responds to the OnClick event of the "Save" button.
+ * @param mixed event sender
+ * @param mixed event parameter
+ */
+ public function saveButtonClicked($sender,$param)
+ {
+ if($this->IsValid) // when all validations succeed
+ {
+ // Retrieves the existing user data. This is equivalent to:
+ // $postRecord=$this->getPost();
+ $postRecord=$this->Post;
+
+ // Fetches the input data
+ $postRecord->title=$this->TitleEdit->SafeText;
+ $postRecord->content=$this->ContentEdit->SafeText;
+
+ // saves to the database via Active Record mechanism
+ $postRecord->save();
+
+ // redirects the browser to the ReadPost page
+ $url=$this->Service->constructUrl('posts.ReadPost',array('id'=>$postRecord->post_id));
+ $this->Response->redirect($url);
+ }
+ }
+
+ /**
+ * Returns the post data to be editted.
+ * @return PostRecord the post data to be editted.
+ * @throws THttpException if the post data is not found.
+ */
+ protected function getPost()
+ {
+ // the ID of the post to be editted is passed via GET parameter 'id'
+ $postID=(int)$this->Request['id'];
+ // use Active Record to look for the specified post ID
+ $postRecord=PostRecord::finder()->findByPk($postID);
+ if($postRecord===null)
+ throw new THttpException(500,'Post is not found.');
+ return $postRecord;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/ListPost.page b/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/ListPost.page
new file mode 100644
index 00000000..e26bc2f5
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/ListPost.page
@@ -0,0 +1,14 @@
+<%@ Title="My Blog" %>
+
+<com:TContent ID="Main">
+
+<com:TRepeater ID="Repeater"
+ ItemRenderer="Application.pages.posts.PostRenderer"
+ AllowPaging="true"
+ AllowCustomPaging="true"
+ PageSize="5"
+ />
+
+<com:TPager ControlToPaginate="Repeater" OnPageIndexChanged="pageChanged" />
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/ListPost.php b/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/ListPost.php
new file mode 100644
index 00000000..7402dace
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/ListPost.php
@@ -0,0 +1,64 @@
+<?php
+
+class ListPost extends TPage
+{
+ /**
+ * Initializes the repeater.
+ * This method is invoked by the framework when initializing the page
+ * @param mixed event parameter
+ */
+ public function onInit($param)
+ {
+ parent::onInit($param);
+ if(!$this->IsPostBack) // if the page is requested the first time
+ {
+ // get the total number of posts available
+ $this->Repeater->VirtualItemCount=PostRecord::finder()->count();
+ // populates post data into the repeater
+ $this->populateData();
+ }
+ }
+
+ /**
+ * Event handler to the OnPageIndexChanged event of the pager.
+ * This method is invoked when the user clicks on a page button
+ * and thus changes the page of posts to display.
+ */
+ public function pageChanged($sender,$param)
+ {
+ // change the current page index to the new one
+ $this->Repeater->CurrentPageIndex=$param->NewPageIndex;
+ // re-populate data into the repeater
+ $this->populateData();
+ }
+
+ /**
+ * Determines which page of posts to be displayed and
+ * populates the repeater with the fetched data.
+ */
+ protected function populateData()
+ {
+ $offset=$this->Repeater->CurrentPageIndex*$this->Repeater->PageSize;
+ $limit=$this->Repeater->PageSize;
+ if($offset+$limit>$this->Repeater->VirtualItemCount)
+ $limit=$this->Repeater->VirtualItemCount-$offset;
+ $this->Repeater->DataSource=$this->getPosts($offset,$limit);
+ $this->Repeater->dataBind();
+ }
+
+ /**
+ * Fetches posts from database with offset and limit.
+ */
+ protected function getPosts($offset, $limit)
+ {
+ // Construts a query criteria
+ $criteria=new TActiveRecordCriteria;
+ $criteria->OrdersBy['create_time']='desc';
+ $criteria->Limit=$limit;
+ $criteria->Offset=$offset;
+ // query for the posts with the above criteria and with author information
+ return PostRecord::finder()->withAuthor()->findAll($criteria);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/NewPost.page b/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/NewPost.page
new file mode 100644
index 00000000..bd46dfbb
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/NewPost.page
@@ -0,0 +1,27 @@
+<%@ Title="My Blog - New Post" %>
+
+<com:TContent ID="Main">
+
+<h1>Create New Post</h1>
+
+<span>Title:</span>
+<com:TRequiredFieldValidator
+ ControlToValidate="TitleEdit"
+ ErrorMessage="Please provide a title."
+ Display="Dynamic" />
+<br/>
+<com:TTextBox ID="TitleEdit" Columns="50" />
+
+<br/>
+<span>Content:</span>
+<com:TRequiredFieldValidator
+ ControlToValidate="ContentEdit"
+ ErrorMessage="Please provide content."
+ Display="Dynamic" />
+<br/>
+<com:THtmlArea ID="ContentEdit" />
+
+<br/>
+<com:TButton Text="Create" OnClick="createButtonClicked" />
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/NewPost.php b/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/NewPost.php
new file mode 100644
index 00000000..a5e3ea4d
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/NewPost.php
@@ -0,0 +1,34 @@
+<?php
+
+class NewPost extends TPage
+{
+ /**
+ * Creates a new post if all inputs are valid.
+ * This method responds to the OnClick event of the "create" button.
+ * @param mixed event sender
+ * @param mixed event parameter
+ */
+ public function createButtonClicked($sender,$param)
+ {
+ if($this->IsValid) // when all validations succeed
+ {
+ // populates a PostRecord object with user inputs
+ $postRecord=new PostRecord;
+ // using SafeText instead of Text avoids Cross Site Scripting attack
+ $postRecord->title=$this->TitleEdit->SafeText;
+ $postRecord->content=$this->ContentEdit->SafeText;
+ $postRecord->author_id=$this->User->Name;
+ $postRecord->create_time=time();
+ $postRecord->status=0;
+
+ // saves to the database via Active Record mechanism
+ $postRecord->save();
+
+ // redirects the browser to the newly created post page
+ $url=$this->Service->constructUrl('posts.ReadPost',array('id'=>$postRecord->post_id));
+ $this->Response->redirect($url);
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/PostRenderer.php b/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/PostRenderer.php
new file mode 100644
index 00000000..cf0539a1
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/PostRenderer.php
@@ -0,0 +1,7 @@
+<?php
+
+class PostRenderer extends TRepeaterItemRenderer
+{
+}
+
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/PostRenderer.tpl b/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/PostRenderer.tpl
new file mode 100644
index 00000000..8f4a0502
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/PostRenderer.tpl
@@ -0,0 +1,17 @@
+<div class="post-box">
+<h3>
+<com:THyperLink Text="<%# $this->Data->title %>"
+ NavigateUrl="<%# $this->Service->constructUrl('posts.ReadPost',array('id'=>$this->Data->post_id)) %>" />
+</h3>
+
+<p>
+Author:
+<com:TLiteral Text="<%# $this->Data->author->username %>" /><br/>
+Time:
+<com:TLiteral Text="<%# date('m/d/Y h:m:sa', $this->Data->create_time) %>" />
+</p>
+
+<p>
+<com:TLiteral Text="<%# $this->Data->content %>" />
+</p>
+</div> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/ReadPost.page b/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/ReadPost.page
new file mode 100644
index 00000000..60a8cf1e
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/ReadPost.page
@@ -0,0 +1,25 @@
+<com:TContent ID="Main">
+
+<h2>
+<com:TLiteral Text="<%= $this->Post->title %>" />
+</h2>
+
+<com:TControl Visible="<%= $this->canEdit() %>">
+ <a href="<%= $this->Service->constructUrl('posts.EditPost',array('id'=>$this->Post->post_id))%>">Edit</a> |
+ <com:TLinkButton Text="Delete"
+ OnClick="deletePost"
+ Attributes.onclick="javascript:if(!confirm('Are you sure?')) return false;" />
+</com:TControl>
+
+<p>
+Author:
+<com:TLiteral Text="<%= $this->Post->author->username %>" /><br/>
+Time:
+<com:TLiteral Text="<%= date('m/d/Y h:m:sa', $this->Post->create_time) %>" />
+</p>
+
+<p>
+<com:TLiteral Text="<%= $this->Post->content %>" />
+</p>
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/ReadPost.php b/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/ReadPost.php
new file mode 100644
index 00000000..0c120824
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/ReadPost.php
@@ -0,0 +1,59 @@
+<?php
+
+Prado::using('Application.BlogException');
+
+class ReadPost extends TPage
+{
+ private $_post;
+ /**
+ * Fetches the post data.
+ * This method is invoked by the framework when initializing the page
+ * @param mixed event parameter
+ */
+ public function onInit($param)
+ {
+ parent::onInit($param);
+ // post id is passed via the 'id' GET parameter
+ $postID=(int)$this->Request['id'];
+ // retrieves PostRecord with author information filled in
+ $this->_post=PostRecord::finder()->withAuthor()->findByPk($postID);
+ if($this->_post===null) // if post id is invalid
+ throw new BlogException(500,'Unable to find the specified post.');
+ // set the page title as the post title
+ $this->Title=$this->_post->title;
+ }
+
+ /**
+ * @return PostRecord the PostRecord currently being viewed
+ */
+ public function getPost()
+ {
+ return $this->_post;
+ }
+
+ /**
+ * Deletes the post currently being viewed
+ * This method is invoked when the user clicks on the "Delete" button
+ */
+ public function deletePost($sender,$param)
+ {
+ // only the author or the administrator can delete a post
+ if(!$this->canEdit())
+ throw new THttpException('You are not allowed to perform this action.');
+ // delete it from DB
+ $this->_post->delete();
+ // redirect the browser to the homepage
+ $this->Response->redirect($this->Service->DefaultPageUrl);
+ }
+
+ /**
+ * @return boolean whether the current user can edit/delete the post being viewed
+ */
+ public function canEdit()
+ {
+ // only the author or the administrator can edit/delete a post
+ return $this->User->Name===$this->Post->author_id || $this->User->IsAdmin;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/config.xml b/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/config.xml
new file mode 100644
index 00000000..64065ed5
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/pages/posts/config.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+ <authorization>
+ <deny pages="NewPost,EditPost" roles="?" />
+ </authorization>
+</configuration> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/pages/users/AdminUser.page b/demos/blog-tutorial/samples/day5/blog/protected/pages/users/AdminUser.page
new file mode 100644
index 00000000..af03b858
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/pages/users/AdminUser.page
@@ -0,0 +1,40 @@
+<%@ Title="My Blog - Manage User Accounts" %>
+
+<com:TContent ID="Main">
+
+<h1>Manage User Accounts</h1>
+
+<a href="<%= $this->Service->constructUrl('users.NewUser')%>">Create New User</a>
+<br/>
+
+<com:TDataGrid ID="UserGrid"
+ DataKeyField="username"
+ AutoGenerateColumns="false"
+ OnDeleteCommand="deleteButtonClicked">
+
+ <com:THyperLinkColumn
+ HeaderText="Username"
+ DataTextField="username"
+ DataNavigateUrlField="username">
+ <prop:DataNavigateUrlFormatString>#
+ $this->Service->constructUrl('users.EditUser',array('username'=>{0}))
+ </prop:DataNavigateUrlFormatString>
+ </com:THyperLinkColumn>
+
+ <com:TBoundColumn
+ HeaderText="Email"
+ DataField="email" />
+
+ <com:TCheckBoxColumn
+ HeaderText="Administrator"
+ DataField="role" />
+
+ <com:TButtonColumn
+ HeaderText="Command"
+ Text="Delete"
+ ButtonType="PushButton"
+ CommandName="delete" />
+
+</com:TDataGrid>
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/pages/users/AdminUser.php b/demos/blog-tutorial/samples/day5/blog/protected/pages/users/AdminUser.php
new file mode 100644
index 00000000..ad8f6e3d
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/pages/users/AdminUser.php
@@ -0,0 +1,36 @@
+<?php
+
+class AdminUser extends TPage
+{
+ /**
+ * Populates the datagrid with user lists.
+ * This method is invoked by the framework when initializing the page
+ * @param mixed event parameter
+ */
+ public function onInit($param)
+ {
+ parent::onInit($param);
+ // fetches all data account information
+ $this->UserGrid->DataSource=UserRecord::finder()->findAll();
+ // binds the data to interface components
+ $this->UserGrid->dataBind();
+ }
+
+ /**
+ * Deletes a specified user record.
+ * This method responds to the datagrid's OnDeleteCommand event.
+ * @param TDataGrid the event sender
+ * @param TDataGridCommandEventParameter the event parameter
+ */
+ public function deleteButtonClicked($sender,$param)
+ {
+ // obtains the datagrid item that contains the clicked delete button
+ $item=$param->Item;
+ // obtains the primary key corresponding to the datagrid item
+ $username=$this->UserGrid->DataKeys[$item->ItemIndex];
+ // deletes the user record with the specified username primary key
+ UserRecord::finder()->deleteByPk($username);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/pages/users/EditUser.page b/demos/blog-tutorial/samples/day5/blog/protected/pages/users/EditUser.page
new file mode 100644
index 00000000..8aa3670e
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/pages/users/EditUser.page
@@ -0,0 +1,61 @@
+<%@ Title="My Blog - Edit User" %>
+
+<com:TContent ID="Main">
+
+<h1>Edit User</h1>
+
+<span>Username:</span>
+<com:TLabel ID="Username" />
+
+<br/>
+<span>Password:</span>
+<br/>
+<com:TTextBox ID="Password" TextMode="Password" />
+
+<br/>
+<span>Re-type Password:</span>
+<com:TCompareValidator
+ ControlToValidate="Password"
+ ControlToCompare="Password2"
+ ErrorMessage="Your password entries did not match."
+ Display="Dynamic" />
+<br/>
+<com:TTextBox ID="Password2" TextMode="Password" />
+
+<br/>
+<span>Email Address:</span>
+<com:TRequiredFieldValidator
+ ControlToValidate="Email"
+ ErrorMessage="Please provide your email address."
+ Display="Dynamic" />
+<com:TEmailAddressValidator
+ ControlToValidate="Email"
+ ErrorMessage="You entered an invalid email address."
+ Display="Dynamic" />
+<br/>
+<com:TTextBox ID="Email" />
+
+<com:TControl Visible="<%= $this->User->IsAdmin %>">
+<br/>
+<span>Role:</span>
+<br/>
+<com:TDropDownList ID="Role">
+ <com:TListItem Text="Normal User" Value="0" />
+ <com:TListItem Text="Administrator" Value="1" />
+</com:TDropDownList>
+</com:TControl>
+
+<br/>
+<span>First Name:</span>
+<br/>
+<com:TTextBox ID="FirstName" />
+
+<br/>
+<span>Last Name:</span>
+<br/>
+<com:TTextBox ID="LastName" />
+
+<br/>
+<com:TButton Text="Save" OnClick="saveButtonClicked" />
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/pages/users/EditUser.php b/demos/blog-tutorial/samples/day5/blog/protected/pages/users/EditUser.php
new file mode 100644
index 00000000..81538c33
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/pages/users/EditUser.php
@@ -0,0 +1,83 @@
+<?php
+
+class EditUser extends TPage
+{
+ /**
+ * Initializes the inputs with existing user data.
+ * This method is invoked by the framework when the page is being initialized.
+ * @param mixed event parameter
+ */
+ public function onInit($param)
+ {
+ parent::onInit($param);
+ if(!$this->IsPostBack) // if the page is initially requested
+ {
+ // Retrieves the existing user data. This is equivalent to:
+ // $userRecord=$this->getUserRecord();
+ $userRecord=$this->UserRecord;
+
+ // Populates the input controls with the existing user data
+ $this->Username->Text=$userRecord->username;
+ $this->Email->Text=$userRecord->email;
+ $this->Role->SelectedValue=$userRecord->role;
+ $this->FirstName->Text=$userRecord->first_name;
+ $this->LastName->Text=$userRecord->last_name;
+ }
+ }
+
+ /**
+ * Saves the user account if all inputs are valid.
+ * This method responds to the OnClick event of the "save" button.
+ * @param mixed event sender
+ * @param mixed event parameter
+ */
+ public function saveButtonClicked($sender,$param)
+ {
+ if($this->IsValid) // when all validations succeed
+ {
+ // Retrieves the existing user data. This is equivalent to:
+ $userRecord=$this->UserRecord;
+
+ // Fetches the input data
+ $userRecord->username=$this->Username->Text;
+ // update password when the input is not empty
+ if(!empty($this->Password->Text))
+ $userRecord->password=$this->Password->Text;
+ $userRecord->email=$this->Email->Text;
+ // update the role if the current user is an administrator
+ if($this->User->IsAdmin)
+ $userRecord->role=(int)$this->Role->SelectedValue;
+ $userRecord->first_name=$this->FirstName->Text;
+ $userRecord->last_name=$this->LastName->Text;
+
+ // saves to the database via Active Record mechanism
+ $userRecord->save();
+
+ // redirects the browser to the homepage
+ $this->Response->redirect($this->Service->DefaultPageUrl);
+ }
+ }
+
+ /**
+ * Returns the user data to be editted.
+ * @return UserRecord the user data to be editted.
+ * @throws THttpException if the user data is not found.
+ */
+ protected function getUserRecord()
+ {
+ // the user to be editted is the currently logged-in user
+ $username=$this->User->Name;
+ // if the 'username' GET var is not empty and the current user
+ // is an administrator, we use the GET var value instead.
+ if($this->User->IsAdmin && $this->Request['username']!==null)
+ $username=$this->Request['username'];
+
+ // use Active Record to look for the specified username
+ $userRecord=UserRecord::finder()->findByPk($username);
+ if(!($userRecord instanceof UserRecord))
+ throw new THttpException(500,'Username is invalid.');
+ return $userRecord;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/pages/users/LoginUser.page b/demos/blog-tutorial/samples/day5/blog/protected/pages/users/LoginUser.page
new file mode 100644
index 00000000..f7fc7367
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/pages/users/LoginUser.page
@@ -0,0 +1,28 @@
+<%@ Title="My Blog - Login" %>
+
+<com:TContent ID="Main">
+
+<h1>Login</h1>
+
+<span>Username:</span>
+<com:TRequiredFieldValidator
+ ControlToValidate="Username"
+ ErrorMessage="Please provide your username."
+ Display="Dynamic" />
+<br/>
+<com:TTextBox ID="Username" />
+
+<br/>
+<span>Password:</span>
+<com:TCustomValidator
+ ControlToValidate="Password"
+ ErrorMessage="Your entered an invalid password."
+ Display="Dynamic"
+ OnServerValidate="validateUser" />
+<br/>
+<com:TTextBox ID="Password" TextMode="Password" />
+
+<br/>
+<com:TButton Text="Login" OnClick="loginButtonClicked" />
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/pages/users/LoginUser.php b/demos/blog-tutorial/samples/day5/blog/protected/pages/users/LoginUser.php
new file mode 100644
index 00000000..a0955490
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/pages/users/LoginUser.php
@@ -0,0 +1,37 @@
+<?php
+
+class LoginUser extends TPage
+{
+ /**
+ * Validates whether the username and password are correct.
+ * This method responds to the TCustomValidator's OnServerValidate event.
+ * @param mixed event sender
+ * @param mixed event parameter
+ */
+ public function validateUser($sender,$param)
+ {
+ $authManager=$this->Application->getModule('auth');
+ if(!$authManager->login($this->Username->Text,$this->Password->Text))
+ $param->IsValid=false; // tell the validator that validation fails
+ }
+
+ /**
+ * Redirects the user's browser to appropriate URL if login succeeds.
+ * This method responds to the login button's OnClick event.
+ * @param mixed event sender
+ * @param mixed event parameter
+ */
+ public function loginButtonClicked($sender,$param)
+ {
+ if($this->Page->IsValid) // all validations succeed
+ {
+ // obtain the URL of the privileged page that the user wanted to visit originally
+ $url=$this->Application->getModule('auth')->ReturnUrl;
+ if(empty($url)) // the user accesses the login page directly
+ $url=$this->Service->DefaultPageUrl;
+ $this->Response->redirect($url);
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/pages/users/NewUser.page b/demos/blog-tutorial/samples/day5/blog/protected/pages/users/NewUser.page
new file mode 100644
index 00000000..d1547a9a
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/pages/users/NewUser.page
@@ -0,0 +1,73 @@
+<%@ Title="My Blog - New User" %>
+
+<com:TContent ID="Main">
+
+<h1>Create New User</h1>
+
+<span>Username:</span>
+<com:TRequiredFieldValidator
+ ControlToValidate="Username"
+ ErrorMessage="Please provide a username."
+ Display="Dynamic" />
+<com:TCustomValidator
+ ControlToValidate="Username"
+ ErrorMessage="Sorry, your username is taken by someone else. Please choose another username."
+ OnServerValidate="checkUsername"
+ Display="Dynamic" />
+<br/>
+<com:TTextBox ID="Username" />
+
+<br/>
+<span>Password:</span>
+<com:TRequiredFieldValidator
+ ControlToValidate="Password"
+ ErrorMessage="Please provide a password."
+ Display="Dynamic" />
+<br/>
+<com:TTextBox ID="Password" TextMode="Password" />
+
+<br/>
+<span>Re-type Password:</span>
+<com:TCompareValidator
+ ControlToValidate="Password"
+ ControlToCompare="Password2"
+ ErrorMessage="Your password entries did not match."
+ Display="Dynamic" />
+<br/>
+<com:TTextBox ID="Password2" TextMode="Password" />
+
+<br/>
+<span>Email Address:</span>
+<com:TRequiredFieldValidator
+ ControlToValidate="Email"
+ ErrorMessage="Please provide your email address."
+ Display="Dynamic" />
+<com:TEmailAddressValidator
+ ControlToValidate="Email"
+ ErrorMessage="You entered an invalid email address."
+ Display="Dynamic" />
+<br/>
+<com:TTextBox ID="Email" />
+
+<br/>
+<span>Role:</span>
+<br/>
+<com:TDropDownList ID="Role">
+ <com:TListItem Text="Normal User" Value="0" />
+ <com:TListItem Text="Administrator" Value="1" />
+</com:TDropDownList>
+
+<br/>
+<span>First Name:</span>
+<br/>
+<com:TTextBox ID="FirstName" />
+
+<br/>
+<span>Last Name:</span>
+<br/>
+<com:TTextBox ID="LastName" />
+
+<br/>
+<com:TButton Text="Create" OnClick="createButtonClicked" />
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/pages/users/NewUser.php b/demos/blog-tutorial/samples/day5/blog/protected/pages/users/NewUser.php
new file mode 100644
index 00000000..76e8cb88
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/pages/users/NewUser.php
@@ -0,0 +1,45 @@
+<?php
+
+class NewUser extends TPage
+{
+ /**
+ * Checks whether the username exists in the database.
+ * This method responds to the OnServerValidate event of username's custom validator.
+ * @param mixed event sender
+ * @param mixed event parameter
+ */
+ public function checkUsername($sender,$param)
+ {
+ // valid if the username is not found in the database
+ $param->IsValid=UserRecord::finder()->findByPk($this->Username->Text)===null;
+ }
+
+ /**
+ * Creates a new user account if all inputs are valid.
+ * This method responds to the OnClick event of the "create" button.
+ * @param mixed event sender
+ * @param mixed event parameter
+ */
+ public function createButtonClicked($sender,$param)
+ {
+ if($this->IsValid) // when all validations succeed
+ {
+ // populates a UserRecord object with user inputs
+ $userRecord=new UserRecord;
+ $userRecord->username=$this->Username->Text;
+ $userRecord->password=$this->Password->Text;
+ $userRecord->email=$this->Email->Text;
+ $userRecord->role=(int)$this->Role->SelectedValue;
+ $userRecord->first_name=$this->FirstName->Text;
+ $userRecord->last_name=$this->LastName->Text;
+
+ // saves to the database via Active Record mechanism
+ $userRecord->save();
+
+ // redirects the browser to the homepage
+ $this->Response->redirect($this->Service->DefaultPageUrl);
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/pages/users/config.xml b/demos/blog-tutorial/samples/day5/blog/protected/pages/users/config.xml
new file mode 100644
index 00000000..56554441
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/pages/users/config.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+ <authorization>
+ <allow pages="NewUser,AdminUser" roles="admin" />
+ <deny users="?" />
+ </authorization>
+</configuration> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/protected/schema.sql b/demos/blog-tutorial/samples/day5/blog/protected/schema.sql
new file mode 100644
index 00000000..89f7388e
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/protected/schema.sql
@@ -0,0 +1,25 @@
+/* create users table */
+CREATE TABLE users (
+ username VARCHAR(128) NOT NULL PRIMARY KEY,
+ email VARCHAR(128) NOT NULL,
+ password VARCHAR(128) NOT NULL, /* in plain text */
+ role INTEGER NOT NULL, /* 0: normal user, 1: administrator */
+ first_name VARCHAR(128),
+ last_name VARCHAR(128)
+);
+
+/* create posts table */
+CREATE TABLE posts (
+ post_id INTEGER NOT NULL PRIMARY KEY,
+ author_id VARCHAR(128) NOT NULL
+ CONSTRAINT fk_author REFERENCES users(username),
+ create_time INTEGER NOT NULL, /* UNIX timestamp */
+ title VARCHAR(256) NOT NULL, /* title of the post */
+ content TEXT, /* post body */
+ status INTEGER NOT NULL /* 0: published; 1: draft; 2: pending; 2: denied */
+);
+
+/* insert some initial data records for testing */
+INSERT INTO users VALUES ('admin', 'admin@example.com', 'demo', 1, 'Qiang', 'Xue');
+INSERT INTO users VALUES ('demo', 'demo@example.com', 'demo', 0, 'Wei', 'Zhuo');
+INSERT INTO posts VALUES (NULL, 'admin', 1175708482, 'first post', 'this is my first post', 0);
diff --git a/demos/blog-tutorial/samples/day5/blog/themes/Basic/button.skin b/demos/blog-tutorial/samples/day5/blog/themes/Basic/button.skin
new file mode 100644
index 00000000..886aa7a5
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/themes/Basic/button.skin
@@ -0,0 +1 @@
+<com:THyperLink SkinID="MainMenu" BackColor="lightgreen" /> \ No newline at end of file
diff --git a/demos/blog-tutorial/samples/day5/blog/themes/Basic/style.css b/demos/blog-tutorial/samples/day5/blog/themes/Basic/style.css
new file mode 100644
index 00000000..a6738366
--- /dev/null
+++ b/demos/blog-tutorial/samples/day5/blog/themes/Basic/style.css
@@ -0,0 +1,37 @@
+body {
+ font-family: verdana, 'trebuchet ms', sans-serif;
+ font-size: 10pt;
+ background: white;
+}
+
+#page {
+ margin: 0 auto 0 auto;
+ width: 600px;
+}
+
+#footer {
+ text-align: center;
+ margin-top: 10px;
+ padding: 10px;
+ border-top: 1px solid silver;
+}
+
+.post-box {
+ margin-bottom: 10px;
+ padding: 5px;
+}
+
+.post-box h3 {
+ padding: 5px;
+ font-size: 13pt;
+ background: lightgray;
+}
+
+.post-box a {
+ color: black;
+ text-decoration: none;
+}
+
+.post-box a:hover {
+ color: red;
+} \ No newline at end of file