ED php framework :: Tutorial #3
Your first end to end application
With this guide you'll understand how to develop a whole application using the ED framework, starting from scratch. We will use version 1.4.3 of the framework and PHP version 5.3.1. The application should work with any other 5.* version, which supports JSON. As it comes with 5.2.0, then if your version is 5.1.0 for example, you have to define json_encode and json_decode first, which is very easy task. So, let's start defining our application.

It will be a simple travel blog consisting of 4 pages. The owner of the blog can publish stories and people can read them. Those who log-in can write comments below each story. For simplicity reasons we won't use database. All the stories are kept in a xml file, which can be easily read with the TDataSource class. So these are our pages. A visitor can log using any combination of user and password, which are equal. myself1/myself1, mike/mike, etc. These credentials are valid only if the visitor's current page is not "About me". When he is there user1/user1 and user2/user2 are the valid ones. This is because the authorization method is different. This page is also special the way it is rendered.

For the first 2 pages we will use buttons and fields as widgets, but for the "About me" page we will use plain html and straight ajax calls. Because all the pages have almost the same content we will use templates. In the "About me" page we will see how the cache works. So let's start step by step.
STEP 1
Open C:\WINDOWS\system32\drivers\etc\hosts and add "127.0.0.1 www.my-first-application.com"

By doing this the domain will point to your dev machine.
STEP 2
Go to the previous tutorial and install the framework. Substitute all occurrences of "www.mysite.com" with "www.my-first-application.com"
NOTE !!!
You have to copy and paste the following source code files. At the top of every file the target directory is marked in grey and the file itself is in black. When necessary I will place my comments below each file.
STEP 3
So we will start with the main config file: appconfig.inc
./
appconfig.inc
<?php
 
    /*********************************************************************************
     *                             LOAD CONTROLLER
     *********************************************************************************/
    include_once("system/controller.inc");
 
 
 
    /*********************************************************************************
     *                             CONFIG APPLICATION
     *********************************************************************************/
    $application = TApplication::createInstance();
    $application->setName("app-my_1st"); // change it to whatever you want
    $application->setCheckForSqlInjection(TRUE);
    $application->setDevMode(TRUE); // TRUE for development, FALSE for production
    $application->setDefaultLocale("en");
 
 
 
    /*********************************************************************************
     *                             CONFIG DATABASE
     *********************************************************************************/
    $dbRef = TPostgreSql::create("host=localhost dbname=db1 user=user1 password=pass1");
    $dbRef->setAutoConnect(TRUE);
    //$dbRef->setDebugMode(TRUE);
    //$dbRef->setPersistent(TRUE);
 
    $application->setDatabase($dbRef);
 
 
 
    /*********************************************************************************
     *                             CONFIG ERROR HANDLER
     *********************************************************************************/
    $application->errorHandler->setLogToFile(TRUE); // can be omitted, the default value is TRUE
    $application->errorHandler->setLogToScreen(FALSE); // can be omitted, the default value is FALSE
    //$application->errorHandler->start(); // uncomment this line for production mode, keep in mind this handler can't catch fatal errors
 
 
 
    /*********************************************************************************
     *                             CONFIG "WWW" APPLICATION CONTEXT
     *********************************************************************************/
    $appCtx = new TApplicationContext("www.my-first-application.com");
    $appCtx->addHostAlias("my-first-application.com");
    $appCtx->setLookAndFeel("/assets/css/skin1/index.php");
    //$appCtx->setLoginPage("/admin/login.php", "AdminToken"); // just a sample
    //$appCtx->addAuthPage("/admin/index.php", "AdminToken"); // just a sample
    //$appCtx->addAuthPage("/admin/(.*).php", "AdminToken"); // just a sample, this line makes the above one useless
 
    $application->addContext($appCtx);
 
 
 
    /*********************************************************************************
     *                             CONFIG CACHE
     *********************************************************************************/
    TCache::setFileBased();
 
 
 
    /*********************************************************************************
     *            THE WIDGETS REGISTERED HERE WILL BE AVAILABLE FOR EVERY PAGE
     *********************************************************************************/
    //registerWidgets(Array("clock","date_field","data_grid"));
?>


Most of the things are self-explaining. We will only mention a couple of them.

$application->setDevMode(TRUE);
The value of TRUE is doing 2 things. First, if on Ajax request/postback the PHP script, which has been executed behind the scene produces error(s), the system will show it in the browser. The second one is connected with the widgets. All the framework widgets are located under "protected/widgets", which is not visible for the browser, but most of them rely on stylesheets, images and javascript(s). In dev mode, the system will copy a fresh copy of any widget, which is used in any particular page to a visible by the browser directory. In production mode this extra operation won't happen.

$dbRef->setDebug(FALSE);
If this one is TRUE, then every sql operation will be logged in "protected/log/system.log"

$application->errorHandler->start();
Uncomment this line when the website is in production mode. Then all application errors will be logged in "protected/log/error.log"

$appCtx->setLookAndFeel("/assets/css/skin1/index.php");
Since version 1.3 this is a new way to use all your stylesheets, which provide your application with unique look and feel. You place all your stylesheets files under "/assets/css/skin1/", they should be named like xxx.css and the index.php will enumerate all of them and send them to the browser as 1 file. Here you may say - I need for page A only this css file and for page B another css file, why should I load all of them. Well, this is true, but if apache and php are set to send compressed pages through zlib/deflate mode it won't be a big deal. For a big application with 30 css files resulting in 100Kb the actual size sent from server to browser won't be more than 10Kb. Of course, you can add css files to pages manually on per page basis if you want. But this way is much easier to control your application.

$appCtx->setLoginPage("/admin/login.php", "AdminToken");
$appCtx->addAuthPage("/admin/(.*).php", "AdminToken");
The second line basically says: check every page for "AdminToken" session key and if not found, then redirect to login.php. There, after successful login you have to set this session key. You can add as well pages, which don't need authentication by excluding them from this too general way for defining security.

TCache::setFileBased();
By default TCache uses the APC cache, but now it will use the file system and the cache will be stored under "protected/persistent/cache_state". This way is much easier to delete it.
STEP 4
Now add our locale files.
protected/i18n/en/
default.i18n
[page charset]
SYS_PAGE_CHARSET="utf-8"
 
 
[shared]
BTN_OK="OK"
BTN_CLOSE="CLOSE"
BTN_CANCEL="CANCEL"
BTN_YES="YEP"
BTN_NO="NOPE"
SYS_MSG_ARE_YOU_SURE="Are you sure ?"
 
 
[system messages]
SYSMSG_ERROR_SQL_INJECTION_DETECTED="Error. Sql injection detected. Please avoid submitting any text that includes drop/delete/update/etc. table/function/database/etc."
SYSMSG_ERROR_NO_RIGHTS_TO_VIEW_PAGE="Error. No rights to view this page."
SYSMSG_ERROR_PAGE_REQUIRES_AUTHORIZATION="Error. This page requires authorization."
SYSMSG_ERROR_APP_FAILURE="Error. Application failure."
SYSMSG_ERROR_ACTION_TERMINATED="Action has been terminated"
 
 
[page info(s) dialog]
SYS_UI_PAGE_INFO_TITLE="INFO"
 
 
[page error(s) dialog]
SYS_UI_PAGE_ERRORS_TITLE="ERRORS"
 
 
[page confirm dialog]
SYS_UI_PAGE_CONFIRM_TITLE="CONFIRM"
 
 
 
 
[======================================== PROJECT DATA ====================================== ]
 
str_title          = "Travel blog"
 
str_choose_lang    = "CHOOSE LANGUAGE"
str_english        = "English"
str_spanish        = "Español"
 
str_login          = "LOGIN"
str_username       = "Username:"
str_password       = "Password:"
str_login_error    = "Username or Password is not valid."
str_logged_as      = "You are logged as '%s'"
str_logout         = "LOGOUT"
 
str_read_more      = "Read more ..."
str_write_comment  = "WRITE COMMENT"
str_send_comment   = "SEND"
str_comment_error  = "Comment field doesn't have value"
str_comment_error2 = "Text must be less than 50 chars"
protected/i18n/es/
default.i18n
[page charset]
SYS_PAGE_CHARSET="utf-8"
 
 
[shared]
BTN_OK="ACEPTAR"
BTN_CLOSE="CERRAR"
BTN_CANCEL="CANCELAR"
BTN_YES="SI"
BTN_NO="NO"
SYS_MSG_ARE_YOU_SURE="¿Estás seguro?"
 
 
[system messages]
SYSMSG_ERROR_SQL_INJECTION_DETECTED="Error. Sql injection detectado."
SYSMSG_ERROR_NO_RIGHTS_TO_VIEW_PAGE="Error. No hay derechos para ver esta página."
SYSMSG_ERROR_PAGE_REQUIRES_AUTHORIZATION="Error. Esta página requiere autorización."
SYSMSG_ERROR_APP_FAILURE="Error. Error de aplicación."
SYSMSG_ERROR_ACTION_TERMINATED="Acción se ha terminado"
 
 
[page info(s) dialog]
SYS_UI_PAGE_INFO_TITLE="NOTIFICACIÓN"
 
 
[page error(s) dialog]
SYS_UI_PAGE_ERRORS_TITLE="ERRORES"
 
 
[page confirm dialog]
SYS_UI_PAGE_CONFIRM_TITLE="CONFIRMAR"
 
 
 
 
[======================================== PROJECT DATA ====================================== ]
 
str_title          = "Blog de viajes"
 
str_choose_lang    = "ELEGIR IDIOMA"
str_english        = "English"
str_spanish        = "Español"
 
str_login          = "ENTRAR"
str_username       = "Usuario:"
str_password       = "Contraseña:"
str_login_error    = "Nombre de usuario o contraseña incorrectos."
str_logged_as      = "Entraste como '%s'"
str_logout         = "SALIR"
 
str_read_more      = "Leer mas ..."
str_write_comment  = "ESCRIBIR COMENTARIO"
str_send_comment   = "ENVIAR"
str_comment_error  = "Comentario no tiene valor"
str_comment_error2 = "El texto debe ser menos que 50 caracteres"
STEP 5
Add theses classes under "protected/lib".
protected/lib/
TStory.inc
<?php
 
    /**
     * Helper class
     */
    class TStory {
 
        /**
         * Get last story
         *
         * @return array Last story
         */
        static public function getLast() {
            $dataSource = TDataSource::load("stories.ds.xml");
            $record = $dataSource->record(0);
            return $record;
        }
 
 
        /**
         * Get list
         *
         * @return TDataSource List of stories
         */
        static public function getList() {
            return TDataSource::load("stories.ds.xml");
        }
 
 
        /**
         * Find story by id
         *
         * @param integer $id Story id
         * @return array story by id
         */
        static public function find($id) {
            $dataSource = TDataSource::load("stories.ds.xml");
            $dataSource->keep("storyId", $id);
            if ($dataSource->size() > 0) {
                return $dataSource->record(0);
            }
            return NULL;
        }
    }
 
?>
protected/lib/
TBasePage.inc
<?php
 
   /**
    * TBasePage
    */
   abstract class TBasePage extends TPage {
 
       public $btnEnglish;
       public $btnSpanish;
       public $txtUsername;
       public $txtPassword;
       public $btnLogin;
       public $btnLogout;
 
 
 
       /**
        * Constructor
        */
       public function TBasePage() {
           $this->btnEnglish  = new TLinkButton();
           $this->btnSpanish  = new TLinkButton();
           $this->txtUsername = new TTextBox();
           $this->txtPassword = new TTextBox();
           $this->btnLogin    = new TButton();
           $this->btnLogout   = new TButton();
       }
 
 
 
 
       /**
        * Configure widgets
        */
       public function configureWidgets() {
           $this->btnEnglish->setProperty("Text", TLocale::get("str_english"));
           $this->btnEnglish->setProperty("SubmitMode", "ajax");
 
           $this->btnSpanish->setProperty("Text", TLocale::get("str_spanish"));
           $this->btnSpanish->setProperty("SubmitMode", "ajax");
 
           $this->txtPassword->setProperty("PasswordMode", TRUE);
 
           $this->btnLogin->setProperty("Text", TLocale::get("str_login"));
           $this->btnLogin->setProperty("SubmitMode", "ajax");
           $this->btnLogin->setProperty("Class", "cssApplButton");
           $this->btnLogin->setProperty("Style", "margin-top:4");
 
           $this->btnLogout->setProperty("Text", TLocale::get("str_logout"));
           $this->btnLogout->setProperty("Class", "cssApplButton");
           $this->btnLogout->setProperty("SubmitMode", "ajax");
       }
 
 
 
 
       /**
        * Create page template
        */
       public function createTemplate() {
           /*********************************************************************************
            *                             BUILD TOP MENU
            *********************************************************************************/
           $repeaterTopMenu = new TDataRepeater();
           $repeaterTopMenu->loadTemplateFromFile("top_menu.tpl");
           $repeaterTopMenu->setDataSource(TDataSource::load("top_menu/" . $this->getLocale() . ".ds.xml"));
           $repeaterTopMenu->build();
 
           /*********************************************************************************
            *                             BUILD PAGE TEMPLATE
            *********************************************************************************/
           $tpl = TPageTemplate::load("base.tpl");
           $tpl->setTitle(TLocale::get("str_title"));
           $tpl->replace("{#top-menu}", $repeaterTopMenu->getContent());
           $tpl->bind("{#btn_english}", $this->btnEnglish);
           $tpl->bind("{#btn_spanish}", $this->btnSpanish);
           $tpl->bind("{#username_field}", $this->txtUsername);
           $tpl->bind("{#password_field}", $this->txtPassword);
           $tpl->bind("{#btn_login}", $this->btnLogin);
           $tpl->bind("{#btn_logout}", $this->btnLogout);
           $tpl->replace("{#locale}", $this->getLocale());
           if ($this->user->isLogged()) {
               $tpl->replace("{#str_logged_as}", TLocale::format("str_logged_as", $this->user->getUsername()));
           }
 
           $tpl->exec("setLoggedIn", $this->user->isLogged());
 
           return $tpl;
       }
 
 
 
 
       /**
        * On page load event
        */
       public function OnPageLoad() {
           if (!$this->isPostBack()) {
               $this->model->setProperty("curUri", $this->request->getUri(), TRUE, TRUE);
           }
       }
 
 
       /**
        * On click button event
        */
       public function btnEnglish_OnClick() {
           $this->setLocale("en");
           $this->redirectToTheSamePage();
       }
 
 
       /**
        * On click button event
        */
       public function btnSpanish_OnClick() {
           $this->setLocale("es");
           $this->redirectToTheSamePage();
       }
 
 
       /**
        * On click button event
        */
       public function btnLogin_OnClick() {
           $user = $this->txtUsername->getValue();
           $pass = $this->txtPassword->getValue();
 
           if ($user && $user === $pass) {
               $this->user->setLoggedAs("TestUser");
               $this->user->setUsername($user);
               return $this->redirectToTheSamePage();
           }
 
           $this->addError(TLocale::get("str_login_error"));
       }
 
 
       /**
        * On click button event
        */
       public function btnLogout_OnClick() {
           $this->user->logOut();
           return $this->redirectToTheSamePage();
       }
 
 
       /**
        * Redirect to the same page
        */
       private function redirectToTheSamePage() {
           $this->redirect($this->model->getProperty("curUri"));
       }
 
    }
?>
protected/lib/
TBase2Page.inc
<?php
 
   /**
    * TBase2Page
    */
   abstract class TBase2Page extends TPage {
 
       /**
        * Constructor
        */
       public function TBase2Page() {
       }
 
 
 
 
       /**
        * On page load event
        */
       public function OnPageLoad() {
           if (!$this->isPostBack()) {
               $this->model->setProperty("curUri", $this->request->getUri(), TRUE, TRUE);
           }
       }
 
 
       /**
        * Change language
        */
       public function cmdLang($rcvData) {
           $this->setLocale($rcvData["lang"]);
           $this->redirectToTheSamePage();
       }
 
 
       /**
        * Log in
        */
       public function cmdLogin($rcvData) {
           $user = $rcvData["user"];
           $pass = $rcvData["pass"];
 
           $this->user->loginAttempt("users.ds.xml", $user, $pass);
 
           if (!$this->user->isLogged()) {
               $this->showError(TLocale::get("str_login_error"));
               return;
           }
 
           return $this->redirectToTheSamePage();
       }
 
 
       /**
        * Log out
        */
       public function cmdLogout($rcvData) {
           $this->user->logOut();
           return $this->redirectToTheSamePage();
       }
 
 
       /**
        * Redirect to the same page
        */
       private function redirectToTheSamePage() {
           $this->redirect($this->model->getProperty("curUri"));
       }
 
    }
?>
QPHP automatically loads all *.inc files from this "lib" directory, so you can define here all your helper classes and methods. The "TBasePage" class should be extended by all the classes/pages that share common template. In it we place all the logic that is common for all the pages. And then in every particular page/class we add only the specific part for that page.
STEP 6
protected/security/
users.ds.xml
<DataSource>
 
    <record>
        <key name="id">1001</key>
        <key name="username">user1</key>
        <key name="password">user1</key>
        <key name="logged-as">TestUser</key>
        <key name="roles">admin,operator,guest</key>
        <key name="Full Name">John Duke</key>
        <key name="propertyA">Property A</key>
        <key name="propertyB">Property B</key>
    </record>
 
    <record>
        <key name="id">5000</key>
        <key name="username">user2</key>
        <key name="password">user2</key>
        <key name="logged-as">TestUser</key>
        <key name="roles">manager</key>
        <key name="Full Name">Mary The Great</key>
        <key name="property1">Property 1</key>
        <key name="property2">Property 2</key>
    </record>
 
</<DataSource>
There is a easy built-in mechanism to authorize users against files with such structure.
STEP 7
Now we will add our template files.
protected/tpl/
base.tpl
<html>
<head>
 
<script type="text/javascript">
//<![CDATA[
 
    function setLoggedIn(value) {
        elem("loginDiv").style.display = value ? "none" : "";
        elem("logoutDiv").style.display = value ? "" : "none";
    }
 
//]]>
</script>
 
</head>
 
<body style="margin:0 0 0 0;;background-color:#FFF4D6">
 
<table cellspacing="0" cellpadding="0" border="0" style="width:900px;height:100%;" align="center">
 
   <tr><td style="height:6px;background-color:#000000;"></td></tr>
 
   <tr><td style="height:150px"><img src="/assets/img/top.jpg" /></td></tr>
 
   <tr><td class="cssTopMenu" style="height:24px;">{#top-menu}</td></tr>

   <tr><td style="height:8px;"></td></tr>
 
   <tr><td style="vertical-align:top;padding:0 0 0 0;">
 
     <table cellspacing="0" cellpadding="0" border="0" style="width:100%;height:100%;">
        <tr>
           <td class="cssInfobox">
              {#subpage}
           </td>
           <td class="cssRightBox" style="width:180px;">
 
              <span class="h">{#str_choose_lang}</span><br/>
              <div class="c">
                 <img src="/assets/img/en.gif" /> {#btn_english}<br/>
                 <img src="/assets/img/es.gif" /> {#btn_spanish}<br/>
              </div>
 
              <div id="loginDiv" class="c" style="display:none">
              <span class="h">{#str_login}</span><br/>
                 {#str_username}<br/> {#username_field}<br/>
                 {#str_password}<br/> {#password_field}<br/>
                 {#btn_login}
              </div>
              <div id="logoutDiv" class="c" style="display:none">
              <span class="h">{#str_logged_as}</span><br/>
                 <br/>
                 {#btn_logout}
              </div>
              <br/><br/><br/>
              <img src="/assets/img/{#locale}_flag.png" />
           </td>
        </tr>
     </table>
 
   </td></tr>
 
   <tr><td class="cssTopMenu" style="text-align:center;height:24px;">Copyright 2010</td></tr>
 
</table>
 
</body>
 
</html>
protected/tpl/
base2.tpl
<html>
<head>
 
<script type="text/javascript">
//<![CDATA[
 
    function setLoggedIn(value) {
        elem("loginDiv").style.display = value ? "none" : "";
        elem("logoutDiv").style.display = value ? "" : "none";
    }
 
    function cmdLang(lang) {
        page.ajax.call("cmdLang", {"lang" : lang});
    }
 
    function cmdLogin() {
        page.ajax.call("cmdLogin", {
            "user" : elem('user').value,
            "pass" : elem('pass').value
        });
    }
 
    function cmdLogout() {
        page.ajax.call("cmdLogout");
    }
//]]>
</script>
 
</head>
 
<body style="margin:0 0 0 0;;background-color:#FFF4D6">
 
<table cellspacing="0" cellpadding="0" border="0" style="width:900px;height:100%;" align="center">
 
   <tr><td style="height:6px;background-color:#000000;"></td></tr>
 
   <tr><td style="height:150px"><img src="/assets/img/top.jpg" /></td></tr>
 
   <tr><td class="cssTopMenu" style="height:24px;">{#top-menu}</td></tr>

   <tr><td style="height:8px;"></td></tr>
 
   <tr><td style="vertical-align:top;padding:0 0 0 0;">
 
     <table cellspacing="0" cellpadding="0" border="0" style="width:100%;height:100%;">
        <tr>
           <td class="cssInfobox">
              {#subpage}
           </td>
           <td class="cssRightBox" style="width:180px;">
 
              <span class="h">{#str_choose_lang}</span><br/>
              <div class="c">
                 <img src="/assets/img/en.gif" /> <a href='#' onclick="cmdLang('en')">{#str_english}</a><br/>
                 <img src="/assets/img/es.gif" /> <a href='#' onclick="cmdLang('es')">{#str_spanish}</a><br/>
              </div>
 
              <div id="loginDiv" class="c" style="display:none">
              <span class="h">{#str_login}</span><br/>
                 {#str_username}<br/> <input type='text' id='user' /><br/>
                 {#str_password}<br/> <input type='password' id='pass' /><br/>
                 <input type='button' value='{#str_login}' class='cssApplButton' style='margin-top:4' onclick='cmdLogin()' />
              </div>
              <div id="logoutDiv" class="c" style="display:none">
              <span class="h">{#str_logged_as}</span><br/>
                 <br/>
                 <input type='button' value='{#str_logout}' class='cssApplButton' onclick='cmdLogout()' />
              </div>
              <br/><br/><br/>
              <img src="/assets/img/{#locale}_flag.png" />
           </td>
        </tr>
     </table>
 
   </td></tr>
 
   <tr><td class="cssTopMenu" style="text-align:center;height:24px;">Copyright 2010</td></tr>
 
</table>
 
</body>
 
</html>
protected/tpl/
top_menu.tpl
[header]
<center>
 
[repeat]
<a href='{#link}' class='cssLinkTopMenu'>{#text}</a>

 
[repeat-alternate]
 
 
[repeat-empty]
 
 
[repeat-separator]
&nbsp;&times;&nbsp;
 
 
[footer]
</center>
protected/tpl/
stories.tpl
[header]
 
 
[repeat]
<div class="cssInfobox">
<span class="title">{#title}</span>
<div class="text">
	{#date}
	<br/>
	{#text}
	<br/><br/>
	<a href='/story.php?storyId={#storyId}'>{#str_read_more}</a>
</div>
</div>
 
 
[repeat-alternate]
 
 
[repeat-empty]
 
 
[repeat-separator]
<br/><br/>
 
 
[footer]
protected/tpl/
story_comments.tpl
[header]
 
 
[repeat]
<div class="cssInfobox">
<span class="title">{#from}</span>
<div class="text">
	{#text}
</div>
</div>
 
 
[repeat-alternate]
 
 
[repeat-empty]
 
 
[repeat-separator]
<br/><br/>
 
 
[footer]
We access them through TPageTemplate::load("base.tpl") or TDataRepeater::loadTemplateFromFile
STEP 8
And these are our data files.
protected/xml/top_menu/
en.ds.xml
<DataSource>
 
    <record>
        <key name="text">HOME</key>
        <key name="link">/index.php</key>
    </record>
 
    <record>
        <key name="text">STORIES</key>
        <key name="link">/list.php</key>
    </record>
 
    <record>
        <key name="text">ABOUT ME</key>
        <key name="link">/about_me.php</key>
    </record>
 
</<DataSource>
protected/xml/top_menu/
es.ds.xml
<DataSource>
 
    <record>
        <key name="text">INICIO</key>
        <key name="link">/index.php</key>
    </record>
 
    <record>
        <key name="text">CUENTAS</key>
        <key name="link">/list.php</key>
    </record>
 
    <record>
        <key name="text">ACERCA DE MI</key>
        <key name="link">/about_me.php</key>
    </record>
 
</<DataSource>
protected/xml/
stories.ds.xml
<DataSource>
 
    <record>
        <key name="storyId">1001</key>
        <key name="title">STORY 1001</key>
        <key name="date">09/12/2009</key>
        <key name="text">
            <![CDATA[
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean non nisl ac diam tincidunt tempus. Pellentesque elit nisi, vehicula non rutrum eget, posuere vel dolor. Vestibulum lacinia, massa eget rutrum aliquet, est velit luctus tellus, vel congue enim sapien lacinia purus. Fusce nulla augue, gravida vitae tempus eu, ultricies ut turpis. Ut sem lorem, sollicitudin et accumsan vel, imperdiet sed eros. Cras nec libero purus, sit amet vulputate dolor. Donec a turpis sapien. Curabitur odio mauris, rutrum id laoreet at, congue vel eros. Mauris eu elit et dui condimentum euismod. Praesent vel tellus congue sem convallis ultrices sed non dui. Cras tempor, lectus sed bibendum posuere, risus augue sodales dolor, a tempus nulla purus semper neque. In odio elit, accumsan at pretium in, faucibus non erat.
<br/><br/>
Nulla suscipit, dolor ut dictum mattis, odio diam placerat nibh, ac faucibus augue nisi vitae eros. Mauris vel sapien leo, vitae congue erat. Aliquam non nisi felis. Ut quis urna non justo dictum aliquam et non libero. Nulla facilisi. Proin tempus nisl quis sapien tempor nec ultricies lacus sagittis. Aliquam erat volutpat. Suspendisse quis imperdiet erat. Fusce blandit nisl vitae justo pulvinar id hendrerit nisl vulputate. Proin commodo sem quis mi facilisis iaculis. Sed hendrerit bibendum sem, sed tristique diam fermentum sit amet. Cras tincidunt scelerisque pharetra. Etiam tempus dignissim enim ac scelerisque. Praesent hendrerit facilisis ullamcorper. Nulla facilisi. Aenean volutpat aliquam risus vitae ullamcorper. Cras volutpat placerat leo, vitae dapibus eros eleifend at.
<br/><br/>
Quisque ut magna elit, vitae hendrerit quam. Donec lectus enim, dapibus id pharetra quis, pellentesque et tortor. Sed mattis posuere enim sit amet dictum. Quisque diam ipsum, placerat sed congue sit amet, faucibus non tortor. In vel erat felis, id facilisis dolor. Maecenas id porta metus. Cras faucibus pellentesque semper. Mauris vel elementum est. Sed tincidunt facilisis faucibus. Integer lobortis feugiat volutpat. Mauris ornare tortor in massa varius egestas. Proin eget magna nec leo pulvinar lacinia ac ac metus. 
            ]]>
        </key>
    </record>
 
    <record>
        <key name="storyId">1002</key>
        <key name="title">STORY 1002</key>
        <key name="date">07/07/2009</key>
        <key name="text">
            <![CDATA[
Nunc suscipit enim quis purus vulputate eu gravida leo luctus. Sed eu volutpat quam. Nullam lacinia velit sit amet mauris aliquet congue eget at metus. Duis posuere, risus id placerat gravida, est sapien tempus nibh, scelerisque aliquet odio metus quis erat. Praesent pellentesque, lacus ut molestie fringilla, diam nisi lacinia ante, vitae semper quam turpis quis lorem. Maecenas fermentum nunc eu dolor dignissim non porttitor lacus bibendum. Sed non lacus vitae odio dignissim convallis. Ut lobortis quam pellentesque leo commodo eu porttitor orci gravida. Praesent mollis fermentum ipsum in eleifend. Duis sed neque tortor. Nullam a lacus augue. Quisque quis justo ac diam pretium hendrerit in eu tortor. Phasellus laoreet scelerisque arcu, ac tincidunt velit varius ac. Suspendisse nec turpis non dolor fermentum congue. Donec nunc quam, sodales sed feugiat et, cursus sit amet est. Nunc ultrices accumsan tempor. 
            ]]>
        </key>
    </record>
 
    <record>
        <key name="storyId">1003</key>
        <key name="title">STORY 1003</key>
        <key name="date">05/11/2009</key>
        <key name="text">
            <![CDATA[
Cras mollis adipiscing augue, eget pretium dui sodales a. Suspendisse tempus turpis sed velit volutpat vel faucibus sapien sodales. Nullam quis aliquet justo. Curabitur nec purus sit amet nulla bibendum volutpat. Proin euismod orci eget diam ullamcorper ac sagittis lacus consectetur. Aliquam erat volutpat. Morbi eu turpis eget mauris porttitor mattis nec ut tortor. Cras nec urna vitae magna viverra tincidunt non non enim. Vestibulum dictum turpis sed erat volutpat tempor. Nulla faucibus ante sit amet nibh vulputate ut fermentum dolor suscipit. Pellentesque varius vehicula nibh scelerisque cursus. Etiam fringilla, elit dignissim molestie tempor, diam nulla luctus nibh, vel vehicula nisl urna non dui.
            ]]>
        </key>
    </record>
 
</<DataSource>
We access them through TDataSource::load("stories.ds.xml")
STEP 9
THE "PUBLIC" FOLDER
Let's adjust first our skin. The first file will enumerate all *.css files.
public/www/assets/css/skin1/
index.php
<?php
 
    Header("Content-Type:text/css");
 
    function loadSkinDirectory($dir) {
        $handle = opendir($dir);
        while (($file = readdir($handle)) !== false) {
         	  if ((preg_match("/^.*\.css.*$/i", $file)||preg_match("/^.*\.php.*$/i", $file)) && !preg_match("/^.*".$file."$/i", $_SERVER["SCRIPT_FILENAME"]) && is_file($dir . $file)) {
         	      include_once($dir . $file);
         	  }
        }
        closedir($handle);
    }
 
 
 
    loadSkinDirectory("./");
?>
public/www/assets/css/skin1/
appl.css
 
.cssTopMenu
{
    background-color:#000000;
    font-family:Tahoma, Verdana, Arial;
    font-size:12px;
    color:#FFFFFF;
    font-weight:bold;
}
 
 
a.cssLinkTopMenu:link, a.cssLinkTopMenu:active, a.cssLinkTopMenu:visited
{
    font-family:Tahoma, Verdana, Arial;
    font-size:11px;
    color:#FFFFFF;
    font-weight:bold;
    text-decoration:none;
}
 
 
a.cssLinkTopMenu:hover
{
    text-decoration:underline;
}
 
 
.cssRightBox
{
    vertical-align:top;
    border-left:1px solid #F1DB9E;
    padding:10 5 0 10;
}
 
 
.cssRightBox .h
{
    font-family:Verdana, Tahoma, Arial;
    font-size:11px;
    color:#000000;
    border-bottom:1px solid #000000;
}
 
 
.cssRightBox .c
{
    font-family:Verdana, Tahoma, Arial;
    font-size:11px;
    color:#000000;
    line-height: 24px;
    padding: 5 5 30 12;
}
 
 
.cssApplButton
{
    font-family:Tahoma,Verdana,Arial;
    font-size:10px;
    font-weight:bold;
    color:#000000;
    background-color:#F2CB61;
    border-top:1px solid #FFFFFF;
    border-left:1px solid #FFFFFF;
    border-right:1px solid #EFD180;
    border-bottom:1px solid #EFD180;
    padding:2 2 2 2;
    width:100px;
    height:24px;
}
 
 
.cssInfobox
{
    background-color: #FFF4D6;
    background-image: url(img/infobox.jpg);
    background-repeat: repeat-x;
    background-position: top left;
    font-family:Verdana, Arial;
    font-size:13px;
    color:#000000;
    font-weight:normal;
    font-style:normal;
    text-align:left;
    vertical-align:top;
    padding:10 10 10 10;
}
 
 
.cssInfobox .title
{
    font-family: Verdana, Tahoma, Courier New, Verdana, Arial;
    font-size:11px;
    color:#000000;
    font-weight:bold;
    font-style:normal;
    border-bottom:1px solid #7B7B7B;
}
 
.cssInfobox .text
{
    font-family:Verdana, Tahoma, Courier New, Arial;
    font-size:13px;
    color:#000000;
    font-style:normal;
    font-weight: normal;
    text-decoration: none;
    padding-top:16px;
    padding-left:6px;
    padding-bottom:16px;
    line-height:18px;
}
 
.cssInfobox .text a:link,
.cssInfobox .text a:active,
.cssInfobox .text a:visited
{
    font-family: Verdana, Sans-Serif;
    font-size:12px;
    color:#039;
    text-decoration:underline;
}
 
.cssInfobox .text a:hover
{
    color:#ff8208;
}
 
.cssTextArea
{
    color:#ff8208;
    width:400px;
    height:120px;
}
 
 
 
 
This is our application stylesheet.
public/www/assets/css/skin1/
qphp.css
 
/******************************************************************************
 * UI widgets: cssDialogInfo, cssDialogError, cssDialogWait
 ******************************************************************************/
 
 
/******************************************************************************
 * cssDialogInfo
 ******************************************************************************/
 
.cssDialogInfo
{
  background-color:#F9F9F9;
  border:#245684 1px solid;
  padding:0px;
  position:absolute;
	width:550px;
}
 
.cssDialogInfo table .header_outside
{
	background-color:#245684;
	padding:0px;
	height:23px;
}
 
.cssDialogInfo table .header_inside
{
  font-family:Verdana,Tahoma,Times New Roman;
	font-size:13px;
	color: #FFFFFF;
	font-weight:bold;
	text-align:center;
	vertical-align:middle;
	padding-left:4px;
	text-decoration:none;
}
 
.cssDialogInfo table .content
{
  font-family:Courier New,Tahoma,Times New Roman;
	font-size:16px;
	color: #000000;
	font-weight:normal;
	padding:10px;
	text-align:left;
	vertical-align:top;
}
 
.cssDialogInfo table .content .textbox
{
	color: #585858;
	border:1px solid #727272;
}
 
.cssDialogInfo table .button
{
    FONT-FAMILY: Verdana, Arial, Geneva;
    color:#000000;
    background-color:#E9EBEA;
    font-size:10px;
    text-decoration:none;
    text-align:center;
    border-top:1px solid #FFFFFF;
    border-left:1px solid #FFFFFF;
    border-right:1px solid #716f64;
    border-bottom:1px solid #716f64;
    font-weight:bold;
    padding:4 4 4 4;
    width:140px;
    height: 22px;
}
 
.cssDialogInfo div.progressbar
{
  border:1px solid #000000;
  background-color:#81B0E5;
}
 
 
/******************************************************************************
 * cssDialogError
 ******************************************************************************/
 
.cssDialogError
{
  background-color:#F9F9F9;
  border:#CC3322 1px solid;
  padding:0px;
  position:absolute;
	width:550px;
}
 
.cssDialogError table .header_outside
{
	background-color:#D03F2F;
	padding:0px;
	height:23px;
}
 
.cssDialogError table .header_inside
{
  font-family:Verdana,Tahoma,Times New Roman;
	font-size:13px;
	color: #FFFFFF;
	font-weight:bold;
	text-align:center;
	vertical-align:middle;
	padding-left:4px;
	text-decoration:none;
}
 
.cssDialogError table .content
{
  font-family:Courier New,Tahoma,Times New Roman;
	font-size:16px;
	color: #000000;
	font-weight:normal;
	padding:10px;
	text-align:left;
	vertical-align:top;
}
 
.cssDialogError table .button
{
    FONT-FAMILY: Verdana, Arial, Geneva;
    color:#000000;
    background-color:#E9EBEA;
    font-size:10px;
    text-decoration:none;
    text-align:center;
    border-top:1px solid #FFFFFF;
    border-left:1px solid #FFFFFF;
    border-right:1px solid #716f64;
    border-bottom:1px solid #716f64;
    font-weight:bold;
    padding:4 4 4 4;
    width:140px;
    height: 22px;
}
 
 
 
/******************************************************************************
 * cssDialogWait
 ******************************************************************************/
 
.cssDialogWait
{
  background-color:#F9F9F9;
  border:#DEDEDE 1px solid;
  padding:0px;
  position:absolute;
	width:400px;
}
 
.cssDialogWait table .header_outside
{
	background-color:#81B0E5;
	padding:0px;
	height:23px;
}
 
.cssDialogWait table .header_inside
{
  font-family:Verdana,Tahoma,Times New Roman;
	font-size:13px;
	color: #F4F4F4;
	font-weight:bold;
	text-align:center;
	vertical-align:middle;
	padding-left:4px;
	text-decoration:none;
}
 
.cssDialogWait table .content
{
  font-family:Courier New,Tahoma,Times New Roman;
	font-size:16px;
	color: #000000;
	font-weight:normal;
	padding:10px;
	text-align:center;
	vertical-align:top;
}
This is our framework stylesheet. All dialogs and messages rely on it. Usually you can copy it from "public/www/system/assets/css/". If we don't have a skin defined in appconfig.inc, then the framework will load it from this location, but when we have a skin defined, it must be part of the skin.
STEP 10
Well, that's it. I won't go and place the remaining files here file by file, but you can download the full source and take a look inside. First download the framework, unzip it and then place on top of it the application.. These are the things you may be interested more. Check the LIVE DEMO for better sample.
$this->model->setProperty(); // TDataModel class
$this->user->setProperty(); // THttpUser class
The first class is extremely valuable when it comes to pass data from the business logic to the presentation layer and vice versa. The second handles user's login.

DOWNLOAD FULL APPLICATION SOURCE CODE