Search This Blog

Monday, June 28, 2010

Uploading Files

Wouldn't it be nice if you could upload files from a local machine to the server using the directory navigator you just created? PHP offers an easy way to put this sort of functionality into your applications. You can let users upload files with their browsers using an <input> form tag of the type file. The <form> tag's enctype attribute, which is usually omitted when creating a normal form, also needs to be set to multipart/form-data.


Here's a sample upload form:


<form method="POST" enctype="multipart/form-data"
      action="<?php echo   "$_SERVER[PHP_SELF]?action=upload_file&dir=$dir";   ?>">
  Local Filename <input type="file" name="userfile">
  <input type="submit" name="submit" value="Upload">
</form>

The action attribute of the form should point to a PHP script that will handle the uploaded file. You don't have to provide the second text input tag unless you want to let users choose a different filename with which the uploaded file will be stored on the server.
Once a file is uploaded, the following variable values become available for use via the superglobal $_FILES array:


Variable
Description
$_FILES[userfile]
The array in $_FILES that contains the other array values
$_FILES[userfile][name]
The original path and the filename of the uploaded file
$_FILES[userfile][size]
The size of the uploaded file in bytes
$_FILE[userfile][type]
The type of the file (if the browser provides the information)


Suppose a user has just uploaded a 20,000-byte ZIP file, C:\docs\projects.zip, using this form. These variables now contain the following:
  • $_FILES[userfile][tmp_name]: The path to the file in the temporary directory (as set in php.ini) plus the temporary filename in the format "php-###" format (where "###" is a number which is automatically generated by PHP), for example, "/tmp/php-512".
  • $_FILES[userfile][name]: "C:\docs\projects.zip"
  • $_FILES[userfile][size]:20000
  • $_FILES[userfile][type]: "application/x-zip-compressed"
You can set these values to normal variables if you like, as shown here:


//get the userfile particulars
   $userfile_name = $_FILES['userfile']['name'];
   $userfile_tmp_name = $_FILES['userfile']['tmp_name'];
   $userfile_size = $_FILES['userfile']['size'];
   $userfile_type = $_FILES['userfile']['type'];
   if(isset($_ENV['WINDIR'])) {
      $userfile = str_replace("\\\\","\\", $_FILES['userfile']['name']);
   }

The uploaded file is saved in the temporary directory set in php.ini and destroyed at the end of the request, so you need to copy it to somewhere else. For security reasons, it's recommended that you use the move_uploaded_file() function instead of the copy() function:


$archive_dir = "/Inetpub/wwwroot/beginning_php5/ch07/docs";
$filename = basename($userfile_name);
if(!@move_uploaded_file($userfile, "$archive_dir/$filename"))
      echo "Error: $filename cannot be copied.";
else echo "Successfully uploaded $filename.";

You may want to set a limit on the size of an uploaded file and check whether the uploaded file is larger than the limit by using the $userfile_size variable:


$archive_dir = "./docs";
$max_filesize = 200000;
$filename = basename($userfile_name);
if($userfile_size > $max_filesize)
   echo "Error: $filename is too big. " .
   number_format($max_filesize) . " bytes is the limit.";
else if(!copy($userfile, "$archive_dir/$filename"))
   echo "Error: $filename cannot be copied.";
else echo "Successfully uploaded $filename.";

Another way to limit the size of the uploaded file is to use a hidden field in the upload form:
<input type="hidden" name="max_file_size" value="200000">
...

if($userfile_size > $max_file_size)
   echo "Error: $filename is too big. " .
   number_format($max_file_size) . " bytes is the limit.";

Setting the limit within the script is a much more secure method because anyone can edit the Web page containing the upload form and increase the limit all he wants.


Try it Out: File Uploading
Start example
Here's a complete example of how to implement the file-uploading feature:
<?php
//file_upload.php
function upload__form() {
    ?>
    <table border="1" align="center">
    <tr><td>
    <form method="POST" enctype= "multipart/form-data"
       action="<? echo $_SERVER['PHP_SELF'] ?>">
       Upload file!
       <input type="file" name="userfile">
       <input type="submit" name="action" value="upload">
    </form>
    </td></tr>
    </table>
    <?
}
function upload_file() {
   //set the archive directory
   $archive_dir = "/Inetpub/wwwroot/beginning_php5/ch07/docs";
   //get the userfile particulars
   $userfile_name =$_FILES['userfile']['name'];
   $userfile_tmp_name = $_FILES['userfile']['size'];
   $userfile_size = $_FILES['userfile']['size'];
   $userfile_type = $_FILES['userfile']['type'];
   if(isset($_ENV['WINDIR'])) {
      $userfile = str_replace("\\\\", "\\", $FILES['userfile']['name']);
   }
   $filename = basename($userfile_name);
   if($userfile_size <= 0) die ("$filename is empty.");
   if (!@move_uploaded_file($userfile_tmp_name, "$archive_dir/$filename"))
       die("Can't copy $userfile_name to $filename.");
   if(isset($_ENV['WINDIR']) && !@unlink($userfile))
      die("Can't delete the file $userfile_name.");
   echo "$filename has been successfully uploaded.<BR>";
   echo "Filesize: " . number_format($userfile_size) . "<BR>";
   echo "Filetype" $userfile_type<BR>";
}
?>
<html>
<head><titile>File Upload</tile></head>
<body>
<?php
if($_POST[action] == 'upload') {
    upload_file();
} else {
    upload_form();
}
?>
</body>
</html>



End example

How it Works

When a user uploads a file from his local machine, the script first retrieves the $_FILES values and then tests the platform on which it is run. If it's the Windows platform, all occurrences of double forward slashes need to be reduced to single slashes:


//set the archive directory
   $archive_dir = "/Inetpub/wwwroot/beginning_php5/ch07/docs";

  //get the userfile particulars
  $userfile_name = $_FILES[userfile][name];
  $userfile_tmp_name = $_FILES[userfile][tmp_name];
  $userfile_size = $_FILES[userfile][size];
  $userfile_type = $_FILES[userfile][type];
if(isset($_ENV[WINDIR])) $userfile =
str_replace("\\\\", "\\$",$_FILES[userfile][name]));

If the size of the file is less then 0, the user submitted the form without designating a file to be uploaded:


if($userfile_size <= 0) die ("$filename is empty.");

If everything looks fine, the script copies the uploaded file to an archive directory and tests to see whether it's running on Windows. If not, it's okay to use unlink() to delete the temporary file. Then echo back to the user that the file has been uploaded, as well as the file's size and type.

Monday, June 21, 2010

Building a Text Editor

With the basics of PHP's file and directory handling capabilities under your belt, let's build a simple text file editor. It will take a filename as an argument, edit the file, and then save it. If it's not given an existing filename, the editor will create a new file.


First, think what the script will look like when finished. The picture in your mind eventually translates to the end user interface. Because it's an editor, it needs a place where it stores text and lets the user manipulate it. You won't have to worry about implementing specific editing features such as typing in characters, deleting them, moving the cursor, and so on, because the HTML tag <textarea> can handle all the editing features you'll need. All you have to do is throw in a form that displays a scrolling text box, an edit field for typing in a file name, and a button to save the text.



Next to consider are how to alert the user upon errors and how to get his confirmation about possibly disastrous actions such as overwriting an existing file. Some simple JavaScript tricks can achieve the goal. You can put a snippet of JavaScript into an HTML page by enclosing the code within the following tag combination:


<script> JavaScript code goes here </script>

For example, if you want to alert the user about an error, you can use the JavaScript's alert() method:


<script> alert("Warning! An error occurred!"); </script>

That opens a small window displaying the specified error message. You can use the history.go(-1) method to tell the browser to return to the previous page upon error:


<script>
alert("Warning! An error occurred! Let's get back to the previous page!");
history.go(-1);
</script>

Getting confirmation from the user is just as easy. The confirm() method does the trick:


<script>
result = confirm("Warning! Are you sure?");
if(!result) history.go(-1);
</script>

The confirm() method returns the user's decision. If he presses the YES button, True is returned and the specified action is executed. If he presses CANCEL, False is assigned to the variable result, causing the browser to fall back to the previous page. You'll use some of these tricks in the sample script.


You start by putting global variables and commonly used functions in the common.inc include file. As previously shown, it's good practice to put common elements that are shared and reusable by related scripts into a common include file. Name the revised include file common_php5_02.inc.php. First, the script presents a function to process error messages. This function provides a message to the user, and also sends him back to the previous page:


<?php
function error_message($msg) {
   echo "<script>alert(\"$msg\"); history.go(-1)</script>";
   exit;
}

The next function is just a copy of the date_str() function that you created earlier:


function date_str($timestamp) {
  $date_str = getdate($timestamp);
  $year = $date_str["year"];
  $mon = $date_str["mon"];
  $mday = $date_str["mday"];
  $hours = $date_str["hours"];
  $minutes = $date_str["minutes"];
  $seconds = $date_str["seconds"];

  return "$hours:$minutes:$seconds $mday/$mon/$year";
}

The last function for the common_php5_02.inc.php file is file_info():


function file_info($file) {
   global $text_file_array;
   $file_info_array["filesize"] = number_format(filesize($file)) . " bytes.";
   $file_info_array["filectime"] = date_str(filectime($file));
   $file_info_array["filemtime"] = date_str(filemtime($file));
   if(!isset($_ENV['WINDIR'])) {
   $file_info_array["fileatime"] = date_str(fileatime($file));
   $file_info_array["filegroup"] = filegroup($file);
   $file_info_array["fileowner"] = fileowner($file);
} else {
   $file_info_array["fileatime"] = "not available";
   $file_info_array["filegroup"] = "not available"
   $file_info_array["fileowner"] = "not available";
}

   $extension = array_pop(explode(".", $file));

   if (in_array($extension, $text_file_array)) {
    $file_info_array["filetype"] = "text";
   } else {
    $file_info_array["filetype"] = "binary";
   }
   return $file_info_array;
}

Note how you extract the extension from a given file with explode(".", $file). This breaks up the filename using the dot (.) as a delimiter, and returns the resulting pieces as an array. The last element of the array is extracted using the array_pop() function, and is stored in $extension. For this example, the extension is not the second element.


One final section in common_php5_02.inc.php creates an array of filename extensions to look for:


//specify the file extensions to handle
$text_file_array = array( "txt", "htm", "html", "php", "inc", "dat" );

Now you're ready to create the structure of the main processing file, editor_php5.php. Start by creating the header for an ordinary HTML Web page:


<html>
<head><title>Welcome to Web Text Editor</title></head>
<body>

<form method="POST" action="<?php echo $_SERVER['PHP_SELF']; ?>">
<input type="hidden" name="posted" value="true">

Next, begin the PHP code and pull in (using the require() function) the common_php5.inc.php file. Then set the directory to use:


require("common_php5_02.inc.php");
//specify the default directory
$dir = "/Inetpub/wwwroot/beginning_php5/ch07/docs";

Check whether the user has clicked a button, and if so, set the proper value for the eventual select..case statement:


if (isset($_POST['save_edited_file'])) {
    $action_chosen = "save_file";
} elseif (isset($_POST['create_new_file'])) {
    $action_chosen = "save_file";
} elseif (isset($_POST['edit_existing_file])) {
    $action_chosen = "edit_existing_file";
}

Begin the select..case statement and lay out the save_file case. This code checks to see if the file exists, and if so, uses JavaScript to ask the user if he wants to overwrite the file:


switch ($action_chosen) {
 case "save_file";

       if (file_exists("$dir/$_POST[filename]")) {
          echo "<script>result = confirm(\"Overwrite
             '$dir/$_POST[filename]'?\"); if(!result) history.go
(-1);</script>";
     }

Try to open (or create) the file, and if it fails, use the error_message() function to send the user an error message:


      if ($file = fopen("$dir/$_POST[filename]", "w")) {
        fputs($file, $_POST[filebody]);
        fclose($file);
      } else {
        error_message("Can't save file $dir/$_POST[filename].");
     }

The last chunk of code in the save_file case uses HTML to display the primary buttons that allow the user to perform the available tasks in the application. Notice the variation of the directory listing code to fill the drop-down box. The same code is also used in the default case. Don't forget the break at the end of the case:


      //display the main buttons
      ?>
      <table border="1" align="center"><tr><td>
      <strong>Create (or Overwrite) New File or Edit Existing File</strong>
      </td></tr>
      <tr><td>
      <input type="submit" name="create_new_file" value="Create New File">
      <input type="text" name="filename" value="new.txt">
      </td></tr> <tr><td>
      <input type="submit" name="edit_existing_file" value="Edit Existing
File">
      <select name="existing_file">
      <?php

      if($dp = opendir($dir)) {
          while($file = readdir($dp)) {
              if($file !== '.' && $file != = '..' &&
                      is_file($dir."/".$file)) {
              ?>
              <option value="<?php echo $file; ?>">
                      <?php echo $file; ?></option>
              <?php
        }
      }
      } else {
             error_msg("Can't open directory $dir.");
           }

        closedir($dp);

        ?>
        </select>
        </td></tr></table>
        <?php

      break;

Now layout the edit_existing_file case. Set the file particulars, including the filebody, and put the results of the file_info() function into $file_info_array:


case "edit_existing_file";

        $filepath = "$dir/$_POST[existing_file]";
        $filebody = implode("",file($filepath));
        $file_info_array = file_info("$filepath");
Check to see if the file is a text file, and if so, reset the filebody so you'll know not to edit it:
       if ($file_info_array["filetype"] != "text") {
           $filebody = $filepath . " is not a text file. Can't edit.";
           $editable = 0;
       } else {
           $editable = 1;
     }

Display the file stats:


        ?>
        <table border="l" width="70%" align="center">
        <tr><th width="100%" colspan="2">
        <center><strong>Stats for Existing File named <?php echo
"$dir/$_POST[existing_file]"; ?>
        </td></tr>
        <?php
        $file_info_array = file_info("$dir/$_POST[existing_file]");
        foreach ($file_info_array as $key=>$val) {
            echo "<tr><th width=\"30%\">" . ucfirst($key)
            ."</td><td width=\"70%\">" . $val
            ."</td></tr>\n" ;
        }
        ?>
      </table>

The final chunk of code in the edit_existing_file case allows the user to edit the code and save the result:


        <br>
        <table border="1" align="center"><tr><td>
        <strong>Editing Existing File named <?php echo $_POST['existing_file'];
?></strong>
        </td></tr>
        <tr><td>
        <?php

        if ($editable == 0) {
            echo $filebody;
        } else {
            ?>
            <input type="hidden" name="filename" value="<?php echo
$_POST['existing_file]; ?>">
            <textarea rows="4" name="filebody" cols="40" wrap="soft">
            <?php
            echo "$filebody";
            ?>
            </textarea><br>
            <input type="submit" name="save_edited_file" value="Save Edited
File">
            <?php
       }
       ?>
       </td></tr></table>
       <?php

     break;

The default case is a copy of the last portion of the save_file case:


  default;
              //display the main buttons
              ?>
              <table border="1" align="center"><tr><td>
              <strong>Create (or Overwrite) New File or Edit Exist-
ing File</strong>
              </td></tr>
              <tr><td>
              <input type="submit" name="create_new_file" value="Create
New File">
              <input type="text" name="filename" value="new.txt">
              </td></tr>
              <tr><td>
              <input type="submit" name="edit_existing_file" value="Edit
Existing File">
              <select name="existing_file">
              <?php

              if($dp = opendir($dir))    {
                 while($file = readdir($dp))    {
                    if($file   !==   '.' && $file   != =   '..'   &&  is_file
($dir."/".$file)) {
                ?>
             <option value="<?php echo $file; ?>"><?php echo $file; ?></option>
             <?php
                 }
               }
             } else {
                error_msg("Can't open directory $dir.");
             }
             closedir($dp);

           ?>
           </select>
           </td></tr></table>
           <?php
          break;

The final code in the file ends the select..case statement and finishes off the HTML for the page, including the closing </form> tag.


}
?>
    </form>
   </body>
</html>

And that pretty well covers how to deal with the members of the server file system family.

Monday, June 14, 2010

Working with Directories

PHP enables you manipulate to directories in much the same way as files, providing a number of equivalent functions. Some directory functions use a directory handle, whereas others use a string containing the name of the directory with which you want to work. A directory handle is similar to a file handle; it's an integer value pointing to a directory, which can be obtained by specifying the directory in a call to the opendir() function:


$dp = opendir ("/home/James/")

Upon error, this returns False. As you may have guessed, you can close a directory by specifying the appropriate handle to the function closedir():


closedir($dp);

The readdir() function returns the next entry listed in the open directory. This list includes entries for . (used to specify the current directory) and .. (likewise, specifying the parent of the current directory). PHP maintains an internal pointer referring to the next entry in the list just as the file position indicator points to the position where the next file operation should occur.


Try it Out: List Directory Entries
Start example
Here's how to set up a loop to get all the entries from a specified directory:


<?php
//dir_list.php
$default_dir = "/Inetpub/wwwroot/beginning_php5/ch07";
if(!($dp = opendir($default_dir))) die("Cannot open $default_dir.");
while($file = readdir($dp))
  if($file != '.' && $file != '..') echo "$file<br>";
closedir($dp);
?>



End example

How it Works

You first get a handle on the directory given (Inetpub/wwwroot/beginning_php5/ch07 for this example), and then set up a loop that reads entries from that directory, and (as long as they're not "." or "..") prints them out. The loop is conditional on the return value of readdir(), which will be False when the list of entries is exhausted:


while($file = readdir($dp))
   if($file ! = '.' && $file != '..') echo "$file<br>";

Finally, closedir(). closes the directory.


The returned filenames are not sorted in any way. To sort them, you need to set up two loops. First though, you read the entries into an array:


<?php
$default_dir = "/Inetpub/wwwroot/beginning_php5/ch07";
if(!($dp = opendir($default_dir))) die("Cannot open $default_dir.");
while($file = readdir($dp)) $filenames[] = $file;
closedir($dp);

The $filenames array now contains every entry in the directory. You didn't use any indexing number in the array, so PHP automatically takes care of the indexing.


Finally, sort() is called to arrange the array entries in ascending order, and display all except the current and parent directories:


sort($filenames);
for($i=0; $i < count($filenames); $i++)
   if($filenames[$i] != '.' && $filenames[$i] != '..')
        echo $filenames[$i] . "<br>";
?>

Other Directory Functions

Just as with files, PHP provides a range of ways to manipulate directories, including the following functions:
  • rewinddir()
  • chdir()
  • rmdir()
  • mkdir()
  • dirname()
  • (dir)
The rewinddir() function resets PHP's internal pointer when you want to move back to the first entry in a given directory while working with it. This function is the directory counterpart to the rewind() function for files, specifying the relevant directory handle. The rest of the directory handling functions take a path string directly instead of using a directory handle.


The chdir() function call changes PHP's current directory to the given directory:


if(chdir("/Inetpub/wwwroot/beginning_php5/ch07"))
echo "The current directory is / Inetpub/wwwroot/beginning_php5/ch07.";
else
echo "Cannot change the current directory to /home
Inetpub/wwwroot/beginning_php5/ch07.";

The rmdir() function removes a given directory. The directory must be empty, and you need appropriate permissions to remove it. For example:


rmdir("/tmp/rubbish/");

The mkdir() function creates a directory as specified in its first argument. You can also specify a directory mode argument as a three-digit octal number. The following code snippet first tests whether / Inetpub/wwwroot/beginning_php5/ch07/test exists. If it does, it's removed, and the same directory is then recreated, with all permissions granted to all users (permissions are applied only on Linux systems):


$default_dir = "/ Inetpub/wwwroot/beginning_php5/ch07";
if(file_exists($default_dir)) rmdir($default_dir);
mkdir($default_dir, 0777);
The dirname() function returns the directory part of a given filename. For example:
$filepath = "/ Inetpub/wwwroot/beginning_php5/ch07/index.html";
$dirname = dirname($filepath);
$filename = basename($filepath);

The string $dirname will now contain "/Inetpub/wwwroot/beginning_php5/ch07", and $filename will hold index.html.


PHP offers an alternative object-oriented mechanism for working with directories: the dir object. To use this method, you need to instantiate the object first by calling the dir() constructor with the name of the directory you want to work with as follows:


$dir = dir("/Inetpub/wwwroot/beginning_php5/ch07");

The dir object provides two properties: handle and path. These refer to the directory handle and the path, respectively:


echo $dir->handle; # echoes the directory handle
echo $dir->path; # echoes "/Inetpub/wwwroot/beginning_php5/ch07"

You can use the handle property with other directory functions such as readdir(), rewinddir() and closedir().


The dir object supports three methods: read(), rewind(), and close(), which are functionally equivalent to readdir(), rewinddir(), and closedir(), respectively. You can use the object to rewrite the directory listing script:


<?php
$default_dir = "/Inetpub/wwwroot/beginning_php5/ch07";
$dir = dir($default_dir);
while($file = $dir->read()) if($file != '.' && $file != '..')
    echo $file . "<br>";
$dir->close ();
?>

Traversing a Directory Hierarchy

As you learned in Chapter 6, recursion is particularly useful when a script has to perform repetitive operations and iterate over a given set of data, and traversing a directory hierarchy is a very good example. A directory may hold subdirectories as well as files. If you want to create a script that lists all the files and subdirectories under a given directory, you'd make one that reads the entries in the current directory, and if the next entry is a file, display its name. If the next entry is a subdirectory, display its name and go into it and return to reading the entries in the current directory.


As you can see, the second step repeats the whole process by itself, when necessary. The recursion continues until there are no more subdirectories left to traverse. Here's an example script:


<?php
//nav_dir.php
$default_dir = "/home/james"
function traverse_dir($dir) {
   echo "Traversing $dir....<br>";
   chdir($dir);
   if(!($dp = opendir($dir))) die("Can't open $dir.");
   while($file = readdir($dp)) {
      if(is_dir($file)) {
         if($file != '.' && $file != '..') {
              echo "/$file<br>";
              traverse_dir("$dir/$file");
              chdir($dir);
         }
      }
      else echo "$file<BR>";
   }
   closedir($dp);
}
traverse_dir($default_dir);
?>

The traverse_dir() function is based on the concept of recursion and traverses the whole directory hierarchy under a specified directory. First, the function echoes out which directory it is currently going through. Then, a call to chdir() ensures that the $dir directory string argument is equal to PHP's current working directory:


function traverse_dir($dir) {
   echo "Traversing $dir....<br>";
   chdir($dir);
   if(!($dp = opendir($dir))) die("Can't open $dir.");
   while($file = readdir($dp)) {

Recursion occurs when the next entry is a subdirectory. You exclude both the single dot (.) file and the double dot (..) file from the returned entries—this is absolutely crucial for this example; otherwise the script would be thrown into an infinite loop.


if(is_dir($file)) {
   if($file != '.' && $file != '..') {
        echo "/$file<br>";

The traverse_dir() function calls itself to go further down the directory hierarchy, and the function changes the current working directory back to its original state:


               traverse_dir("$dir/$file");
           chdir($dir);
         }
       }
     else echo "$file<BR>";
   }
  closedir($dp);
 }
 traverse_dir($default_dir);
?>

With the power of recursion as demonstrated in this sample script, you can create your own version of the Linux shell command find.

Creating a Directory Navigator

Now you're ready to build a fairly powerful directory navigator that you can use to scan the contents of existing directories and to make new ones. You'll create a file named common_php5.inc.php file, with a number of sections that help the application perform its job.


First you set the default directory, filename, and size of the textbox:


<?php
//specify the default directory
$default_dir = "./docs";

//specify the default name for new files
$default_filename = "new.txt";

//specify the size of the text area box
$edit_form_cols = 80;
$edit_form_rows = 25;
Set the file extensions you can handle:
//specify the file extensions to handle
$text_file_array = array( "txt", "htm", "html", "php", "inc", "dat" );
$image_file_array = array("gif", "jpeg", "jpg", "png");

These three functions create the header, the footer, and any error messages you need:


function html_header() {
   ?>
   <html>
   <head><title>Welcome to Web Text Editor</title></head>
   <body>
   <?php
}

function html_footer() {
   ?>
   </body>
   </html>
   <?php
}

function error_message($msg) {
   html_header();
   echo "<script>alert(\"$msg\"); history.go(-1)</script>";
   html_footer();
   exit;
}

Process the date information:


function date_str($timestamp) {
   $date_str = getdate($timestamp);
   $year = $date_str["year"];
   $mon = $date_str["mon"];
   $mday = $date_str["mday"];
   $hours = $date_str["hours"];
   $minutes = $date_str["minutes"];
   $seconds = $date_str["seconds"];

   return "$hours:$minutes:$seconds $mday/$mon/$year";
}

Then you return information about the file, and also check the filename extension:


function file_info($file) {
   global $text_file_array;
   $file_info_array["filesize"] =
                   number_format(filesize($file)) . " bytes.";
      $file_info_array["filectime"] = date_str(filectime($file));
   $file_info_array["filemtime"] = date_str(filemtime($file));
   if(!isset($_ENV[WINDIR])) {
      $file_info_array["fileatime"] = date_str(fileatime($file));
      $file_info_array["filegroup"] = filegroup($file);
      $file_info_array["fileowner"] = fileowner($file);
   } else {
      $file_info_array["fileatime"] = "not available";
      $file_info_array["filegroup"] = "not available";
      $file_info_array["fileowner"] = "not available";
   }

   $extension = array_pop(explode(".", $file));

   if (in_array($extension, $text_file_array)) {
    $file_info_array["filetype"] = "text";
   } else {
    $file_info_array["filetype"] = "binary";
   }

   return $file_info_array;
}

Save your file as common_php5.inc.php.


Start your navigator script by including the file you just saved:


<?php
//navigator.php
include "common_php5.inc.php";

Then define some functions. First is the mkdir_form() function, which displays a form to create a new directory:


function mkdir_form()
   global $dir;
?>
<center>
<form method="POST"
   action="<?php echo "$_SERVER[PHP_SELF]?action=make_dir&dir=$dir"; ?>">
<input type="hidden" name="action" value="make_dir">
<input type="hidden" name="dir" value="<? echo $dir ?>">

<?php
echo "<strong>$dir</strong>"
?>

<br>
<input type="text" name="new_dir" size="10">
<input type="submit" value="Make Dir" name="Submit">

</form>
</center>
<?php
}

The make_dir() function creates a given directory:


function make_dir()
   global $dir;
   if(!@mkdir("$dir/$_POST[new_dir]", 0700)) {
      error_message("Can't create the directory $dir/$_POST[new_dir].");
   }
   html_header();
   dir_page();
   html_footer();
}

And then the display() function prints out the contents of a given file in a new window. By comparing the file extension with elements in $text_file_array and $image_file_array, it determines the file type—text, image, or binary:


function display() {
    global $text_file_array, $image_file_array;
    $extension = array_pop(explode(".", $_GET[filename]));

Next, the extension is checked against the types included in the text file array and the image file array. It will refuse to display a binary file:


if(in_array($extension, $text_file_array)) {
    readfile("$_GET[dir]/$_GET[filename]");
    }
    else if(in_array($extension, $image_file_array)) {
                echo "<img src=\"$_GET[dir]/$_GET[filename]\">";
    }
    else echo "Cannot be displayed. $_GET[dir]/$_GET[filename] has not been
                recognized as a text file, nor as a valid image file. ";
    }

The dir_page() is the main function, the one that scans the directory hierarchy and lists directory entries, displaying them with a trailing slash. This is where the meat of the script lies:


function dir_page() {
   global $dir, $default_dir, $default_filename;

   if($dir == '') {
       $dir = $default_dir;
   }
   if (isset($_GET['dir']) ) {
    $dir = $_GET['dir ' ] ;
   }

   $dp = opendir($dir) ;

?>
<table border="0" width="100%" cellspacing="0" cellpadding="0">
<?php

Two loops are set up to sort the entries in a given directory: a while loop to read all the entries in the current working directory, and a for loop to display the entries after they're sorted:


while($file = readdir($dp)) $filenames[] = $file;
sort($filenames);
for($i = 0; $i < count($filenames); $i++)
{
  $file = $filenames[$i];

If the next entry is "." (indicating the current directory), the function ignores it and continues the next loop cycle. However, if the current working directory is the default directory, both "." and ". ." are ignored:


if($dir == $default_dir && ($file = = "." | | $file = = ".."))
      continue;
  if(is_dir("$dir/$file") && $file = = ".")
     continue;
  if(is_dir("$dir/$file")) {

If the entry is ". ." (indicating the parent directory), the function trims the name of the current directory from the $dir variable, to create a hyperlink pointing to the parent directory.


if($file = = "..''){

The $current_dir holds the rightmost directory name with the following basename() function call:


$current_dir = basename($dir);

If the $dir variable contains "/home/apache/htdocs/images", for example, $current_dir is assigned the value "images".


When creating a link, the dir_page() function uses the ereg_replace() function to remove occurrences of the value in the $dir variable:


$parent_dir = ereg_replace("/$current_dir$","",$dir);

Following this line, the $parent_dir holds the value "/home/apache/htdocs" because the pattern /$current_dir$ matches the trailing string"/images".


   echo "<tr><td width=\"100%\ " nowrap>
     <a href=\"$_SERVER[PHP_SELF]?dir=$parent_dir\">$file/
      </a></td></tr>\n" ;
}

If the next entry is a subdirectory, the function creates a link pointing to it:


   else echo "<tr><td width=\"100%\" nowrap>
              <a href=\"$_SERVER[PHP_SELF]?dir=$dir/$file\">
                 $file/</a></td></tr>\n";
}

If the next entry is a file, it creates a link to open a new browser that displays its contents:


    else echo "<tr><td width=\"100%\" nowrap>
           <a href=\"$_SERVER[PHP_SELF]?action=display&dir=$dir&filename=$file\"
                target=\"_blank\">$file</a></td></tr>\n";
    }
?>
</table>
<?php
   mkdir_form();
}

When you run the script, it first tests to see if the directory specified is above the default directory (for security reasons, the user should never be able to access files that aren't directly under the default directory). The ereg() pattern matching function returns False if the $dir variable doesn't contain the value in $default_dir, in which case the $dir variable is assigned that value:


if (empty ($dir) | | !ereg($default_dir, $dir)) {
    $dir = $default_dir;
}

Finally, you call functions that correspond to the value in $action, defaulting to dir_page():


if {!empty($_POST['action'])) {
   $action = $_POST['action'];
}
if (!empty($_GET['action'])) {
   $action = $_GET['action'];
}
switch ($action) {
   case "make_dir":
      make_dir();
      break;
   case "display":
      display();
      break;
   default:
      html_header();
      dir_page();
      html_footer();
      break;
}
?>