Updated PEAR and PEAR packages.
[timetracker.git] / WEB-INF / lib / pear / PEAR / DependencyDB.php
1 <?php
2 /**
3  * PEAR_DependencyDB, advanced installed packages dependency database
4  *
5  * PHP versions 4 and 5
6  *
7  * @category   pear
8  * @package    PEAR
9  * @author     Tomas V. V. Cox <cox@idecnet.com>
10  * @author     Greg Beaver <cellog@php.net>
11  * @copyright  1997-2009 The Authors
12  * @license    http://opensource.org/licenses/bsd-license.php New BSD License
13  * @link       http://pear.php.net/package/PEAR
14  * @since      File available since Release 1.4.0a1
15  */
16
17 /**
18  * Needed for error handling
19  */
20 require_once 'PEAR.php';
21 require_once 'PEAR/Config.php';
22
23 $GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'] = array();
24 /**
25  * Track dependency relationships between installed packages
26  * @category   pear
27  * @package    PEAR
28  * @author     Greg Beaver <cellog@php.net>
29  * @author     Tomas V.V.Cox <cox@idec.net.com>
30  * @copyright  1997-2009 The Authors
31  * @license    http://opensource.org/licenses/bsd-license.php New BSD License
32  * @version    Release: 1.10.1
33  * @link       http://pear.php.net/package/PEAR
34  * @since      Class available since Release 1.4.0a1
35  */
36 class PEAR_DependencyDB
37 {
38     // {{{ properties
39
40     /**
41      * This is initialized by {@link setConfig()}
42      * @var PEAR_Config
43      * @access private
44      */
45     var $_config;
46     /**
47      * This is initialized by {@link setConfig()}
48      * @var PEAR_Registry
49      * @access private
50      */
51     var $_registry;
52     /**
53      * Filename of the dependency DB (usually .depdb)
54      * @var string
55      * @access private
56      */
57     var $_depdb = false;
58     /**
59      * File name of the lockfile (usually .depdblock)
60      * @var string
61      * @access private
62      */
63     var $_lockfile = false;
64     /**
65      * Open file resource for locking the lockfile
66      * @var resource|false
67      * @access private
68      */
69     var $_lockFp = false;
70     /**
71      * API version of this class, used to validate a file on-disk
72      * @var string
73      * @access private
74      */
75     var $_version = '1.0';
76     /**
77      * Cached dependency database file
78      * @var array|null
79      * @access private
80      */
81     var $_cache;
82
83     // }}}
84     // {{{ & singleton()
85
86     /**
87      * Get a raw dependency database.  Calls setConfig() and assertDepsDB()
88      * @param PEAR_Config
89      * @param string|false full path to the dependency database, or false to use default
90      * @return PEAR_DependencyDB|PEAR_Error
91      */
92     public static function &singleton(&$config, $depdb = false)
93     {
94         $phpdir = $config->get('php_dir', null, 'pear.php.net');
95         if (!isset($GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$phpdir])) {
96             $a = new PEAR_DependencyDB;
97             $GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$phpdir] = &$a;
98             $a->setConfig($config, $depdb);
99             $e = $a->assertDepsDB();
100             if (PEAR::isError($e)) {
101                 return $e;
102             }
103         }
104
105         return $GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$phpdir];
106     }
107
108     /**
109      * Set up the registry/location of dependency DB
110      * @param PEAR_Config|false
111      * @param string|false full path to the dependency database, or false to use default
112      */
113     function setConfig(&$config, $depdb = false)
114     {
115         if (!$config) {
116             $this->_config = &PEAR_Config::singleton();
117         } else {
118             $this->_config = &$config;
119         }
120
121         $this->_registry = &$this->_config->getRegistry();
122         if (!$depdb) {
123             $dir = $this->_config->get('metadata_dir', null, 'pear.php.net');
124             if (!$dir) {
125                 $dir = $this->_config->get('php_dir', null, 'pear.php.net');
126             }
127             $this->_depdb =  $dir . DIRECTORY_SEPARATOR . '.depdb';
128         } else {
129             $this->_depdb = $depdb;
130         }
131
132         $this->_lockfile = dirname($this->_depdb) . DIRECTORY_SEPARATOR . '.depdblock';
133     }
134     // }}}
135
136     function hasWriteAccess()
137     {
138         if (!file_exists($this->_depdb)) {
139             $dir = $this->_depdb;
140             while ($dir && $dir != '.') {
141                 $dir = dirname($dir); // cd ..
142                 if ($dir != '.' && file_exists($dir)) {
143                     if (is_writeable($dir)) {
144                         return true;
145                     }
146
147                     return false;
148                 }
149             }
150
151             return false;
152         }
153
154         return is_writeable($this->_depdb);
155     }
156
157     // {{{ assertDepsDB()
158
159     /**
160      * Create the dependency database, if it doesn't exist.  Error if the database is
161      * newer than the code reading it.
162      * @return void|PEAR_Error
163      */
164     function assertDepsDB()
165     {
166         if (!is_file($this->_depdb)) {
167             $this->rebuildDB();
168             return;
169         }
170
171         $depdb = $this->_getDepDB();
172         // Datatype format has been changed, rebuild the Deps DB
173         if ($depdb['_version'] < $this->_version) {
174             $this->rebuildDB();
175         }
176
177         if ($depdb['_version']{0} > $this->_version{0}) {
178             return PEAR::raiseError('Dependency database is version ' .
179                 $depdb['_version'] . ', and we are version ' .
180                 $this->_version . ', cannot continue');
181         }
182     }
183
184     /**
185      * Get a list of installed packages that depend on this package
186      * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array
187      * @return array|false
188      */
189     function getDependentPackages(&$pkg)
190     {
191         $data = $this->_getDepDB();
192         if (is_object($pkg)) {
193             $channel = strtolower($pkg->getChannel());
194             $package = strtolower($pkg->getPackage());
195         } else {
196             $channel = strtolower($pkg['channel']);
197             $package = strtolower($pkg['package']);
198         }
199
200         if (isset($data['packages'][$channel][$package])) {
201             return $data['packages'][$channel][$package];
202         }
203
204         return false;
205     }
206
207     /**
208      * Get a list of the actual dependencies of installed packages that depend on
209      * a package.
210      * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array
211      * @return array|false
212      */
213     function getDependentPackageDependencies(&$pkg)
214     {
215         $data = $this->_getDepDB();
216         if (is_object($pkg)) {
217             $channel = strtolower($pkg->getChannel());
218             $package = strtolower($pkg->getPackage());
219         } else {
220             $channel = strtolower($pkg['channel']);
221             $package = strtolower($pkg['package']);
222         }
223
224         $depend = $this->getDependentPackages($pkg);
225         if (!$depend) {
226             return false;
227         }
228
229         $dependencies = array();
230         foreach ($depend as $info) {
231             $temp = $this->getDependencies($info);
232             foreach ($temp as $dep) {
233                 if (
234                     isset($dep['dep'], $dep['dep']['channel'], $dep['dep']['name']) &&
235                     strtolower($dep['dep']['channel']) == $channel &&
236                     strtolower($dep['dep']['name']) == $package
237                 ) {
238                     if (!isset($dependencies[$info['channel']])) {
239                         $dependencies[$info['channel']] = array();
240                     }
241
242                     if (!isset($dependencies[$info['channel']][$info['package']])) {
243                         $dependencies[$info['channel']][$info['package']] = array();
244                     }
245                     $dependencies[$info['channel']][$info['package']][] = $dep;
246                 }
247             }
248         }
249
250         return $dependencies;
251     }
252
253     /**
254      * Get a list of dependencies of this installed package
255      * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array
256      * @return array|false
257      */
258     function getDependencies(&$pkg)
259     {
260         if (is_object($pkg)) {
261             $channel = strtolower($pkg->getChannel());
262             $package = strtolower($pkg->getPackage());
263         } else {
264             $channel = strtolower($pkg['channel']);
265             $package = strtolower($pkg['package']);
266         }
267
268         $data = $this->_getDepDB();
269         if (isset($data['dependencies'][$channel][$package])) {
270             return $data['dependencies'][$channel][$package];
271         }
272
273         return false;
274     }
275
276     /**
277      * Determine whether $parent depends on $child, near or deep
278      * @param array|PEAR_PackageFile_v2|PEAR_PackageFile_v2
279      * @param array|PEAR_PackageFile_v2|PEAR_PackageFile_v2
280      */
281     function dependsOn($parent, $child)
282     {
283         $c = array();
284         $this->_getDepDB();
285         return $this->_dependsOn($parent, $child, $c);
286     }
287
288     function _dependsOn($parent, $child, &$checked)
289     {
290         if (is_object($parent)) {
291             $channel = strtolower($parent->getChannel());
292             $package = strtolower($parent->getPackage());
293         } else {
294             $channel = strtolower($parent['channel']);
295             $package = strtolower($parent['package']);
296         }
297
298         if (is_object($child)) {
299             $depchannel = strtolower($child->getChannel());
300             $deppackage = strtolower($child->getPackage());
301         } else {
302             $depchannel = strtolower($child['channel']);
303             $deppackage = strtolower($child['package']);
304         }
305
306         if (isset($checked[$channel][$package][$depchannel][$deppackage])) {
307             return false; // avoid endless recursion
308         }
309
310         $checked[$channel][$package][$depchannel][$deppackage] = true;
311         if (!isset($this->_cache['dependencies'][$channel][$package])) {
312             return false;
313         }
314
315         foreach ($this->_cache['dependencies'][$channel][$package] as $info) {
316             if (isset($info['dep']['uri'])) {
317                 if (is_object($child)) {
318                     if ($info['dep']['uri'] == $child->getURI()) {
319                         return true;
320                     }
321                 } elseif (isset($child['uri'])) {
322                     if ($info['dep']['uri'] == $child['uri']) {
323                         return true;
324                     }
325                 }
326                 return false;
327             }
328
329             if (strtolower($info['dep']['channel']) == $depchannel &&
330                   strtolower($info['dep']['name']) == $deppackage) {
331                 return true;
332             }
333         }
334
335         foreach ($this->_cache['dependencies'][$channel][$package] as $info) {
336             if (isset($info['dep']['uri'])) {
337                 if ($this->_dependsOn(array(
338                         'uri' => $info['dep']['uri'],
339                         'package' => $info['dep']['name']), $child, $checked)) {
340                     return true;
341                 }
342             } else {
343                 if ($this->_dependsOn(array(
344                         'channel' => $info['dep']['channel'],
345                         'package' => $info['dep']['name']), $child, $checked)) {
346                     return true;
347                 }
348             }
349         }
350
351         return false;
352     }
353
354     /**
355      * Register dependencies of a package that is being installed or upgraded
356      * @param PEAR_PackageFile_v2|PEAR_PackageFile_v2
357      */
358     function installPackage(&$package)
359     {
360         $data = $this->_getDepDB();
361         unset($this->_cache);
362         $this->_setPackageDeps($data, $package);
363         $this->_writeDepDB($data);
364     }
365
366     /**
367      * Remove dependencies of a package that is being uninstalled, or upgraded.
368      *
369      * Upgraded packages first uninstall, then install
370      * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array If an array, then it must have
371      *        indices 'channel' and 'package'
372      */
373     function uninstallPackage(&$pkg)
374     {
375         $data = $this->_getDepDB();
376         unset($this->_cache);
377         if (is_object($pkg)) {
378             $channel = strtolower($pkg->getChannel());
379             $package = strtolower($pkg->getPackage());
380         } else {
381             $channel = strtolower($pkg['channel']);
382             $package = strtolower($pkg['package']);
383         }
384
385         if (!isset($data['dependencies'][$channel][$package])) {
386             return true;
387         }
388
389         foreach ($data['dependencies'][$channel][$package] as $dep) {
390             $found      = false;
391             $depchannel = isset($dep['dep']['uri']) ? '__uri' : strtolower($dep['dep']['channel']);
392             $depname    = strtolower($dep['dep']['name']);
393             if (isset($data['packages'][$depchannel][$depname])) {
394                 foreach ($data['packages'][$depchannel][$depname] as $i => $info) {
395                     if ($info['channel'] == $channel && $info['package'] == $package) {
396                         $found = true;
397                         break;
398                     }
399                 }
400             }
401
402             if ($found) {
403                 unset($data['packages'][$depchannel][$depname][$i]);
404                 if (!count($data['packages'][$depchannel][$depname])) {
405                     unset($data['packages'][$depchannel][$depname]);
406                     if (!count($data['packages'][$depchannel])) {
407                         unset($data['packages'][$depchannel]);
408                     }
409                 } else {
410                     $data['packages'][$depchannel][$depname] =
411                         array_values($data['packages'][$depchannel][$depname]);
412                 }
413             }
414         }
415
416         unset($data['dependencies'][$channel][$package]);
417         if (!count($data['dependencies'][$channel])) {
418             unset($data['dependencies'][$channel]);
419         }
420
421         if (!count($data['dependencies'])) {
422             unset($data['dependencies']);
423         }
424
425         if (!count($data['packages'])) {
426             unset($data['packages']);
427         }
428
429         $this->_writeDepDB($data);
430     }
431
432     /**
433      * Rebuild the dependency DB by reading registry entries.
434      * @return true|PEAR_Error
435      */
436     function rebuildDB()
437     {
438         $depdb = array('_version' => $this->_version);
439         if (!$this->hasWriteAccess()) {
440             // allow startup for read-only with older Registry
441             return $depdb;
442         }
443
444         $packages = $this->_registry->listAllPackages();
445         if (PEAR::isError($packages)) {
446             return $packages;
447         }
448
449         foreach ($packages as $channel => $ps) {
450             foreach ($ps as $package) {
451                 $package = $this->_registry->getPackage($package, $channel);
452                 if (PEAR::isError($package)) {
453                     return $package;
454                 }
455                 $this->_setPackageDeps($depdb, $package);
456             }
457         }
458
459         $error = $this->_writeDepDB($depdb);
460         if (PEAR::isError($error)) {
461             return $error;
462         }
463
464         $this->_cache = $depdb;
465         return true;
466     }
467
468     /**
469      * Register usage of the dependency DB to prevent race conditions
470      * @param int one of the LOCK_* constants
471      * @return true|PEAR_Error
472      * @access private
473      */
474     function _lock($mode = LOCK_EX)
475     {
476         if (stristr(php_uname(), 'Windows 9')) {
477             return true;
478         }
479
480         if ($mode != LOCK_UN && is_resource($this->_lockFp)) {
481             // XXX does not check type of lock (LOCK_SH/LOCK_EX)
482             return true;
483         }
484
485         $open_mode = 'w';
486         // XXX People reported problems with LOCK_SH and 'w'
487         if ($mode === LOCK_SH) {
488             if (!file_exists($this->_lockfile)) {
489                 touch($this->_lockfile);
490             } elseif (!is_file($this->_lockfile)) {
491                 return PEAR::raiseError('could not create Dependency lock file, ' .
492                     'it exists and is not a regular file');
493             }
494             $open_mode = 'r';
495         }
496
497         if (!is_resource($this->_lockFp)) {
498             $this->_lockFp = @fopen($this->_lockfile, $open_mode);
499         }
500
501         if (!is_resource($this->_lockFp)) {
502             return PEAR::raiseError("could not create Dependency lock file" .
503                                      (isset($php_errormsg) ? ": " . $php_errormsg : ""));
504         }
505
506         if (!(int)flock($this->_lockFp, $mode)) {
507             switch ($mode) {
508                 case LOCK_SH: $str = 'shared';    break;
509                 case LOCK_EX: $str = 'exclusive'; break;
510                 case LOCK_UN: $str = 'unlock';    break;
511                 default:      $str = 'unknown';   break;
512             }
513
514             return PEAR::raiseError("could not acquire $str lock ($this->_lockfile)");
515         }
516
517         return true;
518     }
519
520     /**
521      * Release usage of dependency DB
522      * @return true|PEAR_Error
523      * @access private
524      */
525     function _unlock()
526     {
527         $ret = $this->_lock(LOCK_UN);
528         if (is_resource($this->_lockFp)) {
529             fclose($this->_lockFp);
530         }
531         $this->_lockFp = null;
532         return $ret;
533     }
534
535     /**
536      * Load the dependency database from disk, or return the cache
537      * @return array|PEAR_Error
538      */
539     function _getDepDB()
540     {
541         if (!$this->hasWriteAccess()) {
542             return array('_version' => $this->_version);
543         }
544
545         if (isset($this->_cache)) {
546             return $this->_cache;
547         }
548
549         if (!$fp = fopen($this->_depdb, 'r')) {
550             $err = PEAR::raiseError("Could not open dependencies file `".$this->_depdb."'");
551             return $err;
552         }
553
554         clearstatcache();
555         fclose($fp);
556         $data = unserialize(file_get_contents($this->_depdb));
557         $this->_cache = $data;
558         return $data;
559     }
560
561     /**
562      * Write out the dependency database to disk
563      * @param array the database
564      * @return true|PEAR_Error
565      * @access private
566      */
567     function _writeDepDB(&$deps)
568     {
569         if (PEAR::isError($e = $this->_lock(LOCK_EX))) {
570             return $e;
571         }
572
573         if (!$fp = fopen($this->_depdb, 'wb')) {
574             $this->_unlock();
575             return PEAR::raiseError("Could not open dependencies file `".$this->_depdb."' for writing");
576         }
577
578         fwrite($fp, serialize($deps));
579         fclose($fp);
580         $this->_unlock();
581         $this->_cache = $deps;
582         return true;
583     }
584
585     /**
586      * Register all dependencies from a package in the dependencies database, in essence
587      * "installing" the package's dependency information
588      * @param array the database
589      * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
590      * @access private
591      */
592     function _setPackageDeps(&$data, &$pkg)
593     {
594         $pkg->setConfig($this->_config);
595         if ($pkg->getPackagexmlVersion() == '1.0') {
596             $gen = &$pkg->getDefaultGenerator();
597             $deps = $gen->dependenciesToV2();
598         } else {
599             $deps = $pkg->getDeps(true);
600         }
601
602         if (!$deps) {
603             return;
604         }
605
606         if (!is_array($data)) {
607             $data = array();
608         }
609
610         if (!isset($data['dependencies'])) {
611             $data['dependencies'] = array();
612         }
613
614         $channel = strtolower($pkg->getChannel());
615         $package = strtolower($pkg->getPackage());
616
617         if (!isset($data['dependencies'][$channel])) {
618             $data['dependencies'][$channel] = array();
619         }
620
621         $data['dependencies'][$channel][$package] = array();
622         if (isset($deps['required']['package'])) {
623             if (!isset($deps['required']['package'][0])) {
624                 $deps['required']['package'] = array($deps['required']['package']);
625             }
626
627             foreach ($deps['required']['package'] as $dep) {
628                 $this->_registerDep($data, $pkg, $dep, 'required');
629             }
630         }
631
632         if (isset($deps['optional']['package'])) {
633             if (!isset($deps['optional']['package'][0])) {
634                 $deps['optional']['package'] = array($deps['optional']['package']);
635             }
636
637             foreach ($deps['optional']['package'] as $dep) {
638                 $this->_registerDep($data, $pkg, $dep, 'optional');
639             }
640         }
641
642         if (isset($deps['required']['subpackage'])) {
643             if (!isset($deps['required']['subpackage'][0])) {
644                 $deps['required']['subpackage'] = array($deps['required']['subpackage']);
645             }
646
647             foreach ($deps['required']['subpackage'] as $dep) {
648                 $this->_registerDep($data, $pkg, $dep, 'required');
649             }
650         }
651
652         if (isset($deps['optional']['subpackage'])) {
653             if (!isset($deps['optional']['subpackage'][0])) {
654                 $deps['optional']['subpackage'] = array($deps['optional']['subpackage']);
655             }
656
657             foreach ($deps['optional']['subpackage'] as $dep) {
658                 $this->_registerDep($data, $pkg, $dep, 'optional');
659             }
660         }
661
662         if (isset($deps['group'])) {
663             if (!isset($deps['group'][0])) {
664                 $deps['group'] = array($deps['group']);
665             }
666
667             foreach ($deps['group'] as $group) {
668                 if (isset($group['package'])) {
669                     if (!isset($group['package'][0])) {
670                         $group['package'] = array($group['package']);
671                     }
672
673                     foreach ($group['package'] as $dep) {
674                         $this->_registerDep($data, $pkg, $dep, 'optional',
675                             $group['attribs']['name']);
676                     }
677                 }
678
679                 if (isset($group['subpackage'])) {
680                     if (!isset($group['subpackage'][0])) {
681                         $group['subpackage'] = array($group['subpackage']);
682                     }
683
684                     foreach ($group['subpackage'] as $dep) {
685                         $this->_registerDep($data, $pkg, $dep, 'optional',
686                             $group['attribs']['name']);
687                     }
688                 }
689             }
690         }
691
692         if ($data['dependencies'][$channel][$package] == array()) {
693             unset($data['dependencies'][$channel][$package]);
694             if (!count($data['dependencies'][$channel])) {
695                 unset($data['dependencies'][$channel]);
696             }
697         }
698     }
699
700     /**
701      * @param array the database
702      * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
703      * @param array the specific dependency
704      * @param required|optional whether this is a required or an optional dep
705      * @param string|false dependency group this dependency is from, or false for ordinary dep
706      */
707     function _registerDep(&$data, &$pkg, $dep, $type, $group = false)
708     {
709         $info = array(
710             'dep'   => $dep,
711             'type'  => $type,
712             'group' => $group
713         );
714
715         $dep  = array_map('strtolower', $dep);
716         $depchannel = isset($dep['channel']) ? $dep['channel'] : '__uri';
717         if (!isset($data['dependencies'])) {
718             $data['dependencies'] = array();
719         }
720
721         $channel = strtolower($pkg->getChannel());
722         $package = strtolower($pkg->getPackage());
723
724         if (!isset($data['dependencies'][$channel])) {
725             $data['dependencies'][$channel] = array();
726         }
727
728         if (!isset($data['dependencies'][$channel][$package])) {
729             $data['dependencies'][$channel][$package] = array();
730         }
731
732         $data['dependencies'][$channel][$package][] = $info;
733         if (isset($data['packages'][$depchannel][$dep['name']])) {
734             $found = false;
735             foreach ($data['packages'][$depchannel][$dep['name']] as $i => $p) {
736                 if ($p['channel'] == $channel && $p['package'] == $package) {
737                     $found = true;
738                     break;
739                 }
740             }
741         } else {
742             if (!isset($data['packages'])) {
743                 $data['packages'] = array();
744             }
745
746             if (!isset($data['packages'][$depchannel])) {
747                 $data['packages'][$depchannel] = array();
748             }
749
750             if (!isset($data['packages'][$depchannel][$dep['name']])) {
751                 $data['packages'][$depchannel][$dep['name']] = array();
752             }
753
754             $found = false;
755         }
756
757         if (!$found) {
758             $data['packages'][$depchannel][$dep['name']][] = array(
759                 'channel' => $channel,
760                 'package' => $package
761             );
762         }
763     }
764 }