source: trunk/library/PEAR/PEAR/PackageFile/v2.php @ 7730

Revision 7730, 68.1 KB checked in by marcieli, 11 years ago (diff)

Ticket #3236 - Correcoes de seguranca com base no segundo relatorio emitido.

Line 
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 */
19require_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 */
30class 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            $releases_count = count($releases['configureoption']);
1216            for ($i = 0; $i < $releases_count; ++$i) {
1217                $releases['configureoption'][$i] = $releases['configureoption'][$i]['attribs'];
1218            }
1219
1220            return $releases['configureoption'];
1221        }
1222
1223        return false;
1224    }
1225
1226    /**
1227     * This is only used at install-time, after all serialization
1228     * is over.
1229     */
1230    function resetFilelist()
1231    {
1232        $this->_packageInfo['filelist'] = array();
1233    }
1234
1235    /**
1236     * Retrieve a list of files that should be installed on this computer
1237     * @return array
1238     */
1239    function getInstallationFilelist($forfilecheck = false)
1240    {
1241        $contents = $this->getFilelist(true);
1242        if (isset($contents['dir']['attribs']['baseinstalldir'])) {
1243            $base = $contents['dir']['attribs']['baseinstalldir'];
1244        }
1245        if (isset($this->_packageInfo['bundle'])) {
1246            return PEAR::raiseError(
1247                'Exception: bundles should be handled in download code only');
1248        }
1249        $release = $this->getReleases();
1250        if ($release) {
1251            if (!isset($release[0])) {
1252                if (!isset($release['installconditions']) && !isset($release['filelist'])) {
1253                    if ($forfilecheck) {
1254                        return $this->getFilelist();
1255                    }
1256                    return $contents;
1257                }
1258                $release = array($release);
1259            }
1260            $depchecker = &$this->getPEARDependency2($this->_config, array(),
1261                array('channel' => $this->getChannel(), 'package' => $this->getPackage()),
1262                PEAR_VALIDATE_INSTALLING);
1263            foreach ($release as $instance) {
1264                if (isset($instance['installconditions'])) {
1265                    $installconditions = $instance['installconditions'];
1266                    if (is_array($installconditions)) {
1267                        PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
1268                        foreach ($installconditions as $type => $conditions) {
1269                            if (!isset($conditions[0])) {
1270                                $conditions = array($conditions);
1271                            }
1272                            foreach ($conditions as $condition) {
1273                                $ret = $depchecker->{"validate{$type}Dependency"}($condition);
1274                                if (PEAR::isError($ret)) {
1275                                    PEAR::popErrorHandling();
1276                                    continue 3; // skip this release
1277                                }
1278                            }
1279                        }
1280                        PEAR::popErrorHandling();
1281                    }
1282                }
1283                // this is the release to use
1284                if (isset($instance['filelist'])) {
1285                    // ignore files
1286                    if (isset($instance['filelist']['ignore'])) {
1287                        $ignore = isset($instance['filelist']['ignore'][0]) ?
1288                            $instance['filelist']['ignore'] :
1289                            array($instance['filelist']['ignore']);
1290                        foreach ($ignore as $ig) {
1291                            unset ($contents[$ig['attribs']['name']]);
1292                        }
1293                    }
1294                    // install files as this name
1295                    if (isset($instance['filelist']['install'])) {
1296                        $installas = isset($instance['filelist']['install'][0]) ?
1297                            $instance['filelist']['install'] :
1298                            array($instance['filelist']['install']);
1299                        foreach ($installas as $as) {
1300                            $contents[$as['attribs']['name']]['attribs']['install-as'] =
1301                                $as['attribs']['as'];
1302                        }
1303                    }
1304                }
1305                if ($forfilecheck) {
1306                    foreach ($contents as $file => $attrs) {
1307                        $contents[$file] = $attrs['attribs'];
1308                    }
1309                }
1310                return $contents;
1311            }
1312        } else { // simple release - no installconditions or install-as
1313            if ($forfilecheck) {
1314                return $this->getFilelist();
1315            }
1316            return $contents;
1317        }
1318        // no releases matched
1319        return PEAR::raiseError('No releases in package.xml matched the existing operating ' .
1320            'system, extensions installed, or architecture, cannot install');
1321    }
1322
1323    /**
1324     * This is only used at install-time, after all serialization
1325     * is over.
1326     * @param string file name
1327     * @param string installed path
1328     */
1329    function setInstalledAs($file, $path)
1330    {
1331        if ($path) {
1332            return $this->_packageInfo['filelist'][$file]['installed_as'] = $path;
1333        }
1334        unset($this->_packageInfo['filelist'][$file]['installed_as']);
1335    }
1336
1337    function getInstalledLocation($file)
1338    {
1339        if (isset($this->_packageInfo['filelist'][$file]['installed_as'])) {
1340            return $this->_packageInfo['filelist'][$file]['installed_as'];
1341        }
1342        return false;
1343    }
1344
1345    /**
1346     * This is only used at install-time, after all serialization
1347     * is over.
1348     */
1349    function installedFile($file, $atts)
1350    {
1351        if (isset($this->_packageInfo['filelist'][$file])) {
1352            $this->_packageInfo['filelist'][$file] =
1353                array_merge($this->_packageInfo['filelist'][$file], $atts['attribs']);
1354        } else {
1355            $this->_packageInfo['filelist'][$file] = $atts['attribs'];
1356        }
1357    }
1358
1359    /**
1360     * Retrieve the contents tag
1361     */
1362    function getContents()
1363    {
1364        if (isset($this->_packageInfo['contents'])) {
1365            return $this->_packageInfo['contents'];
1366        }
1367        return false;
1368    }
1369
1370    /**
1371     * @param string full path to file
1372     * @param string attribute name
1373     * @param string attribute value
1374     * @param int risky but fast - use this to choose a file based on its position in the list
1375     *            of files.  Index is zero-based like PHP arrays.
1376     * @return bool success of operation
1377     */
1378    function setFileAttribute($filename, $attr, $value, $index = false)
1379    {
1380        $this->_isValid = 0;
1381        if (in_array($attr, array('role', 'name', 'baseinstalldir'))) {
1382            $this->_filesValid = false;
1383        }
1384        if ($index !== false &&
1385              isset($this->_packageInfo['contents']['dir']['file'][$index]['attribs'])) {
1386            $this->_packageInfo['contents']['dir']['file'][$index]['attribs'][$attr] = $value;
1387            return true;
1388        }
1389        if (!isset($this->_packageInfo['contents']['dir']['file'])) {
1390            return false;
1391        }
1392        $files = $this->_packageInfo['contents']['dir']['file'];
1393        if (!isset($files[0])) {
1394            $files = array($files);
1395            $ind = false;
1396        } else {
1397            $ind = true;
1398        }
1399        foreach ($files as $i => $file) {
1400            if (isset($file['attribs'])) {
1401                if ($file['attribs']['name'] == $filename) {
1402                    if ($ind) {
1403                        $this->_packageInfo['contents']['dir']['file'][$i]['attribs'][$attr] = $value;
1404                    } else {
1405                        $this->_packageInfo['contents']['dir']['file']['attribs'][$attr] = $value;
1406                    }
1407                    return true;
1408                }
1409            }
1410        }
1411        return false;
1412    }
1413
1414    function setDirtree($path)
1415    {
1416        if (!isset($this->_packageInfo['dirtree'])) {
1417            $this->_packageInfo['dirtree'] = array();
1418        }
1419        $this->_packageInfo['dirtree'][$path] = true;
1420    }
1421
1422    function getDirtree()
1423    {
1424        if (isset($this->_packageInfo['dirtree']) && count($this->_packageInfo['dirtree'])) {
1425            return $this->_packageInfo['dirtree'];
1426        }
1427        return false;
1428    }
1429
1430    function resetDirtree()
1431    {
1432        unset($this->_packageInfo['dirtree']);
1433    }
1434
1435    /**
1436     * Determines whether this package claims it is compatible with the version of
1437     * the package that has a recommended version dependency
1438     * @param PEAR_PackageFile_v2|PEAR_PackageFile_v1|PEAR_Downloader_Package
1439     * @return boolean
1440     */
1441    function isCompatible($pf)
1442    {
1443        if (!isset($this->_packageInfo['compatible'])) {
1444            return false;
1445        }
1446        if (!isset($this->_packageInfo['channel'])) {
1447            return false;
1448        }
1449        $me = $pf->getVersion();
1450        $compatible = $this->_packageInfo['compatible'];
1451        if (!isset($compatible[0])) {
1452            $compatible = array($compatible);
1453        }
1454        $found = false;
1455        foreach ($compatible as $info) {
1456            if (strtolower($info['name']) == strtolower($pf->getPackage())) {
1457                if (strtolower($info['channel']) == strtolower($pf->getChannel())) {
1458                    $found = true;
1459                    break;
1460                }
1461            }
1462        }
1463        if (!$found) {
1464            return false;
1465        }
1466        if (isset($info['exclude'])) {
1467            if (!isset($info['exclude'][0])) {
1468                $info['exclude'] = array($info['exclude']);
1469            }
1470            foreach ($info['exclude'] as $exclude) {
1471                if (version_compare($me, $exclude, '==')) {
1472                    return false;
1473                }
1474            }
1475        }
1476        if (version_compare($me, $info['min'], '>=') && version_compare($me, $info['max'], '<=')) {
1477            return true;
1478        }
1479        return false;
1480    }
1481
1482    /**
1483     * @return array|false
1484     */
1485    function getCompatible()
1486    {
1487        if (isset($this->_packageInfo['compatible'])) {
1488            return $this->_packageInfo['compatible'];
1489        }
1490        return false;
1491    }
1492
1493    function getDependencies()
1494    {
1495        if (isset($this->_packageInfo['dependencies'])) {
1496            return $this->_packageInfo['dependencies'];
1497        }
1498        return false;
1499    }
1500
1501    function isSubpackageOf($p)
1502    {
1503        return $p->isSubpackage($this);
1504    }
1505
1506    /**
1507     * Determines whether the passed in package is a subpackage of this package.
1508     *
1509     * No version checking is done, only name verification.
1510     * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
1511     * @return bool
1512     */
1513    function isSubpackage($p)
1514    {
1515        $sub = array();
1516        if (isset($this->_packageInfo['dependencies']['required']['subpackage'])) {
1517            $sub = $this->_packageInfo['dependencies']['required']['subpackage'];
1518            if (!isset($sub[0])) {
1519                $sub = array($sub);
1520            }
1521        }
1522        if (isset($this->_packageInfo['dependencies']['optional']['subpackage'])) {
1523            $sub1 = $this->_packageInfo['dependencies']['optional']['subpackage'];
1524            if (!isset($sub1[0])) {
1525                $sub1 = array($sub1);
1526            }
1527            $sub = array_merge($sub, $sub1);
1528        }
1529        if (isset($this->_packageInfo['dependencies']['group'])) {
1530            $group = $this->_packageInfo['dependencies']['group'];
1531            if (!isset($group[0])) {
1532                $group = array($group);
1533            }
1534            foreach ($group as $deps) {
1535                if (isset($deps['subpackage'])) {
1536                    $sub2 = $deps['subpackage'];
1537                    if (!isset($sub2[0])) {
1538                        $sub2 = array($sub2);
1539                    }
1540                    $sub = array_merge($sub, $sub2);
1541                }
1542            }
1543        }
1544        foreach ($sub as $dep) {
1545            if (strtolower($dep['name']) == strtolower($p->getPackage())) {
1546                if (isset($dep['channel'])) {
1547                    if (strtolower($dep['channel']) == strtolower($p->getChannel())) {
1548                        return true;
1549                    }
1550                } else {
1551                    if ($dep['uri'] == $p->getURI()) {
1552                        return true;
1553                    }
1554                }
1555            }
1556        }
1557        return false;
1558    }
1559
1560    function dependsOn($package, $channel)
1561    {
1562        if (!($deps = $this->getDependencies())) {
1563            return false;
1564        }
1565        foreach (array('package', 'subpackage') as $type) {
1566            foreach (array('required', 'optional') as $needed) {
1567                if (isset($deps[$needed][$type])) {
1568                    if (!isset($deps[$needed][$type][0])) {
1569                        $deps[$needed][$type] = array($deps[$needed][$type]);
1570                    }
1571                    foreach ($deps[$needed][$type] as $dep) {
1572                        $depchannel = isset($dep['channel']) ? $dep['channel'] : '__uri';
1573                        if (strtolower($dep['name']) == strtolower($package) &&
1574                              $depchannel == $channel) {
1575                            return true;
1576                        }
1577                    }
1578                }
1579            }
1580            if (isset($deps['group'])) {
1581                if (!isset($deps['group'][0])) {
1582                    $dep['group'] = array($deps['group']);
1583                }
1584                foreach ($deps['group'] as $group) {
1585                    if (isset($group[$type])) {
1586                        if (!is_array($group[$type])) {
1587                            $group[$type] = array($group[$type]);
1588                        }
1589                        foreach ($group[$type] as $dep) {
1590                            $depchannel = isset($dep['channel']) ? $dep['channel'] : '__uri';
1591                            if (strtolower($dep['name']) == strtolower($package) &&
1592                                  $depchannel == $channel) {
1593                                return true;
1594                            }
1595                        }
1596                    }
1597                }
1598            }
1599        }
1600        return false;
1601    }
1602
1603    /**
1604     * Get the contents of a dependency group
1605     * @param string
1606     * @return array|false
1607     */
1608    function getDependencyGroup($name)
1609    {
1610        $name = strtolower($name);
1611        if (!isset($this->_packageInfo['dependencies']['group'])) {
1612            return false;
1613        }
1614        $groups = $this->_packageInfo['dependencies']['group'];
1615        if (!isset($groups[0])) {
1616            $groups = array($groups);
1617        }
1618        foreach ($groups as $group) {
1619            if (strtolower($group['attribs']['name']) == $name) {
1620                return $group;
1621            }
1622        }
1623        return false;
1624    }
1625
1626    /**
1627     * Retrieve a partial package.xml 1.0 representation of dependencies
1628     *
1629     * a very limited representation of dependencies is returned by this method.
1630     * The <exclude> tag for excluding certain versions of a dependency is
1631     * completely ignored.  In addition, dependency groups are ignored, with the
1632     * assumption that all dependencies in dependency groups are also listed in
1633     * the optional group that work with all dependency groups
1634     * @param boolean return package.xml 2.0 <dependencies> tag
1635     * @return array|false
1636     */
1637    function getDeps($raw = false, $nopearinstaller = false)
1638    {
1639        if (isset($this->_packageInfo['dependencies'])) {
1640            if ($raw) {
1641                return $this->_packageInfo['dependencies'];
1642            }
1643            $ret = array();
1644            $map = array(
1645                'php' => 'php',
1646                'package' => 'pkg',
1647                'subpackage' => 'pkg',
1648                'extension' => 'ext',
1649                'os' => 'os',
1650                'pearinstaller' => 'pkg',
1651                );
1652            foreach (array('required', 'optional') as $type) {
1653                $optional = ($type == 'optional') ? 'yes' : 'no';
1654                if (!isset($this->_packageInfo['dependencies'][$type])
1655                    || empty($this->_packageInfo['dependencies'][$type])) {
1656                    continue;
1657                }
1658                foreach ($this->_packageInfo['dependencies'][$type] as $dtype => $deps) {
1659                    if ($dtype == 'pearinstaller' && $nopearinstaller) {
1660                        continue;
1661                    }
1662                    if (!isset($deps[0])) {
1663                        $deps = array($deps);
1664                    }
1665                    foreach ($deps as $dep) {
1666                        if (!isset($map[$dtype])) {
1667                            // no support for arch type
1668                            continue;
1669                        }
1670                        if ($dtype == 'pearinstaller') {
1671                            $dep['name'] = 'PEAR';
1672                            $dep['channel'] = 'pear.php.net';
1673                        }
1674                        $s = array('type' => $map[$dtype]);
1675                        if (isset($dep['channel'])) {
1676                            $s['channel'] = $dep['channel'];
1677                        }
1678                        if (isset($dep['uri'])) {
1679                            $s['uri'] = $dep['uri'];
1680                        }
1681                        if (isset($dep['name'])) {
1682                            $s['name'] = $dep['name'];
1683                        }
1684                        if (isset($dep['conflicts'])) {
1685                            $s['rel'] = 'not';
1686                        } else {
1687                            if (!isset($dep['min']) &&
1688                                  !isset($dep['max'])) {
1689                                $s['rel'] = 'has';
1690                                $s['optional'] = $optional;
1691                            } elseif (isset($dep['min']) &&
1692                                  isset($dep['max'])) {
1693                                $s['rel'] = 'ge';
1694                                $s1 = $s;
1695                                $s1['rel'] = 'le';
1696                                $s['version'] = $dep['min'];
1697                                $s1['version'] = $dep['max'];
1698                                if (isset($dep['channel'])) {
1699                                    $s1['channel'] = $dep['channel'];
1700                                }
1701                                if ($dtype != 'php') {
1702                                    $s['name'] = $dep['name'];
1703                                    $s1['name'] = $dep['name'];
1704                                }
1705                                $s['optional'] = $optional;
1706                                $s1['optional'] = $optional;
1707                                $ret[] = $s1;
1708                            } elseif (isset($dep['min'])) {
1709                                if (isset($dep['exclude']) &&
1710                                      $dep['exclude'] == $dep['min']) {
1711                                    $s['rel'] = 'gt';
1712                                } else {
1713                                    $s['rel'] = 'ge';
1714                                }
1715                                $s['version'] = $dep['min'];
1716                                $s['optional'] = $optional;
1717                                if ($dtype != 'php') {
1718                                    $s['name'] = $dep['name'];
1719                                }
1720                            } elseif (isset($dep['max'])) {
1721                                if (isset($dep['exclude']) &&
1722                                      $dep['exclude'] == $dep['max']) {
1723                                    $s['rel'] = 'lt';
1724                                } else {
1725                                    $s['rel'] = 'le';
1726                                }
1727                                $s['version'] = $dep['max'];
1728                                $s['optional'] = $optional;
1729                                if ($dtype != 'php') {
1730                                    $s['name'] = $dep['name'];
1731                                }
1732                            }
1733                        }
1734                        $ret[] = $s;
1735                    }
1736                }
1737            }
1738            if (count($ret)) {
1739                return $ret;
1740            }
1741        }
1742        return false;
1743    }
1744
1745    /**
1746     * @return php|extsrc|extbin|zendextsrc|zendextbin|bundle|false
1747     */
1748    function getPackageType()
1749    {
1750        if (isset($this->_packageInfo['phprelease'])) {
1751            return 'php';
1752        }
1753        if (isset($this->_packageInfo['extsrcrelease'])) {
1754            return 'extsrc';
1755        }
1756        if (isset($this->_packageInfo['extbinrelease'])) {
1757            return 'extbin';
1758        }
1759        if (isset($this->_packageInfo['zendextsrcrelease'])) {
1760            return 'zendextsrc';
1761        }
1762        if (isset($this->_packageInfo['zendextbinrelease'])) {
1763            return 'zendextbin';
1764        }
1765        if (isset($this->_packageInfo['bundle'])) {
1766            return 'bundle';
1767        }
1768        return false;
1769    }
1770
1771    /**
1772     * @return array|false
1773     */
1774    function getReleases()
1775    {
1776        $type = $this->getPackageType();
1777        if ($type != 'bundle') {
1778            $type .= 'release';
1779        }
1780        if ($this->getPackageType() && isset($this->_packageInfo[$type])) {
1781            return $this->_packageInfo[$type];
1782        }
1783        return false;
1784    }
1785
1786    /**
1787     * @return array
1788     */
1789    function getChangelog()
1790    {
1791        if (isset($this->_packageInfo['changelog'])) {
1792            return $this->_packageInfo['changelog'];
1793        }
1794        return false;
1795    }
1796
1797    function hasDeps()
1798    {
1799        return isset($this->_packageInfo['dependencies']);
1800    }
1801
1802    function getPackagexmlVersion()
1803    {
1804        if (isset($this->_packageInfo['zendextsrcrelease'])) {
1805            return '2.1';
1806        }
1807        if (isset($this->_packageInfo['zendextbinrelease'])) {
1808            return '2.1';
1809        }
1810        return '2.0';
1811    }
1812
1813    /**
1814     * @return array|false
1815     */
1816    function getSourcePackage()
1817    {
1818        if (isset($this->_packageInfo['extbinrelease']) ||
1819              isset($this->_packageInfo['zendextbinrelease'])) {
1820            return array('channel' => $this->_packageInfo['srcchannel'],
1821                         'package' => $this->_packageInfo['srcpackage']);
1822        }
1823        return false;
1824    }
1825
1826    function getBundledPackages()
1827    {
1828        if (isset($this->_packageInfo['bundle'])) {
1829            return $this->_packageInfo['contents']['bundledpackage'];
1830        }
1831        return false;
1832    }
1833
1834    function getLastModified()
1835    {
1836        if (isset($this->_packageInfo['_lastmodified'])) {
1837            return $this->_packageInfo['_lastmodified'];
1838        }
1839        return false;
1840    }
1841
1842    /**
1843     * Get the contents of a file listed within the package.xml
1844     * @param string
1845     * @return string
1846     */
1847    function getFileContents($file)
1848    {
1849        if ($this->_archiveFile == $this->_packageFile) { // unpacked
1850            $dir = dirname($this->_packageFile);
1851            $file = $dir . DIRECTORY_SEPARATOR . $file;
1852            $file = str_replace(array('/', '\\'),
1853                array(DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR), $file);
1854            if (file_exists($file) && is_readable($file)) {
1855                return implode('', file($file));
1856            }
1857        } else { // tgz
1858            $tar = &new Archive_Tar($this->_archiveFile);
1859            $tar->pushErrorHandling(PEAR_ERROR_RETURN);
1860            if ($file != 'package.xml' && $file != 'package2.xml') {
1861                $file = $this->getPackage() . '-' . $this->getVersion() . '/' . $file;
1862            }
1863            $file = $tar->extractInString($file);
1864            $tar->popErrorHandling();
1865            if (PEAR::isError($file)) {
1866                return PEAR::raiseError("Cannot locate file '$file' in archive");
1867            }
1868            return $file;
1869        }
1870    }
1871
1872    function &getRW()
1873    {
1874        if (!class_exists('PEAR_PackageFile_v2_rw')) {
1875            require_once 'PEAR/PackageFile/v2/rw.php';
1876        }
1877        $a = new PEAR_PackageFile_v2_rw;
1878        foreach (get_object_vars($this) as $name => $unused) {
1879            if (!isset($this->$name)) {
1880                continue;
1881            }
1882            if ($name == '_config' || $name == '_logger'|| $name == '_registry' ||
1883                  $name == '_stack') {
1884                $a->$name = &$this->$name;
1885            } else {
1886                $a->$name = $this->$name;
1887            }
1888        }
1889        return $a;
1890    }
1891
1892    function &getDefaultGenerator()
1893    {
1894        if (!class_exists('PEAR_PackageFile_Generator_v2')) {
1895            require_once 'PEAR/PackageFile/Generator/v2.php';
1896        }
1897        $a = &new PEAR_PackageFile_Generator_v2($this);
1898        return $a;
1899    }
1900
1901    function analyzeSourceCode($file, $string = false)
1902    {
1903        if (!isset($this->_v2Validator) ||
1904              !is_a($this->_v2Validator, 'PEAR_PackageFile_v2_Validator')) {
1905            if (!class_exists('PEAR_PackageFile_v2_Validator')) {
1906                require_once 'PEAR/PackageFile/v2/Validator.php';
1907            }
1908            $this->_v2Validator = new PEAR_PackageFile_v2_Validator;
1909        }
1910        return $this->_v2Validator->analyzeSourceCode($file, $string);
1911    }
1912
1913    function validate($state = PEAR_VALIDATE_NORMAL)
1914    {
1915        if (!isset($this->_packageInfo) || !is_array($this->_packageInfo)) {
1916            return false;
1917        }
1918        if (!isset($this->_v2Validator) ||
1919              !is_a($this->_v2Validator, 'PEAR_PackageFile_v2_Validator')) {
1920            if (!class_exists('PEAR_PackageFile_v2_Validator')) {
1921                require_once 'PEAR/PackageFile/v2/Validator.php';
1922            }
1923            $this->_v2Validator = new PEAR_PackageFile_v2_Validator;
1924        }
1925        if (isset($this->_packageInfo['xsdversion'])) {
1926            unset($this->_packageInfo['xsdversion']);
1927        }
1928        return $this->_v2Validator->validate($this, $state);
1929    }
1930
1931    function getTasksNs()
1932    {
1933        if (!isset($this->_tasksNs)) {
1934            if (isset($this->_packageInfo['attribs'])) {
1935                foreach ($this->_packageInfo['attribs'] as $name => $value) {
1936                    if ($value == 'http://pear.php.net/dtd/tasks-1.0') {
1937                        $this->_tasksNs = str_replace('xmlns:', '', $name);
1938                        break;
1939                    }
1940                }
1941            }
1942        }
1943        return $this->_tasksNs;
1944    }
1945
1946    /**
1947     * Determine whether a task name is a valid task.  Custom tasks may be defined
1948     * using subdirectories by putting a "-" in the name, as in <tasks:mycustom-task>
1949     *
1950     * Note that this method will auto-load the task class file and test for the existence
1951     * of the name with "-" replaced by "_" as in PEAR/Task/mycustom/task.php makes class
1952     * PEAR_Task_mycustom_task
1953     * @param string
1954     * @return boolean
1955     */
1956    function getTask($task)
1957    {
1958        $this->getTasksNs();
1959        // transform all '-' to '/' and 'tasks:' to '' so tasks:replace becomes replace
1960        $task = str_replace(array($this->_tasksNs . ':', '-'), array('', ' '), $task);
1961        $taskfile = str_replace(' ', '/', ucwords($task));
1962        $task = str_replace(array(' ', '/'), '_', ucwords($task));
1963        if (class_exists("PEAR_Task_$task")) {
1964            return "PEAR_Task_$task";
1965        }
1966        $fp = @fopen("PEAR/Task/$taskfile.php", 'r', true);
1967        if ($fp) {
1968            fclose($fp);
1969            require_once "PEAR/Task/$taskfile.php";
1970            return "PEAR_Task_$task";
1971        }
1972        return false;
1973    }
1974
1975    /**
1976     * Key-friendly array_splice
1977     * @param tagname to splice a value in before
1978     * @param mixed the value to splice in
1979     * @param string the new tag name
1980     */
1981    function _ksplice($array, $key, $value, $newkey)
1982    {
1983        $offset = array_search($key, array_keys($array));
1984        $after = array_slice($array, $offset);
1985        $before = array_slice($array, 0, $offset);
1986        $before[$newkey] = $value;
1987        return array_merge($before, $after);
1988    }
1989
1990    /**
1991     * @param array a list of possible keys, in the order they may occur
1992     * @param mixed contents of the new package.xml tag
1993     * @param string tag name
1994     * @access private
1995     */
1996    function _insertBefore($array, $keys, $contents, $newkey)
1997    {
1998        foreach ($keys as $key) {
1999            if (isset($array[$key])) {
2000                return $array = $this->_ksplice($array, $key, $contents, $newkey);
2001            }
2002        }
2003        $array[$newkey] = $contents;
2004        return $array;
2005    }
2006
2007    /**
2008     * @param subsection of {@link $_packageInfo}
2009     * @param array|string tag contents
2010     * @param array format:
2011     * <pre>
2012     * array(
2013     *   tagname => array(list of tag names that follow this one),
2014     *   childtagname => array(list of child tag names that follow this one),
2015     * )
2016     * </pre>
2017     *
2018     * This allows construction of nested tags
2019     * @access private
2020     */
2021    function _mergeTag($manip, $contents, $order)
2022    {
2023        if (count($order)) {
2024            foreach ($order as $tag => $curorder) {
2025                if (!isset($manip[$tag])) {
2026                    // ensure that the tag is set up
2027                    $manip = $this->_insertBefore($manip, $curorder, array(), $tag);
2028                }
2029                if (count($order) > 1) {
2030                    $manip[$tag] = $this->_mergeTag($manip[$tag], $contents, array_slice($order, 1));
2031                    return $manip;
2032                }
2033            }
2034        } else {
2035            return $manip;
2036        }
2037        if (is_array($manip[$tag]) && !empty($manip[$tag]) && isset($manip[$tag][0])) {
2038            $manip[$tag][] = $contents;
2039        } else {
2040            if (!count($manip[$tag])) {
2041                $manip[$tag] = $contents;
2042            } else {
2043                $manip[$tag] = array($manip[$tag]);
2044                $manip[$tag][] = $contents;
2045            }
2046        }
2047        return $manip;
2048    }
2049}
2050?>
Note: See TracBrowser for help on using the repository browser.