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
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);
?>
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;
}
?>