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