Computing: Website and Database Programming

Webserver directory listing.


2. Custom directory listing using PHP.
  2.1. What and how to list?
   
With all these Lazarus/Free Pascal applications, that I developed and actually am developing, I want to give the users the possibility to download them by directly by accessing the directories containing the (Windows 64bit) executables, the sources and the programs' previous versions, as well as the data files, not included within the application's download archive. So, the aim of the scripts is to display a personalized listing of the content of these directories (without being specific for them, of course). The listing should include a file-type related icon, the filename (with a link to download it), a description what's the file about, the filesize and the date, when it last has been modified.
  2.2. What and how to do?
   
As I said above, the aim of the scripts is to list the content of my download directories, but should also be usable for any other webserver directory. Thus, the first thing to do is to write some generic functions to retrieve the directory content and to display it (as a HTML table). A simple possibility to create listings, with subdirectories displayed first, is to make the retrieve function return 2 arrays, one with the subdirectories, the other with the files. The simplest way to include the file-type specific icons in the listing, is to create another PHP file, just containing the declaration of an array with the MIME-types (or, alternately, the file-types) as keys and the URL of the file-type-images as values.
The Lazarus/Free Pascal download directories specific part should be done in another script, that first calls the file retrieval function, then the function to display the listing, passing as arguments not only the directory content, but also the descriptions of the file content; as for the file-icons, these may be declared in an array (with the filename as keys and the description text as values) contained in a separate PHP file.
Finally, we need index.php files, placed in the directories to be listed using my personalized application (index.php will be executed when the browser accesses the directory without specifying any filename). In my case, these files are nothing else than the common HTML header, as it appears on all pages of my site and the inclusion of the Lazarus/Free Pascal specific PHP script, described above. This script has of course to know which directory it should list. The simplest way to let it know this information, is to set a directory specific variable in each of the index.php files.
  2.3. My directory listing PHP scripts.
   
You can view the code by opening the tabs below. Or click the following link to download all that you need to install this appliation. Please note, that I place all my PHP scripts in a directory called php, located directly beneath the server document root (The index.php files have to be placed into the directory to be listed, of course).
a. The directory content retrieval and display functions (filelist.php).
<?php

//
// Function: Get list of subdirectories and files in a given directory
//
// Arguments:
//     list-directory (as string)
//     skip scripts flag; if set .php, .pl, .pm and .cgi files are not included in the list
// Return:
//     subdirectories list (as associative array)
//     files list (as associative array)
// Hidden items (Unix .filename items) and log files will be ignored!!
// Lists will be sorted on subdirectory/file name; case-sensitive sort (uppercase before lowercase)

function getFileList($dir, $skipscripts) {
    // ----------------------------------------------------------------------------------------------------
    // Custom sort function for usort
    function sort_it($a, $b) {
        return ($a["name"] <= $b["name"]) ? -1 : 1;     // sort on subdirectory/file name
    }
    // ----------------------------------------------------------------------------------------------------
    $subdirlist = []; $filelist = [];
    if(substr($dir, -1) != "/") {     // add trailing slash if missing
        $dir .= "/";
    }
    // Open pointer to directory and read list of files
    $d = @dir($dir) or die("getFileList: Failed opening directory {$dir} for reading");
    while(FALSE !== ($entry = $d->read())) {
        if($entry{0} == ".") {     // skip hidden files
            continue;
        }
        // Subdirectories
        if(is_dir("{$dir}{$entry}")) {
            $subdirlist[] = [
                'path' => "{$dir}{$entry}/",
                'name' => "{$entry}",
                'type' => filetype("{$dir}{$entry}"),
                'size' => 0,
                'date' => filemtime("{$dir}{$entry}")
           ];
        }
        // Files
        elseif(is_readable("{$dir}{$entry}")) {
            if (!(stripos($entry, 'error') === FALSE && stripos($entry, 'log') === FALSE)) {     // ignore error and other log files (in particular PHP error log)
                continue;
            }
            if ($skipscripts) {     // if skip scripts flag is set, ignore script files
                $fileext = substr($entry, strrpos($entry, '.'));
                if ($fileext == '.php' || $fileext == '.pl' || $fileext == '.pm' || $fileext == '.cgi') {
                    continue;
                }
            }
            $filelist[] = [
                'path' => "{$dir}{$entry}",
                'name' => "{$entry}",
                'type' => mime_content_type("{$dir}{$entry}"),
                'size' => filesize("{$dir}{$entry}"),
                'date' => filemtime("{$dir}{$entry}")
            ];
        }
    }
    $d->close();
    // Sort subdirectories and files arrays
    usort($subdirlist, "sort_it");
    usort($filelist, "sort_it");
    return array($subdirlist, $filelist);
}

//
// Function: Display list of directory subdirectories/files as HTML table
//
// Arguments:
//     subdirectories (as associative array)
//     files (as associative array)
//     file descriptions (as associative array)
//     ignore filename suffix flag; if set, "_xxx..." at end of filename is removed for file description lookup
// Return:
//     0 if directory is empty, else 1

function displayFileList($subdirlist, $filelist, $filedescription, $suffixignore) {
    $phpdir = $_SERVER['DOCUMENT_ROOT'] . '/php/';
    include("{$phpdir}icons.php");     // file-type icon URLs
    if (count($subdirlist) == 0 && count($filelist) == 0) {     // empty directory
        $ret = 0;
    }
    // List directory as HTML table
    else {
        $ret = 1;
        echo "<table border=\"0\" class=\"small\">\n";
        // Display 'parent directory' link
        echo "<tr>\n";
        echo "<td><img src=\"{$icon['PARENT']}\"/></td>";
        echo "<td width=\"10px\"> </td>";
        echo "<td colspan=\"7\"><a href=\"../\">Parent directory</a></td>\n";
        echo "</tr>\n";
        echo "<tr><td colspan=\"9\"> </td></tr>";
        // Display subdirectories
        if (count($subdirlist) != 0) {
            foreach($subdirlist as $subdir) {
                $name = $subdir['name'];
                $url = str_replace($_SERVER['DOCUMENT_ROOT'], '', $name) . '/';     // directory URL (for link)
                $type = 'DIRECTORY';
                $description = ' ';
                $date = substr(date('r', $subdir['date']), 5, strlen($subdir['date']) - 25);
                $size = ' ';
                echo "<tr>\n";
                echo "<td><img src=\"{$icon[$type]}\"/></td>";     // directory icon
                echo "<td width=\"10px\"> </td>";
                echo "<td><a href=\"{$url}\">{$name}</a></td>\n";     // subdirectory name (and link)
                echo "<td width=\"20px\"> </td>";
                echo "<td>{$description}</td>\n";     // subdirectory description (blank)
                echo "<td width=\"20px\"> </td>";
                echo "<td align=\"right\">{$size}</td>\n";     // subdirectory size (actually blank)
                echo "<td width=\"20px\"> </td>";
                echo "<td>{$date}</td>\n";     // subdirectory modification date
                echo "</tr>\n";
            }
            echo "<tr><td> </td></tr>";     // blank line between subdirectories and files
        }
        // Display files
        if (count($filelist) != 0) {
            foreach($filelist as $file) {
                $name = $file['name'];
                $url = str_replace($_SERVER['DOCUMENT_ROOT'], '', $name);     // file URL (for link)
                $name = substr($name, 0, strrpos($name, '.'));     // remove file extension
                $lookupname = $name;     // filename used for file description lookup
                if ($suffixignore) {     // ignore filename's final _xxx... if flag tells so
                    if (strpos($lookupname, '_') != 0) {
                        $lookupname = substr($lookupname, 0, strrpos($name, '_'));
                    }
                }
                $type = $file['type'];
                $date = substr(date('r', $file['date']), 5, strlen($file['date']) - 25);
                $size = $file['size'];
                if ($size <= 1000) {     // transform filesize to kb if > 1K
                    $size .= ' bytes  ';
                }
                else {
                    $size = round($file['size'] / 1000) . ' kbytes';
                }
                echo "<tr>\n";
                if (!array_key_exists($type, $icon)) {     // lookup file-type icon
                    $type = 'FILE';     // default icon if file-type not found
                }
                echo "<td><img src=\"{$icon[$type]}\"/></td>";     // file-type icon
                echo "<td width=\"10px\"> </td>";
                echo "<td><a href=\"{$url}\">{$name}</a></td>\n";     // file name (and link)
                echo "<td width=\"20px\"> </td>";
                if (array_key_exists($lookupname, $filedescription)) {     // lookup file description
                    $description = $filedescription[$lookupname];
                }
                else {
                    $description = ' ';
                }
                echo "<td>{$description}</td>\n";     // file description (or blank if none found)
                echo "<td width=\"20px\"> </td>";
                echo "<td align=\"right\">{$size}</td>\n";     // file size
                echo "<td width=\"20px\"> </td>";
                echo "<td>{$date}</td>\n";     // file modification date
                echo "</tr>\n";
            }
        }
        echo "</tbody>\n";
        echo "</table>\n\n";
    }
    return $ret;
}

?>
b. The file-type icons URL array code (icons.php).
<?php

// File-type icon URLs

$resurl = '/res/';     // icon directory URL
$icon = array (
    'PARENT' => "{$resurl}up.png",
    'DIRECTORY' => "{$resurl}folder.png",
    'FILE' => "{$resurl}file.png",
    'application/zip' => "{$resurl}zip.png",
    'text/plain' => "{$resurl}text.png"
);

?>
c. The Lazarus/Free Pascal directories specific script (lazarus_filelist.php).
<?php

//
// Create HTML table with info about Lazarus files contained in caller's directory
//

$phpdir = $_SERVER['DOCUMENT_ROOT'] . '/php/';
include("{$phpdir}filelist.php");     // directory list and display functions
include("{$phpdir}lazarus_filedescription.php");     // Lazarus/Free Pascal file descriptions
if (!isset($dir)) {     // $dir has to be set by calling script
    echo "<p>Script error: No directory set!</p>";
}
else {
    $dir = $_SERVER['DOCUMENT_ROOT'] . '/computing/lazarus/' . $dir;     // full URL of directory to be listed
    list($subdirlist, $filelist) = getFileList($dir, 1);     // get lists of subdirectories and files
    $ret = displayFileList($subdirlist, $filelist, $filedescription, 1);     // display directory list as HTML table
    if ($ret == 0) {     // empty directory
        echo "<p>This directory is empty!</p>";
    }
}

?>
d. The Lazarus/Free Pascal file description array code (lazarus_filedescription.php).
<?php

// Lazarus/Free Pascal file descriptions

$filedescription = array (
    'AAStats' => 'Protein analysis: Amino acids statistics',
    'AcidBase' => 'Mineral acid-base reactions exercise generator',
    'ACircuits1' => 'Electronics trainer - RLC circuits',
    'Adjectifs' => 'French grammar: The adjective suffixes',
    - truncated -
    'WorldQuiz' => 'World countries quiz',
    'Zuelespill' => 'Number game',
    'ZueleRaetsel' => 'Guess which number hides behind the different shapes',
    'Zodiac' => 'Astrology: Zodiac signs'
);

?>
e. The index.php files (e.g. PHP code inserted into index.php listing the Lazarus/Free Pascal sources).
<?php

$script = $_SERVER['DOCUMENT_ROOT'] . '/php/lazarus_filelist.php';
$dir = 'files/sources';
include($script);

?>
  2.4. Error handling.
   
The file retrieval function checks if the directory exists and dies if it doesn't; thus, no need to do so in the calling script. For an empty directory, the file listing function displays nothing and returns 0 (instead of 1, indicating that all is ok); it's up to the calling script to display or not a message in this case.
  2.5. Modifying and extending the scripts.
   
Please, note that the scripts described here are intended to list the content of 1 single directory and not to display a recursive directory listing of a given path! To do this, another function would have to be written. You are entirely free to adapt the scripts to your needs just as you like. A useful extension could, for example, be to display the number of subdirectories and the number and total size of the files.
  2.6. Script output.
   
Here, as example of how the page generated by my personalized directory listing script looks like, a screenshot of the listing of the Lazarus/Free Pascal source files on my local webserver.
Free PHP script: Personalized webserver directory listing