commiting uncommited changes on live site
[weblabels.fsf.org.git] / crm.fsf.org / 20131203 / files / sites / all / modules-old / civicrm / packages / ezc / Base / src / file.php
1 <?php
2 /**
3 * @copyright Copyright (C) 2005-2009 eZ Systems AS. All rights reserved.
4 * @license http://ez.no/licenses/new_bsd New BSD License
5 * @version 1.7
6 * @filesource
7 * @package Base
8 */
9
10 /**
11 * Provides a selection of static independent methods to provide functionality
12 * for file and file system handling.
13 *
14 * This example shows how to use the findRecursive method:
15 * <code>
16 * <?php
17 * // lists all the files under /etc (including subdirectories) that end in
18 * // .conf
19 * $confFiles = ezcBaseFile::findRecursive( "/etc", array( '@\.conf$@' ) );
20 *
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/@' ),
28 * $stats
29 * );
30 *
31 * // lists all binaries in /bin except the ones starting with a "g"
32 * $data = ezcBaseFile::findRecursive( "/bin", array(), array( '@^/bin/g@' ) );
33 * ?>
34 * </code>
35 *
36 * @package Base
37 * @version 1.7
38 * @mainclass
39 */
40 class ezcBaseFile
41 {
42 /**
43 * This is the callback used by findRecursive to collect data.
44 *
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.
52 *
53 * @param ezcBaseFileFindContext $context
54 * @param string $sourceDir
55 * @param string $fileName
56 * @param array(stat) $fileInfo
57 */
58 static protected function findRecursiveCallback( ezcBaseFileFindContext $context, $sourceDir, $fileName, $fileInfo )
59 {
60 // ignore if we have a directory
61 if ( $fileInfo['mode'] & 0x4000 )
62 {
63 return;
64 }
65
66 // update the statistics
67 $context->elements[] = $sourceDir . DIRECTORY_SEPARATOR . $fileName;
68 $context->count++;
69 $context->size += $fileInfo['size'];
70 }
71
72 /**
73 * Walks files and directories recursively on a file system
74 *
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.
80 *
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
86 * in order:
87 *
88 * <ul>
89 * <li>ezcBaseFileFindContext $context</li>
90 * <li>string $sourceDir</li>
91 * <li>string $fileName</li>
92 * <li>array(stat) $fileInfo</li>
93 * </ul>
94 *
95 * See {@see findRecursiveCallback()} for an example of a callback function.
96 *
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.
100 *
101 * @param string $sourceDir
102 * @param array(string) $includeFilters
103 * @param array(string) $excludeFilters
104 * @param callback $callback
105 * @param mixed $callbackContext
106 *
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.
111 * @return array
112 */
113 static public function walkRecursive( $sourceDir, array $includeFilters = array(), array $excludeFilters = array(), $callback, &$callbackContext )
114 {
115 if ( !is_dir( $sourceDir ) )
116 {
117 throw new ezcBaseFileNotFoundException( $sourceDir, 'directory' );
118 }
119 $elements = array();
120 $d = @dir( $sourceDir );
121 if ( !$d )
122 {
123 throw new ezcBaseFilePermissionException( $sourceDir, ezcBaseFileException::READ );
124 }
125
126 while ( ( $entry = $d->read() ) !== false )
127 {
128 if ( $entry == '.' || $entry == '..' )
129 {
130 continue;
131 }
132
133 $fileInfo = @stat( $sourceDir . DIRECTORY_SEPARATOR . $entry );
134 if ( !$fileInfo )
135 {
136 $fileInfo = array( 'size' => 0, 'mode' => 0 );
137 }
138
139 if ( $fileInfo['mode'] & 0x4000 )
140 {
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.
144 try
145 {
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 );
149 }
150 catch ( ezcBaseFilePermissionException $e )
151 {
152 }
153 }
154 else
155 {
156 // By default a file is included in the return list
157 $ok = true;
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 )
161 {
162 if ( !preg_match( $filter, $sourceDir . DIRECTORY_SEPARATOR . $entry ) )
163 {
164 $ok = false;
165 break;
166 }
167 }
168 // Iterate over the $excludeFilters and prohibit the file from
169 // being returns when atleast one of them matches
170 foreach ( $excludeFilters as $filter )
171 {
172 if ( preg_match( $filter, $sourceDir . DIRECTORY_SEPARATOR . $entry ) )
173 {
174 $ok = false;
175 break;
176 }
177 }
178
179 // If everything's allright, call the callback and add the
180 // entry to the elements array
181 if ( $ok )
182 {
183 call_user_func( $callback, $callbackContext, $sourceDir, $entry, $fileInfo );
184 $elements[] = $sourceDir . DIRECTORY_SEPARATOR . $entry;
185 }
186 }
187 }
188 sort( $elements );
189 return $elements;
190 }
191
192 /**
193 * Finds files recursively on a file system
194 *
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.
200 *
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.
204 *
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()".
210 *
211 * @param string $sourceDir
212 * @param array(string) $includeFilters
213 * @param array(string) $excludeFilters
214 * @param array() $statistics
215 *
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.
220 * @return array
221 */
222 static public function findRecursive( $sourceDir, array $includeFilters = array(), array $excludeFilters = array(), &$statistics = null )
223 {
224 // init statistics array
225 if ( !is_array( $statistics ) || !array_key_exists( 'size', $statistics ) || !array_key_exists( 'count', $statistics ) )
226 {
227 $statistics['size'] = 0;
228 $statistics['count'] = 0;
229 }
230
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 );
234
235 // collect the statistics
236 $statistics['size'] = $context->size;
237 $statistics['count'] = $context->count;
238
239 // return the found and pattern-matched files
240 sort( $context->elements );
241 return $context->elements;
242 }
243
244
245 /**
246 * Removes files and directories recursively from a file system
247 *
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.
251 *
252 * @param string $directory
253 */
254 static public function removeRecursive( $directory )
255 {
256 $sourceDir = realpath( $directory );
257 if ( !$sourceDir )
258 {
259 throw new ezcBaseFileNotFoundException( $directory, 'directory' );
260 }
261 $d = @dir( $sourceDir );
262 if ( !$d )
263 {
264 throw new ezcBaseFilePermissionException( $directory, ezcBaseFileException::READ );
265 }
266 // check if we can remove the dir
267 $parentDir = realpath( $directory . DIRECTORY_SEPARATOR . '..' );
268 if ( !is_writable( $parentDir ) )
269 {
270 throw new ezcBaseFilePermissionException( $parentDir, ezcBaseFileException::WRITE );
271 }
272 // loop over contents
273 while ( ( $entry = $d->read() ) !== false )
274 {
275 if ( $entry == '.' || $entry == '..' )
276 {
277 continue;
278 }
279
280 if ( is_dir( $sourceDir . DIRECTORY_SEPARATOR . $entry ) )
281 {
282 self::removeRecursive( $sourceDir . DIRECTORY_SEPARATOR . $entry );
283 }
284 else
285 {
286 if ( @unlink( $sourceDir . DIRECTORY_SEPARATOR . $entry ) === false )
287 {
288 throw new ezcBaseFilePermissionException( $directory . DIRECTORY_SEPARATOR . $entry, ezcBaseFileException::REMOVE );
289 }
290 }
291 }
292 $d->close();
293 rmdir( $sourceDir );
294 }
295
296 /**
297 * Recursively copy a file or directory.
298 *
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.
304 *
305 * You may optionally define modes used to create files and directories.
306 *
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.
312 *
313 * @param string $source
314 * @param string $destination
315 * @param int $depth
316 * @param int $dirMode
317 * @param int $fileMode
318 * @return void
319 */
320 static public function copyRecursive( $source, $destination, $depth = -1, $dirMode = 0775, $fileMode = 0664 )
321 {
322 // Check if source file exists at all.
323 if ( !is_file( $source ) && !is_dir( $source ) )
324 {
325 throw new ezcBaseFileNotFoundException( $source );
326 }
327
328 // Destination file should NOT exist
329 if ( is_file( $destination ) || is_dir( $destination ) )
330 {
331 throw new ezcBaseFilePermissionException( $destination, ezcBaseFileException::WRITE );
332 }
333
334 // Skip non readable files in source directory
335 if ( !is_readable( $source ) )
336 {
337 return;
338 }
339
340 // Copy
341 if ( is_dir( $source ) )
342 {
343 mkdir( $destination );
344 // To ignore umask, umask() should not be changed with
345 // multithreaded servers...
346 chmod( $destination, $dirMode );
347 }
348 elseif ( is_file( $source ) )
349 {
350 copy( $source, $destination );
351 chmod( $destination, $fileMode );
352 }
353
354 if ( ( $depth === 0 ) ||
355 ( !is_dir( $source ) ) )
356 {
357 // Do not recurse (any more)
358 return;
359 }
360
361 // Recurse
362 $dh = opendir( $source );
363 while ( ( $file = readdir( $dh ) ) !== false )
364 {
365 if ( ( $file === '.' ) ||
366 ( $file === '..' ) )
367 {
368 continue;
369 }
370
371 self::copyRecursive(
372 $source . '/' . $file,
373 $destination . '/' . $file,
374 $depth - 1, $dirMode, $fileMode
375 );
376 }
377 }
378
379 /**
380 * Calculates the relative path of the file/directory '$path' to a given
381 * $base path.
382 *
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
386 * filesystem.
387 *
388 * @param string $path
389 * @param string $base
390 * @return string
391 */
392 static public function calculateRelativePath( $path, $base )
393 {
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 );
397
398 $base = explode( DIRECTORY_SEPARATOR, $base );
399 $path = explode( DIRECTORY_SEPARATOR, $path );
400
401 // If the paths are the same we return
402 if ( $base === $path )
403 {
404 return '.';
405 }
406
407 $result = '';
408
409 $pathPart = array_shift( $path );
410 $basePart = array_shift( $base );
411 while ( $pathPart == $basePart )
412 {
413 $pathPart = array_shift( $path );
414 $basePart = array_shift( $base );
415 }
416
417 if ( $pathPart != null )
418 {
419 array_unshift( $path, $pathPart );
420 }
421 if ( $basePart != null )
422 {
423 array_unshift( $base, $basePart );
424 }
425
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 )
429 {
430 $result = substr( $result, 0, -strlen( DIRECTORY_SEPARATOR ) );
431 }
432 $result .= join( DIRECTORY_SEPARATOR, $path );
433
434 return $result;
435 }
436
437 /**
438 * Returns whether the passed $path is an absolute path, giving the current $os.
439 *
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
443 * on.
444 *
445 * @param string $path
446 * @param string $os
447 * @return bool
448 */
449 public static function isAbsolutePath( $path, $os = null )
450 {
451 if ( $os === null )
452 {
453 $os = ezcBaseFeatures::os();
454 }
455
456 // Stream wrapper like phar can also be considered absolute paths
457 if ( preg_match( '(^[a-z]{3,}://)S', $path ) )
458 {
459 return true;
460 }
461
462 switch ( $os )
463 {
464 case 'Windows':
465 // Sanitize the paths to use the correct directory separator for the platform
466 $path = strtr( $path, '\\/', '\\\\' );
467
468 // Absolute paths with drive letter: X:\
469 if ( preg_match( '@^[A-Z]:\\\\@i', $path ) )
470 {
471 return true;
472 }
473
474 // Absolute paths with network paths: \\server\share\
475 if ( preg_match( '@^\\\\\\\\[A-Z]+\\\\[^\\\\]@i', $path ) )
476 {
477 return true;
478 }
479 break;
480 case 'Mac':
481 case 'Linux':
482 case 'FreeBSD':
483 default:
484 // Sanitize the paths to use the correct directory separator for the platform
485 $path = strtr( $path, '\\/', '//' );
486
487 if ( $path[0] == '/' )
488 {
489 return true;
490 }
491 }
492 return false;
493 }
494 }
495 ?>