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