4 Building the AnyCo Application

This chapter contains the following topics:

4.1 A Cascading Style Sheet

To start creating the AnyCo application, create a cascading style sheet file style.css. It contains:

/* style.css */
 
body {
    background: #FFFFFF;
    color:      #000000;
    font-family: Arial, sans-serif;
}
 
table {
    border-collapse: collapse;
    margin: 5px;
}
 
tr:nth-child(even) {background-color: #FFFFFF}
tr:nth-child(odd) {background-color: #EDF3FE}
 
td, th {
    border: solid #000000 1px;
    text-align: left;
    padding: 5px;
}
 
#header {
    font-weight: bold;
    font-size: 160%;
    text-align: center;
    border-bottom: solid #334B66 4px;
    margin-bottom: 10px;
}
 
#menu {
    position: absolute;
    left: 5px;
    width: 180px;
    display: block;
    background-color: #dddddd;
}
#user {
    font-size: 90%;
    font-style:italic;
    padding: 3px;
}
 
#content {
    margin-left: 200px;
}

This gives a simple styling to the application, keeping a menu to the left hand side of the main content. Alternate rows of table output are colored differently. See Figure 1-1 in “Introducing PHP with Oracle Database”.

4.2 Creating an Application Class for Sessions

For the AnyCo application we will create two classes, Session and Page, to give some reusable components.

The Session class is where web user authentication will be added. It also provides the components for saving and retrieving web user "session" information on the mid-tier, allowing the application to be stateful. PHP sessions are not directly related to Oracle sessions, which were discussed in the DRCP overview. Data such as starting row number of the currently displayed page of query results can be stored in the PHP session. The next HTTP request can retrieve this value from the session storage and show the next page of results.

Create a new PHP file called ac_equip.inc.php initially containing:

<?php
 
/**
 * ac_equip.inc.php: PHP classes for the employee equipment example
 * @package Equipment
 */
namespace Equipment;
 
/**
 * URL of the company logo
 */
//define('LOGO_URL', 'http://localhost/ac_logo_img.php');
 
/**
 * @package Equipment
 * @subpackage Session
 */
class Session {
    /**
     *
     * @var string Web user's name
     */
    public $username = null;
    /**
     *
     * @var integer  current record number for paged employee results
     */
    public $empstartrow = 1;
    /**
     *
     * @var string CSRF token for HTML forms
     */
    public $csrftoken = null;
 
}
 
?>

The file starts with a namespace declaration, Equipment in this case.

The commented out LOGO_URL constant will be described later in “Uploading and Displaying BLOBs”.

The $username attribute will store the web user's name. The $empstartrow attribute stores the first row number of the currently displayed set of employees. This allows employee data to be "paged" through with Next and Previous buttons as shown in Figure 1-1. The $csrftoken value will be described in “Inserting Data”.

Add two authentication methods to the Session class:

    /**
     * Simple authentication of the web end-user
     *
     * @param string $username
     * @return boolean True if the user is allowed to use the application
     */
    public function authenticateUser($username) {
        switch ($username) {
            case 'admin':
            case 'simon':
                $this->username = $username;
                return(true);  // OK to login
            default:
                $this->username = null;
                return(false); // Not OK
        }
    }
 
    /**
     * Check if the current user is allowed to do administrator tasks
     *
     * @return boolean
     */
    public function isPrivilegedUser() {
        if ($this->username === 'admin')
            return(true);
        else
            return(false);
    }

The authenticateUser() method implements extremely unsophisticated and insecure user authentication. Typically PHP web applications do their own user authentication. Here only admin and simon will be allowed to use the application. For more information on authentication refer to

http://www.oracle.com/technetwork/articles/mclaughlin-phpid1-091467.html

The isPrivilegedUser() method returns a boolean value indicating if the current user is considered privileged. In the AnyCo application this will be used to determine if the user can see extra reports and can upload new data. Only the AnyCo "admin" will be allowed to do these privileged operations.

4.3 Providing a Stateful Web Experience with PHP Sessions

PHP can store session values that appear persistent as users move from HTML page to HTML page. By default the session data is stored in a file on the PHP server's disk. The session data is identified by a unique cookie value, or a value passed in the URL if the user has cookies turned off. The cookie allows PHP to associate its local session storage with the correct web user.

PHP sessions allow user HTTP page requests to be handled seamlessly by random mid-tier Apache processes while still allowing access to the current session data for each user. PHP allow extensive customization of session handling, including ways to perform session expiry and giving you ways to store the session data in a database. Refer to the PHP documentation for more information.

To store, fetch and clear the session values in the AnyCo application, add these three methods to the Session class:

    /**
     * Store the session data to provide a stateful web experience
     */
    public function setSession() {
        $_SESSION['username']    = $this->username;
        $_SESSION['empstartrow'] = (int)$this->empstartrow;
        $_SESSION['csrftoken']   = $this->csrftoken;
    }
 
    /**
     * Get the session data to provide a stateful web experience
     */
    public function getSession() {
        $this->username = isset($_SESSION['username']) ?
             $_SESSION['username'] : null;
        $this->empstartrow = isset($_SESSION['empstartrow']) ?
             (int)$_SESSION['empstartrow'] : 1;
        $this->csrftoken = isset($_SESSION['csrftoken']) ?
             $_SESSION['csrftoken'] : null;
    }
 
    /**
     * Logout the current user
     */
    public function clearSession() {
        $_SESSION = array();
        $this->username = null;
        $this->empstartrow = 1;
        $this->csrftoken = null;
    }

These reference the superglobal associative array $_SESSION that gives access to PHP's session data. When any of the Session attributes change, the AnyCo application will call setSession() to record the changed state. Later when another application request starts processing, its script will call the getSession() method to retrieve the saved attribute values. The ternary "?:" tests will use the session value if there is one, or else use a hardcoded default.

Finally, add the following method to the Session class to aid CSRF protection in HTML forms. This will be described in “Preventing CSRF with ac_add_one.php” in “Inserting Data”.

    /**
     * Records a token to check that any submitted form was generated
     * by the application.
     *
     * For real systems the CSRF token should be securely,
     * randomly generated so it cannot be guessed by a hacker
     * mt_rand() is not sufficient for production systems.
     */
    public function setCsrfToken() {
        $this->csrftoken = mt_rand();
        $this->setSession();
    }

4.4 Adding a Page Class

A Page class will provide methods to output blocks of HTML output so each web page of the application has the same appearance.

Add the new Page class to the ac_equip.inc.php file after the closing brace of the Session class, but before the PHP closing tag '?>'. The class initially looks like:

/**
 * @package Equipment
 * @subpackage Page
 */
class Page {
    /**
     * Print the top section of each HTML page
     * @param string $title The page title
     */
    public function printHeader($title) {
        $title = htmlspecialchars($title, ENT_NOQUOTES, 'UTF-8');
        echo <<<EOF
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
     "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
  <meta http-equiv="Content-Type"
        content="text/html; charset=utf-8">
  <link rel="stylesheet" type="text/css" href="style.css">
  <title>$title</title>
</head>
<body>
<div id="header">
EOF;
// Important: do not have white space on the 'EOF;' line before or after the tag
 
        if (defined('LOGO_URL')) {
            echo '<img src="' . LOGO_URL . '" alt="Company Icon">&nbsp;';
        }
        echo "$title</div>";
    }
    /**
     * Print the bottom of each HTML page
     */
    public function printFooter() {
        echo "</body></html>\n";
    }
 
}

The printHeader() method prints the HTML page prologue, includes the style sheet, and prints the page title.

A PHP 'heredoc' is used to print the big block of HTML content. The variable $title in the text will be expanded and its value displayed. The closing tag EOF; must be at the start of the line and also not have any trailing white space. Otherwise the PHP parser will treat the rest of the file as part of the string text and will produce a random parsing error when it encounters something that looks like a PHP variable.

A logo will also be displayed in the header when LOGO_URL is defined in a later example, remember it is currently commented out at the top of ac_equip.inc.php.

The printFooter() methods simply ends the HTML page body. A general application could augment this to display content that should be printed at the bottom of each page, such as site copyright information.

The AnyCo application has a left hand navigation menu. Add a method to the Page class to print this:

    /**
     * Print the navigation menu for each HTML page
     *
     * @param string $username The current web user
     * @param type $isprivilegeduser True if the web user is privileged
     */
    public function printMenu($username, $isprivilegeduser) {
        $username = htmlspecialchars($username, ENT_NOQUOTES, 'UTF-8');
        echo <<<EOF
<div id='menu'>
<div id='user'>Logged in as: $username </div>
<ul>
<li><a href='ac_emp_list.php'>Employee List</a></li>
EOF;
        if ($isprivilegeduser) {
            echo <<<EOF
<li><a href='ac_report.php'>Equipment Report</a></li>
<li><a href='ac_graph_page.php'>Equipment Graph</a></li>
<li><a href='ac_logo_upload.php'>Upload Logo</a></li>
EOF;
        }
        echo <<<EOF
<li><a href="index.php">Logout</a></li>
</ul>
</div>
EOF;
    }

The user name and privileged status of the user will be passed in to customize the menu for each user. These values will come from the Session class.

Later chapters in this manual will create the PHP files referenced in the links. Clicking those link without having the files created will give an expected error.

The three classes: Db, Session, and Page, used by the AnyCo application are now complete.

4.5 Creating the Application Login Page

The start page of the AnyCo application is the login page. Create a new PHP file called index.php. In NetBeans replace the existing contents of this file. The index.php file should contain:

<?php
 
/**
 * index.php: Start page for the AnyCo Equipment application
 *
 * @package Application
 */
 
session_start();
require('ac_equip.inc.php');
 
$sess = new \Equipment\Session;
$sess->clearSession();
 
if (!isset($_POST['username'])) {
    $page = new \Equipment\Page;
    $page->printHeader("Welcome to AnyCo Corp.");
    echo <<< EOF
<div id="content">
<h3>Select User</h3>
<form method="post" action="index.php">
<div>
<input type="radio" name="username" value="admin">Administrator<br>
<input type="radio" name="username" value="simon">Simon<br>
<input type="submit" value="Login">
</div>
</form>
</div>
EOF;
// Important: do not have white space on the 'EOF;' line before or after the tag
    $page->printFooter();
} else {
    if ($sess->authenticateUser($_POST['username'])) {
        $sess->setSession();
        header('Location: ac_emp_list.php');
    } else {
        header('Location: index.php');
    }
}
 
?>

The index.php file begins with a session_start() call. This must occur in code that wants to use the $_SESSION superglobal and should be called before any output is created.

An instance of the Session class is created and any existing session data is discarded by the $sess->clearSession() call. This allows the file to serve as a logout page. Any time index.php is loaded, the web user will be logged out of the application.

The bulk of the file is in two parts, one creating an HTML form and the other processing it. The execution path is determined by the PHP superglobal $_POST. The first time this file is run $_POST['username'] will not be set so the HTML form along with the page header and footer will be displayed. The form allows the web user login as Administrator or Simon.

The submission action target for the form is index.php itself. So after the user submits the form in their browser, this same PHP file is run. Since the submission method is "post", PHP will populate the superglobal $_POST with the form values. This time the second branch of the 'if' statement will be run.

The user is then authenticated. The radio button input values 'admin' and 'simon' are the values that will be passed to $sess->authenticateUser(). A valid user will be recorded in the session data. PHP then sends back an HTTP header causing a browser redirect to ac_emp_list.php. This file will be created in the next section.

If the user is not validated by $sess->authenticateUser() then the login form is redisplayed.

Note that scripts should not display text before a header() call is run.

To run the application as it stands, load index.php in a browser. In NetBeans, use Run->Run Project, or press F6. The browser will show:

AnyCo Corp login page