]> git.mxchange.org Git - friendica.git/blob - vendor/pear-pear.php.net/PEAR/PEAR/PackageFile/v2.php
Merge pull request #3443 from annando/1705-diaspora-new-salmon
[friendica.git] / vendor / pear-pear.php.net / 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.3
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'], null);
629                 if (!$res) {
630                     continue; // skip this file
631                 }
632                 if (PEAR::isError($res)) {
633                     return $res;
634                 }
635                 $this->_scripts[] = $task;
636             }
637         }
638         if (count($this->_scripts)) {
639             return true;
640         }
641         return false;
642     }
643
644     function runPostinstallScripts()
645     {
646         if ($this->initPostinstallScripts()) {
647             $ui = &PEAR_Frontend::singleton();
648             if ($ui) {
649                 $ui->runPostinstallScripts($this->_scripts, $this);
650             }
651         }
652     }
653
654
655     /**
656      * Convert a recursive set of <dir> and <file> tags into a single <dir> tag with
657      * <file> tags.
658      */
659     function flattenFilelist()
660     {
661         if (isset($this->_packageInfo['bundle'])) {
662             return;
663         }
664         $filelist = array();
665         if (isset($this->_packageInfo['contents']['dir']['dir'])) {
666             $this->_getFlattenedFilelist($filelist, $this->_packageInfo['contents']['dir']);
667             if (!isset($filelist[1])) {
668                 $filelist = $filelist[0];
669             }
670             $this->_packageInfo['contents']['dir']['file'] = $filelist;
671             unset($this->_packageInfo['contents']['dir']['dir']);
672         } else {
673             // else already flattened but check for baseinstalldir propagation
674             if (isset($this->_packageInfo['contents']['dir']['attribs']['baseinstalldir'])) {
675                 if (isset($this->_packageInfo['contents']['dir']['file'][0])) {
676                     foreach ($this->_packageInfo['contents']['dir']['file'] as $i => $file) {
677                         if (isset($file['attribs']['baseinstalldir'])) {
678                             continue;
679                         }
680                         $this->_packageInfo['contents']['dir']['file'][$i]['attribs']['baseinstalldir']
681                             = $this->_packageInfo['contents']['dir']['attribs']['baseinstalldir'];
682                     }
683                 } else {
684                     if (!isset($this->_packageInfo['contents']['dir']['file']['attribs']['baseinstalldir'])) {
685                        $this->_packageInfo['contents']['dir']['file']['attribs']['baseinstalldir']
686                             = $this->_packageInfo['contents']['dir']['attribs']['baseinstalldir'];
687                     }
688                 }
689             }
690         }
691     }
692
693     /**
694      * @param array the final flattened file list
695      * @param array the current directory being processed
696      * @param string|false any recursively inherited baeinstalldir attribute
697      * @param string private recursion variable
698      * @return array
699      * @access protected
700      */
701     function _getFlattenedFilelist(&$files, $dir, $baseinstall = false, $path = '')
702     {
703         if (isset($dir['attribs']) && isset($dir['attribs']['baseinstalldir'])) {
704             $baseinstall = $dir['attribs']['baseinstalldir'];
705         }
706         if (isset($dir['dir'])) {
707             if (!isset($dir['dir'][0])) {
708                 $dir['dir'] = array($dir['dir']);
709             }
710             foreach ($dir['dir'] as $subdir) {
711                 if (!isset($subdir['attribs']) || !isset($subdir['attribs']['name'])) {
712                     $name = '*unknown*';
713                 } else {
714                     $name = $subdir['attribs']['name'];
715                 }
716                 $newpath = empty($path) ? $name :
717                     $path . '/' . $name;
718                 $this->_getFlattenedFilelist($files, $subdir,
719                     $baseinstall, $newpath);
720             }
721         }
722         if (isset($dir['file'])) {
723             if (!isset($dir['file'][0])) {
724                 $dir['file'] = array($dir['file']);
725             }
726             foreach ($dir['file'] as $file) {
727                 $attrs = $file['attribs'];
728                 $name = $attrs['name'];
729                 if ($baseinstall && !isset($attrs['baseinstalldir'])) {
730                     $attrs['baseinstalldir'] = $baseinstall;
731                 }
732                 $attrs['name'] = empty($path) ? $name : $path . '/' . $name;
733                 $attrs['name'] = preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'),
734                     $attrs['name']);
735                 $file['attribs'] = $attrs;
736                 $files[] = $file;
737             }
738         }
739     }
740
741     function setConfig(&$config)
742     {
743         $this->_config = &$config;
744         $this->_registry = &$config->getRegistry();
745     }
746
747     function setLogger(&$logger)
748     {
749         if (!is_object($logger) || !method_exists($logger, 'log')) {
750             return PEAR::raiseError('Logger must be compatible with PEAR_Common::log');
751         }
752         $this->_logger = &$logger;
753     }
754
755     /**
756      * WARNING - do not use this function directly unless you know what you're doing
757      */
758     function setDeps($deps)
759     {
760         $this->_packageInfo['dependencies'] = $deps;
761     }
762
763     /**
764      * WARNING - do not use this function directly unless you know what you're doing
765      */
766     function setCompatible($compat)
767     {
768         $this->_packageInfo['compatible'] = $compat;
769     }
770
771     function setPackagefile($file, $archive = false)
772     {
773         $this->_packageFile = $file;
774         $this->_archiveFile = $archive ? $archive : $file;
775     }
776
777     /**
778      * Wrapper to {@link PEAR_ErrorStack::getErrors()}
779      * @param boolean determines whether to purge the error stack after retrieving
780      * @return array
781      */
782     function getValidationWarnings($purge = true)
783     {
784         return $this->_stack->getErrors($purge);
785     }
786
787     function getPackageFile()
788     {
789         return $this->_packageFile;
790     }
791
792     function getArchiveFile()
793     {
794         return $this->_archiveFile;
795     }
796
797
798     /**
799      * Directly set the array that defines this packagefile
800      *
801      * WARNING: no validation.  This should only be performed by internal methods
802      * inside PEAR or by inputting an array saved from an existing PEAR_PackageFile_v2
803      * @param array
804      */
805     function fromArray($pinfo)
806     {
807         unset($pinfo['old']);
808         unset($pinfo['xsdversion']);
809         // If the changelog isn't an array then it was passed in as an empty tag
810         if (isset($pinfo['changelog']) && !is_array($pinfo['changelog'])) {
811           unset($pinfo['changelog']);
812         }
813         $this->_incomplete = false;
814         $this->_packageInfo = $pinfo;
815     }
816
817     function isIncomplete()
818     {
819         return $this->_incomplete;
820     }
821
822     /**
823      * @return array
824      */
825     function toArray($forreg = false)
826     {
827         if (!$this->validate(PEAR_VALIDATE_NORMAL)) {
828             return false;
829         }
830         return $this->getArray($forreg);
831     }
832
833     function getArray($forReg = false)
834     {
835         if ($forReg) {
836             $arr = $this->_packageInfo;
837             $arr['old'] = array();
838             $arr['old']['version'] = $this->getVersion();
839             $arr['old']['release_date'] = $this->getDate();
840             $arr['old']['release_state'] = $this->getState();
841             $arr['old']['release_license'] = $this->getLicense();
842             $arr['old']['release_notes'] = $this->getNotes();
843             $arr['old']['release_deps'] = $this->getDeps();
844             $arr['old']['maintainers'] = $this->getMaintainers();
845             $arr['xsdversion'] = '2.0';
846             return $arr;
847         } else {
848             $info = $this->_packageInfo;
849             unset($info['dirtree']);
850             if (isset($info['_lastversion'])) {
851                 unset($info['_lastversion']);
852             }
853             if (isset($info['#binarypackage'])) {
854                 unset($info['#binarypackage']);
855             }
856             return $info;
857         }
858     }
859
860     function packageInfo($field)
861     {
862         $arr = $this->getArray(true);
863         if ($field == 'state') {
864             return $arr['stability']['release'];
865         }
866         if ($field == 'api-version') {
867             return $arr['version']['api'];
868         }
869         if ($field == 'api-state') {
870             return $arr['stability']['api'];
871         }
872         if (isset($arr['old'][$field])) {
873             if (!is_string($arr['old'][$field])) {
874                 return null;
875             }
876             return $arr['old'][$field];
877         }
878         if (isset($arr[$field])) {
879             if (!is_string($arr[$field])) {
880                 return null;
881             }
882             return $arr[$field];
883         }
884         return null;
885     }
886
887     function getName()
888     {
889         return $this->getPackage();
890     }
891
892     function getPackage()
893     {
894         if (isset($this->_packageInfo['name'])) {
895             return $this->_packageInfo['name'];
896         }
897         return false;
898     }
899
900     function getChannel()
901     {
902         if (isset($this->_packageInfo['uri'])) {
903             return '__uri';
904         }
905         if (isset($this->_packageInfo['channel'])) {
906             return strtolower($this->_packageInfo['channel']);
907         }
908         return false;
909     }
910
911     function getUri()
912     {
913         if (isset($this->_packageInfo['uri'])) {
914             return $this->_packageInfo['uri'];
915         }
916         return false;
917     }
918
919     function getExtends()
920     {
921         if (isset($this->_packageInfo['extends'])) {
922             return $this->_packageInfo['extends'];
923         }
924         return false;
925     }
926
927     function getSummary()
928     {
929         if (isset($this->_packageInfo['summary'])) {
930             return $this->_packageInfo['summary'];
931         }
932         return false;
933     }
934
935     function getDescription()
936     {
937         if (isset($this->_packageInfo['description'])) {
938             return $this->_packageInfo['description'];
939         }
940         return false;
941     }
942
943     function getMaintainers($raw = false)
944     {
945         if (!isset($this->_packageInfo['lead'])) {
946             return false;
947         }
948         if ($raw) {
949             $ret = array('lead' => $this->_packageInfo['lead']);
950             (isset($this->_packageInfo['developer'])) ?
951                 $ret['developer'] = $this->_packageInfo['developer'] :null;
952             (isset($this->_packageInfo['contributor'])) ?
953                 $ret['contributor'] = $this->_packageInfo['contributor'] :null;
954             (isset($this->_packageInfo['helper'])) ?
955                 $ret['helper'] = $this->_packageInfo['helper'] :null;
956             return $ret;
957         } else {
958             $ret = array();
959             $leads = isset($this->_packageInfo['lead'][0]) ? $this->_packageInfo['lead'] :
960                 array($this->_packageInfo['lead']);
961             foreach ($leads as $lead) {
962                 $s = $lead;
963                 $s['handle'] = $s['user'];
964                 unset($s['user']);
965                 $s['role'] = 'lead';
966                 $ret[] = $s;
967             }
968             if (isset($this->_packageInfo['developer'])) {
969                 $leads = isset($this->_packageInfo['developer'][0]) ?
970                     $this->_packageInfo['developer'] :
971                     array($this->_packageInfo['developer']);
972                 foreach ($leads as $maintainer) {
973                     $s = $maintainer;
974                     $s['handle'] = $s['user'];
975                     unset($s['user']);
976                     $s['role'] = 'developer';
977                     $ret[] = $s;
978                 }
979             }
980             if (isset($this->_packageInfo['contributor'])) {
981                 $leads = isset($this->_packageInfo['contributor'][0]) ?
982                     $this->_packageInfo['contributor'] :
983                     array($this->_packageInfo['contributor']);
984                 foreach ($leads as $maintainer) {
985                     $s = $maintainer;
986                     $s['handle'] = $s['user'];
987                     unset($s['user']);
988                     $s['role'] = 'contributor';
989                     $ret[] = $s;
990                 }
991             }
992             if (isset($this->_packageInfo['helper'])) {
993                 $leads = isset($this->_packageInfo['helper'][0]) ?
994                     $this->_packageInfo['helper'] :
995                     array($this->_packageInfo['helper']);
996                 foreach ($leads as $maintainer) {
997                     $s = $maintainer;
998                     $s['handle'] = $s['user'];
999                     unset($s['user']);
1000                     $s['role'] = 'helper';
1001                     $ret[] = $s;
1002                 }
1003             }
1004             return $ret;
1005         }
1006         return false;
1007     }
1008
1009     function getLeads()
1010     {
1011         if (isset($this->_packageInfo['lead'])) {
1012             return $this->_packageInfo['lead'];
1013         }
1014         return false;
1015     }
1016
1017     function getDevelopers()
1018     {
1019         if (isset($this->_packageInfo['developer'])) {
1020             return $this->_packageInfo['developer'];
1021         }
1022         return false;
1023     }
1024
1025     function getContributors()
1026     {
1027         if (isset($this->_packageInfo['contributor'])) {
1028             return $this->_packageInfo['contributor'];
1029         }
1030         return false;
1031     }
1032
1033     function getHelpers()
1034     {
1035         if (isset($this->_packageInfo['helper'])) {
1036             return $this->_packageInfo['helper'];
1037         }
1038         return false;
1039     }
1040
1041     function setDate($date)
1042     {
1043         if (!isset($this->_packageInfo['date'])) {
1044             // ensure that the extends tag is set up in the right location
1045             $this->_packageInfo = $this->_insertBefore($this->_packageInfo,
1046                 array('time', 'version',
1047                     'stability', 'license', 'notes', 'contents', 'compatible',
1048                     'dependencies', 'providesextension', 'srcpackage', 'srcuri',
1049                     'phprelease', 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease',
1050                     'zendextbinrelease', 'bundle', 'changelog'), array(), 'date');
1051         }
1052         $this->_packageInfo['date'] = $date;
1053         $this->_isValid = 0;
1054     }
1055
1056     function setTime($time)
1057     {
1058         $this->_isValid = 0;
1059         if (!isset($this->_packageInfo['time'])) {
1060             // ensure that the time tag is set up in the right location
1061             $this->_packageInfo = $this->_insertBefore($this->_packageInfo,
1062                     array('version',
1063                     'stability', 'license', 'notes', 'contents', 'compatible',
1064                     'dependencies', 'providesextension', 'srcpackage', 'srcuri',
1065                     'phprelease', 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease',
1066                     'zendextbinrelease', 'bundle', 'changelog'), $time, 'time');
1067         }
1068         $this->_packageInfo['time'] = $time;
1069     }
1070
1071     function getDate()
1072     {
1073         if (isset($this->_packageInfo['date'])) {
1074             return $this->_packageInfo['date'];
1075         }
1076         return false;
1077     }
1078
1079     function getTime()
1080     {
1081         if (isset($this->_packageInfo['time'])) {
1082             return $this->_packageInfo['time'];
1083         }
1084         return false;
1085     }
1086
1087     /**
1088      * @param package|api version category to return
1089      */
1090     function getVersion($key = 'release')
1091     {
1092         if (isset($this->_packageInfo['version'][$key])) {
1093             return $this->_packageInfo['version'][$key];
1094         }
1095         return false;
1096     }
1097
1098     function getStability()
1099     {
1100         if (isset($this->_packageInfo['stability'])) {
1101             return $this->_packageInfo['stability'];
1102         }
1103         return false;
1104     }
1105
1106     function getState($key = 'release')
1107     {
1108         if (isset($this->_packageInfo['stability'][$key])) {
1109             return $this->_packageInfo['stability'][$key];
1110         }
1111         return false;
1112     }
1113
1114     function getLicense($raw = false)
1115     {
1116         if (isset($this->_packageInfo['license'])) {
1117             if ($raw) {
1118                 return $this->_packageInfo['license'];
1119             }
1120             if (is_array($this->_packageInfo['license'])) {
1121                 return $this->_packageInfo['license']['_content'];
1122             } else {
1123                 return $this->_packageInfo['license'];
1124             }
1125         }
1126         return false;
1127     }
1128
1129     function getLicenseLocation()
1130     {
1131         if (!isset($this->_packageInfo['license']) || !is_array($this->_packageInfo['license'])) {
1132             return false;
1133         }
1134         return $this->_packageInfo['license']['attribs'];
1135     }
1136
1137     function getNotes()
1138     {
1139         if (isset($this->_packageInfo['notes'])) {
1140             return $this->_packageInfo['notes'];
1141         }
1142         return false;
1143     }
1144
1145     /**
1146      * Return the <usesrole> tag contents, if any
1147      * @return array|false
1148      */
1149     function getUsesrole()
1150     {
1151         if (isset($this->_packageInfo['usesrole'])) {
1152             return $this->_packageInfo['usesrole'];
1153         }
1154         return false;
1155     }
1156
1157     /**
1158      * Return the <usestask> tag contents, if any
1159      * @return array|false
1160      */
1161     function getUsestask()
1162     {
1163         if (isset($this->_packageInfo['usestask'])) {
1164             return $this->_packageInfo['usestask'];
1165         }
1166         return false;
1167     }
1168
1169     /**
1170      * This should only be used to retrieve filenames and install attributes
1171      */
1172     function getFilelist($preserve = false)
1173     {
1174         if (isset($this->_packageInfo['filelist']) && !$preserve) {
1175             return $this->_packageInfo['filelist'];
1176         }
1177         $this->flattenFilelist();
1178         if ($contents = $this->getContents()) {
1179             $ret = array();
1180             if (!isset($contents['dir'])) {
1181                 return false;
1182             }
1183             if (!isset($contents['dir']['file'][0])) {
1184                 $contents['dir']['file'] = array($contents['dir']['file']);
1185             }
1186             foreach ($contents['dir']['file'] as $file) {
1187                 if (!isset($file['attribs']['name'])) {
1188                     continue;
1189                 }
1190                 $name = $file['attribs']['name'];
1191                 if (!$preserve) {
1192                     $file = $file['attribs'];
1193                 }
1194                 $ret[$name] = $file;
1195             }
1196             if (!$preserve) {
1197                 $this->_packageInfo['filelist'] = $ret;
1198             }
1199             return $ret;
1200         }
1201         return false;
1202     }
1203
1204     /**
1205      * Return configure options array, if any
1206      *
1207      * @return array|false
1208      */
1209     function getConfigureOptions()
1210     {
1211         if ($this->getPackageType() != 'extsrc' && $this->getPackageType() != 'zendextsrc') {
1212             return false;
1213         }
1214
1215         $releases = $this->getReleases();
1216         if (isset($releases[0])) {
1217             $releases = $releases[0];
1218         }
1219
1220         if (isset($releases['configureoption'])) {
1221             if (!isset($releases['configureoption'][0])) {
1222                 $releases['configureoption'] = array($releases['configureoption']);
1223             }
1224
1225             for ($i = 0; $i < count($releases['configureoption']); $i++) {
1226                 $releases['configureoption'][$i] = $releases['configureoption'][$i]['attribs'];
1227             }
1228
1229             return $releases['configureoption'];
1230         }
1231
1232         return false;
1233     }
1234
1235     /**
1236      * This is only used at install-time, after all serialization
1237      * is over.
1238      */
1239     function resetFilelist()
1240     {
1241         $this->_packageInfo['filelist'] = array();
1242     }
1243
1244     /**
1245      * Retrieve a list of files that should be installed on this computer
1246      * @return array
1247      */
1248     function getInstallationFilelist($forfilecheck = false)
1249     {
1250         $contents = $this->getFilelist(true);
1251         if (isset($contents['dir']['attribs']['baseinstalldir'])) {
1252             $base = $contents['dir']['attribs']['baseinstalldir'];
1253         }
1254         if (isset($this->_packageInfo['bundle'])) {
1255             return PEAR::raiseError(
1256                 'Exception: bundles should be handled in download code only');
1257         }
1258         $release = $this->getReleases();
1259         if ($release) {
1260             if (!isset($release[0])) {
1261                 if (!isset($release['installconditions']) && !isset($release['filelist'])) {
1262                     if ($forfilecheck) {
1263                         return $this->getFilelist();
1264                     }
1265                     return $contents;
1266                 }
1267                 $release = array($release);
1268             }
1269             $depchecker = &$this->getPEARDependency2($this->_config, array(),
1270                 array('channel' => $this->getChannel(), 'package' => $this->getPackage()),
1271                 PEAR_VALIDATE_INSTALLING);
1272             foreach ($release as $instance) {
1273                 if (isset($instance['installconditions'])) {
1274                     $installconditions = $instance['installconditions'];
1275                     if (is_array($installconditions)) {
1276                         PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
1277                         foreach ($installconditions as $type => $conditions) {
1278                             if (!isset($conditions[0])) {
1279                                 $conditions = array($conditions);
1280                             }
1281                             foreach ($conditions as $condition) {
1282                                 $ret = $depchecker->{"validate{$type}Dependency"}($condition);
1283                                 if (PEAR::isError($ret)) {
1284                                     PEAR::popErrorHandling();
1285                                     continue 3; // skip this release
1286                                 }
1287                             }
1288                         }
1289                         PEAR::popErrorHandling();
1290                     }
1291                 }
1292                 // this is the release to use
1293                 if (isset($instance['filelist'])) {
1294                     // ignore files
1295                     if (isset($instance['filelist']['ignore'])) {
1296                         $ignore = isset($instance['filelist']['ignore'][0]) ?
1297                             $instance['filelist']['ignore'] :
1298                             array($instance['filelist']['ignore']);
1299                         foreach ($ignore as $ig) {
1300                             unset ($contents[$ig['attribs']['name']]);
1301                         }
1302                     }
1303                     // install files as this name
1304                     if (isset($instance['filelist']['install'])) {
1305                         $installas = isset($instance['filelist']['install'][0]) ?
1306                             $instance['filelist']['install'] :
1307                             array($instance['filelist']['install']);
1308                         foreach ($installas as $as) {
1309                             $contents[$as['attribs']['name']]['attribs']['install-as'] =
1310                                 $as['attribs']['as'];
1311                         }
1312                     }
1313                 }
1314                 if ($forfilecheck) {
1315                     foreach ($contents as $file => $attrs) {
1316                         $contents[$file] = $attrs['attribs'];
1317                     }
1318                 }
1319                 return $contents;
1320             }
1321         } else { // simple release - no installconditions or install-as
1322             if ($forfilecheck) {
1323                 return $this->getFilelist();
1324             }
1325             return $contents;
1326         }
1327         // no releases matched
1328         return PEAR::raiseError('No releases in package.xml matched the existing operating ' .
1329             'system, extensions installed, or architecture, cannot install');
1330     }
1331
1332     /**
1333      * This is only used at install-time, after all serialization
1334      * is over.
1335      * @param string file name
1336      * @param string installed path
1337      */
1338     function setInstalledAs($file, $path)
1339     {
1340         if ($path) {
1341             return $this->_packageInfo['filelist'][$file]['installed_as'] = $path;
1342         }
1343         unset($this->_packageInfo['filelist'][$file]['installed_as']);
1344     }
1345
1346     function getInstalledLocation($file)
1347     {
1348         if (isset($this->_packageInfo['filelist'][$file]['installed_as'])) {
1349             return $this->_packageInfo['filelist'][$file]['installed_as'];
1350         }
1351         return false;
1352     }
1353
1354     /**
1355      * This is only used at install-time, after all serialization
1356      * is over.
1357      */
1358     function installedFile($file, $atts)
1359     {
1360         if (isset($this->_packageInfo['filelist'][$file])) {
1361             $this->_packageInfo['filelist'][$file] =
1362                 array_merge($this->_packageInfo['filelist'][$file], $atts['attribs']);
1363         } else {
1364             $this->_packageInfo['filelist'][$file] = $atts['attribs'];
1365         }
1366     }
1367
1368     /**
1369      * Retrieve the contents tag
1370      */
1371     function getContents()
1372     {
1373         if (isset($this->_packageInfo['contents'])) {
1374             return $this->_packageInfo['contents'];
1375         }
1376         return false;
1377     }
1378
1379     /**
1380      * @param string full path to file
1381      * @param string attribute name
1382      * @param string attribute value
1383      * @param int risky but fast - use this to choose a file based on its position in the list
1384      *            of files.  Index is zero-based like PHP arrays.
1385      * @return bool success of operation
1386      */
1387     function setFileAttribute($filename, $attr, $value, $index = false)
1388     {
1389         $this->_isValid = 0;
1390         if (in_array($attr, array('role', 'name', 'baseinstalldir'))) {
1391             $this->_filesValid = false;
1392         }
1393         if ($index !== false &&
1394               isset($this->_packageInfo['contents']['dir']['file'][$index]['attribs'])) {
1395             $this->_packageInfo['contents']['dir']['file'][$index]['attribs'][$attr] = $value;
1396             return true;
1397         }
1398         if (!isset($this->_packageInfo['contents']['dir']['file'])) {
1399             return false;
1400         }
1401         $files = $this->_packageInfo['contents']['dir']['file'];
1402         if (!isset($files[0])) {
1403             $files = array($files);
1404             $ind = false;
1405         } else {
1406             $ind = true;
1407         }
1408         foreach ($files as $i => $file) {
1409             if (isset($file['attribs'])) {
1410                 if ($file['attribs']['name'] == $filename) {
1411                     if ($ind) {
1412                         $this->_packageInfo['contents']['dir']['file'][$i]['attribs'][$attr] = $value;
1413                     } else {
1414                         $this->_packageInfo['contents']['dir']['file']['attribs'][$attr] = $value;
1415                     }
1416                     return true;
1417                 }
1418             }
1419         }
1420         return false;
1421     }
1422
1423     function setDirtree($path)
1424     {
1425         if (!isset($this->_packageInfo['dirtree'])) {
1426             $this->_packageInfo['dirtree'] = array();
1427         }
1428         $this->_packageInfo['dirtree'][$path] = true;
1429     }
1430
1431     function getDirtree()
1432     {
1433         if (isset($this->_packageInfo['dirtree']) && count($this->_packageInfo['dirtree'])) {
1434             return $this->_packageInfo['dirtree'];
1435         }
1436         return false;
1437     }
1438
1439     function resetDirtree()
1440     {
1441         unset($this->_packageInfo['dirtree']);
1442     }
1443
1444     /**
1445      * Determines whether this package claims it is compatible with the version of
1446      * the package that has a recommended version dependency
1447      * @param PEAR_PackageFile_v2|PEAR_PackageFile_v1|PEAR_Downloader_Package
1448      * @return boolean
1449      */
1450     function isCompatible($pf)
1451     {
1452         if (!isset($this->_packageInfo['compatible'])) {
1453             return false;
1454         }
1455         if (!isset($this->_packageInfo['channel'])) {
1456             return false;
1457         }
1458         $me = $pf->getVersion();
1459         $compatible = $this->_packageInfo['compatible'];
1460         if (!isset($compatible[0])) {
1461             $compatible = array($compatible);
1462         }
1463         $found = false;
1464         foreach ($compatible as $info) {
1465             if (strtolower($info['name']) == strtolower($pf->getPackage())) {
1466                 if (strtolower($info['channel']) == strtolower($pf->getChannel())) {
1467                     $found = true;
1468                     break;
1469                 }
1470             }
1471         }
1472         if (!$found) {
1473             return false;
1474         }
1475         if (isset($info['exclude'])) {
1476             if (!isset($info['exclude'][0])) {
1477                 $info['exclude'] = array($info['exclude']);
1478             }
1479             foreach ($info['exclude'] as $exclude) {
1480                 if (version_compare($me, $exclude, '==')) {
1481                     return false;
1482                 }
1483             }
1484         }
1485         if (version_compare($me, $info['min'], '>=') && version_compare($me, $info['max'], '<=')) {
1486             return true;
1487         }
1488         return false;
1489     }
1490
1491     /**
1492      * @return array|false
1493      */
1494     function getCompatible()
1495     {
1496         if (isset($this->_packageInfo['compatible'])) {
1497             return $this->_packageInfo['compatible'];
1498         }
1499         return false;
1500     }
1501
1502     function getDependencies()
1503     {
1504         if (isset($this->_packageInfo['dependencies'])) {
1505             return $this->_packageInfo['dependencies'];
1506         }
1507         return false;
1508     }
1509
1510     function isSubpackageOf($p)
1511     {
1512         return $p->isSubpackage($this);
1513     }
1514
1515     /**
1516      * Determines whether the passed in package is a subpackage of this package.
1517      *
1518      * No version checking is done, only name verification.
1519      * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
1520      * @return bool
1521      */
1522     function isSubpackage($p)
1523     {
1524         $sub = array();
1525         if (isset($this->_packageInfo['dependencies']['required']['subpackage'])) {
1526             $sub = $this->_packageInfo['dependencies']['required']['subpackage'];
1527             if (!isset($sub[0])) {
1528                 $sub = array($sub);
1529             }
1530         }
1531         if (isset($this->_packageInfo['dependencies']['optional']['subpackage'])) {
1532             $sub1 = $this->_packageInfo['dependencies']['optional']['subpackage'];
1533             if (!isset($sub1[0])) {
1534                 $sub1 = array($sub1);
1535             }
1536             $sub = array_merge($sub, $sub1);
1537         }
1538         if (isset($this->_packageInfo['dependencies']['group'])) {
1539             $group = $this->_packageInfo['dependencies']['group'];
1540             if (!isset($group[0])) {
1541                 $group = array($group);
1542             }
1543             foreach ($group as $deps) {
1544                 if (isset($deps['subpackage'])) {
1545                     $sub2 = $deps['subpackage'];
1546                     if (!isset($sub2[0])) {
1547                         $sub2 = array($sub2);
1548                     }
1549                     $sub = array_merge($sub, $sub2);
1550                 }
1551             }
1552         }
1553         foreach ($sub as $dep) {
1554             if (strtolower($dep['name']) == strtolower($p->getPackage())) {
1555                 if (isset($dep['channel'])) {
1556                     if (strtolower($dep['channel']) == strtolower($p->getChannel())) {
1557                         return true;
1558                     }
1559                 } else {
1560                     if ($dep['uri'] == $p->getURI()) {
1561                         return true;
1562                     }
1563                 }
1564             }
1565         }
1566         return false;
1567     }
1568
1569     function dependsOn($package, $channel)
1570     {
1571         if (!($deps = $this->getDependencies())) {
1572             return false;
1573         }
1574         foreach (array('package', 'subpackage') as $type) {
1575             foreach (array('required', 'optional') as $needed) {
1576                 if (isset($deps[$needed][$type])) {
1577                     if (!isset($deps[$needed][$type][0])) {
1578                         $deps[$needed][$type] = array($deps[$needed][$type]);
1579                     }
1580                     foreach ($deps[$needed][$type] as $dep) {
1581                         $depchannel = isset($dep['channel']) ? $dep['channel'] : '__uri';
1582                         if (strtolower($dep['name']) == strtolower($package) &&
1583                               $depchannel == $channel) {
1584                             return true;
1585                         }
1586                     }
1587                 }
1588             }
1589             if (isset($deps['group'])) {
1590                 if (!isset($deps['group'][0])) {
1591                     $dep['group'] = array($deps['group']);
1592                 }
1593                 foreach ($deps['group'] as $group) {
1594                     if (isset($group[$type])) {
1595                         if (!is_array($group[$type])) {
1596                             $group[$type] = array($group[$type]);
1597                         }
1598                         foreach ($group[$type] as $dep) {
1599                             $depchannel = isset($dep['channel']) ? $dep['channel'] : '__uri';
1600                             if (strtolower($dep['name']) == strtolower($package) &&
1601                                   $depchannel == $channel) {
1602                                 return true;
1603                             }
1604                         }
1605                     }
1606                 }
1607             }
1608         }
1609         return false;
1610     }
1611
1612     /**
1613      * Get the contents of a dependency group
1614      * @param string
1615      * @return array|false
1616      */
1617     function getDependencyGroup($name)
1618     {
1619         $name = strtolower($name);
1620         if (!isset($this->_packageInfo['dependencies']['group'])) {
1621             return false;
1622         }
1623         $groups = $this->_packageInfo['dependencies']['group'];
1624         if (!isset($groups[0])) {
1625             $groups = array($groups);
1626         }
1627         foreach ($groups as $group) {
1628             if (strtolower($group['attribs']['name']) == $name) {
1629                 return $group;
1630             }
1631         }
1632         return false;
1633     }
1634
1635     /**
1636      * Retrieve a partial package.xml 1.0 representation of dependencies
1637      *
1638      * a very limited representation of dependencies is returned by this method.
1639      * The <exclude> tag for excluding certain versions of a dependency is
1640      * completely ignored.  In addition, dependency groups are ignored, with the
1641      * assumption that all dependencies in dependency groups are also listed in
1642      * the optional group that work with all dependency groups
1643      * @param boolean return package.xml 2.0 <dependencies> tag
1644      * @return array|false
1645      */
1646     function getDeps($raw = false, $nopearinstaller = false)
1647     {
1648         if (isset($this->_packageInfo['dependencies'])) {
1649             if ($raw) {
1650                 return $this->_packageInfo['dependencies'];
1651             }
1652             $ret = array();
1653             $map = array(
1654                 'php' => 'php',
1655                 'package' => 'pkg',
1656                 'subpackage' => 'pkg',
1657                 'extension' => 'ext',
1658                 'os' => 'os',
1659                 'pearinstaller' => 'pkg',
1660                 );
1661             foreach (array('required', 'optional') as $type) {
1662                 $optional = ($type == 'optional') ? 'yes' : 'no';
1663                 if (!isset($this->_packageInfo['dependencies'][$type])
1664                     || empty($this->_packageInfo['dependencies'][$type])) {
1665                     continue;
1666                 }
1667                 foreach ($this->_packageInfo['dependencies'][$type] as $dtype => $deps) {
1668                     if ($dtype == 'pearinstaller' && $nopearinstaller) {
1669                         continue;
1670                     }
1671                     if (!isset($deps[0])) {
1672                         $deps = array($deps);
1673                     }
1674                     foreach ($deps as $dep) {
1675                         if (!isset($map[$dtype])) {
1676                             // no support for arch type
1677                             continue;
1678                         }
1679                         if ($dtype == 'pearinstaller') {
1680                             $dep['name'] = 'PEAR';
1681                             $dep['channel'] = 'pear.php.net';
1682                         }
1683                         $s = array('type' => $map[$dtype]);
1684                         if (isset($dep['channel'])) {
1685                             $s['channel'] = $dep['channel'];
1686                         }
1687                         if (isset($dep['uri'])) {
1688                             $s['uri'] = $dep['uri'];
1689                         }
1690                         if (isset($dep['name'])) {
1691                             $s['name'] = $dep['name'];
1692                         }
1693                         if (isset($dep['conflicts'])) {
1694                             $s['rel'] = 'not';
1695                         } else {
1696                             if (!isset($dep['min']) &&
1697                                   !isset($dep['max'])) {
1698                                 $s['rel'] = 'has';
1699                                 $s['optional'] = $optional;
1700                             } elseif (isset($dep['min']) &&
1701                                   isset($dep['max'])) {
1702                                 $s['rel'] = 'ge';
1703                                 $s1 = $s;
1704                                 $s1['rel'] = 'le';
1705                                 $s['version'] = $dep['min'];
1706                                 $s1['version'] = $dep['max'];
1707                                 if (isset($dep['channel'])) {
1708                                     $s1['channel'] = $dep['channel'];
1709                                 }
1710                                 if ($dtype != 'php') {
1711                                     $s['name'] = $dep['name'];
1712                                     $s1['name'] = $dep['name'];
1713                                 }
1714                                 $s['optional'] = $optional;
1715                                 $s1['optional'] = $optional;
1716                                 $ret[] = $s1;
1717                             } elseif (isset($dep['min'])) {
1718                                 if (isset($dep['exclude']) &&
1719                                       $dep['exclude'] == $dep['min']) {
1720                                     $s['rel'] = 'gt';
1721                                 } else {
1722                                     $s['rel'] = 'ge';
1723                                 }
1724                                 $s['version'] = $dep['min'];
1725                                 $s['optional'] = $optional;
1726                                 if ($dtype != 'php') {
1727                                     $s['name'] = $dep['name'];
1728                                 }
1729                             } elseif (isset($dep['max'])) {
1730                                 if (isset($dep['exclude']) &&
1731                                       $dep['exclude'] == $dep['max']) {
1732                                     $s['rel'] = 'lt';
1733                                 } else {
1734                                     $s['rel'] = 'le';
1735                                 }
1736                                 $s['version'] = $dep['max'];
1737                                 $s['optional'] = $optional;
1738                                 if ($dtype != 'php') {
1739                                     $s['name'] = $dep['name'];
1740                                 }
1741                             }
1742                         }
1743                         $ret[] = $s;
1744                     }
1745                 }
1746             }
1747             if (count($ret)) {
1748                 return $ret;
1749             }
1750         }
1751         return false;
1752     }
1753
1754     /**
1755      * @return php|extsrc|extbin|zendextsrc|zendextbin|bundle|false
1756      */
1757     function getPackageType()
1758     {
1759         if (isset($this->_packageInfo['phprelease'])) {
1760             return 'php';
1761         }
1762         if (isset($this->_packageInfo['extsrcrelease'])) {
1763             return 'extsrc';
1764         }
1765         if (isset($this->_packageInfo['extbinrelease'])) {
1766             return 'extbin';
1767         }
1768         if (isset($this->_packageInfo['zendextsrcrelease'])) {
1769             return 'zendextsrc';
1770         }
1771         if (isset($this->_packageInfo['zendextbinrelease'])) {
1772             return 'zendextbin';
1773         }
1774         if (isset($this->_packageInfo['bundle'])) {
1775             return 'bundle';
1776         }
1777         return false;
1778     }
1779
1780     /**
1781      * @return array|false
1782      */
1783     function getReleases()
1784     {
1785         $type = $this->getPackageType();
1786         if ($type != 'bundle') {
1787             $type .= 'release';
1788         }
1789         if ($this->getPackageType() && isset($this->_packageInfo[$type])) {
1790             return $this->_packageInfo[$type];
1791         }
1792         return false;
1793     }
1794
1795     /**
1796      * @return array
1797      */
1798     function getChangelog()
1799     {
1800         if (isset($this->_packageInfo['changelog'])) {
1801             return $this->_packageInfo['changelog'];
1802         }
1803         return false;
1804     }
1805
1806     function hasDeps()
1807     {
1808         return isset($this->_packageInfo['dependencies']);
1809     }
1810
1811     function getPackagexmlVersion()
1812     {
1813         if (isset($this->_packageInfo['zendextsrcrelease'])) {
1814             return '2.1';
1815         }
1816         if (isset($this->_packageInfo['zendextbinrelease'])) {
1817             return '2.1';
1818         }
1819         return '2.0';
1820     }
1821
1822     /**
1823      * @return array|false
1824      */
1825     function getSourcePackage()
1826     {
1827         if (isset($this->_packageInfo['extbinrelease']) ||
1828               isset($this->_packageInfo['zendextbinrelease'])) {
1829             return array('channel' => $this->_packageInfo['srcchannel'],
1830                          'package' => $this->_packageInfo['srcpackage']);
1831         }
1832         return false;
1833     }
1834
1835     function getBundledPackages()
1836     {
1837         if (isset($this->_packageInfo['bundle'])) {
1838             return $this->_packageInfo['contents']['bundledpackage'];
1839         }
1840         return false;
1841     }
1842
1843     function getLastModified()
1844     {
1845         if (isset($this->_packageInfo['_lastmodified'])) {
1846             return $this->_packageInfo['_lastmodified'];
1847         }
1848         return false;
1849     }
1850
1851     /**
1852      * Get the contents of a file listed within the package.xml
1853      * @param string
1854      * @return string
1855      */
1856     function getFileContents($file)
1857     {
1858         if ($this->_archiveFile == $this->_packageFile) { // unpacked
1859             $dir = dirname($this->_packageFile);
1860             $file = $dir . DIRECTORY_SEPARATOR . $file;
1861             $file = str_replace(array('/', '\\'),
1862                 array(DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR), $file);
1863             if (file_exists($file) && is_readable($file)) {
1864                 return implode('', file($file));
1865             }
1866         } else { // tgz
1867             $tar = new Archive_Tar($this->_archiveFile);
1868             $tar->pushErrorHandling(PEAR_ERROR_RETURN);
1869             if ($file != 'package.xml' && $file != 'package2.xml') {
1870                 $file = $this->getPackage() . '-' . $this->getVersion() . '/' . $file;
1871             }
1872             $file = $tar->extractInString($file);
1873             $tar->popErrorHandling();
1874             if (PEAR::isError($file)) {
1875                 return PEAR::raiseError("Cannot locate file '$file' in archive");
1876             }
1877             return $file;
1878         }
1879     }
1880
1881     function &getRW()
1882     {
1883         if (!class_exists('PEAR_PackageFile_v2_rw')) {
1884             require_once 'PEAR/PackageFile/v2/rw.php';
1885         }
1886         $a = new PEAR_PackageFile_v2_rw;
1887         foreach (get_object_vars($this) as $name => $unused) {
1888             if (!isset($this->$name)) {
1889                 continue;
1890             }
1891             if ($name == '_config' || $name == '_logger'|| $name == '_registry' ||
1892                   $name == '_stack') {
1893                 $a->$name = &$this->$name;
1894             } else {
1895                 $a->$name = $this->$name;
1896             }
1897         }
1898         return $a;
1899     }
1900
1901     function &getDefaultGenerator()
1902     {
1903         if (!class_exists('PEAR_PackageFile_Generator_v2')) {
1904             require_once 'PEAR/PackageFile/Generator/v2.php';
1905         }
1906         $a = new PEAR_PackageFile_Generator_v2($this);
1907         return $a;
1908     }
1909
1910     function analyzeSourceCode($file, $string = false)
1911     {
1912         if (!isset($this->_v2Validator) ||
1913               !is_a($this->_v2Validator, 'PEAR_PackageFile_v2_Validator')) {
1914             if (!class_exists('PEAR_PackageFile_v2_Validator')) {
1915                 require_once 'PEAR/PackageFile/v2/Validator.php';
1916             }
1917             $this->_v2Validator = new PEAR_PackageFile_v2_Validator;
1918         }
1919         return $this->_v2Validator->analyzeSourceCode($file, $string);
1920     }
1921
1922     function validate($state = PEAR_VALIDATE_NORMAL)
1923     {
1924         if (!isset($this->_packageInfo) || !is_array($this->_packageInfo)) {
1925             return false;
1926         }
1927         if (!isset($this->_v2Validator) ||
1928               !is_a($this->_v2Validator, 'PEAR_PackageFile_v2_Validator')) {
1929             if (!class_exists('PEAR_PackageFile_v2_Validator')) {
1930                 require_once 'PEAR/PackageFile/v2/Validator.php';
1931             }
1932             $this->_v2Validator = new PEAR_PackageFile_v2_Validator;
1933         }
1934         if (isset($this->_packageInfo['xsdversion'])) {
1935             unset($this->_packageInfo['xsdversion']);
1936         }
1937         return $this->_v2Validator->validate($this, $state);
1938     }
1939
1940     function getTasksNs()
1941     {
1942         if (!isset($this->_tasksNs)) {
1943             if (isset($this->_packageInfo['attribs'])) {
1944                 foreach ($this->_packageInfo['attribs'] as $name => $value) {
1945                     if ($value == 'http://pear.php.net/dtd/tasks-1.0') {
1946                         $this->_tasksNs = str_replace('xmlns:', '', $name);
1947                         break;
1948                     }
1949                 }
1950             }
1951         }
1952         return $this->_tasksNs;
1953     }
1954
1955     /**
1956      * Determine whether a task name is a valid task.  Custom tasks may be defined
1957      * using subdirectories by putting a "-" in the name, as in <tasks:mycustom-task>
1958      *
1959      * Note that this method will auto-load the task class file and test for the existence
1960      * of the name with "-" replaced by "_" as in PEAR/Task/mycustom/task.php makes class
1961      * PEAR_Task_mycustom_task
1962      * @param string
1963      * @return boolean
1964      */
1965     function getTask($task)
1966     {
1967         $this->getTasksNs();
1968         // transform all '-' to '/' and 'tasks:' to '' so tasks:replace becomes replace
1969         $task = str_replace(array($this->_tasksNs . ':', '-'), array('', ' '), $task);
1970         $taskfile = str_replace(' ', '/', ucwords($task));
1971         $task = str_replace(array(' ', '/'), '_', ucwords($task));
1972         if (class_exists("PEAR_Task_$task")) {
1973             return "PEAR_Task_$task";
1974         }
1975         $fp = @fopen("PEAR/Task/$taskfile.php", 'r', true);
1976         if ($fp) {
1977             fclose($fp);
1978             require_once "PEAR/Task/$taskfile.php";
1979             return "PEAR_Task_$task";
1980         }
1981         return false;
1982     }
1983
1984     /**
1985      * Key-friendly array_splice
1986      * @param tagname to splice a value in before
1987      * @param mixed the value to splice in
1988      * @param string the new tag name
1989      */
1990     function _ksplice($array, $key, $value, $newkey)
1991     {
1992         $offset = array_search($key, array_keys($array));
1993         $after = array_slice($array, $offset);
1994         $before = array_slice($array, 0, $offset);
1995         $before[$newkey] = $value;
1996         return array_merge($before, $after);
1997     }
1998
1999     /**
2000      * @param array a list of possible keys, in the order they may occur
2001      * @param mixed contents of the new package.xml tag
2002      * @param string tag name
2003      * @access private
2004      */
2005     function _insertBefore($array, $keys, $contents, $newkey)
2006     {
2007         foreach ($keys as $key) {
2008             if (isset($array[$key])) {
2009                 return $array = $this->_ksplice($array, $key, $contents, $newkey);
2010             }
2011         }
2012         $array[$newkey] = $contents;
2013         return $array;
2014     }
2015
2016     /**
2017      * @param subsection of {@link $_packageInfo}
2018      * @param array|string tag contents
2019      * @param array format:
2020      * <pre>
2021      * array(
2022      *   tagname => array(list of tag names that follow this one),
2023      *   childtagname => array(list of child tag names that follow this one),
2024      * )
2025      * </pre>
2026      *
2027      * This allows construction of nested tags
2028      * @access private
2029      */
2030     function _mergeTag($manip, $contents, $order)
2031     {
2032         if (count($order)) {
2033             foreach ($order as $tag => $curorder) {
2034                 if (!isset($manip[$tag])) {
2035                     // ensure that the tag is set up
2036                     $manip = $this->_insertBefore($manip, $curorder, array(), $tag);
2037                 }
2038                 if (count($order) > 1) {
2039                     $manip[$tag] = $this->_mergeTag($manip[$tag], $contents, array_slice($order, 1));
2040                     return $manip;
2041                 }
2042             }
2043         } else {
2044             return $manip;
2045         }
2046         if (is_array($manip[$tag]) && !empty($manip[$tag]) && isset($manip[$tag][0])) {
2047             $manip[$tag][] = $contents;
2048         } else {
2049             if (!count($manip[$tag])) {
2050                 $manip[$tag] = $contents;
2051             } else {
2052                 $manip[$tag] = array($manip[$tag]);
2053                 $manip[$tag][] = $contents;
2054             }
2055         }
2056         return $manip;
2057     }
2058 }
2059 ?>