Skip to content

7. Case Study: Managing a Product Database on the Web

The code for this case study is available |ICI|.

Objectives:

  • write a class for managing a product database
  • Write a web application based on this class
  • introduce style sheets
  • propose an initial development methodology for simple web applications
  • load javascript into the client browser

Credits: The essence of this case study was drawn from the book "Les cahiers du programmeur - PHP/MySQL" by Jean-Philippe Leboeuf, published by Eyrolles.

7.1. Introduction

A retailer wants to manage the items he sells in his store. He already has a ACCESS application at home that does this job, but he is tempted by the world of the web. He has an account with an internet service provider that allows its users to install scripts in their personal folders. This enables them to create dynamic websites. In addition, these same clients have a MySQL account that allows them to create tables to serve data to their PHP scripts. Thus, the merchant has a MySQL account with the login "adarticles" and password "mdparticles." He has a "darticles" database over which he has full access rights. Our merchant therefore has everything he needs to put his product management system online. With your help—since you have web development skills—he is embarking on this venture.

7.2. The Database

Our merchant has created the following mockup of the web homepage interface he would like:

Image

There would be two types of users:

  • administrators who could perform all actions on the product table (add, modify, delete, view, etc.). They will be able to use all the items in the menu above. In particular, they will be able to issue any query SQL via the option [Requête SQL].
  • Normal users (non-administrators) with restricted rights: rights to add, modify, delete, and view. They may have only some of these rights, such as view-only access.

Because there are various types of database users with different rights, authentication is required. This is why the home page begins with this step. To determine who is who and who has the right to do what, two tables, USERS and DROITS, will be used. The table USERS would have the following structure:

login
The user's login, which uniquely identifies them. This field is the table's primary key.
password
The user's plaintext password
admin
The character 'y' (yes) if the user is an administrator, otherwise the character 'n' (no).

The table contents could be as follows:

Image

The table DROITS specifies the permissions for non-administrator users listed in the table USERS. Its structure is as follows:

login
The user's login, which uniquely identifies them.
This field is a foreign key in table DROITS and references
the login column of table USERS.
table
the name of the table for which the user has permissions.
add
the character 'y' (yes) if the user has add rights on the table,
otherwise the character 'n' (no).
modify
update permission: 'y' or 'n'
supprimer
Delete permission: 'y' or 'n'
view
Read permission: 'y' or 'n'

The table contents could be as follows:

Image

Notes:

  • A user U who is in table USERS but not in table DROITS has no rights.
  • In our example, users will only have access to a single table, table ARTICLES. However, our forward-thinking merchant has nevertheless added the table field to the structure of table DROITS to allow for the possibility of adding new tables to the application in the future.
  • Why manage permissions in our own tables when we assume we’ll be using a database named MySQL that is itself capable (and better equipped than we are) of managing these permissions in its own tables? Simply because our merchant does not have administrative rights to the MySQL database that would allow them to create users and grant them permissions. Let’s not forget that the MySQL database is hosted by an ISP and that the merchant is merely a user of that database with no administrative rights (fortunately). However, he does have full rights to a database called dbarticles, which he currently accesses using the login admarticles and the password mdparticles. This database contains all the application’s tables.

The ARTICLES table contains information about the items sold by the merchant. Its structure is as follows:

code
item code - primary key of the table
- exactly 4 characters
name
item name
price
its price
stockActuel
current stock level
stockMinimum
the level below which a
reorder must be placed

Its content, initially used for testing purposes, could be as follows:

Image

7.3. Project constraints

The merchant is migrating a local ACCESS application to a web application. He does not know what will become of it or how it will evolve. However, he would like the new application to be easy to use and scalable. For this reason, his IT consultant envisioned, during the design of the tables, that there could be:

  • various users with different permissions: this will allow the merchant to delegate certain tasks to other people without giving them administrative rights
  • in the future, tables other than table ARTICLES

The same consultant makes other suggestions:

  • he knows that in software development, the presentation layer and the processing layer must be clearly separated. The architecture of a web application is often as follows:

The user interface here is a web browser, but it could also be a standalone application that sends HTTP requests to the web service via the network and formats the results it receives. The application logic consists of scripts that process user requests, in this case PHP scripts. The data source is often a database, but it can also be a directory or a remote web service. The developer should maintain a high degree of independence between these three entities so that if one of them changes, the other two do not have to change, or only minimally. The merchant’s IT consultant then makes the following proposals:

  • We will place the application’s business logic in a class PHP. Thus, the block [Logique applicative] above will consist of the following elements:

In the [Logique Applicative] block, we can distinguish

  • the [IE=Interface d'Entrée] block, which is the application’s entry point. It is the same regardless of the client type.
  • the block [Classes métier], which contains the classes necessary for the application’s logic. These are client-independent.
  • the block of response page generators [IS1 IS2 ... IS=Interface de Sortie]. Each generator is responsible for formatting the results provided by the application logic for a given client type: code HTML for a browser or a phone (WAP), code XML for a standalone application, ...

This model ensures good independence from the clients. Whether the client changes or you want to update how results are presented, it is the [IS] output generators that will need to be created or adapted.

  • In a web application, the separation between the presentation layer and the business logic layer can be improved by using style sheets. These govern the presentation of a web page within a browser. To change this presentation, simply modify the associated style sheet. There is no need to alter the business logic. We will therefore use a style sheet here.
  • In the diagram above, the business class will act as the interface to the data source. By assumption, this source is a MySQL database. To allow for migration to another database, we will use the PEAR library, which provides database access classes independent of the actual database type. Thus, if our retailer becomes wealthy enough to install a Microsoft IIS web server in their company, they will be able to replace the MySQL database with SQL Server without having to (or with very little need to) change the business class.

7.4. The Items Class

The product class could be defined as follows:

<?php

     // articles class, working on an article database composed of the following tables
     // items: (code, name, price, stockActuel, stockMinimum)
     // users : (login, mdp, admin)
     // rights: (login, table, add, modify, delete, view)

     // it is the user of the class who must provide the login/mdp to perform any operation on the database
     // so he already has all rights to the base. This means that there is no need to take
     // special safety precautions here

     // libraries
  require_once 'DB.php';

  class articles{

           // attributes
    var $sDSN;                        // the connection chain
          var $sDatabase;            // base name
    var $oDB;                        // connection to base
    var $aErreurs;                // error list
    var $oRésultats;            // result of a select query
        var $connecté;                // boolean indicating whether or not you are connected to the database
        var $sQuery;                    // the last query executed
    var $sUser;                    // identity of connection user
    var $bAdmin;                    // true if the user is an administrator
    var $dDroits;                // the dictionary of its rights table ->> array(consult,add,delete,modify)

     // manufacturer
    function articles($dDSN,$sUser,$sMdp){

             // $dDSN: dictionary defining the link to be established
       // $dDSN['sgbd']: type of SGBD to be connected to
       // $dDSN['host']: name of the host machine hosting it      
       // $dDSN['database']: name of the database to be connected to      
       // $dDSN['admin']: the login of the owner of the database to be connected to
       // $dDSN['mdpadmin']: its password
       // $sUser: login of the user who wants to use the article database
       // $sMdp: password

       // creates in $oDB a connection to the database defined by $dDSN as $dDSN['admin']
       // if connection succeeds and user $sUser is authenticated  
           // loads rights into $bAdmin and $dDroits user rights $sUser
           // sets $sDSN to the database connection string
           // sets $sDataBase to the name of the database to which you are connecting
         // sets $connecté to true
       // if connection fails or if user $sUser is not correctly identified
           // puts the appropriate error msg in the $aErreurs list
         // closes the connection if necessary
         // sets $connecté to false 

  ...
    }//manufacturer

    // ------------------------------------------------------------------
    function connect(){
             // (re)connecting to the base
...
    }//connect

    // ------------------------------------------------------------------
    function disconnect(){
      // close the connection to the $sDSN database
...
    }//disconnect

    // -------------------------------------------------------------------
    function execute($sQuery,$bAdmin){
            // $sQuery: query to be executed
       // $bAdmin : true if requesting execution as administrator
...
    }//execute

    // --------------------------------------------------------------------------
    function addArticle($dArticle){
         // adds item $dArticle (code, name, price, stockActuel, stockMinimum) to the item table
   ...
    }//add          

    // ----------------------------------------------------------------------
    function modifyArticle($dArticle){
             // modifies an item $dArticle (code, name, price, stockActuel, stockMinimum) in the item table
...
    }//update

    // ----------------------------------------------------------------------
    function deleteArticle($sCode){
             // deletes an item from the table of items
       // whose code is $sCode
...
    }//delete

    // ----------------------------------------------------------------------
    function vérifierArticle(&$dArticle){
         // checks the validity of an item $dArticle (code, name, price, stockActuel, stockMinimum)
...
    }//check

    // --------------------------------------------------------------------------
    function selectArticles($dQuery){
             // executes a select query on the item table
       // it has three components
       // list of columns in $dQuery['columns']
      // filtrage dans $dQuery['where']
       // order of presentation in $dQuery['orderby']
...
    }//selectArticles            

        // --------------------------------
    function existeArticle($sCode){
         // returns TRUE if item code $sCode exists in the item table
...
    }//existeArticle

    // --------------------------------------
    function existeUser($sUser,$sMdp){
             // check existence of user $sUser with password $sMdp
       // render (int $iErreur, string $sAdmin, hashtable $dDroits)
       // $iErreur = -1 for any database operating error - the $aErreurs list is then filled in
       // $iErreur = 1 if user not found (absent or wrong password)
       // $iErreur = 2 if the user exists but has no rights in the rights table
       // $iErreur = 3 if the user exists and is an administrator
       // $iErreur = 0 if the user exists and is not an administrator
       // $sAdmin="y" if the user exists and is an administrator ($iErreur==3), otherwise it is equal to the empty string
       // $dDroits is the user's rights dictionary if he/she is not an administrator ($iErreur==0)
       // otherwise it's an empty array
       // dictionary keys are tables to which the user has rights
       // the value associated with this table is in turn a dictionary where the keys are the rights
       // (consult, add, modify, delete) and the values are the strings 'y' (yes) or 'n' (no) depending on the case
...
    }//existeUser

    // --------------------------------------
    function getCodes(){
         // makes the code table
....
    }//getCodes    

  }//class
?>      

Comments

  • The articles class uses the PEAR::DB library for its database access, hence the command
require_once 'DB.php';

This inclusion assumes that the script DB.php is located in one of the directories listed in the option or include_path configuration file of PHP.

  • The constructor needs to know which database to connect to and under which identity. This information is provided in the $dDSN dictionary. Recall that the initial assumption was that the database was named dbarticles and belonged to a user named admarticles with the password mdparticles. Also recall that this application allows multiple users with different permissions. There is an ambiguity here that needs to be resolved. The connection is indeed established under the identity of admarticles, and ultimately all operations on the dbarticles database will be performed under this identity, since it is the only name known to SGBD and MySQL that has sufficient rights to manage the dbarticles database. To "simulate" the existence of different users, we will have the admarticles user operate with the permissions of the user whose login ($sUser) and password ($sMdp) are passed as parameters to the constructor. Thus, before performing an operation on the article database, we will verify that the user ($sUser, $sMdp) has the necessary permissions to do so. If so, the user admarticles will perform the operation on their behalf.
  • The login and password for the article database administrator must be passed to the constructor. This is a sound precaution. If these two pieces of information were hard-coded into the class, any user of the class could easily impersonate the article database administrator. Indeed, a class named PHP is not protected. Also, the $bAdmin attribute of the class, which indicates whether the user ($sUser, $sMdp) for whom you are working is an administrator or not, could very well be set directly from the outside, as in the following example:
$oArticles=new articles($dDSN,$sUser,$sMdp)
// here $sUser has been recognized as a non-administrator user of the database
$oArticle->bAdmin=TRUE;
// now $sUser has become administrator

PHP is not JAVA or C# and a class PHP is merely a data structure slightly more advanced than a dictionary but does not offer the security of a true class where the attribute bAdmin would have been declared private or protected, making it impossible to modify from the outside. Because the user of the class must know the login and password of the product database administrator, only the administrator can use the class. The previous operation is therefore no longer of any interest to the user. The class is there solely to provide development conveniences. An important consequence is that there is no need to take security precautions. Once again, anyone using the article class is necessarily an administrator of the article database.

  • The class handles database connection errors or any other errors in a unique way by populating the attribute $aErreurs with the error message(s). After each operation, the class user must therefore check this list.
  • The methods addArticle, updateArticle, deleteArticle, and selectArticles are directly derived from the web interface mockup presented earlier. They correspond to the options in the menu shown. The methods addArticle and modifyArticle rely on the method vérifierArticle to verify that the article to be added or modified contains correct data. In the same vein, the existeArticle method ensures that you are not about to add an item that already exists. This method could be omitted if using an article table where the code is the primary key. In that case, SGBD itself will signal the failure of the addition due to a duplicate. It will likely do so with an error message that is difficult to read and in English.
  • An item to be modified or deleted will be identified by its unique code. The getCodes method retrieves all these codes.
  • The disconnect method closes the connection to the database, which was opened when the object was created. The connect method, which re-establishes a connection to the database, does not seem useful here. This allows the connection to be opened and closed at will using the same object. Its usefulness becomes apparent only in conjunction with the web application. The application will create an articles object and store it in a session. While the session can retain most of the object’s attributes across successive client-server exchanges, it cannot retain the attribute representing the open connection. Therefore, the connection must be reopened with each new client-server exchange. We will request a persistent connection so that the open connection is stored in a connection pool and remains open permanently. Thus, when the script requests a new connection, it will be retrieved from the connection pool. We therefore achieve the same result as if the session had been able to store the open connection.
  • The existeUser method allows the constructor to determine whether the user $sUser, identified by the password $sMdp, actually exists. If so, the method determines whether the user is an administrator (as indicated in the USERS table) and stores this information in the $bAdmin attribute. If they are not an administrator, the method retrieves their permissions from the DROITS table and stores them in the $dDroits attribute, which is a double-indexed dictionary: $dDroits[$table][$droit] is 'y' if the user $sUser has permission $droit on the table $table, and is 'n' otherwise.

Write the articles class. Database access will be performed using the PEAR::DB library, which allows you to work independently of the exact database type.

7.5. The structure of the WEB application

Now that we have the "business" class for managing the product database, we can use it in various environments. Here, we propose using it in a web application. Let’s explore it through these different pages:

7.5.1. The application’s main page

Let’s return to the home page we’ve already seen:

1234

Image

All pages in the application will have the structure shown above: a table with two rows and three columns comprising four sections:

  • Zone 1 forms the first row of the table. It is reserved for the title, possibly accompanied by an image. The three columns of the row are merged here.
  • The second row has three areas, one per column:
    • Zone 2 contains the menu options. It, in turn, contains a table with one column and several rows. The menu options are placed in the rows of the table.
    • Zone 3 is empty and serves only to separate zones 2 and 4. We could have used a different approach to achieve this separation.
    • Zone 4 is the one that contains the dynamic part of the page. This is the part that changes from one action to another, while the others remain the same.

The PHP script that generates this template page will be named main.php and could look like this:


<html>
  <head>
      <title>Gestion d'articles</title>
      <link type="text/css" href="<?php echo $dConfig['urlPageStyle'] ?>" rel="stylesheet" />
   </head>
  <body background="<?php echo $dConfig['urlBackGround'] ?>">
    <table>
      <tr height="60">
        <td colspan="3" align="left" valign="top" >
          <h1><?php echo $main["title"] ?></h1>
        </td>
      </tr>
      <tr>
        <td>
          <table>
            <tr>
              <td class="menutitle" >
                                    <a href="<?php echo  $main["liens"]["login"] ?>" ?>Authentification</a>
              </td>
            </tr>
            <tr>
                <td><br /></td>
            </tr>
            <tr>
              <td class="menutitle" >
                Utilisation
              </td>
            </tr>
            <tr height="10"></tr>
            <tr>
              <td class="menublock" >
                <img alt="-" src="../images/radio.gif" />
                <a href="<?php echo  $main["liens"]["addArticle"] ?>" ?>
                  Ajouter un article
                </a>
                 </td>
            </tr>
            <tr>
              <td class="menublock" >                    
                <img alt="-" src="../images/radio.gif" />
                <a href="<?php echo $main["liens"]["updateArticle"] ?>">
                  Modifier un article
                </a>
                    </td>
            </tr>
              <td class="menublock" >                
                <img alt="-" src="../images/radio.gif" />
                <a href="<?php echo $main["liens"]["deleteArticle"] ?>">
                  Supprimer un article
                </a>
              </td>
            </tr>
            <tr>
              <td class="menublock" >
                <img alt="-" src="../images/radio.gif" />
                <a href="<?php echo $main["liens"]["selectArticle"] ?>">
                  Lister des articles
                </a>
              </td>
            </tr>
            <tr>
                <td><br /></td>
            </tr>                
            <tr>
              <td class="menutitle" >
                Administration
              </td>
            </tr>
            <tr height="10"></tr>
            <tr>
              <td class="menublock" >                
                <img alt="-" src="../images/radio.gif" />
                <a href="<?php echo $main["liens"]["sql"] ?>" >
                  Requête SQL
                </a>
              </td>
            </tr>
          </table>
        </td>
            <td>  
            <img alt="/" src="../images/pix.gif" width="10" height="1" />
            </td>
            <td>
            <fieldset>
              <legend><?php echo $main["légende"] ?></legend>
            <?php
                include $main["contenu"];
            ?>
          </fieldset>
        </td>
      </tr>
    </table>
  </body>
</html>

The configured sections of the page have been highlighted in the listing above. The template page is configured in several ways:

  • by a dictionary $main with the following keys:
    • title: title to be placed in zone 1 of the page
    • links: dictionaries of links to be generated in the menu column. These links are associated with the menu options in zone 2
    • content: url of the page to display in zone 4
  • via a dictionary $dConfig that collects information from an application configuration file named config.php
  • by classes that are part of the stylesheet used by the page:
      <link type="text/css" href="<?php echo $dConfig['urlPageStyle'] ?>" rel="stylesheet" />

The page uses the following style classes here:

  • menutitle: for a main menu item
  • menublock: for a secondary menu item

Changing any of these parameters changes the page's appearance. For example, changing $main['title'] will change the title of section 1.

7.5.2. Standard processing of a customer request

The customer interacts with the application using the links in area 2 of the standard page. These links will be of the following type:

apparticles.php?action=xx&phase=y&PHPSESSID=zzzzzzzzzzzz
action
refers to the current action from among the following:
authentifier
client authentication
selectArticles
select items (view)
updateArticle
edit an item
deleteArticle
delete an article
sql
Execute any SQL query (administrator)
phase
an action can be performed in several steps - refers to the current step
PHPSESSID
session token when the session has started - allows the server to retrieve information stored in the session during previous exchanges

Similarly, the action attribute in forms will have the same format. For example, on the home page there is a login form in section 4. The HTML tag for this form is defined as follows:

<form name="frmLogin" method="post" action="apparticles.php?action=authentifier&phase=1">

The client’s request is processed by the application’s main script, named apparticles.php. Its job is to construct the response to the client. It will always proceed in the same way:

  • using the action name and the current phase, it will delegate the request to a specialized function. This function will process the request and generate the appropriate response page. For each client request, there may be several possible response pages: page1, page2, ..., pagen. These pages contain information that must be calculated by the function. These are therefore parameterized pages. They will be generated by the scripts page1.php, page2.php, ..., pagen.php.
  • For consistency, the variable parts of the pages to be displayed in area 4 of the template page will also be placed in the dictionary $main.

Suppose that, in response to a request, the server must send the page pagex.php to the client. It will proceed as follows:

  • it will place the values required for the page pagex.php into the dictionary $main
  • it will place in $main['contenu']—which designates the URL of the page to be displayed in zone 4 of the template page—the URL of pagex.php
  • it will request the display of the template page with the instruction
include "main.php";

The template page will then be displayed with the code from the pagex.php script in zone 4, which will be evaluated to generate the content of zone 4. Recall that this is a simple cell in a table. Therefore, the HTML code generated by pagex.php must not begin with the tags <HTML>, <HEAD>, <BODY>, .... These have already been issued at the beginning of the template page. Here is an example of what the login.php script that generates section 4 of the home page might look like:


<form name="frmLogin" method="post" action="<?php echo $main["post"] ?>">
    <table>
        <tr>
            <td>login</td>
            <td><input type="text" value="<?php echo $main["login"] ?>" name="txtLogin" class="text"></td>
        </tr>
        <tr>
            <td>mot de passe</td>
            <td><input type="password" value="" name="txtMdp" class="text"></td>
      <td><input type="submit" value="Connexion" class="submit"></td>      
        </tr>
    </table>
</form>

We can see that the page:

  • is reduced to a form
  • is configured by both the $main dictionary and the stylesheet.

7.5.3. The configuration file

It is always best to configure applications as much as possible to avoid having to modify the code simply because, for example, you decided to change the path of a script or an image. The main application, apparticles.php, will therefore load a configuration file named config.php upon startup:

     // loading the configuration file
  include "config.php";

In this file, we will include configuration directives for PHP and initializations of global variables:

<?php

     // configuration of php
  ini_set("register_globals","off");
  ini_set("display_errors","off");
  ini_set("expose_php","off");
    ini_set("session.use_cookies","0");    // no cookies

     // item base configuration
    $dConfig["DSN"]=array(
        "sgbd"=>"mysql",
        "admin"=>"admarticles",
        "mdpadmin"=>"mdparticles",
        "host"=>"localhost",
        "database"=>"dbarticles"
    );

   // url pages
    $dConfig['urlBackGround']="../images/standard.jpg";  
  $dConfig["urlPageStyle"]="mystyle.css";  
  $dConfig["urlAppArticles"]="apparticles.php";
  $dConfig["urlPageMain"]="main.php";
  $dConfig["urlPageLogin"]="login.php";
  $dConfig["urlPageErreurs"]="erreurs.php";
  $dConfig["urlPageInfos"]="infos.php";
  $dConfig["urlPageAddArticle"]="addarticle.php";
  $dConfig["urlPageUpdateArticle1"]="updatearticle1.php";
  $dConfig["urlPageUpdateArticle2"]="updatearticle2.php";
  $dConfig["urlPageDeleteArticle1"]="deletearticle1.php";
  $dConfig["urlPageDeleteArticle2"]="deletearticle2.php";
  $dConfig["urlPageSelectArticle1"]="selectarticle1.php";
  $dConfig["urlPageSelectArticle2"]="selectarticle2.php";
  $dConfig["urlPageSQL1"]="sql1.php";
  $dConfig["urlPageSQL2"]="sql2.php";
  $dConfig["urlPageSQL3"]="sql3.php";

   // main page links
  $main["liens"]["login"]="$sUrlAppArticles?action=authentifier&phase=0";  
  $main["liens"]["addArticle"]="$sUrlAppArticles?action=addArticle&phase=0";
  $main["liens"]["updateArticle"]="$sUrlAppArticles?action=updateArticle&phase=0";
  $main["liens"]["deleteArticle"]="$sUrlAppArticles?action=deleteArticle&phase=0";
  $main["liens"]["selectArticle"]="$sUrlAppArticles?action=selectArticle&phase=0";
  $main["liens"]["sql"]="$sUrlAppArticles?action=sql&phase=0";

   // we store $main in the configuration
  $dConfig["main"]=$main;    
?>

7.5.4. The stylesheet associated with the template page

We saw that the server’s response had a unique format: main.php. You may have noticed that this script produces a raw page devoid of presentation effects. This is a good thing for several reasons:

  • The developer does not have to worry about the visual presentation of the page they are creating. After all, they may not necessarily have the skills to create visually appealing pages. This allows them to focus entirely on the code.
  • Script maintenance is simplified. If scripts included presentation attributes, neither the code structure nor the presentation structure would be clearly visible. The visual design of the pages is often delegated to a graphic designer. The designer would likely not want to have to search through a script they do not understand to find the presentation attributes they need to modify.

However, the visual appearance of the pages must be carefully considered. After all, this is what attracts users to a website. Here, the presentation is delegated to a stylesheet. The page main.php specifies in its code the stylesheet to use for displaying it:

  <head>
      <title>Gestion d'articles</title>
      <link type="text/css" href="<?php echo $dConfig['urlPageStyle'] ?>" rel="stylesheet" />
   </head>

The stylesheet used in this document is as follows:

BODY {
    background : url(../images/standard.jpg);
    border : 2px none #FFDAB9;
    font-family : Garamond;
    font-size : 16px;
    margin-left : 0px;
    padding-left : 20px;
}

INPUT {
    background : #EEE8AA;
    border : 1px solid #EE82EE;
    font-family : Garamond;
    font-size : 18px;
}

INPUT.submit{
    font-family : "Times New Roman";
    font-size : 16px;
    background : #FA8072;
    border : 2px double Green;
    font-weight : bold;
    text-align : center;
    vertical-align : middle;
    cursor : pointer;
}

TD.menutitle{
    background-image : url(../images/menugelgd.gif);
    height : 23px;
    text-align : center;
    vertical-align : middle;
    background : url(../images/menugelgd.gif) no-repeat center;
}

TD.menublock{
    background : url(../images/bandegrismenugd.gif) repeat-x;
    text-align : left;
    vertical-align : middle;
}

A {
    font-family : "Comic Sans MS";
    color : #FF7F50;
    font-size : 15px;
    text-decoration : none;
}

A:HOVER {
    background : #FFA07A;
    color : Red;
}

FIELDSET {
    border : 1px solid #A0522D;
    background : #FFE4C4;
    margin : 10px 10px 10px 10px;
    padding-left : 10px;
    padding-right : 10px;
    padding-bottom : 10px;
}

LEGEND{
    background : #FFA500;
}

TH {
    background : #228B22;
    text-align : center;
    vertical-align : middle;
}

TD.libellé{
    border : 1px solid #008B8B;
    color : #339966;
}

H1 {
    font : bold 20px/30px Garamond;
    color : #FF7F50;
    background : #D1E1F8;
    background-attachment : fixed;
    text-align : center;
    vertical-align : middle;
    font-family : Garamond;
}

SELECT.TEXT {
    background : #6495ED;
    text-align : center;
    color : Aqua;
}

We won’t go into the details of this stylesheet. We’ll accept it as is. A little later, we’ll see how to build and modify it. There is software available for that. Nevertheless, let’s outline the role of the presentation attributes used in the sheet:

Attribute:
controls the presentation of the HTML tag:
BODY
<BODY>
H1
<H1> (Header1)
A
<A> (Anchor)
A:HOVER
sets the anchor's presentation attributes when the user hovers over it
FIELDSET
<FIELDSET> - this tag is not recognized by all browsers
LEGEND
<LEGEND> - this tag is not recognized by all browsers
INPUT
<INPUT>
INPUT.TEXT
<INPUT class="TEXT">
INPUT.SUBMIT
<INPUT class="SUBMIT">
TH
<TH> (Table Header)
TD.menutitle
<TD class="menutitle"> (Table Data)
TD.menublock
<TD class="menublock">
TD.libellé
<TD class="label">

Let's look at an example of how these formatting rules can be written. In this example, we will use the TopStyle Lite software, available for free at URL http://www.bradsoft.com. Once the style sheet is loaded, a window with three sections appears:

  1. a text editing area. Presentation attributes can be defined manually provided you know the rules for writing style sheets, which follow a standard called CSS (Cascading Style Sheets).
  2. Area 2 displays the editable properties of the attribute currently being created. This is the simplest method. It eliminates the need to know the exact names of the numerous presentation attributes
  3. Area 3 displays the visual appearance of the attribute currently being created

In area 1 above, copy and paste the INPUT.submit attribute into a INPUT.fantaisie attribute. This attribute will set the presentation of the HTML tag <INPUT class="fantasy">

Let’s use Zone 2 to modify some of the properties of the INPUT.fantaisie attribute:

From now on, any <INPUT ... class="fantaisie"> tag found on a page associated with the previous stylesheet will be displayed as shown in the example in section 3 above.

Style sheets are very useful. Using them allows you to change the "look" of a web application by modifying only one thing: its style sheet. Style sheets are not supported by older browsers. The <link ..> directive below will be ignored by some of them:

  <head>
      <title>Gestion d'articles</title>
      <link type="text/css" href="<?php echo $dConfig['urlPageStyle'] ?>" rel="stylesheet" />
   </head>

In our application, this will result in the following home page:

Image

This is a minimal page with no graphical elements. It could be worse. Some browser versions recognize style sheets but interpret them incorrectly. This can result in a distorted and unusable page. This raises the question of the client browser type. There are techniques that help determine the client browser type. They are not entirely reliable. We could then write different style sheets for different browsers or even write a version without a style sheet for browsers that ignore them. This, of course, complicates the development process. This significant issue has been ignored here.

With style sheets, we can provide a personalized experience for our app’s users. We could present them with a page offering several possible layout styles. They could choose the one that suits them best. This choice could be saved in a database. When the user logs in again, we could then launch the app with the style sheet they selected.

7.5.5. The application’s input module

The clients will only be aware of the application’s input module: apparticles.php. The general outline of its operation is as follows:

  • The client’s request is retrieved and analyzed. It may or may not be configured. When it is parameterized, the expected parameters are as follows: action=[action]&phase=[phase]&PHPSESSID=[PHPSESSID]
  • If the request is not configured or if the retrieved parameters do not match the expected ones, the server responds by sending the authentication page (username, password). Once the user has successfully authenticated, a session is created. This session is used to store information throughout the client-server exchanges.
  • If a request is correctly recognized, it is processed by a module that depends on both the action and the current phase.
  • All database access is performed through the articles.php business class.
  • The processing of a request always ends with the main.php, in which the URL of the page to be placed in zone 4 of the template page has been specified in $main['contenu'].

The skeleton of the script apparticles.php could be as follows:

<?php
     // item table management
  include "config.php";
  include "articles.php";  

  // action to be taken
  $sAction=$_POST["action"] ? $_POST["action"] : $_GET["action"] ? $_GET["action"] : "authentifier";
  $sAction=strtolower($sAction);
   // possible phase
  $sPhase=$_POST["phase"] ? $_POST["phase"] : $_GET["phase"] ? $_GET["phase"] : "0";

  // session
  session_start();
  $dSession=$_SESSION["session"];

     // is there a session in progress?
  if(! isset($dSession)){
      // user authentication
    if($sAction=="authentifier" && $sPhase=="0") authentifier_0($dConfig);
    if($sAction=="authentifier" && $sPhase=="1") authentifier_1($dConfig);
    if($sAction=="authentifier" && $sPhase=="2") authentifier_2($dConfig);
     // incorrect request
    authentifier_0($dConfig);        
  }//if - no session

     // retrieve the session
  $dSession=unserialize($dSession);

     // processing the request
     // ----- authentication
  if($sAction=="authentifier" && $sPhase=="0") authentifier_0($dConfig);
  if($sAction=="authentifier" && $sPhase=="1") authentifier_1($dConfig);
  if($sAction=="authentifier" && $sPhase=="2") authentifier_2($dConfig);  
     // ----- add article
  if($sAction=="addarticle" && $sPhase=="0") addArticle_0($dConfig,$dSession);
  if($sAction=="addarticle" && $sPhase=="1") addArticle_1($dConfig,$dSession);
  if($sAction=="addarticle" && $sPhase=="2") addArticle_2($dConfig,$dSession);
     // ----- article update
  if($sAction=="updatearticle" && $sPhase=="0") updateArticle_0($dConfig,$dSession);
  if($sAction=="updatearticle" && $sPhase=="1") updateArticle_1($dConfig,$dSession);
  if($sAction=="updatearticle" && $sPhase=="2") updateArticle_2($dConfig,$dSession);
  if($sAction=="updatearticle" && $sPhase=="3") updateArticle_3($dConfig,$dSession);
     // ----- article deletion
  if($sAction=="deletearticle" && $sPhase=="0") deleteArticle_0($dConfig,$dSession);
  if($sAction=="deletearticle" && $sPhase=="1") deleteArticle_1($dConfig,$dSession);
  if($sAction=="deletearticle" && $sPhase=="2") deleteArticle_2($dConfig,$dSession);
    // ----- article consultation
  if($sAction=="selectarticle" && $sPhase=="0") selectArticle_0($dConfig,$dSession);
  if($sAction=="selectarticle" && $sPhase=="1") selectArticle_1($dConfig,$dSession);
  if($sAction=="selectarticle" && $sPhase=="2") selectArticle_2($dConfig,$dSession);
     // ----- send request SQL
  if($sAction=="sql" && $sPhase=="0") sql_0($dConfig,$dSession);
  if($sAction=="sql" && $sPhase=="1") sql_1($dConfig,$dSession);
  if($sAction=="sql" && $sPhase=="2") sql_2($dConfig,$dSession);


    // erroneous action - authentication page is presented
  session_destroy();
  authentifier_0($dConfig,"0");
...
?>

Note the following points:

  • Functions handling a specific client request end by generating the response page and with an exit statement that terminates the execution of the script apparticles.php. In other words, you do not "return" from these functions.
  • The functions accept one or two parameters:
    • $dConfig is a dictionary containing information from the configuration file config.php. All functions use it.
    • $dSession is a dictionary containing session information. It exists only when the session has been created, i.e., after the user has successfully authenticated. This is why the authentication functions do not have this parameter.

7.5.6. The error page

Every software application must be able to properly handle errors that may occur. A web application is no exception to this rule. Here, when an error occurs, we will place the following error page (php) in zone 4 of the template page:

Les erreurs suivantes se sont produites :
<ul>
    <?php
        for($i=0;$i<count($main["erreurs"]);$i++){
            echo "<li>".$main["erreurs"][$i]."</li>\n";
        }//for
    ?>
</ul>
<a href="<?php echo $main["href"] ?>"><?php echo $main["lien"] ?></a>

It displays the list of errors defined in $main['erreurs']. Additionally, it may provide a back link, typically to the page that preceded the error page. This link will be defined by a label $main['lien'] and a URL $main['href']. To remove this link, simply enter an empty string in $main['lien']. Here is an example of an error page in the event that the user logs in incorrectly:

Image

7.5.7. The information page

Sometimes you may want to provide the user with simple information, such as a successful login. To do this, use the following infos.php page:

<?php echo $main["infos"] ?>

To display information in response to a client request,

  • place the information in $main['infos']
  • place the URL from infos.php into $main['contenu']

Here is an example of the information returned when the user has logged in successfully:

Image

7.6. How the application works

We now have a good idea of the general structure of the application to be written. We still need to outline the user’s flow through the application, the actions they can perform, and the responses they receive from the server. Once this is done, we can write the functions that handle the various requests from a client. In the following, we will describe how the application works through the pages presented to the user in response to certain actions. We will specify the following points each time:

action utilisateur
the user’s initial action that led to the displayed response
paramètres envoyés
the parameters sent by the client browser to the server in response to the user’s manual action
page réponse
the script that generates section 4 of the template page

7.6.1. Authentication

Before being able to use the application, the user must log in using the following page:

Image

action utilisateur
1 - Initial request for URL from articles.php
2 - Use of the option Authentication menu
3 - Direct request for the URL and php files with incorrect parameters
paramètres envoyés
1 - no parameters
2 - action=authenticate?phase=0
3 - a list of incorrect parameters
page réponse
login.php

On the home page, the link [Ajouter un article] has the following format: action=addarticle?phase=0. The other links have the same format with action=(authenticate, updatearticle, deletearticle, selectarticle, sql). The user fills out the form and clicks the [Connexion] button:

Image

The response is as follows:

Image

action utilisateur
[Connexion] button
paramètres envoyés
action=authenticate?phase=1
page réponse
infos.php

The page title has been modified to indicate the user's login and their administrator/user rights. Additionally, all links in area 2 have been modified to reflect the fact that a session has started. The parameter PHPSESSID=[PHPSESSID] has been added to them.

If the server was unable to identify the client, the client will receive a different response:

Image

action utilisateur
button [Connexion]
paramètres envoyés
action=authenticate?phase=1
page réponse
errors.php

The link [Retour à la page de login] is a link to URL articles.php?action=authenticate&phase=2&txtLogin=x. This link takes the client back to the login page, where the login field is populated with the value of the txtLogin parameter:

Image

action utilisateur
link [Retour à la page de login]
paramètres envoyés
action=authenticate?phase=2&txtLogin=x
page réponse
login.php

7.6.2. Add an article

The menu link [Ajouter un article] brings the following page to zone 4 of the template page:

Image

action utilisateur
link [Ajouter un article]
paramètres envoyés
action=addArticle?phase=0&PHPSESSID=[PHPSESSID]
page réponse
addarticle.php

The user fills in the fields and submits everything to the server using the [Ajouter] button, which is of type submit. No validation is performed on the client side. The server handles this. It may return an error page in response, as shown in the example below:

Request
Response
action utilisateur
[Ajouter] button
paramètres envoyés
action=addArticle?phase=1&PHPSESSID=[PHPSESSID]
page réponse
errors.php

The link [Retour à la page d'ajout d'article] allows you to return to the entry page:

Request
Response
action utilisateur
link [Retour à la page d'ajout d'article]
paramètres envoyés
action=addArticle?phase=2&PHPSESSID=[PHPSESSID]
page réponse
article.php

If the addition is successful, the user receives a confirmation message:

Request
Response
action utilisateur
button [Ajouter]
paramètres envoyés
action=addArticle?phase=1&PHPSESSID=[PHPSESSID]
page réponse
infos.php

7.6.3. Viewing articles

The menu link [Lister des articles] brings the following page into zone 4 of the template page:

Image

action utilisateur
menu link [Lister des articles]
paramètres envoyés
action=selectArticle?phase=0&PHPSESSID=[PHPSESSID]
page réponse
select1.php

A SELECT query like **[colonnes] FROM articles WHERE [where] ORDER BY [orderby]** will be executed on the articles table, where [colonnes], [where], and [orderby] are the contents of the fields above. For example:

Request
Response
action utilisateur
button [Afficher]
paramètres envoyés
action=selectArticle?phase=1&PHPSESSID=[PHPSESSID]
page réponse
select2.php

The request may be invalid, in which case the client receives an error page:

Request
Response

In both cases (whether there are errors or not), the link [Retour à la page de sélection d'articles] allows you to return to the page select1.php:

Request
Response
action utilisateur
link [Retour à la page de sélection d'articles]
paramètres envoyés
action=selectArticle?phase=2&PHPSESSID=[PHPSESSID]
page réponse
select1.php

7.6.4. Editing articles

The menu link [Modifier un article] brings the following page into zone 4 of the template page:

Image

action utilisateur
menu link [Modifier un article]
paramètres envoyés
action=updateArticle?phase=0&PHPSESSID=[PHPSESSID]
page réponse
updatearticle1.php

Select the article code to be modified from the drop-down list and enter [OK] to modify the article with that code:

Request
Response
action utilisateur
[OK] button
paramètres envoyés
action=updateArticle?phase=1&PHPSESSID=[PHPSESSID]
page réponse
updatearticle2.php

Once the product page to be edited is displayed, the user can make changes:

Request
Response
action utilisateur
button [Modifier]
paramètres envoyés
action=updateArticle?phase=2&PHPSESSID=[PHPSESSID]
page réponse
infos.php

The user may make errors during editing:

Request
Response

The link [Retour à la page de modification d'article] allows you to return to the entry page:

Image

action utilisateur
link [Retour à la page de modification d'article]
paramètres envoyés
action=updateArticle?phase=3&PHPSESSID=[PHPSESSID]
page réponse
updatearticle2.php

7.6.5. Deleting an article

The menu link [Supprimer un article] brings the following page to zone 4 of the template page:

Image

action utilisateur
menu link [Supprimer un article]
paramètres envoyés
action=deleteArticle?phase=0&PHPSESSID=[PHPSESSID]
page réponse
deletearticle1.php

The user selects the code of the item to be deleted from a drop-down list:

Request
Response
action utilisateur
button [OK]
paramètres envoyés
action=deleteArticle?phase=1&PHPSESSID=[PHPSESSID]
page réponse
deletearticle2.php

The user confirms the deletion of the article with the button [Supprimer]:

Request
Response
action utilisateur
Button [Supprimer]
paramètres envoyés
action=deleteArticle?phase=2&PHPSESSID=[PHPSESSID]
page réponse
infos.php

7.6.6. Administrator request submissions

The menu link [Requête SQL] brings the following page to zone 4 of the template page:

Image

action utilisateur
menu link [Requête SQL]
paramètres envoyés
action=sql?phase=0&PHPSESSID=[PHPSESSID]
page réponse
sql1.php

Enter the text of the query SQL in the input field and use the button [Exécuter] to execute it. Only an administrator can issue these queries, as shown in the following example:

Request
Response
action utilisateur
[Exécuter] button
paramètres envoyés
action=sql?phase=1&PHPSESSID=[PHPSESSID]
page réponse
errors.php

The link [Retour à la page d'émission de requêtes SQL] takes you back to the input page:

Image

action utilisateur
link [Retour à la page d'émission de requêtes SQL]
paramètres envoyés
action=sql?phase=2&PHPSESSID=[PHPSESSID]
page réponse
sql1.php

If you are an administrator and the request is syntactically correct:

Request

you get the result of the query:

Response
action utilisateur
button [Exécuter]
paramètres envoyés
action=sql?phase=1&PHPSESSID=[PHPSESSID]
page réponse
sql2.php

You can send requests to update tables:

Request
Response
action utilisateur
button [Exécuter]
paramètres envoyés
action=sql?phase=1&PHPSESSID=[PHPSESSID]
page réponse
infos.php

7.6.7. Work to be done

Write the scripts and functions required for the application:

username
type
role
apparticles.php
script
the entry point for processing requests from clients
authentifier_0
function
processes the request with parameters action=authenticate&phase=0
authentifier_1
function
processes the request with parameters action=authenticate&phase=1
authentifier_2
function
processes the request with parameters action=authenticate&phase=2
addarticle_0
function
processes the request with parameters action=addArticle&phase=0
addarticle_1
function
processes the request with parameters action=addArticle&phase=1
addarticle_2
function
processes the request with parameters action=addArticle&phase=2
updatearticle_0
function
processes the request with parameters action=updatearticle&phase=0
updatearticle_1
function
processes the request with parameters action=updatearticle&phase=1
updatearticle_2
function
processes the request with parameters action=updatearticle&phase=2
updatearticle_3
function
processes the request with parameters action=updatearticle&phase=3
deletearticle_0
function
processes the request with parameters action=deletearticle&phase=0
deletearticle_1
function
processes the request with parameters action=deletearticle&phase=1
deletearticle_2
function
processes the request with parameters action=deletearticle&phase=2
selectarticle_0
function
processes the request with parameters action=selectarticle&phase=0
selectarticle_1
function
processes the request with parameters action=selectarticle&phase=1
selectarticle_2
function
processes the request with parameters action=selectarticle&phase=2
sql_0
function
processes the request with parameters action=sql&phase=0
sql_1
function
processes the request with parameters action=sql&phase=1
sql_2
function
processes the request with parameters action=sql&phase=2
main.php
script
generates the template page
login.php
script
generates the login page
erreurs.php
script
generates the error page
infos.php
script
generates the information page
addarticle.php
script
generates the page for adding an article
updatearticle1.php
script
generates page 1 for editing an article
updatearticle2.php
script
generates page 2 of the article edit
deletearticle1.php
script
generates page 1 of the article deletion
deletearticle2.php
script
generates page 2 of the article deletion
select1.php
script
generates page 1 of the article selection
select2.php
script
generates page 2 of the article selection
sql1.php
script
generates page 1 of the query output
sql2.php
script
generates page 2 of the query output

7.7. Enhancing the application

At this point, we have an application that does what it’s supposed to do with acceptable usability. We’re going to improve it in several areas:

  • SGBD
  • its security
  • its appearance
  • its performance

7.7.1. Changing the database type

Our study assumed that the SGBD used was MySQL. Change to SGBD and show that the only modification needed is in the definition of the variable $dDSN in the configuration file config.php.

7.7.2. Improving Security

When developing a web application, you should never assume that the client is a browser and that the request it sends us is controlled by the form we sent it prior to that request. Any program can act as a client for a web application and therefore send any request—whether parameterized or not—to the application. The application must therefore verify everything.

If we look at the code for the script **apparticles.php**, we see

  • that no action other than authentication can take place without a session. A session exists only if the user has successfully authenticated. Recall that a session is identified by a fairly long string of characters called the session token, which has the following form: 176a43609572907333118333edf6d1fb. This token can be sent to the application in various ways, for example by using a configured URL:

apparticles.php?PHPSESSID=176a43609572907333118333edf6d1fb. 

A program that repeatedly requests the previous URL by randomly varying the token in the hope of finding the correct one would likely take many days to generate the correct combination, given the sheer number of possible combinations. By that time, since the session has a limited duration, it will most likely have expired. Another risk is that the token, transmitted in plain text over the network, could be intercepted. This is a real risk. An encrypted connection between the server and its client can therefore be used.

  • Once the session is started, only certain actions are allowed. A token configured as action=cheat&phase=0&URL would be rejected because the action 'cheat' is not an allowed action. When the parameters (action, phase) are not recognized, our application responds with the authentication page.

However, the application does not check whether the authorized actions are performed in the correct order. For example, the following two actions:

  1. action=addArticle&phase=0&PHPSESSID=[PHPSESSID]
  2. action=updateArticle&phase=1&PHPSESSID=[PHPSESSID]

are two authorized actions. However, action 2 is not authorized to follow action 1.

How can we track the sequence of URL requests made by the client browser?

We can use two variables: $_SERVER['REQUEST_URI] and $_SERVER['HTTP_REFERER], which are two pieces of information sent by the browsers clients in their headers HTTP headers.

$_SERVER['REQUEST_URI]: This is the URI requested by the client. For example

/apparticles.php?action=addArticle&phase=0&PHPSESSID=[PHPSESSID]

$_SERVER['HTTP_REFERER]: This is the URL that was displayed in the browser before the new URL that the browser is currently requesting (the previous URI). For example, if the browser that displayed the previously mentioned URI makes a new request to a server, the server’s $_SERVER['HTTP_REFERER'] variable will have the value

http://machine:port//apparticles.php?action=addArticle&phase=0&PHPSESSID=[PHPSESSID]

To verify that two actions in our application follow one another in order, we can proceed as follows:

During action 1:

  • we note the requested URI (URI1) and store it in the session

During action 2:

  • retrieve the HTTP-REFERER from action 2. From this, derive the URI (URI2) from the URL that was previously displayed in the browser making the request.
  • We retrieve the URI URI1 that was stored in the session and which is the URI of the action previously requested from the server
  • If action 2 follows action 1, then URI2 must equal URI1. If this is not the case, the requested action is denied, and the authentication page is displayed.
  • We record the URI and URI2 from the current action in the session for verification of the next action. And so on.

Here is an example. After authentication, we select the link [Ajouter un article]:

Image

The URL for this page is:

http://localhost:81/st/php/articles/gestion/articles8/apparticles.php?action=addArticle&phase=0&PHPSESSID=006a63e6027f16c70b63cdae93405eeb

Directly in the browser's [Adresse] field, we modify the URL as follows:

http://localhost:81/st/php/articles/gestion/articles8/apparticles.php?action=deleteArticle&phase=1&PHPSESSID=006a63e6027f16c70b63cdae93405eeb

We then get the authentication page:

Image

This warrants an explanation. When requesting a URL by typing its ID directly into the browser’s address bar, the browser does not send the HTTP_REFERER header. Our application therefore cannot find the URI from the previous action, URI, which it had stored in the session. It then returns the authentication page in response.

This mechanism works for browsers but not at all for a programmed client. The client can send whatever HTTP_REFERER header it wants. It can therefore "cheat" by claiming it has gone through a certain step when it has not. It is therefore necessary to ensure that the sequence of steps is followed. Thus, if the requested action is action=addArticle&phase=1 (entry), then the previous action must necessarily be action=deleteArticle&phase=0 (initial request for the entry page) or action=addArticle&phase=2 (return to entry after an incorrect addition). Similarly, if the requested action is action=addArticle&phase=2 (add), then the previous action must be action=addArticle&phase=1 (entry). We can force the user to follow these sequences.

While the first mechanism is general and can be applied to any application, the second requires application-specific coding and is more complex: all possible user actions and their sequences must be reviewed. These can be stored in a dictionary as shown in the following code:

  // authentication
  $dPrec['authentifier']['0']=array();
  $dPrec['authentifier']['1']=array(
        array('action'=> 'authenticate','phase'=> '0'),
    array('action'=> 'authenticate','phase'=> '2')
  );
  $dPrec['authentifier']['2']=array(
        array('action'=> 'authenticate','phase'=> '1'),
  );

   // add article
  $dPrec['addarticle']['0']=array();  
  $dPrec['addarticle']['1']=array(
        array('action'=> 'addarticle','phase'=> '0'),
    array('action'=> 'addarticle','phase'=> '2')
  );
  $dPrec['addarticle']['2']=array(
        array('action'=> 'addarticle','phase'=> '1'),
  );

   // article modification
  $dPrec['updatearticle']['0']=array();  
  $dPrec['updatearticle']['1']=array(
        array('action'=> 'updatearticle','phase'=>'0'),
  );
  $dPrec['updatearticle']['2']=array(
        array('action'=> 'updatearticle','phase'=>'1'),
    array('action'=> 'updatearticle','phase'=> '3')
  );
  $dPrec['updatearticle']['3']=array(
        array('action'=> 'updatearticle','phase'=> '2'),
  );

   // article deletion
  $dPrec['deletearticle']['0']=array();  
  $dPrec['deletearticle']['1']=array(
        array('action'=> 'deletearticle','phase'=>'0'),
  );
  $dPrec['deletearticle']['2']=array(
        array('action'=> 'deletearticle','phase'=>'1'),
  );

      // article selection
  $dPrec['selectarticle']['0']=array();  
  $dPrec['selectarticle']['1']=array(
        array('action'=> 'selectarticle','phase'=>'0'),
    array('action'=> 'selectarticle','phase'=>'2')
  );
  $dPrec['selectarticle']['2']=array(
        array('action'=> 'selectarticle','phase'=>'1'),
  );

      // administrator request
  $dPrec['sql']['0']=array();  
  $dPrec['sql']['1']=array(
        array('action'=>'sql','phase'=>'0'),
    array('action'=> 'sql','phase'=> '2')
  );
  $dPrec['sql']['2']=array(
        array('action'=> 'sql','phase'=> '1'),
  );

$dPrec['action']['phase'] is an array containing the actions that can precede the action and the phase that serve as the dictionary's index. These preceding actions are also represented by a dictionary with two keys: 'action' and 'phase'. If an action can be preceded by any action, then $dPrec['action']['phase'] will be an empty array. The absence of an action in the dictionary means that it is not permitted. Consider the "authenticate" action above:

  // authentication
  $dPrec['authentifier']['0']=array();
  $dPrec['authentifier']['1']=array(
        array('action'=> 'authenticate','phase'=> '0'),
    array('action'=> 'authenticate','phase'=> '2')
  );
  $dPrec['authentifier']['2']=array(
        array('action'=> 'authenticate','phase'=> '1'),
  );

The code above means that the action action=authenticate&phase=0 can be preceded by any action, that action=authenticate&phase=1 can be preceded by either action=authenticate&phase=0 or action=authenticate&phase=2, and that action=authenticate&phase=2 can be preceded by the action action=authenticate&phase=1.

Write the following function:

  // ---------------------------------------------------------------
  function enchainementOK(&$dConfig, &$dSession, $sAction, $sPhase){
       // checks whether the current action ($sAction, $sPhase) can follow the previous action
         // stored in $dSession['previous']
         // the dictionary of authorized sequences is in $dConfig['précédents']
         // returns TRUE if chaining is possible, FALSE otherwise
....

This function allows the main application to verify that the sequence of actions is correct:

<?php
     // item table management
  include "config.php";
  include "articles.php";  

  // session
  session_start();
  $dSession=$_SESSION["session"];

   // action to be taken
  $sAction=$_POST["action"] ? $_POST["action"] : $_GET["action"] ? $_GET["action"] : "authentifier";
  $sAction=strtolower($sAction);
   // possible action phase
  $sPhase=$_POST["phase"] ? $_POST["phase"] : $_GET["phase"] ? $_GET["phase"] : "0";

     // is there a session in progress?  
  if(! isset($dSession)){
      // user authentication
    if($sAction=="authentifier" && $sPhase=="0") authentifier_0($dConfig);
    if($sAction=="authentifier" && $sPhase=="1") authentifier_1($dConfig);   
    if($sAction=="authentifier" && $sPhase=="2") authentifier_2($dConfig);    
     // abnormal action
    authentifier_0($dConfig);        
  }//if - no session

     // retrieve the session
  $dSession=unserialize($dSession);

     // is the sequence of actions normal?
  if( ! enchainementOK($dConfig,$dSession,$sAction,$sPhase)){
     // abnormal sequence
    authentifier_0($dConfig);        
  }//if

     // stock processing
  if($sAction=="authentifier"){
   if($sPhase=="0") authentifier_0($dConfig);  
   if($sPhase=="1") authentifier_1($dConfig);  
   if($sPhase=="2") authentifier_2($dConfig);  
  }//if     
  if($sAction=="addarticle"){
...

7.7.3. Updating the "look"

Remember that one of the requirements established during the design of this application was that it had to be scalable. Suppose that after a few weeks, we realize that the application’s usability needs to be improved. Modify the application so that the structure and layout of the template page are changed. The changes will be made in two places:

  • in the main.php script, which defines the structure of the template page. Update this script.
  • in the style sheet that determines the application’s “look.” Change this.

7.7.4. Improve performance

For now, we have opted for a thin-client browser: it does nothing but display content. We can make it perform processing by including scripts in the web pages we send to it. These scripts can be written in various languages, notably vbscript and javascript. Internet Explorer and Netscape dominate the browser market in a ratio of roughly 60/40. Furthermore, IE is only available on Windows and not on Unix, for example, where Netscape is the dominant browser. Netscape does not natively execute vbscript scripts, whereas both browsers execute javascript scripts. Since Netscape still holds a significant share of the browser market, vbscript scripts should be avoided. Therefore, javascript is generally used in client-side scripts.

Processing tasks that do not require server intervention are delegated to client-side scripts. In our application, it would be beneficial for the client browser to send a request to the server only after verifying it. Thus, there is no point in sending an authentication request to the server if the user has left the [login] field blank in the authentication form. It is preferable to notify the user that their request is incorrect:

Image

Note that this will not prevent the server from verifying that the login field is not empty, since its client is not necessarily a browser, and thus the previous verification may not have been performed. Assuming that the client is a browser poses a major security risk to the application.

Review the various points at which the browser sends information to the server, and where this information can be verified, write one or more javascript functions that will allow the browser to verify the validity of the information before sending it to the server.

To revisit the previous example, the login.php script that generates the authentication page becomes the following:


<script language="javascript">
    function check(){
       // check that there is a login
    with(document.frmLogin){
        champs=/^\s*$/.exec(txtLogin.value);
      if(champs!=null){
          // no login
        alert("Vous n'avez pas indiqué de login");
        txtLogin.focus();
        return;
      }//if
       // the data is there - we send it to the server
      submit();
    }//with
  }//check
</script>   
 
<form name="frmLogin" method="post" action="<?php echo $main["post"] ?>">
    <table>
        <tr>
            <td>login</td>
            <td><input type="text" value="<?php echo $main["login"] ?>" name="txtLogin" class="text"></td>
        </tr>
        <tr>
            <td>mot de passe</td>
            <td><input type="password" value="" name="txtMdp" class="text"></td>
      <td><input type="button" onclick="check()" value="Connexion" class="submit"></td>      
        </tr>
    </table>
</form>    

7.8. Further Reading

To conclude, here are a few ideas for expanding on this case study:

  • It would be interesting to see if the template page for this application could be turned into a class. This class could then be used in other applications.
  • Our application is well-suited for browser-type clients applications but less so for "Standalone Application" type clients applications. These must:
    • establish a tcp connection with the server
    • "communicate" with it
    • analyze its responses to find the desired information, since the standalone client will likely not be interested in the presentation code intended for browsers

It would be beneficial for our application to generate XML rather than HTML. Its clients could then be used interchangeably by browsers (recent ones, at least) or standalone applications. The latter would have no trouble finding the information they are looking for, since the server’s XML response would contain no presentation information, only content.

  • We would certainly need to consider simultaneous access to the article database. There are at least two points to clarify:
  1. Does the SGBD used by the application correctly handle simultaneous access to the same article? For example, what happens if two users edit the same article at the same time (they click the [Modifier] button simultaneously)? This likely depends on the underlying SGBD.
  2. Currently, our application does not support concurrent access. However, the database should remain in a consistent state, although unexpected behavior may occur. Consider the following sequence of events:
      • User U1 enters to edit an item
      • User U2 enters the delete screen for the same item shortly after
      • Each of these actions requires client-server communication. Depending on how each user works, User U2 may finish their work before User U1. When U1 finishes their edits and saves them using [Modifier], they will receive the information page in response, with SGBD indicating that [0 ligne(s) ont été modifiées], because the page they wanted to edit has been deleted in the meantime. The user will likely be surprised. From a usability standpoint, it would probably be better to display a page that more clearly indicates the error. Furthermore, one could consider granting the user exclusive access to an article as soon as they begin editing it. Another user attempting to edit the same article would be informed that another edit is in progress. This will pose a problem if the first user delays in saving their changes: others will be blocked. Solutions need to be found here, and these will depend largely on the capabilities of the SGBD being used. Oracle, for example, has more capabilities in this area than MySQL.