title besides title

 
Showing posts with label PHP - Directories. Show all posts
Showing posts with label PHP - Directories. Show all posts

Sunday, November 25, 2012

PHP : Directories - [19.1] Introduction

A filesystem stores a lot of additional information about files aside from their actual contents. This information includes such particulars as the file's size, what directory it's in, and access permissions for the file. If you're working with files, you may also need to manipulate this metadata. PHP gives you a variety of functions to read and manipulate directories, directory entries, and file attributes. Like other file-related parts of PHP, the functions are similar to the C functions that accomplish the same tasks, with some simplifications.
Files are organized with inodes . Each file (and other parts of the filesystem, such as directories, devices, and links) has its own inode. That inode contains a pointer to where the file's data blocks are as well as all the metadata about the file. The data blocks for a directory hold the names of the files in that directory and the inode of each file.
PHP provides two ways to look in a directory to see what files it holds. The first way is to use opendir( ) to get a directory handle, readdir( ) to iterate through the files, and closedir( ) to close the directory handle:
$d = opendir('/usr/local/images') or die($php_errormsg);
while (false !== ($f = readdir($d))) {
    // process file
}
closedir($d);
The second method is to use the directory class. Instantiate the class with dir( ), read each filename with the read( ) method, and close the directory with close( ):
$d = dir('/usr/local/images') or die($php_errormsg);
while (false !== ($f = $d->read())) {
    // process file
}
$d->close();
Section 19.8 shows how to use opendir( ) or dir( ) to process each file in a directory. Making new directories is covered in Section 19.11 and removing directories in Section 19.12.
The filesystem holds more than just files and directories. On Unix, it can also hold symbolic links. These are special files whose contents are a pointer to another file. You can delete the link without affecting the file it points to. To create a symbolic link, use symlink( ):
symlink('/usr/local/images','/www/docroot/images') or die($php_errormsg);
This creates a symbolic link called images in /www/docroot that points to /usr/local/images.
To find information about a file, directory, or link you must examine its inode. The function stat( ) retrieves the metadata in an inode for you. Section 19.3 discusses stat( ). PHP also has many functions that use stat( ) internally to give you a specific piece of information about a file. These are listed in Table 19-1.
Table 19-1. File information functions
Function name
What file information does the function provide?
file_exists( )
Does the file exist?
fileatime( )
Last access time
filectime( )
Last metadata change time
filegroup( )
Group (numeric)
fileinode( )
Inode number
filemtime( )
Last change time of contents
fileowner( )
Owner (numeric)
fileperms( )
Permissions (decimal, numeric)
filesize( )
Size
filetype( )
Type (fifo, char, dir, block, link, file, unknown)
is_dir( )
Is it a directory?
is_executable( )
Is it executable?
is_file( )
Is it a regular file?
is_link( )
Is it a symbolic link?
is_readable( )
Is it readable?
is_writable( )
Is it writeable?
On Unix, the file permissions indicate what operations the file's owner, users in the file's group, and all users can perform on the file. The operations are reading, writing, and executing. For programs, executing means the ability to run the program; for directories, it's the ability to search through the directory and see the files in it.

PHP : Directories - [19.2] Getting and Setting File Timestamps

19.2.1 Problem

You want to know when a file was last accessed or changed, or you want to update a file's access or change time; for example, you want each page on your web site to display when it was last modified.

19.2.2 Solution

The fileatime( ) , filemtime( ), and filectime( ) functions return the time of last access, modification, and metadata change of a file:
$last_access = fileatime('larry.php');
$last_modification = filemtime('moe.php');
$last_change = filectime('curly.php');
The touch( ) function changes a file's modification time:
touch('shemp.php');          // set modification time to now
touch('joe.php',$timestamp); // set modification time to $timestamp

19.2.3 Discussion

The fileatime( ) function returns the last time a file was opened for reading or writing. The filemtime( ) function returns the last time a file's contents were changed. The filectime( ) function returns the last time a file's contents or metadata (such as owner or permissions) were changed. Each function returns the time as an epoch timestamp.
A file's modification time can be updated with touch( ). Without a second argument, touch( ) sets the modification time to the current date and time. To set a file's modification time to a specific value, pass that value as an epoch timestamp to touch( ) as a second argument.
This code prints the time a page on your web site was last updated:
print "Last Modified: ".strftime('%c',filemtime($_SERVER['SCRIPT_FILENAME']));

PHP : Directories - [19.3] Getting File Information

19.3.1 Problem

You want to read a file's metadata; for example, permissions and ownership.

19.3.2 Solution

Use stat( ), which returns an array of information about a file:
$info = stat('harpo.php');

19.3.3 Discussion

The function stat( ) returns an array with both numeric and string indexes with information about a file. The elements of this array are in Table 19-3.
Table 19-3. Information returned by stat( )
Numeric index
String index
Value
0
dev
Device
1
ino
Inode
2
mode
Permissions
3
nlink
Link count
4
uid
Owner's user ID
5
gid
Group's group ID
6
rdev
Device type for inode devices (-1 on Windows)
7
size
Size (in bytes)
8
atime
Last access time (epoch timestamp)
9
mtime
Last change time of contents (epoch timestamp)
10
ctime
Last change time of contents or metadata (epoch timestamp)
11
blksize
Block size for I/O (-1 on Windows)
12
blocks
Number of block allocated to this file
The mode element of the returned array contains the permissions expressed as a base 10 integer. This is confusing since permissions are usually either expressed symbolically (e.g., ls's -rw-r--r-- output) or as an octal integer (e.g., 0644). To convert the permissions to a more understandable format, use base_convert( ) to change the permissions to octal:
$file_info = stat('/tmp/session.txt');
$permissions = base_convert($file_info['mode'],10,8);
This results in a six-digit octal number. For example, if ls displays the following about /tmp/session.txt:
-rw-rw-r--    1 sklar    sklar          12 Oct 23 17:55 /tmp/session.txt
Then $file_info['mode'] is 33204 and $permissions is 100664. The last three digits (664) are the user (read and write), group (read and write), and other (read) permissions for the file. The third digit, 0, means that the file is not setuid or setgid. The leftmost 10 means that the file is a regular file (and not a socket, symbolic link, or other special file).
Because stat( ) returns an array with both numeric and string indexes, using foreach to iterate through the returned array produces two copies of each value. Instead, use a for loop from element 0 to element 12 of the returned array.
Calling stat( ) on a symbolic link returns information about the file the symbolic link points to. To get information about the symbolic link itself, use lstat( ).
Similar to stat( ) is fstat( ), which takes a file handle (returned from fopen( ) or popen( )) as an argument. You can use fstat( ) only on local files, however, not URLs passed to fopen( ).
PHP's stat( ) function uses the underlying stat(2) system call, which is expensive. To minimize overhead, PHP caches the result of calling stat(2). So, if you call stat( ) on a file, change its permissions, and call stat( ) on the same file again, you get the same results. To force PHP to reload the file's metadata, call clearstatcache( ), which flushes PHP's cached information. PHP also uses this cache for the other functions that return file metadata: file_exists( ), fileatime( ), filectime( ), filegroup( ), fileinode( ), filemtime( ), fileowner( ), fileperms( ), filesize( ), filetype( ), fstat( ), is_dir( ), is_executable( ), is_file( ), is_link( ), is_readable( ), is_writable( ), and lstat( ).

PHP : Directories - [19.4] Changing File Permissions or Ownership

19.4.1 Problem

You want to change a file's permissions or ownership; for example, you want to prevent other users from being able to look at a file of sensitive data.

19.4.2 Solution

Use chmod( ) to change the permissions of a file:
chmod('/home/user/secrets.txt',0400);
Use chown( ) to change a file's owner and chgrp( ) to change a file's group:
chown('/tmp/myfile.txt','sklar');           // specify user by name
chgrp('/home/sklar/schedule.txt','soccer'); // specify group by name

chown('/tmp/myfile.txt',5001);              // specify user by uid
chgrp('/home/sklar/schedule.txt',102);      // specify group by gid

19.4.3 Discussion

The permissions passed to chmod( ) must be specified as an octal number.
The superuser can change the permissions, owner, and group of any file. Other users are restricted. They can change only the permissions and group of files that they own, and can't change the owner at all. Nonsuperusers can also change only the group of a file to a group they belong to.
The functions chmod( ), chgrp( ), and chown( ) don't work on Windows.

PHP : Directories - [19.5] Splitting a Filename into Its Component Parts

19.5.1 Problem

You want to find a file's path and filename; for example, you want to create a file in the same directory as an existing file.

19.5.2 Solution

Use basename( ) to get the filename and dirname( ) to get the path:
$full_name = '/usr/local/php/php.ini';
$base = basename($full_name);  // $base is php.ini
$dir  = dirname($full_name);   // $dir is /usr/local/php
Use pathinfo( ) to get the directory name, base name, and extension in an associative array:
$info = pathinfo('/usr/local/php/php.ini');

19.5.3 Discussion

To create a temporary file in the same directory as an existing file, use dirname( ) to find the directory, and pass that directory to tempnam( ):
$dir = dirname($existing_file);
$temp = tempnam($dir,'temp');
$temp_fh = fopen($temp,'w');
The elements in the associative array returned by pathinfo( ) are dirname, basename, and extension:
$info = pathinfo('/usr/local/php/php.ini');
print_r($info);
Array
(
    [dirname] => /usr/local/php
    [basename] => php.ini
    [extension] => ini
)
You can also pass basename( ) an optional suffix to remove it from the filename. This sets $base to php:
$base = basename('/usr/local/php/php.ini','.ini');
Using functions such as basename( ), dirname( ), and pathinfo( ) is more portable than just separating a full filename on / because they use an operating-system appropriate separator. On Windows, these functions treat both / and \ as file and directory separators. On other platforms, only / is used.
There's no built-in PHP function to combine the parts produced by basename( ), dirname( ), and pathinfo( ) back into a full filename. To do this you have to combine the parts with . and /:
$dirname = '/usr/local/php';
$basename = 'php';
$extension = 'ini';

$full_name = $dirname . '/' . $basename . '.' . $extension;
You can pass a full filename produced like this to other PHP file functions on Windows, because PHP accepts / as a directory separator on Windows.

PHP : Directories - [19.6] Deleting a File

19.6.1 Problem

You want to delete a file.

19.6.2 Solution

Use unlink( ):
unlink($file) or die ("can't delete $file: $php_errormsg");

19.6.3 Discussion

The function unlink( ) is only able to delete files that the user of the PHP process is able to delete. If you're having trouble getting unlink( ) to work, check the permissions on the file and how you're running PHP.

PHP : Directories - [19.8] Processing All Files in a Directory

19.8.1 Problem

You want to iterate over all files in a directory. For example, you want to create a select box in a form that lists all the files in a directory.

19.8.2 Solution

Get a directory handle with opendir( ) and then retrieve each filename with readdir( ):
$d = opendir('/tmp') or die($php_errormsg);
while (false !== ($f = readdir($d))) {
    print "$f\n";
}
closedir($d);

19.8.3 Discussion

The code in the solution tests the return value of readdir( ) with the nonidentity operator (!==) so that the code works properly with filenames that evaluate to false, such as a file named 0.
The function readdir( ) returns each entry in a directory, whether it is a file, directory, or something else (such as a link or a socket). This includes the metaentries "." (current directory) and ".." (parent directory). To just return files, use the is_file( ) function as well:
print '<select name="files">';
$d = opendir('/usr/local/upload') or die($php_errormsg);
while (false !== ($f = readdir($d))) {
    if (is_file("/usr/local/upload/$f")) {
        print '<option> ' . $f . '</option>';
    }
}
closedir($d);
print '</select>';
Because readdir( ) returns only the filename of each directory entry, not a full pathname, you have to prepend the directory name to $f before you pass it to is_file( ).
PHP also has an object-oriented interface to directory information. The dir( ) function returns an object on which you can call read( ), rewind( ), and close( ) methods, which act like the readdir( ), rewinddir( ), and closedir( ) functions. There's also a $path property that contains the full path of the opened directory.
Here's how to iterate through files with the object-oriented interface:
print '<select name="files">';
$d = dir('/usr/local/upload') or die($php_errormsg);
while (false !== ($f = $d->read())) {
    if (is_file($d->path.'/'.$f)) {
        print '<option> ' . $f . '</option>';
    }
}
$d->close();
In this example, $d->path is /usr/local/upload.

PHP : Directories - [19.7] Copying or Moving a File

19.7.1 Problem

You want to copy or move a file.

19.7.2 Solution

Use copy( ) to copy a file:
copy($old,$new) or die("couldn't copy $old to $new: $php_errormsg");
Use rename( ) to move a file:
rename($old,$new) or die("couldn't move $old to $new: $php_errormsg");

19.7.3 Discussion

On Unix, rename( ) can't move files across filesystems. To do so, copy the file to the new location and then delete the old file:
if (copy("/tmp/code.c","/usr/local/src/code.c")) {
  unlink("/tmp/code.c");
}
If you have multiple files to copy or move, call copy( ) or rename( ) in a loop. You can operate only on one file each time you call these functions.

PHP : Directories - [19.9] Getting a List of Filenames Matching a Pattern

19.9.1 Problem

You want to find all filenames that match a pattern.

19.9.2 Solution

If your pattern is a regular expression, read each file from the directory and test the name with preg_match( ):
$d = dir('/tmp') or die($php_errormsg);
while (false !== ($f = $d->read())) {
    // only match alphabetic names
    if (preg_match('/^[a-zA-Z]+$/',$f)) {
        print "$f\n";
    }
}
$d->close();

19.9.3 Discussion

If your pattern is a shell glob (e.g., *.*), use the backtick operator with ls (Unix) or dir (Windows) to get the matching filenames. For Unix:
$files = explode("\n",`ls -1 *.gif`);
foreach ($files as $file) {
  print "$b\n";
}
For Windows:
$files = explode("\n",`dir /b *.gif`);
foreach ($files as $file) {
  print "$b\n";
}

PHP : Directories - [19.10] Processing All Files in a Directory

19.10.1 Problem

You want to do something to all the files in a directory and in any subdirectories.

19.10.2 Solution

Use the pc_process_dir( ) function, shown in Example 19-1, which returns a list of all files in and beneath a given directory.
Example 19-1. pc_process_dir( )
function pc_process_dir($dir_name,$max_depth = 10,$depth = 0) {
    if ($depth >= $max_depth) {
        error_log("Reached max depth $max_depth in $dir_name.");
        return false;
    }
    $subdirectories = array();
    $files = array();
    if (is_dir($dir_name) && is_readable($dir_name)) {
        $d = dir($dir_name);
        while (false !== ($f = $d->read())) {
            // skip . and .. 
            if (('.' == $f) || ('..' == $f)) {
                continue;
            }
            if (is_dir("$dir_name/$f")) {
                array_push($subdirectories,"$dir_name/$f");
            } else {
                array_push($files,"$dir_name/$f");
            }
        }
        $d->close();
        foreach ($subdirectories as $subdirectory) {
            $files = array_merge($files,pc_process_dir($subdirectory,$max_depth,$depth+1));
        }
    } 
    return $files;
}

19.10.3 Discussion

Here's an example: if /tmp contains the files a and b, as well as the directory c, and /tmp/c contains files d and e, pc_process_dir('/tmp') returns an array with elements /tmp/a, /tmp/b, /tmp/c/d, and /tmp/c/e. To perform an operation on each file, iterate through the array:
$files = pc_process_dir('/tmp');
foreach ($files as $file) {
  print "$file was last accessed at ".strftime('%c',fileatime($file))."\n";
}
Instead of returning an array of files, you can also write a function that processes them as it finds them. The pc_process_dir2( ) function, shown in Example 19-2, does this by taking an additional argument, the name of the function to call on each file found.
Example 19-2. pc_process_dir2( )
function pc_process_dir2($dir_name,$func_name,$max_depth = 10,$depth = 0) {
    if ($depth >= $max_depth) {
        error_log("Reached max depth $max_depth in $dir_name.");
        return false;
    }
    $subdirectories = array();
    $files = array();
    if (is_dir($dir_name) && is_readable($dir_name)) {
        $d = dir($dir_name);
        while (false !== ($f = $d->read())) {
            // skip . and ..
            if (('.' == $f) || ('..' == $f)) {
                continue;
            }
            if (is_dir("$dir_name/$f")) {
                array_push($subdirectories,"$dir_name/$f");
            } else {
                $func_name("$dir_name/$f");
            }
        }
        $d->close();
        foreach ($subdirectories as $subdirectory) {
            pc_process_dir2($subdirectory,$func_name,$max_depth,$depth+1);
        }
    } 
}
The pc_process_dir2( ) function doesn't return a list of directories; instead, the function $func_name is called with the file as its argument. Here's how to print out the last access times:
function printatime($file) {
    print "$file was last accessed at ".strftime('%c',fileatime($file))."\n";
}

pc_process_dir2('/tmp','printatime');
Although the two functions produce the same results, the second version uses less memory because potentially large arrays of files aren't passed around.
The pc_process_dir( ) and pc_process_dir2( ) functions use a breadth-first search . In this type of search, the functions handle all the files in the current directory; then they recurse into each subdirectory. In a depth-first search , they recurse into a subdirectory as soon as the subdirectory is found, whether or not there are files remaining in the current directory. The breadth-first search is more memory efficient; each pointer to the current directory is closed (with $d->close( )) before the function recurses into subdirectories, so there's only one directory pointer open at a time.
Because is_dir( ) returns true when passed a symbolic link that points to a directory, both versions of the function follow symbolic links as they traverse down the directory tree. If you don't want to follow links, change the line:
if (is_dir("$dir_name/$f")) {
to:
if (is_dir("$dir_name/$f") && (! is_link("$dir_name/$f"))) {

PHP : Directories - [19.11] Making New Directories

19.11.1 Problem

You want to create a directory.

19.11.2 Solution

Use mkdir( ):
mkdir('/tmp/apples',0777) or die($php_errormsg);

19.11.3 Discussion

The second argument to mkdir( ) is the permission mode for the new directory, which must be an octal number. The current umask is taken away from this permission value to create the permissions for the new directory. So, if the current umask is 0002, calling mkdir('/tmp/apples',0777) sets the permissions on the resulting directory to 0775 (user and group can read, write, and execute; others can only read and execute).
PHP's built-in mkdir( ) can make a directory only if its parent exists. For example, if /tmp/a doesn't exist, you can't create /tmp/a/b until /tmp/a is created. To create a directory and its parents, you have two choices: you can call your system's mkdir program, or you can use the pc_mkdir_parents( ) function, shown in Example 19-3. To use your system's mkdir program, on Unix, use this:
system('/bin/mkdir -p '.escapeshellarg($directory));
On Windows do:
system('mkdir '.escapeshellarg($directory));
You can also use the pc_mkdir_parents( ) function shown in Example 19-3.
Example 19-3. pc_mkdir_parents( )
function pc_mkdir_parents($d,$umask = 0777) {
    $dirs = array($d);
    $d = dirname($d);
    $last_dirname = '';
    while($last_dirname != $d) { 
        array_unshift($dirs,$d);
        $last_dirname = $d;
        $d = dirname($d);
    }

    foreach ($dirs as $dir) {
        if (! file_exists($dir)) {
            if (! mkdir($dir,$umask)) {
                error_log("Can't make directory: $dir");
                return false;
            }
        } elseif (! is_dir($dir)) {
            error_log("$dir is not a directory");
            return false;
        }
    }
    return true;
}
For example:
pc_mkdir_parents('/usr/local/upload/test',0777);

PHP : Directories - [19.12] Removing a Directory and Its Contents

19.12.1 Problem

You want to remove a directory and all of its contents, including subdirectories and their contents.

19.12.2 Solution

On Unix, use rm:
$directory = escapeshellarg($directory);
exec("rm -rf $directory");
On Windows, use rmdir:
$directory = escapeshellarg($directory);
exec("rmdir /s /q $directory");

19.12.3 Discussion

Removing files, obviously, can be dangerous. Be sure to escape $directory with escapeshellarg( ) so that you don't delete unintended files.
Because PHP's built-in directory removal function, rmdir( ) , works only on empty directories, and unlink( ) can't accept shell wildcards, calling a system program is much easier than recursively looping through all files in a directory, removing them, and then removing each directory. If an external utility isn't available, however, you can modify the pc_process_dir( ) function from Section 19.10 to remove each subdirectory.

PHP : Directories - [19.13] Program: Web Server Directory Listing

The web-ls.php program shown in Example 19-4 provides a view of the files inside your web server's document root, formatted like the output of the Unix command ls. Filenames are linked so that you can download each file, and directory names are linked so that you can browse in each directory, as shown in Figure 19-1.
Figure 19-1. Web listing
Most lines in Example 19-4 are devoted to building an easy-to-read representation of the file's permissions, but the guts of the program are in the while loop at the end. The $d->read( ) method gets the name of each file in the directory. Then, lstat( ) retrieves information about that file, and printf( ) prints out the formatted information about that file.
The mode_string( ) functions and the constants it uses turn the octal representation of a file's mode (e.g., 35316) into an easier-to-read string (e.g., -rwsrw-r--).
Example 19-4. web-ls.php
/* Bit masks for determining file permissions and type. The names and values
 * listed below are POSIX-compliant, individual systems may have their own 
 * extensions.
 */

define('S_IFMT',0170000);   // mask for all types 
define('S_IFSOCK',0140000); // type: socket 
define('S_IFLNK',0120000);  // type: symbolic link 
define('S_IFREG',0100000);  // type: regular file 
define('S_IFBLK',0060000);  // type: block device 
define('S_IFDIR',0040000);  // type: directory 
define('S_IFCHR',0020000);  // type: character device 
define('S_IFIFO',0010000);  // type: fifo 
define('S_ISUID',0004000);  // set-uid bit 
define('S_ISGID',0002000);  // set-gid bit 
define('S_ISVTX',0001000);  // sticky bit 
define('S_IRWXU',00700);    // mask for owner permissions 
define('S_IRUSR',00400);    // owner: read permission 
define('S_IWUSR',00200);    // owner: write permission 
define('S_IXUSR',00100);    // owner: execute permission 
define('S_IRWXG',00070);    // mask for group permissions 
define('S_IRGRP',00040);    // group: read permission 
define('S_IWGRP',00020);    // group: write permission 
define('S_IXGRP',00010);    // group: execute permission 
define('S_IRWXO',00007);    // mask for others permissions 
define('S_IROTH',00004);    // others: read permission 
define('S_IWOTH',00002);    // others: write permission 
define('S_IXOTH',00001);    // others: execute permission 

/* mode_string() is a helper function that takes an octal mode and returns
 * a ten character string representing the file type and permissions that
 * correspond to the octal mode. This is a PHP version of the mode_string()
 * function in the GNU fileutils package.
 */

PHP : Directories - [19.14] Program: Site Search

You can use site-search.php, shown in Example 19-5, as a search engine for a small-to-medium size, file-based site.
The program looks for a search term (in $_REQUEST['term']) in all files within a specified set of directories under the document root. Those directories are set in $search_dirs. It also recurses into subdirectories and follows symbolic links but keeps track of which files and directories it has seen so that it doesn't get caught in an endless loop.
If any pages are found that contain the search term, it prints list of links to those pages, alphabetically ordered by each page's title. If a page doesn't have a title (between the <title> and </title> tags), the page's relative URI from the document root is used.
The program looks for the search term between the <body> and </body> tags in each file. If you have a lot of text in your pages inside <body> tags that you want to exclude from the search, surround the text that should be searched with specific HTML comments and then modify $body_regex to look for those tags instead. Say, for example, if your page looks like this:
<body>

// Some HTML for menus, headers, etc.

<!-- search-start -->

<h1>Aliens Invade Earth</h1>

<h3>by H.G. Wells</h3>

<p>Aliens invaded earth today. Uh Oh.</p>

// More of the story

<!-- search-end -->

// Some HTML for footers, etc.

</body>
To match the search term against just the title, author, and story inside the HTML comments, change $body_regex to:
$body_regex = '#<!-- search-start -->(.*' . preg_quote($_REQUEST['term'],'#'). 
              '.*)<!-- search-end -->#Sis';
If you don't want the search term to match text that's inside HTML or PHP tags in your pages, add a call to strip_tags( ) to the code that loads the contents of the file for searching:
// load the contents of the file into $file
$file = strip_tags(join('',file($path)));
Example 19-5. site-search.php

function pc_search_dir($dir) { 
    global $body_regex,$title_regex,$seen;

    // array to hold pages that match
    $pages = array();

    // array to hold directories to recurse into
    $dirs = array();

    // mark this directory as seen so we don't look in it again
    $seen[realpath($dir)] = true;
    
    // if we can get a directory handle for this directory
    if (is_readable($dir) && ($d = dir($dir))) {
        // get each file name in the directory
        while (false !== ($f = $d->read())) {
            // build the full path of the file
            $path = $d->path.'/'.$f;
            // if it's a regular file and we can read it
            if (is_file($path) && is_readable($path)) {
                
                $realpath = realpath($path);
                // if we've seen this file already,
                if ($seen[$realpath]) {
                    // then skip it
                    continue;
                } else {

PHP : Client-Side PHP - [20.8] Responding to User Actions

20.8.1 Problem

You want to do something when a user clicks a button, chooses an item from a dropdown list, or otherwise interacts with a GUI widget.

20.8.2 Solution

Write a callback function and then associate the callback function with a signal using the connect( ) method:
// create the window
$window = &new GtkWindow();

// create a button with the current time as its label
$button = &new GtkButton(strftime('%c'));

// set the update_time() function as the callback for the "clicked" signal
$button->connect('clicked','update_time');

function update_time($b) {
    // the button's text is in a child of the button - a label widget
    $b_label = $b->child;
    // set the label text to the current time
    $b_label->set_text(strftime('%c'));
}

// add the button to the window
$window->add($button);

// display the window
$window->show_all();

// necessary so that the program exits properly
function shutdown() { gtk::main_quit(); }
$window->connect('destroy','shutdown');

// start GTK's signal handling loop
gtk::main();

20.8.3 Discussion

The code in the Solution displays a window with a button in it. On the button is the time, rendered by strftime('%c'). When the button is clicked, its label is updated with the current time.
The update_time( ) function is called each time the button is clicked because $button->connect('clicked','update_time') makes update_time( ) the callback function associated with the button's clicked signal. The first argument to the callback function is the widget whose signal triggered the call as its first argument. In this case, that means that $button is passed to update_time( ). You tell connect( ) to pass additional arguments to the callback by passing them to connect( ) after the callback function name. This example displays a window with a button and a separate label. The time is printed in the label and updated when the button is clicked:
// create the window
$window = &new GtkWindow();

// create a container for the label and the button
$container = &new GtkVBox();

// create a label showing the time
$label = &new GtkLabel(strftime('%c'));

// add the label to the container
$container->pack_start($label);

// create a button
$button = &new GtkButton('Update Time');

/* set the update_time() function as the callback for the "clicked" signal
   and pass $label to the callback */
$button->connect('clicked','update_time',$label);

function update_time($b,$lb) {
    $lb->set_text(strftime('%c'));
}

// add the button to the container
$container->pack_start($button);

// add the container to the window
$window->add($container);

// display the window
$window->show_all();

// necessary so that the program exits properly
function shutdown() { gtk::main_quit(); }
$window->connect('destroy','shutdown');

// start GTK's signal handling loop
gtk::main();
Because $label is on the list of arguments passed to $button->connect( ), $label is passed to update_time( ). Calling set_text( ) on $label updates the text displayed in the label.