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