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