Updated PEAR and PEAR packages.
[timetracker.git] / WEB-INF / lib / pear / PEAR / PackageFile / v2.php
1 <?php
2 /**
3  * PEAR_PackageFile_v2, package.xml version 2.0
4  *
5  * PHP versions 4 and 5
6  *
7  * @category   pear
8  * @package    PEAR
9  * @author     Greg Beaver <cellog@php.net>
10  * @copyright  1997-2009 The Authors
11  * @license    http://opensource.org/licenses/bsd-license.php New BSD License
12  * @link       http://pear.php.net/package/PEAR
13  * @since      File available since Release 1.4.0a1
14  */
15 /**
16  * For error handling
17  */
18 require_once 'PEAR/ErrorStack.php';
19 /**
20  * @category   pear
21  * @package    PEAR
22  * @author     Greg Beaver <cellog@php.net>
23  * @copyright  1997-2009 The Authors
24  * @license    http://opensource.org/licenses/bsd-license.php New BSD License
25  * @version    Release: 1.10.1
26  * @link       http://pear.php.net/package/PEAR
27  * @since      Class available since Release 1.4.0a1
28  */
29 class PEAR_PackageFile_v2
30 {
31
32     /**
33      * Parsed package information
34      * @var array
35      * @access private
36      */
37     var $_packageInfo = array();
38
39     /**
40      * path to package .tgz or false if this is a local/extracted package.xml
41      * @var string|false
42      * @access private
43      */
44     var $_archiveFile;
45
46     /**
47      * path to package .xml or false if this is an abstract parsed-from-string xml
48      * @var string|false
49      * @access private
50      */
51     var $_packageFile;
52
53     /**
54      * This is used by file analysis routines to log progress information
55      * @var PEAR_Common
56      * @access protected
57      */
58     var $_logger;
59
60     /**
61      * This is set to the highest validation level that has been validated
62      *
63      * If the package.xml is invalid or unknown, this is set to 0.  If
64      * normal validation has occurred, this is set to PEAR_VALIDATE_NORMAL.  If
65      * downloading/installation validation has occurred it is set to PEAR_VALIDATE_DOWNLOADING
66      * or INSTALLING, and so on up to PEAR_VALIDATE_PACKAGING.  This allows validation
67      * "caching" to occur, which is particularly important for package validation, so
68      * that PHP files are not validated twice
69      * @var int
70      * @access private
71      */
72     var $_isValid = 0;
73
74     /**
75      * True if the filelist has been validated
76      * @param bool
77      */
78     var $_filesValid = false;
79
80     /**
81      * @var PEAR_Registry
82      * @access protected
83      */
84     var $_registry;
85
86     /**
87      * @var PEAR_Config
88      * @access protected
89      */
90     var $_config;
91
92     /**
93      * Optional Dependency group requested for installation
94      * @var string
95      * @access private
96      */
97     var $_requestedGroup = false;
98
99     /**
100      * @var PEAR_ErrorStack
101      * @access protected
102      */
103     var $_stack;
104
105     /**
106      * Namespace prefix used for tasks in this package.xml - use tasks: whenever possible
107      */
108     var $_tasksNs;
109
110     /**
111      * Determines whether this packagefile was initialized only with partial package info
112      *
113      * If this package file was constructed via parsing REST, it will only contain
114      *
115      * - package name
116      * - channel name
117      * - dependencies
118      * @var boolean
119      * @access private
120      */
121     var $_incomplete = true;
122
123     /**
124      * @var PEAR_PackageFile_v2_Validator
125      */
126     var $_v2Validator;
127
128     /**
129      * The constructor merely sets up the private error stack
130      */
131     function __construct()
132     {
133         $this->_stack = new PEAR_ErrorStack('PEAR_PackageFile_v2', false, null);
134         $this->_isValid = false;
135     }
136
137     /**
138      * PHP 4 style constructor for backwards compatibility.
139      * Used by PEAR_PackageFileManager2
140      */
141     public function PEAR_PackageFile_v2()
142     {
143         $this->__construct();
144     }
145
146     /**
147      * To make unit-testing easier
148      * @param PEAR_Frontend_*
149      * @param array options
150      * @param PEAR_Config
151      * @return PEAR_Downloader
152      * @access protected
153      */
154     function &getPEARDownloader(&$i, $o, &$c)
155     {
156         $z = new PEAR_Downloader($i, $o, $c);
157         return $z;
158     }
159
160     /**
161      * To make unit-testing easier
162      * @param PEAR_Config
163      * @param array options
164      * @param array package name as returned from {@link PEAR_Registry::parsePackageName()}
165      * @param int PEAR_VALIDATE_* constant
166      * @return PEAR_Dependency2
167      * @access protected
168      */
169     function &getPEARDependency2(&$c, $o, $p, $s = PEAR_VALIDATE_INSTALLING)
170     {
171         if (!class_exists('PEAR_Dependency2')) {
172             require_once 'PEAR/Dependency2.php';
173         }
174         $z = new PEAR_Dependency2($c, $o, $p, $s);
175         return $z;
176     }
177
178     function getInstalledBinary()
179     {
180         return isset($this->_packageInfo['#binarypackage']) ? $this->_packageInfo['#binarypackage'] :
181             false;
182     }
183
184     /**
185      * Installation of source package has failed, attempt to download and install the
186      * binary version of this package.
187      * @param PEAR_Installer
188      * @return array|false
189      */
190     function installBinary(&$installer)
191     {
192         if (!OS_WINDOWS) {
193             $a = false;
194             return $a;
195         }
196         if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') {
197             $releasetype = $this->getPackageType() . 'release';
198             if (!is_array($installer->getInstallPackages())) {
199                 $a = false;
200                 return $a;
201             }
202             foreach ($installer->getInstallPackages() as $p) {
203                 if ($p->isExtension($this->_packageInfo['providesextension'])) {
204                     if ($p->getPackageType() != 'extsrc' && $p->getPackageType() != 'zendextsrc') {
205                         $a = false;
206                         return $a; // the user probably downloaded it separately
207                     }
208                 }
209             }
210             if (isset($this->_packageInfo[$releasetype]['binarypackage'])) {
211                 $installer->log(0, 'Attempting to download binary version of extension "' .
212                     $this->_packageInfo['providesextension'] . '"');
213                 $params = $this->_packageInfo[$releasetype]['binarypackage'];
214                 if (!is_array($params) || !isset($params[0])) {
215                     $params = array($params);
216                 }
217                 if (isset($this->_packageInfo['channel'])) {
218                     foreach ($params as $i => $param) {
219                         $params[$i] = array('channel' => $this->_packageInfo['channel'],
220                             'package' => $param, 'version' => $this->getVersion());
221                     }
222                 }
223                 $dl = &$this->getPEARDownloader($installer->ui, $installer->getOptions(),
224                     $installer->config);
225                 $verbose = $dl->config->get('verbose');
226                 $dl->config->set('verbose', -1);
227                 foreach ($params as $param) {
228                     PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
229                     $ret = $dl->download(array($param));
230                     PEAR::popErrorHandling();
231                     if (is_array($ret) && count($ret)) {
232                         break;
233                     }
234                 }
235                 $dl->config->set('verbose', $verbose);
236                 if (is_array($ret)) {
237                     if (count($ret) == 1) {
238                         $pf = $ret[0]->getPackageFile();
239                         PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
240                         $err = $installer->install($ret[0]);
241                         PEAR::popErrorHandling();
242                         if (is_array($err)) {
243                             $this->_packageInfo['#binarypackage'] = $ret[0]->getPackage();
244                             // "install" self, so all dependencies will work transparently
245                             $this->_registry->addPackage2($this);
246                             $installer->log(0, 'Download and install of binary extension "' .
247                                 $this->_registry->parsedPackageNameToString(
248                                     array('channel' => $pf->getChannel(),
249                                           'package' => $pf->getPackage()), true) . '" successful');
250                             $a = array($ret[0], $err);
251                             return $a;
252                         }
253                         $installer->log(0, 'Download and install of binary extension "' .
254                             $this->_registry->parsedPackageNameToString(
255                                     array('channel' => $pf->getChannel(),
256                                           'package' => $pf->getPackage()), true) . '" failed');
257                     }
258                 }
259             }
260         }
261         $a = false;
262         return $a;
263     }
264
265     /**
266      * @return string|false Extension name
267      */
268     function getProvidesExtension()
269     {
270         if (in_array($this->getPackageType(),
271               array('extsrc', 'extbin', 'zendextsrc', 'zendextbin'))) {
272             if (isset($this->_packageInfo['providesextension'])) {
273                 return $this->_packageInfo['providesextension'];
274             }
275         }
276         return false;
277     }
278
279     /**
280      * @param string Extension name
281      * @return bool
282      */
283     function isExtension($extension)
284     {
285         if (in_array($this->getPackageType(),
286               array('extsrc', 'extbin', 'zendextsrc', 'zendextbin'))) {
287             return $this->_packageInfo['providesextension'] == $extension;
288         }
289         return false;
290     }
291
292     /**
293      * Tests whether every part of the package.xml 1.0 is represented in
294      * this package.xml 2.0
295      * @param PEAR_PackageFile_v1
296      * @return bool
297      */
298     function isEquivalent($pf1)
299     {
300         if (!$pf1) {
301             return true;
302         }
303         if ($this->getPackageType() == 'bundle') {
304             return false;
305         }
306         $this->_stack->getErrors(true);
307         if (!$pf1->validate(PEAR_VALIDATE_NORMAL)) {
308             return false;
309         }
310         $pass = true;
311         if ($pf1->getPackage() != $this->getPackage()) {
312             $this->_differentPackage($pf1->getPackage());
313             $pass = false;
314         }
315         if ($pf1->getVersion() != $this->getVersion()) {
316             $this->_differentVersion($pf1->getVersion());
317             $pass = false;
318         }
319         if (trim($pf1->getSummary()) != $this->getSummary()) {
320             $this->_differentSummary($pf1->getSummary());
321             $pass = false;
322         }
323         if (preg_replace('/\s+/', '', $pf1->getDescription()) !=
324               preg_replace('/\s+/', '', $this->getDescription())) {
325             $this->_differentDescription($pf1->getDescription());
326             $pass = false;
327         }
328         if ($pf1->getState() != $this->getState()) {
329             $this->_differentState($pf1->getState());
330             $pass = false;
331         }
332         if (!strstr(preg_replace('/\s+/', '', $this->getNotes()),
333               preg_replace('/\s+/', '', $pf1->getNotes()))) {
334             $this->_differentNotes($pf1->getNotes());
335             $pass = false;
336         }
337         $mymaintainers = $this->getMaintainers();
338         $yourmaintainers = $pf1->getMaintainers();
339         for ($i1 = 0; $i1 < count($yourmaintainers); $i1++) {
340             $reset = false;
341             for ($i2 = 0; $i2 < count($mymaintainers); $i2++) {
342                 if ($mymaintainers[$i2]['handle'] == $yourmaintainers[$i1]['handle']) {
343                     if ($mymaintainers[$i2]['role'] != $yourmaintainers[$i1]['role']) {
344                         $this->_differentRole($mymaintainers[$i2]['handle'],
345                             $yourmaintainers[$i1]['role'], $mymaintainers[$i2]['role']);
346                         $pass = false;
347                     }
348                     if ($mymaintainers[$i2]['email'] != $yourmaintainers[$i1]['email']) {
349                         $this->_differentEmail($mymaintainers[$i2]['handle'],
350                             $yourmaintainers[$i1]['email'], $mymaintainers[$i2]['email']);
351                         $pass = false;
352                     }
353                     if ($mymaintainers[$i2]['name'] != $yourmaintainers[$i1]['name']) {
354                         $this->_differentName($mymaintainers[$i2]['handle'],
355                             $yourmaintainers[$i1]['name'], $mymaintainers[$i2]['name']);
356                         $pass = false;
357                     }
358                     unset($mymaintainers[$i2]);
359                     $mymaintainers = array_values($mymaintainers);
360                     unset($yourmaintainers[$i1]);
361                     $yourmaintainers = array_values($yourmaintainers);
362                     $reset = true;
363                     break;
364                 }
365             }
366             if ($reset) {
367                 $i1 = -1;
368             }
369         }
370         $this->_unmatchedMaintainers($mymaintainers, $yourmaintainers);
371         $filelist = $this->getFilelist();
372         foreach ($pf1->getFilelist() as $file => $atts) {
373             if (!isset($filelist[$file])) {
374                 $this->_missingFile($file);
375                 $pass = false;
376             }
377         }
378         return $pass;
379     }
380
381     function _differentPackage($package)
382     {
383         $this->_stack->push(__FUNCTION__, 'error', array('package' => $package,
384             'self' => $this->getPackage()),
385             'package.xml 1.0 package "%package%" does not match "%self%"');
386     }
387
388     function _differentVersion($version)
389     {
390         $this->_stack->push(__FUNCTION__, 'error', array('version' => $version,
391             'self' => $this->getVersion()),
392             'package.xml 1.0 version "%version%" does not match "%self%"');
393     }
394
395     function _differentState($state)
396     {
397         $this->_stack->push(__FUNCTION__, 'error', array('state' => $state,
398             'self' => $this->getState()),
399             'package.xml 1.0 state "%state%" does not match "%self%"');
400     }
401
402     function _differentRole($handle, $role, $selfrole)
403     {
404         $this->_stack->push(__FUNCTION__, 'error', array('handle' => $handle,
405             'role' => $role, 'self' => $selfrole),
406             'package.xml 1.0 maintainer "%handle%" role "%role%" does not match "%self%"');
407     }
408
409     function _differentEmail($handle, $email, $selfemail)
410     {
411         $this->_stack->push(__FUNCTION__, 'error', array('handle' => $handle,
412             'email' => $email, 'self' => $selfemail),
413             'package.xml 1.0 maintainer "%handle%" email "%email%" does not match "%self%"');
414     }
415
416     function _differentName($handle, $name, $selfname)
417     {
418         $this->_stack->push(__FUNCTION__, 'error', array('handle' => $handle,
419             'name' => $name, 'self' => $selfname),
420             'package.xml 1.0 maintainer "%handle%" name "%name%" does not match "%self%"');
421     }
422
423     function _unmatchedMaintainers($my, $yours)
424     {
425         if ($my) {
426             array_walk($my, create_function('&$i, $k', '$i = $i["handle"];'));
427             $this->_stack->push(__FUNCTION__, 'error', array('handles' => $my),
428                 'package.xml 2.0 has unmatched extra maintainers "%handles%"');
429         }
430         if ($yours) {
431             array_walk($yours, create_function('&$i, $k', '$i = $i["handle"];'));
432             $this->_stack->push(__FUNCTION__, 'error', array('handles' => $yours),
433                 'package.xml 1.0 has unmatched extra maintainers "%handles%"');
434         }
435     }
436
437     function _differentNotes($notes)
438     {
439         $truncnotes = strlen($notes) < 25 ? $notes : substr($notes, 0, 24) . '...';
440         $truncmynotes = strlen($this->getNotes()) < 25 ? $this->getNotes() :
441             substr($this->getNotes(), 0, 24) . '...';
442         $this->_stack->push(__FUNCTION__, 'error', array('notes' => $truncnotes,
443             'self' => $truncmynotes),
444             'package.xml 1.0 release notes "%notes%" do not match "%self%"');
445     }
446
447     function _differentSummary($summary)
448     {
449         $truncsummary = strlen($summary) < 25 ? $summary : substr($summary, 0, 24) . '...';
450         $truncmysummary = strlen($this->getsummary()) < 25 ? $this->getSummary() :
451             substr($this->getsummary(), 0, 24) . '...';
452         $this->_stack->push(__FUNCTION__, 'error', array('summary' => $truncsummary,
453             'self' => $truncmysummary),
454             'package.xml 1.0 summary "%summary%" does not match "%self%"');
455     }
456
457     function _differentDescription($description)
458     {
459         $truncdescription = trim(strlen($description) < 25 ? $description : substr($description, 0, 24) . '...');
460         $truncmydescription = trim(strlen($this->getDescription()) < 25 ? $this->getDescription() :
461             substr($this->getdescription(), 0, 24) . '...');
462         $this->_stack->push(__FUNCTION__, 'error', array('description' => $truncdescription,
463             'self' => $truncmydescription),
464             'package.xml 1.0 description "%description%" does not match "%self%"');
465     }
466
467     function _missingFile($file)
468     {
469         $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
470             'package.xml 1.0 file "%file%" is not present in <contents>');
471     }
472
473     /**
474      * WARNING - do not use this function unless you know what you're doing
475      */
476     function setRawState($state)
477     {
478         if (!isset($this->_packageInfo['stability'])) {
479             $this->_packageInfo['stability'] = array();
480         }
481         $this->_packageInfo['stability']['release'] = $state;
482     }
483
484     /**
485      * WARNING - do not use this function unless you know what you're doing
486      */
487     function setRawCompatible($compatible)
488     {
489         $this->_packageInfo['compatible'] = $compatible;
490     }
491
492     /**
493      * WARNING - do not use this function unless you know what you're doing
494      */
495     function setRawPackage($package)
496     {
497         $this->_packageInfo['name'] = $package;
498     }
499
500     /**
501      * WARNING - do not use this function unless you know what you're doing
502      */
503     function setRawChannel($channel)
504     {
505         $this->_packageInfo['channel'] = $channel;
506     }
507
508     function setRequestedGroup($group)
509     {
510         $this->_requestedGroup = $group;
511     }
512
513     function getRequestedGroup()
514     {
515         if (isset($this->_requestedGroup)) {
516             return $this->_requestedGroup;
517         }
518         return false;
519     }
520
521     /**
522      * For saving in the registry.
523      *
524      * Set the last version that was installed
525      * @param string
526      */
527     function setLastInstalledVersion($version)
528     {
529         $this->_packageInfo['_lastversion'] = $version;
530     }
531
532     /**
533      * @return string|false
534      */
535     function getLastInstalledVersion()
536     {
537         if (isset($this->_packageInfo['_lastversion'])) {
538             return $this->_packageInfo['_lastversion'];
539         }
540         return false;
541     }
542
543     /**
544      * Determines whether this package.xml has post-install scripts or not
545      * @return array|false
546      */
547     function listPostinstallScripts()
548     {
549         $filelist = $this->getFilelist();
550         $contents = $this->getContents();
551         $contents = $contents['dir']['file'];
552         if (!is_array($contents) || !isset($contents[0])) {
553             $contents = array($contents);
554         }
555         $taskfiles = array();
556         foreach ($contents as $file) {
557             $atts = $file['attribs'];
558             unset($file['attribs']);
559             if (count($file)) {
560                 $taskfiles[$atts['name']] = $file;
561             }
562         }
563         $common = new PEAR_Common;
564         $common->debug = $this->_config->get('verbose');
565         $this->_scripts = array();
566         $ret = array();
567         foreach ($taskfiles as $name => $tasks) {
568             if (!isset($filelist[$name])) {
569                 // ignored files will not be in the filelist
570                 continue;
571             }
572             $atts = $filelist[$name];
573             foreach ($tasks as $tag => $raw) {
574                 $task = $this->getTask($tag);
575                 $task = new $task($this->_config, $common, PEAR_TASK_INSTALL);
576                 if ($task->isScript()) {
577                     $ret[] = $filelist[$name]['installed_as'];
578                 }
579             }
580         }
581         if (count($ret)) {
582             return $ret;
583         }
584         return false;
585     }
586
587     /**
588      * Initialize post-install scripts for running
589      *
590      * This method can be used to detect post-install scripts, as the return value
591      * indicates whether any exist
592      * @return bool
593      */
594     function initPostinstallScripts()
595     {
596         $filelist = $this->getFilelist();
597         $contents = $this->getContents();
598         $contents = $contents['dir']['file'];
599         if (!is_array($contents) || !isset($contents[0])) {
600             $contents = array($contents);
601         }
602         $taskfiles = array();
603         foreach ($contents as $file) {
604             $atts = $file['attribs'];
605             unset($file['attribs']);
606             if (count($file)) {
607                 $taskfiles[$atts['name']] = $file;
608             }
609         }
610         $common = new PEAR_Common;
611         $common->debug = $this->_config->get('verbose');
612         $this->_scripts = array();
613         foreach ($taskfiles as $name => $tasks) {
614             if (!isset($filelist[$name])) {
615                 // file was not installed due to installconditions
616                 continue;
617             }
618             $atts = $filelist[$name];
619             foreach ($tasks as $tag => $raw) {
620                 $taskname = $this->getTask($tag);
621                 $task = new $taskname($this->_config, $common, PEAR_TASK_INSTALL);
622                 if (!$task->isScript()) {
623                     continue; // scripts are only handled after installation
624                 }
625                 $lastversion = isset($this->_packageInfo['_lastversion']) ?
626                     $this->_packageInfo['_lastversion'] : null;
627                 $task->init($raw, $atts, $lastversion);
628                 $res = $task->startSession($this, $atts['installed_as']);
629                 if (!$res) {
630                     continue; // skip this file
631                 }
632                 if (PEAR::isError($res)) {
633                     return $res;
634                 }
635                 $assign = &$task;
636                 $this->_scripts[] = &$assign;
637             }
638         }
639         if (count($this->_scripts)) {
640             return true;
641         }
642         return false;
643     }
644
645     function runPostinstallScripts()
646     {
647         if ($this->initPostinstallScripts()) {
648             $ui = &PEAR_Frontend::singleton();
649             if ($ui) {
650                 $ui->runPostinstallScripts($this->_scripts, $this);
651             }
652         }
653     }
654
655
656     /**
657      * Convert a recursive set of <dir> and <file> tags into a single <dir> tag with
658      * <file> tags.
659      */
660     function flattenFilelist()
661     {
662         if (isset($this->_packageInfo['bundle'])) {
663             return;
664         }
665         $filelist = array();
666         if (isset($this->_packageInfo['contents']['dir']['dir'])) {
667             $this->_getFlattenedFilelist($filelist, $this->_packageInfo['contents']['dir']);
668             if (!isset($filelist[1])) {
669                 $filelist = $filelist[0];
670             }
671             $this->_packageInfo['contents']['dir']['file'] = $filelist;
672             unset($this->_packageInfo['contents']['dir']['dir']);
673         } else {
674             // else already flattened but check for baseinstalldir propagation
675             if (isset($this->_packageInfo['contents']['dir']['attribs']['baseinstalldir'])) {
676                 if (isset($this->_packageInfo['contents']['dir']['file'][0])) {
677                     foreach ($this->_packageInfo['contents']['dir']['file'] as $i => $file) {
678                         if (isset($file['attribs']['baseinstalldir'])) {
679                             continue;
680                         }
681                         $this->_packageInfo['contents']['dir']['file'][$i]['attribs']['baseinstalldir']
682                             = $this->_packageInfo['contents']['dir']['attribs']['baseinstalldir'];
683                     }
684                 } else {
685                     if (!isset($this->_packageInfo['contents']['dir']['file']['attribs']['baseinstalldir'])) {
686                        $this->_packageInfo['contents']['dir']['file']['attribs']['baseinstalldir']
687                             = $this->_packageInfo['contents']['dir']['attribs']['baseinstalldir'];
688                     }
689                 }
690             }
691         }
692     }
693
694     /**
695      * @param array the final flattened file list
696      * @param array the current directory being processed
697      * @param string|false any recursively inherited baeinstalldir attribute
698      * @param string private recursion variable
699      * @return array
700      * @access protected
701      */
702     function _getFlattenedFilelist(&$files, $dir, $baseinstall = false, $path = '')
703     {
704         if (isset($dir['attribs']) && isset($dir['attribs']['baseinstalldir'])) {
705             $baseinstall = $dir['attribs']['baseinstalldir'];
706         }
707         if (isset($dir['dir'])) {
708             if (!isset($dir['dir'][0])) {
709                 $dir['dir'] = array($dir['dir']);
710             }
711             foreach ($dir['dir'] as $subdir) {
712                 if (!isset($subdir['attribs']) || !isset($subdir['attribs']['name'])) {
713                     $name = '*unknown*';
714                 } else {
715                     $name = $subdir['attribs']['name'];
716                 }
717                 $newpath = empty($path) ? $name :
718                     $path . '/' . $name;
719                 $this->_getFlattenedFilelist($files, $subdir,
720                     $baseinstall, $newpath);
721             }
722         }
723         if (isset($dir['file'])) {
724             if (!isset($dir['file'][0])) {
725                 $dir['file'] = array($dir['file']);
726             }
727             foreach ($dir['file'] as $file) {
728                 $attrs = $file['attribs'];
729                 $name = $attrs['name'];
730                 if ($baseinstall && !isset($attrs['baseinstalldir'])) {
731                     $attrs['baseinstalldir'] = $baseinstall;
732                 }
733                 $attrs['name'] = empty($path) ? $name : $path . '/' . $name;
734                 $attrs['name'] = preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'),
735                     $attrs['name']);
736                 $file['attribs'] = $attrs;
737                 $files[] = $file;
738             }
739         }
740     }
741
742     function setConfig(&$config)
743     {
744         $this->_config = &$config;
745         $this->_registry = &$config->getRegistry();
746     }
747
748     function setLogger(&$logger)
749     {
750         if (!is_object($logger) || !method_exists($logger, 'log')) {
751             return PEAR::raiseError('Logger must be compatible with PEAR_Common::log');
752         }
753         $this->_logger = &$logger;
754     }
755
756     /**
757      * WARNING - do not use this function directly unless you know what you're doing
758      */
759     function setDeps($deps)
760     {
761         $this->_packageInfo['dependencies'] = $deps;
762     }
763
764     /**
765      * WARNING - do not use this function directly unless you know what you're doing
766      */
767     function setCompatible($compat)
768     {
769         $this->_packageInfo['compatible'] = $compat;
770     }
771
772     function setPackagefile($file, $archive = false)
773     {
774         $this->_packageFile = $file;
775         $this->_archiveFile = $archive ? $archive : $file;
776     }
777
778     /**
779      * Wrapper to {@link PEAR_ErrorStack::getErrors()}
780      * @param boolean determines whether to purge the error stack after retrieving
781      * @return array
782      */
783     function getValidationWarnings($purge = true)
784     {
785         return $this->_stack->getErrors($purge);
786     }
787
788     function getPackageFile()
789     {
790         return $this->_packageFile;
791     }
792
793     function getArchiveFile()
794     {
795         return $this->_archiveFile;
796     }
797
798
799     /**
800      * Directly set the array that defines this packagefile
801      *
802      * WARNING: no validation.  This should only be performed by internal methods
803      * inside PEAR or by inputting an array saved from an existing PEAR_PackageFile_v2
804      * @param array
805      */
806     function fromArray($pinfo)
807     {
808         unset($pinfo['old']);
809         unset($pinfo['xsdversion']);
810         // If the changelog isn't an array then it was passed in as an empty tag
811         if (isset($pinfo['changelog']) && !is_array($pinfo['changelog'])) {
812           unset($pinfo['changelog']);
813         }
814         $this->_incomplete = false;
815         $this->_packageInfo = $pinfo;
816     }
817
818     function isIncomplete()
819     {
820         return $this->_incomplete;
821     }
822
823     /**
824      * @return array
825      */
826     function toArray($forreg = false)
827     {
828         if (!$this->validate(PEAR_VALIDATE_NORMAL)) {
829             return false;
830         }
831         return $this->getArray($forreg);
832     }
833
834     function getArray($forReg = false)
835     {
836         if ($forReg) {
837             $arr = $this->_packageInfo;
838             $arr['old'] = array();
839             $arr['old']['version'] = $this->getVersion();
840             $arr['old']['release_date'] = $this->getDate();
841             $arr['old']['release_state'] = $this->getState();
842             $arr['old']['release_license'] = $this->getLicense();
843             $arr['old']['release_notes'] = $this->getNotes();
844             $arr['old']['release_deps'] = $this->getDeps();
845             $arr['old']['maintainers'] = $this->getMaintainers();
846             $arr['xsdversion'] = '2.0';
847             return $arr;
848         } else {
849             $info = $this->_packageInfo;
850             unset($info['dirtree']);
851             if (isset($info['_lastversion'])) {
852                 unset($info['_lastversion']);
853             }
854             if (isset($info['#binarypackage'])) {
855                 unset($info['#binarypackage']);
856             }
857             return $info;
858         }
859     }
860
861     function packageInfo($field)
862     {
863         $arr = $this->getArray(true);
864         if ($field == 'state') {
865             return $arr['stability']['release'];
866         }
867         if ($field == 'api-version') {
868             return $arr['version']['api'];
869         }
870         if ($field == 'api-state') {
871             return $arr['stability']['api'];
872         }
873         if (isset($arr['old'][$field])) {
874             if (!is_string($arr['old'][$field])) {
875                 return null;
876             }
877             return $arr['old'][$field];
878         }
879         if (isset($arr[$field])) {
880             if (!is_string($arr[$field])) {
881                 return null;
882             }
883             return $arr[$field];
884         }
885         return null;
886     }
887
888     function getName()
889     {
890         return $this->getPackage();
891     }
892
893     function getPackage()
894     {
895         if (isset($this->_packageInfo['name'])) {
896             return $this->_packageInfo['name'];
897         }
898         return false;
899     }
900
901     function getChannel()
902     {
903         if (isset($this->_packageInfo['uri'])) {
904             return '__uri';
905         }
906         if (isset($this->_packageInfo['channel'])) {
907             return strtolower($this->_packageInfo['channel']);
908         }
909         return false;
910     }
911
912     function getUri()
913     {
914         if (isset($this->_packageInfo['uri'])) {
915             return $this->_packageInfo['uri'];
916         }
917         return false;
918     }
919
920     function getExtends()
921     {
922         if (isset($this->_packageInfo['extends'])) {
923             return $this->_packageInfo['extends'];
924         }
925         return false;
926     }
927
928     function getSummary()
929     {
930         if (isset($this->_packageInfo['summary'])) {
931             return $this->_packageInfo['summary'];
932         }
933         return false;
934     }
935
936     function getDescription()
937     {
938         if (isset($this->_packageInfo['description'])) {
939             return $this->_packageInfo['description'];
940         }
941         return false;
942     }
943
944     function getMaintainers($raw = false)
945     {
946         if (!isset($this->_packageInfo['lead'])) {
947             return false;
948         }
949         if ($raw) {
950             $ret = array('lead' => $this->_packageInfo['lead']);
951             (isset($this->_packageInfo['developer'])) ?
952                 $ret['developer'] = $this->_packageInfo['developer'] :null;
953             (isset($this->_packageInfo['contributor'])) ?
954                 $ret['contributor'] = $this->_packageInfo['contributor'] :null;
955             (isset($this->_packageInfo['helper'])) ?
956                 $ret['helper'] = $this->_packageInfo['helper'] :null;
957             return $ret;
958         } else {
959             $ret = array();
960             $leads = isset($this->_packageInfo['lead'][0]) ? $this->_packageInfo['lead'] :
961                 array($this->_packageInfo['lead']);
962             foreach ($leads as $lead) {
963                 $s = $lead;
964                 $s['handle'] = $s['user'];
965                 unset($s['user']);
966                 $s['role'] = 'lead';
967                 $ret[] = $s;
968             }
969             if (isset($this->_packageInfo['developer'])) {
970                 $leads = isset($this->_packageInfo['developer'][0]) ?
971                     $this->_packageInfo['developer'] :
972                     array($this->_packageInfo['developer']);
973                 foreach ($leads as $maintainer) {
974                     $s = $maintainer;
975                     $s['handle'] = $s['user'];
976                     unset($s['user']);
977                     $s['role'] = 'developer';
978                     $ret[] = $s;
979                 }
980             }
981             if (isset($this->_packageInfo['contributor'])) {
982                 $leads = isset($this->_packageInfo['contributor'][0]) ?
983                     $this->_packageInfo['contributor'] :
984                     array($this->_packageInfo['contributor']);
985                 foreach ($leads as $maintainer) {
986                     $s = $maintainer;
987                     $s['handle'] = $s['user'];
988                     unset($s['user']);
989                     $s['role'] = 'contributor';
990                     $ret[] = $s;
991                 }
992             }
993             if (isset($this->_packageInfo['helper'])) {
994                 $leads = isset($this->_packageInfo['helper'][0]) ?
995                     $this->_packageInfo['helper'] :
996                     array($this->_packageInfo['helper']);
997                 foreach ($leads as $maintainer) {
998                     $s = $maintainer;
999                     $s['handle'] = $s['user'];
1000                     unset($s['user']);
1001                     $s['role'] = 'helper';
1002                     $ret[] = $s;
1003                 }
1004             }
1005             return $ret;
1006         }
1007         return false;
1008     }
1009
1010     function getLeads()
1011     {
1012         if (isset($this->_packageInfo['lead'])) {
1013             return $this->_packageInfo['lead'];
1014         }
1015         return false;
1016     }
1017
1018     function getDevelopers()
1019     {
1020         if (isset($this->_packageInfo['developer'])) {
1021             return $this->_packageInfo['developer'];
1022         }
1023         return false;
1024     }
1025
1026     function getContributors()
1027     {
1028         if (isset($this->_packageInfo['contributor'])) {
1029             return $this->_packageInfo['contributor'];
1030         }
1031         return false;
1032     }
1033
1034     function getHelpers()
1035     {
1036         if (isset($this->_packageInfo['helper'])) {
1037             return $this->_packageInfo['helper'];
1038         }
1039         return false;
1040     }
1041
1042     function setDate($date)
1043     {
1044         if (!isset($this->_packageInfo['date'])) {
1045             // ensure that the extends tag is set up in the right location
1046             $this->_packageInfo = $this->_insertBefore($this->_packageInfo,
1047                 array('time', 'version',
1048                     'stability', 'license', 'notes', 'contents', 'compatible',
1049                     'dependencies', 'providesextension', 'srcpackage', 'srcuri',
1050                     'phprelease', 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease',
1051                     'zendextbinrelease', 'bundle', 'changelog'), array(), 'date');
1052         }
1053         $this->_packageInfo['date'] = $date;
1054         $this->_isValid = 0;
1055     }
1056
1057     function setTime($time)
1058     {
1059         $this->_isValid = 0;
1060         if (!isset($this->_packageInfo['time'])) {
1061             // ensure that the time tag is set up in the right location
1062             $this->_packageInfo = $this->_insertBefore($this->_packageInfo,
1063                     array('version',
1064                     'stability', 'license', 'notes', 'contents', 'compatible',
1065                     'dependencies', 'providesextension', 'srcpackage', 'srcuri',
1066                     'phprelease', 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease',
1067                     'zendextbinrelease', 'bundle', 'changelog'), $time, 'time');
1068         }
1069         $this->_packageInfo['time'] = $time;
1070     }
1071
1072     function getDate()
1073     {
1074         if (isset($this->_packageInfo['date'])) {
1075             return $this->_packageInfo['date'];
1076         }
1077         return false;
1078     }
1079
1080     function getTime()
1081     {
1082         if (isset($this->_packageInfo['time'])) {
1083             return $this->_packageInfo['time'];
1084         }
1085         return false;
1086     }
1087
1088     /**
1089      * @param package|api version category to return
1090      */
1091     function getVersion($key = 'release')
1092     {
1093         if (isset($this->_packageInfo['version'][$key])) {
1094             return $this->_packageInfo['version'][$key];
1095         }
1096         return false;
1097     }
1098
1099     function getStability()
1100     {
1101         if (isset($this->_packageInfo['stability'])) {
1102             return $this->_packageInfo['stability'];
1103         }
1104         return false;
1105     }
1106
1107     function getState($key = 'release')
1108     {
1109         if (isset($this->_packageInfo['stability'][$key])) {
1110             return $this->_packageInfo['stability'][$key];
1111         }
1112         return false;
1113     }
1114
1115     function getLicense($raw = false)
1116     {
1117         if (isset($this->_packageInfo['license'])) {
1118             if ($raw) {
1119                 return $this->_packageInfo['license'];
1120             }
1121             if (is_array($this->_packageInfo['license'])) {
1122                 return $this->_packageInfo['license']['_content'];
1123             } else {
1124                 return $this->_packageInfo['license'];
1125             }
1126         }
1127         return false;
1128     }
1129
1130     function getLicenseLocation()
1131     {
1132         if (!isset($this->_packageInfo['license']) || !is_array($this->_packageInfo['license'])) {
1133             return false;
1134         }
1135         return $this->_packageInfo['license']['attribs'];
1136     }
1137
1138     function getNotes()
1139     {
1140         if (isset($this->_packageInfo['notes'])) {
1141             return $this->_packageInfo['notes'];
1142         }
1143         return false;
1144     }
1145
1146     /**
1147      * Return the <usesrole> tag contents, if any
1148      * @return array|false
1149      */
1150     function getUsesrole()
1151     {
1152         if (isset($this->_packageInfo['usesrole'])) {
1153             return $this->_packageInfo['usesrole'];
1154         }
1155         return false;
1156     }
1157
1158     /**
1159      * Return the <usestask> tag contents, if any
1160      * @return array|false
1161      */
1162     function getUsestask()
1163     {
1164         if (isset($this->_packageInfo['usestask'])) {
1165             return $this->_packageInfo['usestask'];
1166         }
1167         return false;
1168     }
1169
1170     /**
1171      * This should only be used to retrieve filenames and install attributes
1172      */
1173     function getFilelist($preserve = false)
1174     {
1175         if (isset($this->_packageInfo['filelist']) && !$preserve) {
1176             return $this->_packageInfo['filelist'];
1177         }
1178         $this->flattenFilelist();
1179         if ($contents = $this->getContents()) {
1180             $ret = array();
1181             if (!isset($contents['dir'])) {
1182                 return false;
1183             }
1184             if (!isset($contents['dir']['file'][0])) {
1185                 $contents['dir']['file'] = array($contents['dir']['file']);
1186             }
1187             foreach ($contents['dir']['file'] as $file) {
1188                 if (!isset($file['attribs']['name'])) {
1189                     continue;
1190                 }
1191                 $name = $file['attribs']['name'];
1192                 if (!$preserve) {
1193                     $file = $file['attribs'];
1194                 }
1195                 $ret[$name] = $file;
1196             }
1197             if (!$preserve) {
1198                 $this->_packageInfo['filelist'] = $ret;
1199             }
1200             return $ret;
1201         }
1202         return false;
1203     }
1204
1205     /**
1206      * Return configure options array, if any
1207      *
1208      * @return array|false
1209      */
1210     function getConfigureOptions()
1211     {
1212         if ($this->getPackageType() != 'extsrc' && $this->getPackageType() != 'zendextsrc') {
1213             return false;
1214         }
1215
1216         $releases = $this->getReleases();
1217         if (isset($releases[0])) {
1218             $releases = $releases[0];
1219         }
1220
1221         if (isset($releases['configureoption'])) {
1222             if (!isset($releases['configureoption'][0])) {
1223                 $releases['configureoption'] = array($releases['configureoption']);
1224             }
1225
1226             for ($i = 0; $i < count($releases['configureoption']); $i++) {
1227                 $releases['configureoption'][$i] = $releases['configureoption'][$i]['attribs'];
1228             }
1229
1230             return $releases['configureoption'];
1231         }
1232
1233         return false;
1234     }
1235
1236     /**
1237      * This is only used at install-time, after all serialization
1238      * is over.
1239      */
1240     function resetFilelist()
1241     {
1242         $this->_packageInfo['filelist'] = array();
1243     }
1244
1245     /**
1246      * Retrieve a list of files that should be installed on this computer
1247      * @return array
1248      */
1249     function getInstallationFilelist($forfilecheck = false)
1250     {
1251         $contents = $this->getFilelist(true);
1252         if (isset($contents['dir']['attribs']['baseinstalldir'])) {
1253             $base = $contents['dir']['attribs']['baseinstalldir'];
1254         }
1255         if (isset($this->_packageInfo['bundle'])) {
1256             return PEAR::raiseError(
1257                 'Exception: bundles should be handled in download code only');
1258         }
1259         $release = $this->getReleases();
1260         if ($release) {
1261             if (!isset($release[0])) {
1262                 if (!isset($release['installconditions']) && !isset($release['filelist'])) {
1263                     if ($forfilecheck) {
1264                         return $this->getFilelist();
1265                     }
1266                     return $contents;
1267                 }
1268                 $release = array($release);
1269             }
1270             $depchecker = &$this->getPEARDependency2($this->_config, array(),
1271                 array('channel' => $this->getChannel(), 'package' => $this->getPackage()),
1272                 PEAR_VALIDATE_INSTALLING);
1273             foreach ($release as $instance) {
1274                 if (isset($instance['installconditions'])) {
1275                     $installconditions = $instance['installconditions'];
1276                     if (is_array($installconditions)) {
1277                         PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
1278                         foreach ($installconditions as $type => $conditions) {
1279                             if (!isset($conditions[0])) {
1280                                 $conditions = array($conditions);
1281                             }
1282                             foreach ($conditions as $condition) {
1283                                 $ret = $depchecker->{"validate{$type}Dependency"}($condition);
1284                                 if (PEAR::isError($ret)) {
1285                                     PEAR::popErrorHandling();
1286                                     continue 3; // skip this release
1287                                 }
1288                             }
1289                         }
1290                         PEAR::popErrorHandling();
1291                     }
1292                 }
1293                 // this is the release to use
1294                 if (isset($instance['filelist'])) {
1295                     // ignore files
1296                     if (isset($instance['filelist']['ignore'])) {
1297                         $ignore = isset($instance['filelist']['ignore'][0]) ?
1298                             $instance['filelist']['ignore'] :
1299                             array($instance['filelist']['ignore']);
1300                         foreach ($ignore as $ig) {
1301                             unset ($contents[$ig['attribs']['name']]);
1302                         }
1303                     }
1304                     // install files as this name
1305                     if (isset($instance['filelist']['install'])) {
1306                         $installas = isset($instance['filelist']['install'][0]) ?
1307                             $instance['filelist']['install'] :
1308                             array($instance['filelist']['install']);
1309                         foreach ($installas as $as) {
1310                             $contents[$as['attribs']['name']]['attribs']['install-as'] =
1311                                 $as['attribs']['as'];
1312                         }
1313                     }
1314                 }
1315                 if ($forfilecheck) {
1316                     foreach ($contents as $file => $attrs) {
1317                         $contents[$file] = $attrs['attribs'];
1318                     }
1319                 }
1320                 return $contents;
1321             }
1322         } else { // simple release - no installconditions or install-as
1323             if ($forfilecheck) {
1324                 return $this->getFilelist();
1325             }
1326             return $contents;
1327         }
1328         // no releases matched
1329         return PEAR::raiseError('No releases in package.xml matched the existing operating ' .
1330             'system, extensions installed, or architecture, cannot install');
1331     }
1332
1333     /**
1334      * This is only used at install-time, after all serialization
1335      * is over.
1336      * @param string file name
1337      * @param string installed path
1338      */
1339     function setInstalledAs($file, $path)
1340     {
1341         if ($path) {
1342             return $this->_packageInfo['filelist'][$file]['installed_as'] = $path;
1343         }
1344         unset($this->_packageInfo['filelist'][$file]['installed_as']);
1345     }
1346
1347     function getInstalledLocation($file)
1348     {
1349         if (isset($this->_packageInfo['filelist'][$file]['installed_as'])) {
1350             return $this->_packageInfo['filelist'][$file]['installed_as'];
1351         }
1352         return false;
1353     }
1354
1355     /**
1356      * This is only used at install-time, after all serialization
1357      * is over.
1358      */
1359     function installedFile($file, $atts)
1360     {
1361         if (isset($this->_packageInfo['filelist'][$file])) {
1362             $this->_packageInfo['filelist'][$file] =
1363                 array_merge($this->_packageInfo['filelist'][$file], $atts['attribs']);
1364         } else {
1365             $this->_packageInfo['filelist'][$file] = $atts['attribs'];
1366         }
1367     }
1368
1369     /**
1370      * Retrieve the contents tag
1371      */
1372     function getContents()
1373     {
1374         if (isset($this->_packageInfo['contents'])) {
1375             return $this->_packageInfo['contents'];
1376         }
1377         return false;
1378     }
1379
1380     /**
1381      * @param string full path to file
1382      * @param string attribute name
1383      * @param string attribute value
1384      * @param int risky but fast - use this to choose a file based on its position in the list
1385      *            of files.  Index is zero-based like PHP arrays.
1386      * @return bool success of operation
1387      */
1388     function setFileAttribute($filename, $attr, $value, $index = false)
1389     {
1390         $this->_isValid = 0;
1391         if (in_array($attr, array('role', 'name', 'baseinstalldir'))) {
1392             $this->_filesValid = false;
1393         }
1394         if ($index !== false &&
1395               isset($this->_packageInfo['contents']['dir']['file'][$index]['attribs'])) {
1396             $this->_packageInfo['contents']['dir']['file'][$index]['attribs'][$attr] = $value;
1397             return true;
1398         }
1399         if (!isset($this->_packageInfo['contents']['dir']['file'])) {
1400             return false;
1401         }
1402         $files = $this->_packageInfo['contents']['dir']['file'];
1403         if (!isset($files[0])) {
1404             $files = array($files);
1405             $ind = false;
1406         } else {
1407             $ind = true;
1408         }
1409         foreach ($files as $i => $file) {
1410             if (isset($file['attribs'])) {
1411                 if ($file['attribs']['name'] == $filename) {
1412                     if ($ind) {
1413                         $this->_packageInfo['contents']['dir']['file'][$i]['attribs'][$attr] = $value;
1414                     } else {
1415                         $this->_packageInfo['contents']['dir']['file']['attribs'][$attr] = $value;
1416                     }
1417                     return true;
1418                 }
1419             }
1420         }
1421         return false;
1422     }
1423
1424     function setDirtree($path)
1425     {
1426         if (!isset($this->_packageInfo['dirtree'])) {
1427             $this->_packageInfo['dirtree'] = array();
1428         }
1429         $this->_packageInfo['dirtree'][$path] = true;
1430     }
1431
1432     function getDirtree()
1433     {
1434         if (isset($this->_packageInfo['dirtree']) && count($this->_packageInfo['dirtree'])) {
1435             return $this->_packageInfo['dirtree'];
1436         }
1437         return false;
1438     }
1439
1440     function resetDirtree()
1441     {
1442         unset($this->_packageInfo['dirtree']);
1443     }
1444
1445     /**
1446      * Determines whether this package claims it is compatible with the version of
1447      * the package that has a recommended version dependency
1448      * @param PEAR_PackageFile_v2|PEAR_PackageFile_v1|PEAR_Downloader_Package
1449      * @return boolean
1450      */
1451     function isCompatible($pf)
1452     {
1453         if (!isset($this->_packageInfo['compatible'])) {
1454             return false;
1455         }
1456         if (!isset($this->_packageInfo['channel'])) {
1457             return false;
1458         }
1459         $me = $pf->getVersion();
1460         $compatible = $this->_packageInfo['compatible'];
1461         if (!isset($compatible[0])) {
1462             $compatible = array($compatible);
1463         }
1464         $found = false;
1465         foreach ($compatible as $info) {
1466             if (strtolower($info['name']) == strtolower($pf->getPackage())) {
1467                 if (strtolower($info['channel']) == strtolower($pf->getChannel())) {
1468                     $found = true;
1469                     break;
1470                 }
1471             }
1472         }
1473         if (!$found) {
1474             return false;
1475         }
1476         if (isset($info['exclude'])) {
1477             if (!isset($info['exclude'][0])) {
1478                 $info['exclude'] = array($info['exclude']);
1479             }
1480             foreach ($info['exclude'] as $exclude) {
1481                 if (version_compare($me, $exclude, '==')) {
1482                     return false;
1483                 }
1484             }
1485         }
1486         if (version_compare($me, $info['min'], '>=') && version_compare($me, $info['max'], '<=')) {
1487             return true;
1488         }
1489         return false;
1490     }
1491
1492     /**
1493      * @return array|false
1494      */
1495     function getCompatible()
1496     {
1497         if (isset($this->_packageInfo['compatible'])) {
1498             return $this->_packageInfo['compatible'];
1499         }
1500         return false;
1501     }
1502
1503     function getDependencies()
1504     {
1505         if (isset($this->_packageInfo['dependencies'])) {
1506             return $this->_packageInfo['dependencies'];
1507         }
1508         return false;
1509     }
1510
1511     function isSubpackageOf($p)
1512     {
1513         return $p->isSubpackage($this);
1514     }
1515
1516     /**
1517      * Determines whether the passed in package is a subpackage of this package.
1518      *
1519      * No version checking is done, only name verification.
1520      * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
1521      * @return bool
1522      */
1523     function isSubpackage($p)
1524     {
1525         $sub = array();
1526         if (isset($this->_packageInfo['dependencies']['required']['subpackage'])) {
1527             $sub = $this->_packageInfo['dependencies']['required']['subpackage'];
1528             if (!isset($sub[0])) {
1529                 $sub = array($sub);
1530             }
1531         }
1532         if (isset($this->_packageInfo['dependencies']['optional']['subpackage'])) {
1533             $sub1 = $this->_packageInfo['dependencies']['optional']['subpackage'];
1534             if (!isset($sub1[0])) {
1535                 $sub1 = array($sub1);
1536             }
1537             $sub = array_merge($sub, $sub1);
1538         }
1539         if (isset($this->_packageInfo['dependencies']['group'])) {
1540             $group = $this->_packageInfo['dependencies']['group'];
1541             if (!isset($group[0])) {
1542                 $group = array($group);
1543             }
1544             foreach ($group as $deps) {
1545                 if (isset($deps['subpackage'])) {
1546                     $sub2 = $deps['subpackage'];
1547                     if (!isset($sub2[0])) {
1548                         $sub2 = array($sub2);
1549                     }
1550                     $sub = array_merge($sub, $sub2);
1551                 }
1552             }
1553         }
1554         foreach ($sub as $dep) {
1555             if (strtolower($dep['name']) == strtolower($p->getPackage())) {
1556                 if (isset($dep['channel'])) {
1557                     if (strtolower($dep['channel']) == strtolower($p->getChannel())) {
1558                         return true;
1559                     }
1560                 } else {
1561                     if ($dep['uri'] == $p->getURI()) {
1562                         return true;
1563                     }
1564                 }
1565             }
1566         }
1567         return false;
1568     }
1569
1570     function dependsOn($package, $channel)
1571     {
1572         if (!($deps = $this->getDependencies())) {
1573             return false;
1574         }
1575         foreach (array('package', 'subpackage') as $type) {
1576             foreach (array('required', 'optional') as $needed) {
1577                 if (isset($deps[$needed][$type])) {
1578                     if (!isset($deps[$needed][$type][0])) {
1579                         $deps[$needed][$type] = array($deps[$needed][$type]);
1580                     }
1581                     foreach ($deps[$needed][$type] as $dep) {
1582                         $depchannel = isset($dep['channel']) ? $dep['channel'] : '__uri';
1583                         if (strtolower($dep['name']) == strtolower($package) &&
1584                               $depchannel == $channel) {
1585                             return true;
1586                         }
1587                     }
1588                 }
1589             }
1590             if (isset($deps['group'])) {
1591                 if (!isset($deps['group'][0])) {
1592                     $dep['group'] = array($deps['group']);
1593                 }
1594                 foreach ($deps['group'] as $group) {
1595                     if (isset($group[$type])) {
1596                         if (!is_array($group[$type])) {
1597                             $group[$type] = array($group[$type]);
1598                         }
1599                         foreach ($group[$type] as $dep) {
1600                             $depchannel = isset($dep['channel']) ? $dep['channel'] : '__uri';
1601                             if (strtolower($dep['name']) == strtolower($package) &&
1602                                   $depchannel == $channel) {
1603                                 return true;
1604                             }
1605                         }
1606                     }
1607                 }
1608             }
1609         }
1610         return false;
1611     }
1612
1613     /**
1614      * Get the contents of a dependency group
1615      * @param string
1616      * @return array|false
1617      */
1618     function getDependencyGroup($name)
1619     {
1620         $name = strtolower($name);
1621         if (!isset($this->_packageInfo['dependencies']['group'])) {
1622             return false;
1623         }
1624         $groups = $this->_packageInfo['dependencies']['group'];
1625         if (!isset($groups[0])) {
1626             $groups = array($groups);
1627         }
1628         foreach ($groups as $group) {
1629             if (strtolower($group['attribs']['name']) == $name) {
1630                 return $group;
1631             }
1632         }
1633         return false;
1634     }
1635
1636     /**
1637      * Retrieve a partial package.xml 1.0 representation of dependencies
1638      *
1639      * a very limited representation of dependencies is returned by this method.
1640      * The <exclude> tag for excluding certain versions of a dependency is
1641      * completely ignored.  In addition, dependency groups are ignored, with the
1642      * assumption that all dependencies in dependency groups are also listed in
1643      * the optional group that work with all dependency groups
1644      * @param boolean return package.xml 2.0 <dependencies> tag
1645      * @return array|false
1646      */
1647     function getDeps($raw = false, $nopearinstaller = false)
1648     {
1649         if (isset($this->_packageInfo['dependencies'])) {
1650             if ($raw) {
1651                 return $this->_packageInfo['dependencies'];
1652             }
1653             $ret = array();
1654             $map = array(
1655                 'php' => 'php',
1656                 'package' => 'pkg',
1657                 'subpackage' => 'pkg',
1658                 'extension' => 'ext',
1659                 'os' => 'os',
1660                 'pearinstaller' => 'pkg',
1661                 );
1662             foreach (array('required', 'optional') as $type) {
1663                 $optional = ($type == 'optional') ? 'yes' : 'no';
1664                 if (!isset($this->_packageInfo['dependencies'][$type])
1665                     || empty($this->_packageInfo['dependencies'][$type])) {
1666                     continue;
1667                 }
1668                 foreach ($this->_packageInfo['dependencies'][$type] as $dtype => $deps) {
1669                     if ($dtype == 'pearinstaller' && $nopearinstaller) {
1670                         continue;
1671                     }
1672                     if (!isset($deps[0])) {
1673                         $deps = array($deps);
1674                     }
1675                     foreach ($deps as $dep) {
1676                         if (!isset($map[$dtype])) {
1677                             // no support for arch type
1678                             continue;
1679                         }
1680                         if ($dtype == 'pearinstaller') {
1681                             $dep['name'] = 'PEAR';
1682                             $dep['channel'] = 'pear.php.net';
1683                         }
1684                         $s = array('type' => $map[$dtype]);
1685                         if (isset($dep['channel'])) {
1686                             $s['channel'] = $dep['channel'];
1687                         }
1688                         if (isset($dep['uri'])) {
1689                             $s['uri'] = $dep['uri'];
1690                         }
1691                         if (isset($dep['name'])) {
1692                             $s['name'] = $dep['name'];
1693                         }
1694                         if (isset($dep['conflicts'])) {
1695                             $s['rel'] = 'not';
1696                         } else {
1697                             if (!isset($dep['min']) &&
1698                                   !isset($dep['max'])) {
1699                                 $s['rel'] = 'has';
1700                                 $s['optional'] = $optional;
1701                             } elseif (isset($dep['min']) &&
1702                                   isset($dep['max'])) {
1703                                 $s['rel'] = 'ge';
1704                                 $s1 = $s;
1705                                 $s1['rel'] = 'le';
1706                                 $s['version'] = $dep['min'];
1707                                 $s1['version'] = $dep['max'];
1708                                 if (isset($dep['channel'])) {
1709                                     $s1['channel'] = $dep['channel'];
1710                                 }
1711                                 if ($dtype != 'php') {
1712                                     $s['name'] = $dep['name'];
1713                                     $s1['name'] = $dep['name'];
1714                                 }
1715                                 $s['optional'] = $optional;
1716                                 $s1['optional'] = $optional;
1717                                 $ret[] = $s1;
1718                             } elseif (isset($dep['min'])) {
1719                                 if (isset($dep['exclude']) &&
1720                                       $dep['exclude'] == $dep['min']) {
1721                                     $s['rel'] = 'gt';
1722                                 } else {
1723                                     $s['rel'] = 'ge';
1724                                 }
1725                                 $s['version'] = $dep['min'];
1726                                 $s['optional'] = $optional;
1727                                 if ($dtype != 'php') {
1728                                     $s['name'] = $dep['name'];
1729                                 }
1730                             } elseif (isset($dep['max'])) {
1731                                 if (isset($dep['exclude']) &&
1732                                       $dep['exclude'] == $dep['max']) {
1733                                     $s['rel'] = 'lt';
1734                                 } else {
1735                                     $s['rel'] = 'le';
1736                                 }
1737                                 $s['version'] = $dep['max'];
1738                                 $s['optional'] = $optional;
1739                                 if ($dtype != 'php') {
1740                                     $s['name'] = $dep['name'];
1741                                 }
1742                             }
1743                         }
1744                         $ret[] = $s;
1745                     }
1746                 }
1747             }
1748             if (count($ret)) {
1749                 return $ret;
1750             }
1751         }
1752         return false;
1753     }
1754
1755     /**
1756      * @return php|extsrc|extbin|zendextsrc|zendextbin|bundle|false
1757      */
1758     function getPackageType()
1759     {
1760         if (isset($this->_packageInfo['phprelease'])) {
1761             return 'php';
1762         }
1763         if (isset($this->_packageInfo['extsrcrelease'])) {
1764             return 'extsrc';
1765         }
1766         if (isset($this->_packageInfo['extbinrelease'])) {
1767             return 'extbin';
1768         }
1769         if (isset($this->_packageInfo['zendextsrcrelease'])) {
1770             return 'zendextsrc';
1771         }
1772         if (isset($this->_packageInfo['zendextbinrelease'])) {
1773             return 'zendextbin';
1774         }
1775         if (isset($this->_packageInfo['bundle'])) {
1776             return 'bundle';
1777         }
1778         return false;
1779     }
1780
1781     /**
1782      * @return array|false
1783      */
1784     function getReleases()
1785     {
1786         $type = $this->getPackageType();
1787         if ($type != 'bundle') {
1788             $type .= 'release';
1789         }
1790         if ($this->getPackageType() && isset($this->_packageInfo[$type])) {
1791             return $this->_packageInfo[$type];
1792         }
1793         return false;
1794     }
1795
1796     /**
1797      * @return array
1798      */
1799     function getChangelog()
1800     {
1801         if (isset($this->_packageInfo['changelog'])) {
1802             return $this->_packageInfo['changelog'];
1803         }
1804         return false;
1805     }
1806
1807     function hasDeps()
1808     {
1809         return isset($this->_packageInfo['dependencies']);
1810     }
1811
1812     function getPackagexmlVersion()
1813     {
1814         if (isset($this->_packageInfo['zendextsrcrelease'])) {
1815             return '2.1';
1816         }
1817         if (isset($this->_packageInfo['zendextbinrelease'])) {
1818             return '2.1';
1819         }
1820         return '2.0';
1821     }
1822
1823     /**
1824      * @return array|false
1825      */
1826     function getSourcePackage()
1827     {
1828         if (isset($this->_packageInfo['extbinrelease']) ||
1829               isset($this->_packageInfo['zendextbinrelease'])) {
1830             return array('channel' => $this->_packageInfo['srcchannel'],
1831                          'package' => $this->_packageInfo['srcpackage']);
1832         }
1833         return false;
1834     }
1835
1836     function getBundledPackages()
1837     {
1838         if (isset($this->_packageInfo['bundle'])) {
1839             return $this->_packageInfo['contents']['bundledpackage'];
1840         }
1841         return false;
1842     }
1843
1844     function getLastModified()
1845     {
1846         if (isset($this->_packageInfo['_lastmodified'])) {
1847             return $this->_packageInfo['_lastmodified'];
1848         }
1849         return false;
1850     }
1851
1852     /**
1853      * Get the contents of a file listed within the package.xml
1854      * @param string
1855      * @return string
1856      */
1857     function getFileContents($file)
1858     {
1859         if ($this->_archiveFile == $this->_packageFile) { // unpacked
1860             $dir = dirname($this->_packageFile);
1861             $file = $dir . DIRECTORY_SEPARATOR . $file;
1862             $file = str_replace(array('/', '\\'),
1863                 array(DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR), $file);
1864             if (file_exists($file) && is_readable($file)) {
1865                 return implode('', file($file));
1866             }
1867         } else { // tgz
1868             $tar = new Archive_Tar($this->_archiveFile);
1869             $tar->pushErrorHandling(PEAR_ERROR_RETURN);
1870             if ($file != 'package.xml' && $file != 'package2.xml') {
1871                 $file = $this->getPackage() . '-' . $this->getVersion() . '/' . $file;
1872             }
1873             $file = $tar->extractInString($file);
1874             $tar->popErrorHandling();
1875             if (PEAR::isError($file)) {
1876                 return PEAR::raiseError("Cannot locate file '$file' in archive");
1877             }
1878             return $file;
1879         }
1880     }
1881
1882     function &getRW()
1883     {
1884         if (!class_exists('PEAR_PackageFile_v2_rw')) {
1885             require_once 'PEAR/PackageFile/v2/rw.php';
1886         }
1887         $a = new PEAR_PackageFile_v2_rw;
1888         foreach (get_object_vars($this) as $name => $unused) {
1889             if (!isset($this->$name)) {
1890                 continue;
1891             }
1892             if ($name == '_config' || $name == '_logger'|| $name == '_registry' ||
1893                   $name == '_stack') {
1894                 $a->$name = &$this->$name;
1895             } else {
1896                 $a->$name = $this->$name;
1897             }
1898         }
1899         return $a;
1900     }
1901
1902     function &getDefaultGenerator()
1903     {
1904         if (!class_exists('PEAR_PackageFile_Generator_v2')) {
1905             require_once 'PEAR/PackageFile/Generator/v2.php';
1906         }
1907         $a = new PEAR_PackageFile_Generator_v2($this);
1908         return $a;
1909     }
1910
1911     function analyzeSourceCode($file, $string = false)
1912     {
1913         if (!isset($this->_v2Validator) ||
1914               !is_a($this->_v2Validator, 'PEAR_PackageFile_v2_Validator')) {
1915             if (!class_exists('PEAR_PackageFile_v2_Validator')) {
1916                 require_once 'PEAR/PackageFile/v2/Validator.php';
1917             }
1918             $this->_v2Validator = new PEAR_PackageFile_v2_Validator;
1919         }
1920         return $this->_v2Validator->analyzeSourceCode($file, $string);
1921     }
1922
1923     function validate($state = PEAR_VALIDATE_NORMAL)
1924     {
1925         if (!isset($this->_packageInfo) || !is_array($this->_packageInfo)) {
1926             return false;
1927         }
1928         if (!isset($this->_v2Validator) ||
1929               !is_a($this->_v2Validator, 'PEAR_PackageFile_v2_Validator')) {
1930             if (!class_exists('PEAR_PackageFile_v2_Validator')) {
1931                 require_once 'PEAR/PackageFile/v2/Validator.php';
1932             }
1933             $this->_v2Validator = new PEAR_PackageFile_v2_Validator;
1934         }
1935         if (isset($this->_packageInfo['xsdversion'])) {
1936             unset($this->_packageInfo['xsdversion']);
1937         }
1938         return $this->_v2Validator->validate($this, $state);
1939     }
1940
1941     function getTasksNs()
1942     {
1943         if (!isset($this->_tasksNs)) {
1944             if (isset($this->_packageInfo['attribs'])) {
1945                 foreach ($this->_packageInfo['attribs'] as $name => $value) {
1946                     if ($value == 'http://pear.php.net/dtd/tasks-1.0') {
1947                         $this->_tasksNs = str_replace('xmlns:', '', $name);
1948                         break;
1949                     }
1950                 }
1951             }
1952         }
1953         return $this->_tasksNs;
1954     }
1955
1956     /**
1957      * Determine whether a task name is a valid task.  Custom tasks may be defined
1958      * using subdirectories by putting a "-" in the name, as in <tasks:mycustom-task>
1959      *
1960      * Note that this method will auto-load the task class file and test for the existence
1961      * of the name with "-" replaced by "_" as in PEAR/Task/mycustom/task.php makes class
1962      * PEAR_Task_mycustom_task
1963      * @param string
1964      * @return boolean
1965      */
1966     function getTask($task)
1967     {
1968         $this->getTasksNs();
1969         // transform all '-' to '/' and 'tasks:' to '' so tasks:replace becomes replace
1970         $task = str_replace(array($this->_tasksNs . ':', '-'), array('', ' '), $task);
1971         $taskfile = str_replace(' ', '/', ucwords($task));
1972         $task = str_replace(array(' ', '/'), '_', ucwords($task));
1973         if (class_exists("PEAR_Task_$task")) {
1974             return "PEAR_Task_$task";
1975         }
1976         $fp = @fopen("PEAR/Task/$taskfile.php", 'r', true);
1977         if ($fp) {
1978             fclose($fp);
1979             require_once "PEAR/Task/$taskfile.php";
1980             return "PEAR_Task_$task";
1981         }
1982         return false;
1983     }
1984
1985     /**
1986      * Key-friendly array_splice
1987      * @param tagname to splice a value in before
1988      * @param mixed the value to splice in
1989      * @param string the new tag name
1990      */
1991     function _ksplice($array, $key, $value, $newkey)
1992     {
1993         $offset = array_search($key, array_keys($array));
1994         $after = array_slice($array, $offset);
1995         $before = array_slice($array, 0, $offset);
1996         $before[$newkey] = $value;
1997         return array_merge($before, $after);
1998     }
1999
2000     /**
2001      * @param array a list of possible keys, in the order they may occur
2002      * @param mixed contents of the new package.xml tag
2003      * @param string tag name
2004      * @access private
2005      */
2006     function _insertBefore($array, $keys, $contents, $newkey)
2007     {
2008         foreach ($keys as $key) {
2009             if (isset($array[$key])) {
2010                 return $array = $this->_ksplice($array, $key, $contents, $newkey);
2011             }
2012         }
2013         $array[$newkey] = $contents;
2014         return $array;
2015     }
2016
2017     /**
2018      * @param subsection of {@link $_packageInfo}
2019      * @param array|string tag contents
2020      * @param array format:
2021      * <pre>
2022      * array(
2023      *   tagname => array(list of tag names that follow this one),
2024      *   childtagname => array(list of child tag names that follow this one),
2025      * )
2026      * </pre>
2027      *
2028      * This allows construction of nested tags
2029      * @access private
2030      */
2031     function _mergeTag($manip, $contents, $order)
2032     {
2033         if (count($order)) {
2034             foreach ($order as $tag => $curorder) {
2035                 if (!isset($manip[$tag])) {
2036                     // ensure that the tag is set up
2037                     $manip = $this->_insertBefore($manip, $curorder, array(), $tag);
2038                 }
2039                 if (count($order) > 1) {
2040                     $manip[$tag] = $this->_mergeTag($manip[$tag], $contents, array_slice($order, 1));
2041                     return $manip;
2042                 }
2043             }
2044         } else {
2045             return $manip;
2046         }
2047         if (is_array($manip[$tag]) && !empty($manip[$tag]) && isset($manip[$tag][0])) {
2048             $manip[$tag][] = $contents;
2049         } else {
2050             if (!count($manip[$tag])) {
2051                 $manip[$tag] = $contents;
2052             } else {
2053                 $manip[$tag] = array($manip[$tag]);
2054                 $manip[$tag][] = $contents;
2055             }
2056         }
2057         return $manip;
2058     }
2059 }
2060 ?>