]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/FileFinder.php
Add function cmp_by_count
[SourceForge/phpwiki.git] / lib / FileFinder.php
1 <?php
2
3 require_once(dirname(__FILE__) . '/stdlib.php');
4
5 /**
6  * A class for finding files.
7  *
8  * This should really provided by pear. We don't want really to mess around
9  * with all the lousy systems. (WindowsNT, Win95, Mac, ...)
10  * But pear has only System and File, which do nothing.
11  * Anyway, in good PHP style we ignore the rest of the world and try to behave
12  * as on unix only. That means we use / as pathsep in all our constants.
13  */
14 class FileFinder
15 {
16     //var $_pathsep, $_path;
17
18     /**
19      * Constructor.
20      *
21      * @param $path array A list of directories in which to search for files.
22      */
23     function FileFinder($path = false)
24     {
25         $this->_pathsep = $this->_get_syspath_separator();
26         if (!isset($this->_path) and $path === false)
27             $path = $this->_get_include_path();
28         $this->_path = $path;
29     }
30
31     /**
32      * Find file.
33      *
34      * @param $file string File to search for.
35      * @return string The filename (including path), if found, otherwise false.
36      */
37     function findFile($file, $missing_okay = false)
38     {
39         if ($this->_is_abs($file)) {
40             if (file_exists($file))
41                 return $file;
42         } elseif (($dir = $this->_search_path($file))) {
43             return $dir . $this->_use_path_separator($dir) . $file;
44         }
45         return $missing_okay ? false : $this->_not_found($file);
46     }
47
48     /**
49      * Unify used pathsep character.
50      * Accepts array of paths also.
51      * This might not work on Windows95 or FAT volumes. (not tested)
52      */
53     function slashifyPath($path)
54     {
55         return $this->forcePathSlashes($path, $this->_pathsep);
56     }
57
58     /**
59      * Force using '/' as path seperator.
60      */
61     function forcePathSlashes($path, $sep = '/')
62     {
63         if (is_array($path)) {
64             $result = array();
65             foreach ($path as $dir) {
66                 $result[] = $this->forcePathSlashes($dir, $sep);
67             }
68             return $result;
69         } else {
70             if (isWindows() or $this->_isOtherPathsep()) {
71                 if (isWindows()) $from = "\\";
72                 else $from = "\\";
73                 // PHP is stupid enough to use \\ instead of \
74                 if (isWindows()) {
75                     if (substr($path, 0, 2) != '\\\\')
76                         $path = str_replace('\\\\', '\\', $path);
77                     else // UNC paths
78                         $path = '\\\\' . str_replace('\\\\', '\\', substr($path, 2));
79                 }
80                 return strtr($path, $from, $sep);
81             } else
82                 return $path;
83         }
84     }
85
86     /**
87      * Try to include file.
88      *
89      * If file is found in the path, then the files directory is added
90      * to PHP's include_path (if it's not already there.) Then the
91      * file is include_once()'d.
92      *
93      * @param $file string File to include.
94      * @return bool True if file was successfully included.
95      */
96     function includeOnce($file)
97     {
98         if (($ret = @include_once($file)))
99             return $ret;
100
101         if (!$this->_is_abs($file)) {
102             if (($dir = $this->_search_path($file)) && is_file($dir . $this->_pathsep . $file)) {
103                 $this->_append_to_include_path($dir);
104                 return include_once($file);
105             }
106         }
107         return $this->_not_found($file);
108     }
109
110     function _isOtherPathsep()
111     {
112         return $this->_pathsep != '/';
113     }
114
115     /**
116      * The system-dependent path-separator character.
117      * UNIX,WindowsNT,MacOSX: /
118      * Windows95: \
119      * Mac:       :
120      *
121      * @access private
122      * @return string path_separator.
123      */
124     function _get_syspath_separator()
125     {
126         if (!empty($this->_pathsep)) return $this->_pathsep;
127         elseif (isWindowsNT()) return "/"; // we can safely use '/'
128         elseif (isWindows()) return "\\"; // FAT might use '\'
129         // VMS or LispM is really weird, we ignore it.
130         else return '/';
131     }
132
133     /**
134      * The path-separator character of the given path.
135      * Windows accepts "/" also, but gets confused with mixed path_separators,
136      * e.g "C:\Apache\phpwiki/locale/button"
137      * > dir "C:\Apache\phpwiki/locale/button" =>
138      *       Parameterformat nicht korrekt - "locale"
139      * So if there's any '\' in the path, either fix them to '/' (not in Win95 or FAT?)
140      * or use '\' for ours.
141      *
142      * @access private
143      * @return string path_separator.
144      */
145     function _use_path_separator($path)
146     {
147         if (isWindows95()) {
148             if (empty($path)) return "\\";
149             else return (strchr($path, "\\")) ? "\\" : '/';
150         } else {
151             return $this->_get_syspath_separator();
152         }
153     }
154
155     /**
156      * Determine if path is absolute.
157      *
158      * @access private
159      * @param $path string Path.
160      * @return bool True if path is absolute.
161      */
162     function _is_abs($path)
163     {
164         if (substr($path, 0, 1) == '/') {
165             return true;
166         } elseif (isWindows() and preg_match("/^[a-z]:/i", $path)
167             and (substr($path, 2, 1) == "/" or substr($path, 2, 1) == "\\")
168         ) {
169             return true;
170         } else {
171             return false;
172         }
173     }
174
175     /**
176      * Strip ending '/' or '\' from path.
177      *
178      * @access private
179      * @param $path string Path.
180      * @return bool New path (destructive)
181      */
182     function _strip_last_pathchar(&$path)
183     {
184         if (substr($path, -1) == '/' or substr($path, -1) == "\\")
185             $path = substr($path, 0, -1);
186         return $path;
187     }
188
189     /**
190      * Report a "file not found" error.
191      *
192      * @access private
193      * @param $file string Name of missing file.
194      * @return bool false.
195      */
196     function _not_found($file)
197     {
198         if (function_exists("_"))
199             trigger_error(sprintf(_("%s: file not found"), $file), E_USER_ERROR);
200         else
201             trigger_error(sprintf("%s: file not found", $file), E_USER_ERROR);
202         return false;
203     }
204
205     /**
206      * Search our path for a file.
207      *
208      * @access private
209      * @param $file string File to find.
210      * @return string Directory which contains $file, or false.
211      * [5x,44ms]
212      */
213     function _search_path($file)
214     {
215         foreach ($this->_path as $dir) {
216             // ensure we use the same pathsep
217             if ($this->_isOtherPathsep()) {
218                 $dir = $this->slashifyPath($dir);
219                 $file = $this->slashifyPath($file);
220                 if (file_exists($dir . $this->_pathsep . $file))
221                     return $dir;
222             } elseif (@file_exists($dir . $this->_pathsep . $file))
223                 return $dir;
224         }
225         return false;
226     }
227
228     /**
229      * The system-dependent path-separator character. On UNIX systems,
230      * this character is ':'; on Win32 systems it is ';'.
231      * Fixme:
232      * On Mac it cannot be : because this is the seperator there!
233      *
234      * @access private
235      * @return string path_separator.
236      */
237     function _get_ini_separator()
238     {
239         return isWindows() ? ';' : ':';
240         // return preg_match('/^Windows/', php_uname())
241     }
242
243     /**
244      * Get the value of PHP's include_path.
245      *
246      * @access private
247      * @return array Include path.
248      */
249     function _get_include_path()
250     {
251         if (defined("INCLUDE_PATH"))
252             $path = INCLUDE_PATH;
253         else {
254             $path = @get_cfg_var('include_path'); // FIXME: report warning
255             if (empty($path)) $path = @ini_get('include_path');
256         }
257         if (empty($path))
258             $path = '.';
259         return explode($this->_get_ini_separator(), $this->slashifyPath($path));
260     }
261
262     /**
263      * Add a directory to the end of PHP's include_path.
264      *
265      * The directory is appended only if it is not already listed in
266      * the include_path.
267      *
268      * @access private
269      * @param $dir string Directory to add.
270      */
271     function _append_to_include_path($dir)
272     {
273         $dir = $this->slashifyPath($dir);
274         if (!in_array($dir, $this->_path)) {
275             $this->_path[] = $dir;
276         }
277         /*
278          * Some (buggy) PHP's (notable SourceForge's PHP 4.0.6)
279          * sometimes don't seem to heed their include_path.
280          * I.e. sometimes a file is not found even though it seems to
281          * be in the current include_path. A simple
282          * ini_set('include_path', ini_get('include_path')) seems to
283          * be enough to fix the problem
284          *
285          * This following line should be in the above if-block, but we
286          * put it here, as it seems to work-around the bug.
287          */
288         $GLOBALS['INCLUDE_PATH'] = implode($this->_get_ini_separator(), $this->_path);
289         @ini_set('include_path', $GLOBALS['INCLUDE_PATH']);
290     }
291
292     /**
293      * Add a directory to the front of PHP's include_path.
294      *
295      * The directory is prepended, and removed from the tail if already existing.
296      *
297      * @access private
298      * @param $dir string Directory to add.
299      */
300     function _prepend_to_include_path($dir)
301     {
302         $dir = $this->slashifyPath($dir);
303         // remove duplicates
304         if ($i = array_search($dir, $this->_path) !== false) {
305             array_splice($this->_path, $i, 1);
306         }
307         array_unshift($this->_path, $dir);
308         $GLOBALS['INCLUDE_PATH'] = implode($this->_path, $this->_get_ini_separator());
309         @ini_set('include_path', $GLOBALS['INCLUDE_PATH']);
310     }
311
312     // Return all the possible shortened locale specifiers for the given locale.
313     // Most specific first.
314     // de_DE.iso8859-1@euro => de_DE.iso8859-1, de_DE, de
315     // This code might needed somewhere else also.
316     function locale_versions($lang)
317     {
318         // Try less specific versions of the locale
319         $langs[] = $lang;
320         foreach (array('@', '.', '_') as $sep) {
321             if (($tail = strchr($lang, $sep)))
322                 $langs[] = substr($lang, 0, -strlen($tail));
323         }
324         return $langs;
325     }
326
327     /**
328      * Try to figure out the appropriate value for $LANG.
329      *
330      * @access private
331      * @return string The value of $LANG.
332      */
333     function _get_lang()
334     {
335         if (!empty($GLOBALS['LANG']))
336             return $GLOBALS['LANG'];
337
338         foreach (array('LC_ALL', 'LC_MESSAGES', 'LC_RESPONSES') as $var) {
339             $lang = setlocale(constant($var), 0);
340             if (!empty($lang))
341                 return $lang;
342         }
343
344         foreach (array('LC_ALL', 'LC_MESSAGES', 'LC_RESPONSES', 'LANG') as $var) {
345             $lang = getenv($var);
346             if (!empty($lang))
347                 return $lang;
348         }
349
350         return "C";
351     }
352 }
353
354 /**
355  * A class for finding PEAR code.
356  *
357  * This is a subclass of FileFinder which searches a standard list of
358  * directories where PEAR code is likely to be installed.
359  *
360  * Example usage:
361  *
362  * <pre>
363  *   $pearFinder = new PearFileFinder;
364  *   $pearFinder->includeOnce('DB.php');
365  * </pre>
366  *
367  * The above code will look for 'DB.php', if found, the directory in
368  * which it was found will be added to PHP's include_path, and the
369  * file will be included. (If the file is not found, and E_USER_ERROR
370  * will be thrown.)
371  */
372 class PearFileFinder
373     extends FileFinder
374 {
375     /**
376      * Constructor.
377      *
378      * @param $path array Where to look for PEAR library code.
379      * A good set of defaults is provided, so you can probably leave
380      * this parameter blank.
381      */
382     function PearFileFinder($path = array())
383     {
384         $this->FileFinder(array_merge(
385             $path,
386             array('/usr/share/php4',
387                 '/usr/share/php',
388                 '/usr/lib/php4',
389                 '/usr/lib/php',
390                 '/usr/local/share/php4',
391                 '/usr/local/share/php',
392                 '/usr/local/lib/php4',
393                 '/usr/local/lib/php',
394                 '/System/Library/PHP',
395                 '/Apache/pear' // Windows
396             )));
397     }
398 }
399
400 /**
401  * Find PhpWiki localized files.
402  *
403  * This is a subclass of FileFinder which searches PHP's include_path
404  * for files. It looks first for "locale/$LANG/$file", then for
405  * "$file".
406  *
407  * If $LANG is something like "de_DE.iso8859-1@euro", this class will
408  * also search under various less specific variations like
409  * "de_DE.iso8859-1", "de_DE" and "de".
410  */
411 class LocalizedFileFinder
412     extends FileFinder
413 {
414     /**
415      * Constructor.
416      */
417     function LocalizedFileFinder()
418     {
419         $this->_pathsep = $this->_get_syspath_separator();
420         $include_path = $this->_get_include_path();
421         $path = array();
422
423         $lang = $this->_get_lang();
424         assert(!empty($lang));
425
426         if ($locales = $this->locale_versions($lang)) {
427             foreach ($locales as $lang) {
428                 if ($lang == 'C') $lang = 'en';
429                 foreach ($include_path as $dir) {
430                     $path[] = $this->slashifyPath($dir . "/locale/$lang");
431                 }
432             }
433         }
434         $this->FileFinder(array_merge($path, $include_path));
435     }
436 }
437
438 /**
439  * Find PhpWiki localized theme buttons.
440  *
441  * This is a subclass of FileFinder which searches PHP's include_path
442  * for files. It looks first for "buttons/$LANG/$file", then for
443  * "$file".
444  *
445  * If $LANG is something like "de_DE.iso8859-1@euro", this class will
446  * also search under various less specific variations like
447  * "de_DE.iso8859-1", "de_DE" and "de".
448  */
449 class LocalizedButtonFinder
450     extends FileFinder
451 {
452     /**
453      * Constructor.
454      */
455     function LocalizedButtonFinder()
456     {
457         global $WikiTheme;
458         $this->_pathsep = $this->_get_syspath_separator();
459         $include_path = $this->_get_include_path();
460         $path = array();
461
462         $lang = $this->_get_lang();
463         assert(!empty($lang));
464         assert(!empty($WikiTheme));
465
466         if (is_object($WikiTheme)) {
467             $langs = $this->locale_versions($lang);
468             foreach ($langs as $lang) {
469                 if ($lang == 'C') $lang = 'en';
470                 foreach ($include_path as $dir) {
471                     $path[] = $this->slashifyPath($WikiTheme->file("buttons/$lang"));
472                 }
473             }
474         }
475
476         $this->FileFinder(array_merge($path, $include_path));
477     }
478 }
479
480 // Search PHP's include_path to find file or directory.
481 function FindFile($file, $missing_okay = false, $slashify = false)
482 {
483     static $finder;
484     if (!isset($finder)) {
485         $finder = new FileFinder;
486         // remove "/lib" from dirname(__FILE__)
487         $wikidir = preg_replace('/.lib$/', '', dirname(__FILE__));
488         // let the system favor its local pear?
489         $finder->_append_to_include_path(dirname(__FILE__) . "/pear");
490         $finder->_prepend_to_include_path($wikidir);
491         // Don't override existing INCLUDE_PATH config.
492         if (!defined("INCLUDE_PATH"))
493             define("INCLUDE_PATH", implode($finder->_get_ini_separator(), $finder->_path));
494     }
495     $s = $finder->findFile($file, $missing_okay);
496     if ($slashify)
497         $s = $finder->slashifyPath($s);
498     return $s;
499 }
500
501 // Search PHP's include_path to find file or directory.
502 // Searches for "locale/$LANG/$file", then for "$file".
503 function FindLocalizedFile($file, $missing_okay = false, $re_init = false)
504 {
505     static $finder;
506     if ($re_init or !isset($finder))
507         $finder = new LocalizedFileFinder;
508     return $finder->findFile($file, $missing_okay);
509 }
510
511 function FindLocalizedButtonFile($file, $missing_okay = false, $re_init = false)
512 {
513     static $buttonfinder;
514     if ($re_init or !isset($buttonfinder))
515         $buttonfinder = new LocalizedButtonFinder;
516     return $buttonfinder->findFile($file, $missing_okay);
517 }
518
519 /**
520  * Prefixes with PHPWIKI_DIR and slashify.
521  * For example to unify with
522  *   require_once dirname(__FILE__).'/lib/file.php'
523  *   require_once 'lib/file.php' loading style.
524  * Doesn't expand "~" or symlinks yet. truename would be perfect.
525  *
526  * NormalizeLocalFileName("lib/config.php") => /home/user/phpwiki/lib/config.php
527  */
528 function NormalizeLocalFileName($file)
529 {
530     static $finder;
531     if (!isset($finder)) {
532         $finder = new FileFinder;
533     }
534     // remove "/lib" from dirname(__FILE__)
535     if ($finder->_is_abs($file))
536         return $finder->slashifyPath($file);
537     else {
538         if (defined("PHPWIKI_DIR")) $wikidir = PHPWIKI_DIR;
539         else $wikidir = preg_replace('/.lib$/', '', dirname(__FILE__));
540         $wikidir = $finder->_strip_last_pathchar($wikidir);
541         $pathsep = $finder->_use_path_separator($wikidir);
542         return $finder->slashifyPath($wikidir . $pathsep . $file);
543         // return PHPWIKI_DIR . "/" . $file;
544     }
545 }
546
547 /**
548  * Prefixes with DATA_PATH and slashify
549  */
550 function NormalizeWebFileName($file)
551 {
552     static $finder;
553     if (!isset($finder)) {
554         $finder = new FileFinder;
555     }
556     if (defined("DATA_PATH")) {
557         $wikipath = DATA_PATH;
558         $wikipath = $finder->_strip_last_pathchar($wikipath);
559         if (!$file)
560             return $finder->forcePathSlashes($wikipath);
561         else
562             return $finder->forcePathSlashes($wikipath . '/' . $file);
563     } else {
564         return $finder->forcePathSlashes($file);
565     }
566 }
567
568 function isWindows()
569 {
570     static $win;
571     if (isset($win)) return $win;
572     //return preg_match('/^Windows/', php_uname());
573     $win = (substr(PHP_OS, 0, 3) == 'WIN');
574     return $win;
575 }
576
577 function isWindows95()
578 {
579     static $win95;
580     if (isset($win95)) return $win95;
581     $win95 = isWindows() and !isWindowsNT();
582     return $win95;
583 }
584
585 function isWindowsNT()
586 {
587     static $winnt;
588     if (isset($winnt)) return $winnt;
589     // FIXME: Do this using PHP_OS instead of php_uname().
590     // $winnt = (PHP_OS == "WINNT"); // example from http://www.php.net/manual/en/ref.readline.php
591     if (function_usable('php_uname'))
592         $winnt = preg_match('/^Windows NT/', php_uname());
593     else
594         $winnt = false; // FIXME: punt.
595     return $winnt;
596 }
597
598 // Local Variables:
599 // mode: php
600 // tab-width: 8
601 // c-basic-offset: 4
602 // c-hanging-comment-ender-p: nil
603 // indent-tabs-mode: nil
604 // End: