3 * @copyright Copyright (C) 2005-2009 eZ Systems AS. All rights reserved.
4 * @license http://ez.no/licenses/new_bsd New BSD License
11 * Provides a selection of static independent methods to provide functionality
12 * for file and file system handling.
14 * This example shows how to use the findRecursive method:
17 * // lists all the files under /etc (including subdirectories) that end in
19 * $confFiles = ezcBaseFile::findRecursive( "/etc", array( '@\.conf$@' ) );
21 * // lists all autoload files in the components source tree and excludes the
22 * // ones in the autoload subdirectory. Statistics are returned in the $stats
23 * // variable which is passed by reference.
24 * $files = ezcBaseFile::findRecursive(
25 * "/dat/dev/ezcomponents",
26 * array( '@src/.*_autoload.php$@' ),
27 * array( '@/autoload/@' ),
31 * // lists all binaries in /bin except the ones starting with a "g"
32 * $data = ezcBaseFile::findRecursive( "/bin", array(), array( '@^/bin/g@' ) );
43 * This is the callback used by findRecursive to collect data.
45 * This callback method works together with walkRecursive() and is called
46 * for every file/and or directory. The $context is a callback specific
47 * container in which data can be stored and shared between the different
48 * calls to the callback function. The walkRecursive() function also passes
49 * in the full absolute directory in $sourceDir, the filename in $fileName
50 * and file information (such as size, modes, types) as an array as
51 * returned by PHP's stat() in the $fileInfo parameter.
53 * @param ezcBaseFileFindContext $context
54 * @param string $sourceDir
55 * @param string $fileName
56 * @param array(stat) $fileInfo
58 static protected function findRecursiveCallback( ezcBaseFileFindContext
$context, $sourceDir, $fileName, $fileInfo )
60 // ignore if we have a directory
61 if ( $fileInfo['mode'] & 0x4000 )
66 // update the statistics
67 $context->elements
[] = $sourceDir . DIRECTORY_SEPARATOR
. $fileName;
69 $context->size +
= $fileInfo['size'];
73 * Walks files and directories recursively on a file system
75 * This method walks over a directory and calls a callback from every file
76 * and directory it finds. You can use $includeFilters to include only
77 * specific files, and $excludeFilters to exclude certain files from being
78 * returned. The function will always go into subdirectories even if the
79 * entry would not have passed the filters.
81 * The callback is passed in the $callback parameter, and the
82 * $callbackContext will be send to the callback function/method as
83 * parameter so that you can store data in there that persists with all the
84 * calls and recursive calls to this method. It's up to the callback method
85 * to do something useful with this. The callback function's parameters are
89 * <li>ezcBaseFileFindContext $context</li>
90 * <li>string $sourceDir</li>
91 * <li>string $fileName</li>
92 * <li>array(stat) $fileInfo</li>
95 * See {@see findRecursiveCallback()} for an example of a callback function.
97 * Filters are regular expressions and are therefore required to have
98 * starting and ending delimiters. The Perl Compatible syntax is used as
99 * regular expression language.
101 * @param string $sourceDir
102 * @param array(string) $includeFilters
103 * @param array(string) $excludeFilters
104 * @param callback $callback
105 * @param mixed $callbackContext
107 * @throws ezcBaseFileNotFoundException if the $sourceDir directory is not
108 * a directory or does not exist.
109 * @throws ezcBaseFilePermissionException if the $sourceDir directory could
110 * not be opened for reading.
113 static public function walkRecursive( $sourceDir, array $includeFilters = array(), array $excludeFilters = array(), $callback, &$callbackContext )
115 if ( !is_dir( $sourceDir ) )
117 throw new ezcBaseFileNotFoundException( $sourceDir, 'directory' );
120 $d = @dir
( $sourceDir );
123 throw new ezcBaseFilePermissionException( $sourceDir, ezcBaseFileException
::READ
);
126 while ( ( $entry = $d->read() ) !== false )
128 if ( $entry == '.' ||
$entry == '..' )
133 $fileInfo = @stat
( $sourceDir . DIRECTORY_SEPARATOR
. $entry );
136 $fileInfo = array( 'size' => 0, 'mode' => 0 );
139 if ( $fileInfo['mode'] & 0x4000 )
141 // We need to ignore the Permission exceptions here as it can
142 // be normal that a directory can not be accessed. We only need
143 // the exception if the top directory could not be read.
146 call_user_func_array( $callback, array( $callbackContext, $sourceDir, $entry, $fileInfo ) );
147 $subList = self
::walkRecursive( $sourceDir . DIRECTORY_SEPARATOR
. $entry, $includeFilters, $excludeFilters, $callback, $callbackContext );
148 $elements = array_merge( $elements, $subList );
150 catch ( ezcBaseFilePermissionException
$e )
156 // By default a file is included in the return list
158 // Iterate over the $includeFilters and prohibit the file from
159 // being returned when atleast one of them does not match
160 foreach ( $includeFilters as $filter )
162 if ( !preg_match( $filter, $sourceDir . DIRECTORY_SEPARATOR
. $entry ) )
168 // Iterate over the $excludeFilters and prohibit the file from
169 // being returns when atleast one of them matches
170 foreach ( $excludeFilters as $filter )
172 if ( preg_match( $filter, $sourceDir . DIRECTORY_SEPARATOR
. $entry ) )
179 // If everything's allright, call the callback and add the
180 // entry to the elements array
183 call_user_func( $callback, $callbackContext, $sourceDir, $entry, $fileInfo );
184 $elements[] = $sourceDir . DIRECTORY_SEPARATOR
. $entry;
193 * Finds files recursively on a file system
195 * With this method you can scan the file system for files. You can use
196 * $includeFilters to include only specific files, and $excludeFilters to
197 * exclude certain files from being returned. The function will always go
198 * into subdirectories even if the entry would not have passed the filters.
199 * It uses the {@see walkRecursive()} method to do the actually recursion.
201 * Filters are regular expressions and are therefore required to have
202 * starting and ending delimiters. The Perl Compatible syntax is used as
203 * regular expression language.
205 * If you pass an empty array to the $statistics argument, the function
206 * will in details about the number of files found into the 'count' array
207 * element, and the total filesize in the 'size' array element. Because this
208 * argument is passed by reference, you *have* to pass a variable and you
209 * can not pass a constant value such as "array()".
211 * @param string $sourceDir
212 * @param array(string) $includeFilters
213 * @param array(string) $excludeFilters
214 * @param array() $statistics
216 * @throws ezcBaseFileNotFoundException if the $sourceDir directory is not
217 * a directory or does not exist.
218 * @throws ezcBaseFilePermissionException if the $sourceDir directory could
219 * not be opened for reading.
222 static public function findRecursive( $sourceDir, array $includeFilters = array(), array $excludeFilters = array(), &$statistics = null )
224 // init statistics array
225 if ( !is_array( $statistics ) ||
!array_key_exists( 'size', $statistics ) ||
!array_key_exists( 'count', $statistics ) )
227 $statistics['size'] = 0;
228 $statistics['count'] = 0;
231 // create the context, and then start walking over the array
232 $context = new ezcBaseFileFindContext
;
233 self
::walkRecursive( $sourceDir, $includeFilters, $excludeFilters, array( 'ezcBaseFile', 'findRecursiveCallback' ), $context );
235 // collect the statistics
236 $statistics['size'] = $context->size
;
237 $statistics['count'] = $context->count
;
239 // return the found and pattern-matched files
240 sort( $context->elements
);
241 return $context->elements
;
246 * Removes files and directories recursively from a file system
248 * This method recursively removes the $directory and all its contents.
249 * You should be <b>extremely</b> careful with this method as it has the
250 * potential to erase everything that the current user has access to.
252 * @param string $directory
254 static public function removeRecursive( $directory )
256 $sourceDir = realpath( $directory );
259 throw new ezcBaseFileNotFoundException( $directory, 'directory' );
261 $d = @dir
( $sourceDir );
264 throw new ezcBaseFilePermissionException( $directory, ezcBaseFileException
::READ
);
266 // check if we can remove the dir
267 $parentDir = realpath( $directory . DIRECTORY_SEPARATOR
. '..' );
268 if ( !is_writable( $parentDir ) )
270 throw new ezcBaseFilePermissionException( $parentDir, ezcBaseFileException
::WRITE
);
272 // loop over contents
273 while ( ( $entry = $d->read() ) !== false )
275 if ( $entry == '.' ||
$entry == '..' )
280 if ( is_dir( $sourceDir . DIRECTORY_SEPARATOR
. $entry ) )
282 self
::removeRecursive( $sourceDir . DIRECTORY_SEPARATOR
. $entry );
286 if ( @unlink
( $sourceDir . DIRECTORY_SEPARATOR
. $entry ) === false )
288 throw new ezcBaseFilePermissionException( $directory . DIRECTORY_SEPARATOR
. $entry, ezcBaseFileException
::REMOVE
);
297 * Recursively copy a file or directory.
299 * Recursively copy a file or directory in $source to the given
300 * destination. If a depth is given, the operation will stop, if the given
301 * recursion depth is reached. A depth of -1 means no limit, while a depth
302 * of 0 means, that only the current file or directory will be copied,
303 * without any recursion.
305 * You may optionally define modes used to create files and directories.
307 * @throws ezcBaseFileNotFoundException
308 * If the $sourceDir directory is not a directory or does not exist.
309 * @throws ezcBaseFilePermissionException
310 * If the $sourceDir directory could not be opened for reading, or the
311 * destination is not writeable.
313 * @param string $source
314 * @param string $destination
316 * @param int $dirMode
317 * @param int $fileMode
320 static public function copyRecursive( $source, $destination, $depth = -1, $dirMode = 0775, $fileMode = 0664 )
322 // Check if source file exists at all.
323 if ( !is_file( $source ) && !is_dir( $source ) )
325 throw new ezcBaseFileNotFoundException( $source );
328 // Destination file should NOT exist
329 if ( is_file( $destination ) ||
is_dir( $destination ) )
331 throw new ezcBaseFilePermissionException( $destination, ezcBaseFileException
::WRITE
);
334 // Skip non readable files in source directory
335 if ( !is_readable( $source ) )
341 if ( is_dir( $source ) )
343 mkdir( $destination );
344 // To ignore umask, umask() should not be changed with
345 // multithreaded servers...
346 chmod( $destination, $dirMode );
348 elseif ( is_file( $source ) )
350 copy( $source, $destination );
351 chmod( $destination, $fileMode );
354 if ( ( $depth === 0 ) ||
355 ( !is_dir( $source ) ) )
357 // Do not recurse (any more)
362 $dh = opendir( $source );
363 while ( ( $file = readdir( $dh ) ) !== false )
365 if ( ( $file === '.' ) ||
372 $source . '/' . $file,
373 $destination . '/' . $file,
374 $depth - 1, $dirMode, $fileMode
380 * Calculates the relative path of the file/directory '$path' to a given
383 * $path and $base should be fully absolute paths. This function returns the
384 * answer of "How do I go from $base to $path". If the $path and $base are
385 * the same path, the function returns '.'. This method does not touch the
388 * @param string $path
389 * @param string $base
392 static public function calculateRelativePath( $path, $base )
394 // Sanitize the paths to use the correct directory separator for the platform
395 $path = strtr( $path, '\\/', DIRECTORY_SEPARATOR
. DIRECTORY_SEPARATOR
);
396 $base = strtr( $base, '\\/', DIRECTORY_SEPARATOR
. DIRECTORY_SEPARATOR
);
398 $base = explode( DIRECTORY_SEPARATOR
, $base );
399 $path = explode( DIRECTORY_SEPARATOR
, $path );
401 // If the paths are the same we return
402 if ( $base === $path )
409 $pathPart = array_shift( $path );
410 $basePart = array_shift( $base );
411 while ( $pathPart == $basePart )
413 $pathPart = array_shift( $path );
414 $basePart = array_shift( $base );
417 if ( $pathPart != null )
419 array_unshift( $path, $pathPart );
421 if ( $basePart != null )
423 array_unshift( $base, $basePart );
426 $result = str_repeat( '..' . DIRECTORY_SEPARATOR
, count( $base ) );
427 // prevent a trailing DIRECTORY_SEPARATOR in case there is only a ..
428 if ( count( $path ) == 0 )
430 $result = substr( $result, 0, -strlen( DIRECTORY_SEPARATOR
) );
432 $result .= join( DIRECTORY_SEPARATOR
, $path );
438 * Returns whether the passed $path is an absolute path, giving the current $os.
440 * With the $os parameter you can tell this function to use the semantics
441 * for a different operating system to determine whether a path is
442 * absolute. The $os argument defaults to the OS that the script is running
445 * @param string $path
449 public static function isAbsolutePath( $path, $os = null )
453 $os = ezcBaseFeatures
::os();
456 // Stream wrapper like phar can also be considered absolute paths
457 if ( preg_match( '(^[a-z]{3,}://)S', $path ) )
465 // Sanitize the paths to use the correct directory separator for the platform
466 $path = strtr( $path, '\\/', '\\\\' );
468 // Absolute paths with drive letter: X:\
469 if ( preg_match( '@^[A-Z]:\\\\@i', $path ) )
474 // Absolute paths with network paths: \\server\share\
475 if ( preg_match( '@^\\\\\\\\[A-Z]+\\\\[^\\\\]@i', $path ) )
484 // Sanitize the paths to use the correct directory separator for the platform
485 $path = strtr( $path, '\\/', '//' );
487 if ( $path[0] == '/' )