1<?php
2/**
3 * PEAR_Command_Package (package, package-validate, cvsdiff, cvstag, package-dependencies,
4 * sign, makerpm, convert commands)
5 *
6 * PHP versions 4 and 5
7 *
8 * @category   pear
9 * @package    PEAR
10 * @author     Stig Bakken <ssb@php.net>
11 * @author     Martin Jansen <mj@php.net>
12 * @author     Greg Beaver <cellog@php.net>
13 * @copyright  1997-2009 The Authors
14 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
15 * @link       http://pear.php.net/package/PEAR
16 * @since      File available since Release 0.1
17 */
18
19/**
20 * base class
21 */
22require_once 'PEAR/Command/Common.php';
23
24/**
25 * PEAR commands for login/logout
26 *
27 * @category   pear
28 * @package    PEAR
29 * @author     Stig Bakken <ssb@php.net>
30 * @author     Martin Jansen <mj@php.net>
31 * @author     Greg Beaver <cellog@php.net>
32 * @copyright  1997-2009 The Authors
33 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
34 * @version    Release: @package_version@
35 * @link       http://pear.php.net/package/PEAR
36 * @since      Class available since Release 0.1
37 */
38
39class PEAR_Command_Package extends PEAR_Command_Common
40{
41    var $commands = array(
42        'package' => array(
43            'summary' => 'Build Package',
44            'function' => 'doPackage',
45            'shortcut' => 'p',
46            'options' => array(
47                'nocompress' => array(
48                    'shortopt' => 'Z',
49                    'doc' => 'Do not gzip the package file'
50                    ),
51                'showname' => array(
52                    'shortopt' => 'n',
53                    'doc' => 'Print the name of the packaged file.',
54                    ),
55                ),
56            'doc' => '[descfile] [descfile2]
57Creates a PEAR package from its description file (usually called
58package.xml).  If a second packagefile is passed in, then
59the packager will check to make sure that one is a package.xml
60version 1.0, and the other is a package.xml version 2.0.  The
61package.xml version 1.0 will be saved as "package.xml" in the archive,
62and the other as "package2.xml" in the archive"
63'
64            ),
65        'package-validate' => array(
66            'summary' => 'Validate Package Consistency',
67            'function' => 'doPackageValidate',
68            'shortcut' => 'pv',
69            'options' => array(),
70            'doc' => '
71',
72            ),
73        'cvsdiff' => array(
74            'summary' => 'Run a "cvs diff" for all files in a package',
75            'function' => 'doCvsDiff',
76            'shortcut' => 'cd',
77            'options' => array(
78                'quiet' => array(
79                    'shortopt' => 'q',
80                    'doc' => 'Be quiet',
81                    ),
82                'reallyquiet' => array(
83                    'shortopt' => 'Q',
84                    'doc' => 'Be really quiet',
85                    ),
86                'date' => array(
87                    'shortopt' => 'D',
88                    'doc' => 'Diff against revision of DATE',
89                    'arg' => 'DATE',
90                    ),
91                'release' => array(
92                    'shortopt' => 'R',
93                    'doc' => 'Diff against tag for package release REL',
94                    'arg' => 'REL',
95                    ),
96                'revision' => array(
97                    'shortopt' => 'r',
98                    'doc' => 'Diff against revision REV',
99                    'arg' => 'REV',
100                    ),
101                'context' => array(
102                    'shortopt' => 'c',
103                    'doc' => 'Generate context diff',
104                    ),
105                'unified' => array(
106                    'shortopt' => 'u',
107                    'doc' => 'Generate unified diff',
108                    ),
109                'ignore-case' => array(
110                    'shortopt' => 'i',
111                    'doc' => 'Ignore case, consider upper- and lower-case letters equivalent',
112                    ),
113                'ignore-whitespace' => array(
114                    'shortopt' => 'b',
115                    'doc' => 'Ignore changes in amount of white space',
116                    ),
117                'ignore-blank-lines' => array(
118                    'shortopt' => 'B',
119                    'doc' => 'Ignore changes that insert or delete blank lines',
120                    ),
121                'brief' => array(
122                    'doc' => 'Report only whether the files differ, no details',
123                    ),
124                'dry-run' => array(
125                    'shortopt' => 'n',
126                    'doc' => 'Don\'t do anything, just pretend',
127                    ),
128                ),
129            'doc' => '<package.xml>
130Compares all the files in a package.  Without any options, this
131command will compare the current code with the last checked-in code.
132Using the -r or -R option you may compare the current code with that
133of a specific release.
134',
135            ),
136         'svntag' => array(
137             'summary' => 'Set SVN Release Tag',
138             'function' => 'doSvnTag',
139             'shortcut' => 'sv',
140             'options' => array(
141                 'quiet' => array(
142                     'shortopt' => 'q',
143                     'doc' => 'Be quiet',
144                     ),
145                 'slide' => array(
146                     'shortopt' => 'F',
147                     'doc' => 'Move (slide) tag if it exists',
148                     ),
149                 'delete' => array(
150                     'shortopt' => 'd',
151                     'doc' => 'Remove tag',
152                     ),
153                 'dry-run' => array(
154                     'shortopt' => 'n',
155                     'doc' => 'Don\'t do anything, just pretend',
156                     ),
157                 ),
158             'doc' => '<package.xml> [files...]
159 Sets a SVN tag on all files in a package.  Use this command after you have
160 packaged a distribution tarball with the "package" command to tag what
161 revisions of what files were in that release.  If need to fix something
162 after running svntag once, but before the tarball is released to the public,
163 use the "slide" option to move the release tag.
164
165 to include files (such as a second package.xml, or tests not included in the
166 release), pass them as additional parameters.
167 ',
168             ),
169        'cvstag' => array(
170            'summary' => 'Set CVS Release Tag',
171            'function' => 'doCvsTag',
172            'shortcut' => 'ct',
173            'options' => array(
174                'quiet' => array(
175                    'shortopt' => 'q',
176                    'doc' => 'Be quiet',
177                    ),
178                'reallyquiet' => array(
179                    'shortopt' => 'Q',
180                    'doc' => 'Be really quiet',
181                    ),
182                'slide' => array(
183                    'shortopt' => 'F',
184                    'doc' => 'Move (slide) tag if it exists',
185                    ),
186                'delete' => array(
187                    'shortopt' => 'd',
188                    'doc' => 'Remove tag',
189                    ),
190                'dry-run' => array(
191                    'shortopt' => 'n',
192                    'doc' => 'Don\'t do anything, just pretend',
193                    ),
194                ),
195            'doc' => '<package.xml> [files...]
196Sets a CVS tag on all files in a package.  Use this command after you have
197packaged a distribution tarball with the "package" command to tag what
198revisions of what files were in that release.  If need to fix something
199after running cvstag once, but before the tarball is released to the public,
200use the "slide" option to move the release tag.
201
202to include files (such as a second package.xml, or tests not included in the
203release), pass them as additional parameters.
204',
205            ),
206        'package-dependencies' => array(
207            'summary' => 'Show package dependencies',
208            'function' => 'doPackageDependencies',
209            'shortcut' => 'pd',
210            'options' => array(),
211            'doc' => '<package-file> or <package.xml> or <install-package-name>
212List all dependencies the package has.
213Can take a tgz / tar file, package.xml or a package name of an installed package.'
214            ),
215        'sign' => array(
216            'summary' => 'Sign a package distribution file',
217            'function' => 'doSign',
218            'shortcut' => 'si',
219            'options' => array(
220                'verbose' => array(
221                    'shortopt' => 'v',
222                    'doc' => 'Display GnuPG output',
223                    ),
224            ),
225            'doc' => '<package-file>
226Signs a package distribution (.tar or .tgz) file with GnuPG.',
227            ),
228        'makerpm' => array(
229            'summary' => 'Builds an RPM spec file from a PEAR package',
230            'function' => 'doMakeRPM',
231            'shortcut' => 'rpm',
232            'options' => array(
233                'spec-template' => array(
234                    'shortopt' => 't',
235                    'arg' => 'FILE',
236                    'doc' => 'Use FILE as RPM spec file template'
237                    ),
238                'rpm-pkgname' => array(
239                    'shortopt' => 'p',
240                    'arg' => 'FORMAT',
241                    'doc' => 'Use FORMAT as format string for RPM package name, %s is replaced
242by the PEAR package name, defaults to "PEAR::%s".',
243                    ),
244                ),
245            'doc' => '<package-file>
246
247Creates an RPM .spec file for wrapping a PEAR package inside an RPM
248package.  Intended to be used from the SPECS directory, with the PEAR
249package tarball in the SOURCES directory:
250
251$ pear makerpm ../SOURCES/Net_Socket-1.0.tgz
252Wrote RPM spec file PEAR::Net_Geo-1.0.spec
253$ rpm -bb PEAR::Net_Socket-1.0.spec
254...
255Wrote: /usr/src/redhat/RPMS/i386/PEAR::Net_Socket-1.0-1.i386.rpm
256',
257            ),
258        'convert' => array(
259            'summary' => 'Convert a package.xml 1.0 to package.xml 2.0 format',
260            'function' => 'doConvert',
261            'shortcut' => 'c2',
262            'options' => array(
263                'flat' => array(
264                    'shortopt' => 'f',
265                    'doc' => 'do not beautify the filelist.',
266                    ),
267                ),
268            'doc' => '[descfile] [descfile2]
269Converts a package.xml in 1.0 format into a package.xml
270in 2.0 format.  The new file will be named package2.xml by default,
271and package.xml will be used as the old file by default.
272This is not the most intelligent conversion, and should only be
273used for automated conversion or learning the format.
274'
275            ),
276        );
277
278    var $output;
279
280    /**
281     * PEAR_Command_Package constructor.
282     *
283     * @access public
284     */
285    function __construct(&$ui, &$config)
286    {
287        parent::__construct($ui, $config);
288    }
289
290    function _displayValidationResults($err, $warn, $strict = false)
291    {
292        foreach ($err as $e) {
293            $this->output .= "Error: $e\n";
294        }
295        foreach ($warn as $w) {
296            $this->output .= "Warning: $w\n";
297        }
298        $this->output .= sprintf('Validation: %d error(s), %d warning(s)'."\n",
299                                       sizeof($err), sizeof($warn));
300        if ($strict && count($err) > 0) {
301            $this->output .= "Fix these errors and try again.";
302            return false;
303        }
304        return true;
305    }
306
307    function &getPackager()
308    {
309        if (!class_exists('PEAR_Packager')) {
310            require_once 'PEAR/Packager.php';
311        }
312        $a = new PEAR_Packager;
313        return $a;
314    }
315
316    function &getPackageFile($config, $debug = false)
317    {
318        if (!class_exists('PEAR_Common')) {
319            require_once 'PEAR/Common.php';
320        }
321        if (!class_exists('PEAR_PackageFile')) {
322            require_once 'PEAR/PackageFile.php';
323        }
324        $a = new PEAR_PackageFile($config, $debug);
325        $common = new PEAR_Common;
326        $common->ui = $this->ui;
327        $a->setLogger($common);
328        return $a;
329    }
330
331    function doPackage($command, $options, $params)
332    {
333        $this->output = '';
334        $pkginfofile = isset($params[0]) ? $params[0] : 'package.xml';
335        $pkg2 = isset($params[1]) ? $params[1] : null;
336        if (!$pkg2 && !isset($params[0]) && file_exists('package2.xml')) {
337            $pkg2 = 'package2.xml';
338        }
339
340        $packager = &$this->getPackager();
341        $compress = empty($options['nocompress']) ? true : false;
342        $result   = $packager->package($pkginfofile, $compress, $pkg2);
343        if (PEAR::isError($result)) {
344            return $this->raiseError($result);
345        }
346
347        // Don't want output, only the package file name just created
348        if (isset($options['showname'])) {
349            $this->output = $result;
350        }
351
352        if ($this->output) {
353            $this->ui->outputData($this->output, $command);
354        }
355
356        return true;
357    }
358
359    function doPackageValidate($command, $options, $params)
360    {
361        $this->output = '';
362        if (count($params) < 1) {
363            $params[0] = 'package.xml';
364        }
365
366        $obj = &$this->getPackageFile($this->config, $this->_debug);
367        $obj->rawReturn();
368        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
369        $info = $obj->fromTgzFile($params[0], PEAR_VALIDATE_NORMAL);
370        if (PEAR::isError($info)) {
371            $info = $obj->fromPackageFile($params[0], PEAR_VALIDATE_NORMAL);
372        } else {
373            $archive = $info->getArchiveFile();
374            $tar = new Archive_Tar($archive);
375            $tar->extract(dirname($info->getPackageFile()));
376            $info->setPackageFile(dirname($info->getPackageFile()) . DIRECTORY_SEPARATOR .
377                $info->getPackage() . '-' . $info->getVersion() . DIRECTORY_SEPARATOR .
378                basename($info->getPackageFile()));
379        }
380
381        PEAR::staticPopErrorHandling();
382        if (PEAR::isError($info)) {
383            return $this->raiseError($info);
384        }
385
386        $valid = false;
387        if ($info->getPackagexmlVersion() == '2.0') {
388            if ($valid = $info->validate(PEAR_VALIDATE_NORMAL)) {
389                $info->flattenFileList();
390                $valid = $info->validate(PEAR_VALIDATE_PACKAGING);
391            }
392        } else {
393            $valid = $info->validate(PEAR_VALIDATE_PACKAGING);
394        }
395
396        $err = $warn = array();
397        if ($errors = $info->getValidationWarnings()) {
398            foreach ($errors as $error) {
399                if ($error['level'] == 'warning') {
400                    $warn[] = $error['message'];
401                } else {
402                    $err[] = $error['message'];
403                }
404            }
405        }
406
407        $this->_displayValidationResults($err, $warn);
408        $this->ui->outputData($this->output, $command);
409        return true;
410    }
411
412    function doSvnTag($command, $options, $params)
413    {
414        $this->output = '';
415        $_cmd = $command;
416        if (count($params) < 1) {
417            $help = $this->getHelp($command);
418            return $this->raiseError("$command: missing parameter: $help[0]");
419        }
420
421        $packageFile = realpath($params[0]);
422        $dir = dirname($packageFile);
423        $dir = substr($dir, strrpos($dir, DIRECTORY_SEPARATOR) + 1);
424        $obj  = &$this->getPackageFile($this->config, $this->_debug);
425        $info = $obj->fromAnyFile($packageFile, PEAR_VALIDATE_NORMAL);
426        if (PEAR::isError($info)) {
427            return $this->raiseError($info);
428        }
429
430        $err = $warn = array();
431        if (!$info->validate()) {
432            foreach ($info->getValidationWarnings() as $error) {
433                if ($error['level'] == 'warning') {
434                    $warn[] = $error['message'];
435                } else {
436                    $err[] = $error['message'];
437                }
438            }
439        }
440
441        if (!$this->_displayValidationResults($err, $warn, true)) {
442            $this->ui->outputData($this->output, $command);
443            return $this->raiseError('SVN tag failed');
444        }
445
446        $version    = $info->getVersion();
447        $package    = $info->getName();
448        $svntag     = "$package-$version";
449
450        if (isset($options['delete'])) {
451            return $this->_svnRemoveTag($version, $package, $svntag, $packageFile, $options);
452        }
453
454        $path = $this->_svnFindPath($packageFile);
455
456        // Check if there are any modified files
457        $fp = popen('svn st --xml ' . dirname($packageFile), "r");
458        $out = '';
459        while ($line = fgets($fp, 1024)) {
460            $out .= rtrim($line)."\n";
461        }
462        pclose($fp);
463
464        if (!isset($options['quiet']) && strpos($out, 'item="modified"')) {
465            $params = array(array(
466                'name' => 'modified',
467                'type' => 'yesno',
468                'default' => 'no',
469                'prompt' => 'You have files in your SVN checkout (' . $path['from']  . ') that have been modified but not committed, do you still want to tag ' . $version . '?',
470            ));
471            $answers = $this->ui->confirmDialog($params);
472
473            if (!in_array($answers['modified'], array('y', 'yes', 'on', '1'))) {
474                return true;
475            }
476        }
477
478        if (isset($options['slide'])) {
479            $this->_svnRemoveTag($version, $package, $svntag, $packageFile, $options);
480        }
481
482        // Check if tag already exists
483        $releaseTag = $path['local']['base'] . 'tags' . DIRECTORY_SEPARATOR . $svntag;
484        $existsCommand = 'svn ls ' . $path['base'] . 'tags/';
485
486        $fp = popen($existsCommand, "r");
487        $out = '';
488        while ($line = fgets($fp, 1024)) {
489            $out .= rtrim($line)."\n";
490        }
491        pclose($fp);
492
493        if (in_array($svntag . DIRECTORY_SEPARATOR, explode("\n", $out))) {
494            $this->ui->outputData($this->output, $command);
495            return $this->raiseError('SVN tag ' . $svntag . ' for ' . $package . ' already exists.');
496        } elseif (file_exists($path['local']['base'] . 'tags') === false) {
497            return $this->raiseError('Can not locate the tags directory at ' . $path['local']['base'] . 'tags');
498        } elseif (is_writeable($path['local']['base'] . 'tags') === false) {
499            return $this->raiseError('Can not write to the tag directory at ' . $path['local']['base'] . 'tags');
500        } else {
501            $makeCommand = 'svn mkdir ' . $releaseTag;
502            $this->output .= "+ $makeCommand\n";
503            if (empty($options['dry-run'])) {
504                // We need to create the tag dir.
505                $fp = popen($makeCommand, "r");
506                $out = '';
507                while ($line = fgets($fp, 1024)) {
508                    $out .= rtrim($line)."\n";
509                }
510                pclose($fp);
511                $this->output .= "$out\n";
512            }
513        }
514
515        $command = 'svn';
516        if (isset($options['quiet'])) {
517            $command .= ' -q';
518        }
519
520        $command .= ' copy --parents ';
521
522        $dir   = dirname($packageFile);
523        $dir   = substr($dir, strrpos($dir, DIRECTORY_SEPARATOR) + 1);
524        $files = array_keys($info->getFilelist());
525        if (!in_array(basename($packageFile), $files)) {
526            $files[] = basename($packageFile);
527        }
528
529        array_shift($params);
530        if (count($params)) {
531            // add in additional files to be tagged (package files and such)
532            $files = array_merge($files, $params);
533        }
534
535        $commands = array();
536        foreach ($files as $file) {
537            if (!file_exists($file)) {
538                $file = $dir . DIRECTORY_SEPARATOR . $file;
539            }
540            $commands[] = $command . ' ' . escapeshellarg($file) . ' ' .
541                          escapeshellarg($releaseTag . DIRECTORY_SEPARATOR . $file);
542        }
543
544        $this->output .= implode("\n", $commands) . "\n";
545        if (empty($options['dry-run'])) {
546            foreach ($commands as $command) {
547                $fp = popen($command, "r");
548                while ($line = fgets($fp, 1024)) {
549                    $this->output .= rtrim($line)."\n";
550                }
551                pclose($fp);
552            }
553        }
554
555        $command = 'svn ci -m "Tagging the ' . $version  . ' release" ' . $releaseTag . "\n";
556        $this->output .= "+ $command\n";
557        if (empty($options['dry-run'])) {
558            $fp = popen($command, "r");
559            while ($line = fgets($fp, 1024)) {
560                $this->output .= rtrim($line)."\n";
561            }
562            pclose($fp);
563        }
564
565        $this->ui->outputData($this->output, $_cmd);
566        return true;
567    }
568
569    function _svnFindPath($file)
570    {
571        $xml = '';
572        $command = "svn info --xml $file";
573        $fp = popen($command, "r");
574        while ($line = fgets($fp, 1024)) {
575            $xml .= rtrim($line)."\n";
576        }
577        pclose($fp);
578        $url_tag = strpos($xml, '<url>');
579        $url = substr($xml, $url_tag + 5, strpos($xml, '</url>', $url_tag + 5) - ($url_tag + 5));
580
581        $path = array();
582        $path['from'] = substr($url, 0, strrpos($url, '/'));
583        $path['base'] = substr($path['from'], 0, strrpos($path['from'], '/') + 1);
584
585        // Figure out the local paths - see http://pear.php.net/bugs/17463
586        $pos = strpos($file, DIRECTORY_SEPARATOR . 'trunk' . DIRECTORY_SEPARATOR);
587        if ($pos === false) {
588            $pos = strpos($file, DIRECTORY_SEPARATOR . 'branches' . DIRECTORY_SEPARATOR);
589        }
590        $path['local']['base'] = substr($file, 0, $pos + 1);
591
592        return $path;
593    }
594
595    function _svnRemoveTag($version, $package, $tag, $packageFile, $options)
596    {
597        $command = 'svn';
598
599        if (isset($options['quiet'])) {
600            $command .= ' -q';
601        }
602
603        $command .= ' remove';
604        $command .= ' -m "Removing tag for the ' . $version  . ' release."';
605
606        $path = $this->_svnFindPath($packageFile);
607        $command .= ' ' . $path['base'] . 'tags/' . $tag;
608
609
610        if ($this->config->get('verbose') > 1) {
611            $this->output .= "+ $command\n";
612        }
613
614        $this->output .= "+ $command\n";
615        if (empty($options['dry-run'])) {
616            $fp = popen($command, "r");
617            while ($line = fgets($fp, 1024)) {
618                $this->output .= rtrim($line)."\n";
619            }
620            pclose($fp);
621        }
622
623        $this->ui->outputData($this->output, $command);
624        return true;
625    }
626
627    function doCvsTag($command, $options, $params)
628    {
629        $this->output = '';
630        $_cmd = $command;
631        if (count($params) < 1) {
632            $help = $this->getHelp($command);
633            return $this->raiseError("$command: missing parameter: $help[0]");
634        }
635
636        $packageFile = realpath($params[0]);
637        $obj  = &$this->getPackageFile($this->config, $this->_debug);
638        $info = $obj->fromAnyFile($packageFile, PEAR_VALIDATE_NORMAL);
639        if (PEAR::isError($info)) {
640            return $this->raiseError($info);
641        }
642
643        $err = $warn = array();
644        if (!$info->validate()) {
645            foreach ($info->getValidationWarnings() as $error) {
646                if ($error['level'] == 'warning') {
647                    $warn[] = $error['message'];
648                } else {
649                    $err[] = $error['message'];
650                }
651            }
652        }
653
654        if (!$this->_displayValidationResults($err, $warn, true)) {
655            $this->ui->outputData($this->output, $command);
656            return $this->raiseError('CVS tag failed');
657        }
658
659        $version    = $info->getVersion();
660        $cvsversion = preg_replace('/[^a-z0-9]/i', '_', $version);
661        $cvstag     = "RELEASE_$cvsversion";
662        $files      = array_keys($info->getFilelist());
663        $command = 'cvs';
664        if (isset($options['quiet'])) {
665            $command .= ' -q';
666        }
667
668        if (isset($options['reallyquiet'])) {
669            $command .= ' -Q';
670        }
671
672        $command .= ' tag';
673        if (isset($options['slide'])) {
674            $command .= ' -F';
675        }
676
677        if (isset($options['delete'])) {
678            $command .= ' -d';
679        }
680
681        $command .= ' ' . $cvstag . ' ' . escapeshellarg($params[0]);
682        array_shift($params);
683        if (count($params)) {
684            // add in additional files to be tagged
685            $files = array_merge($files, $params);
686        }
687
688        $dir = dirname($packageFile);
689        $dir = substr($dir, strrpos($dir, '/') + 1);
690        foreach ($files as $file) {
691            if (!file_exists($file)) {
692                $file = $dir . DIRECTORY_SEPARATOR . $file;
693            }
694            $command .= ' ' . escapeshellarg($file);
695        }
696
697        if ($this->config->get('verbose') > 1) {
698            $this->output .= "+ $command\n";
699        }
700
701        $this->output .= "+ $command\n";
702        if (empty($options['dry-run'])) {
703            $fp = popen($command, "r");
704            while ($line = fgets($fp, 1024)) {
705                $this->output .= rtrim($line)."\n";
706            }
707            pclose($fp);
708        }
709
710        $this->ui->outputData($this->output, $_cmd);
711        return true;
712    }
713
714    function doCvsDiff($command, $options, $params)
715    {
716        $this->output = '';
717        if (sizeof($params) < 1) {
718            $help = $this->getHelp($command);
719            return $this->raiseError("$command: missing parameter: $help[0]");
720        }
721
722        $file = realpath($params[0]);
723        $obj  = &$this->getPackageFile($this->config, $this->_debug);
724        $info = $obj->fromAnyFile($file, PEAR_VALIDATE_NORMAL);
725        if (PEAR::isError($info)) {
726            return $this->raiseError($info);
727        }
728
729        $err = $warn = array();
730        if (!$info->validate()) {
731            foreach ($info->getValidationWarnings() as $error) {
732                if ($error['level'] == 'warning') {
733                    $warn[] = $error['message'];
734                } else {
735                    $err[] = $error['message'];
736                }
737            }
738        }
739
740        if (!$this->_displayValidationResults($err, $warn, true)) {
741            $this->ui->outputData($this->output, $command);
742            return $this->raiseError('CVS diff failed');
743        }
744
745        $info1 = $info->getFilelist();
746        $files = $info1;
747        $cmd = "cvs";
748        if (isset($options['quiet'])) {
749            $cmd .= ' -q';
750            unset($options['quiet']);
751        }
752
753        if (isset($options['reallyquiet'])) {
754            $cmd .= ' -Q';
755            unset($options['reallyquiet']);
756        }
757
758        if (isset($options['release'])) {
759            $cvsversion = preg_replace('/[^a-z0-9]/i', '_', $options['release']);
760            $cvstag = "RELEASE_$cvsversion";
761            $options['revision'] = $cvstag;
762            unset($options['release']);
763        }
764
765        $execute = true;
766        if (isset($options['dry-run'])) {
767            $execute = false;
768            unset($options['dry-run']);
769        }
770
771        $cmd .= ' diff';
772        // the rest of the options are passed right on to "cvs diff"
773        foreach ($options as $option => $optarg) {
774            $arg = $short = false;
775            if (isset($this->commands[$command]['options'][$option])) {
776                $arg = $this->commands[$command]['options'][$option]['arg'];
777                $short = $this->commands[$command]['options'][$option]['shortopt'];
778            }
779            $cmd .= $short ? " -$short" : " --$option";
780            if ($arg && $optarg) {
781                $cmd .= ($short ? '' : '=') . escapeshellarg($optarg);
782            }
783        }
784
785        foreach ($files as $file) {
786            $cmd .= ' ' . escapeshellarg($file['name']);
787        }
788
789        if ($this->config->get('verbose') > 1) {
790            $this->output .= "+ $cmd\n";
791        }
792
793        if ($execute) {
794            $fp = popen($cmd, "r");
795            while ($line = fgets($fp, 1024)) {
796                $this->output .= rtrim($line)."\n";
797            }
798            pclose($fp);
799        }
800
801        $this->ui->outputData($this->output, $command);
802        return true;
803    }
804
805    function doPackageDependencies($command, $options, $params)
806    {
807        // $params[0] -> the PEAR package to list its information
808        if (count($params) !== 1) {
809            return $this->raiseError("bad parameter(s), try \"help $command\"");
810        }
811
812        $obj = &$this->getPackageFile($this->config, $this->_debug);
813        if (is_file($params[0]) || strpos($params[0], '.xml') > 0) {
814           $info = $obj->fromAnyFile($params[0], PEAR_VALIDATE_NORMAL);
815        } else {
816            $reg  = $this->config->getRegistry();
817            $info = $obj->fromArray($reg->packageInfo($params[0]));
818        }
819
820        if (PEAR::isError($info)) {
821            return $this->raiseError($info);
822        }
823
824        $deps = $info->getDeps();
825        if (is_array($deps)) {
826            if ($info->getPackagexmlVersion() == '1.0') {
827                $data = array(
828                    'caption' => 'Dependencies for pear/' . $info->getPackage(),
829                    'border' => true,
830                    'headline' => array("Required?", "Type", "Name", "Relation", "Version"),
831                    );
832
833                foreach ($deps as $d) {
834                    if (isset($d['optional'])) {
835                        if ($d['optional'] == 'yes') {
836                            $req = 'No';
837                        } else {
838                            $req = 'Yes';
839                        }
840                    } else {
841                        $req = 'Yes';
842                    }
843
844                    if (isset($this->_deps_rel_trans[$d['rel']])) {
845                        $rel = $this->_deps_rel_trans[$d['rel']];
846                    } else {
847                        $rel = $d['rel'];
848                    }
849
850                    if (isset($this->_deps_type_trans[$d['type']])) {
851                        $type = ucfirst($this->_deps_type_trans[$d['type']]);
852                    } else {
853                        $type = $d['type'];
854                    }
855
856                    if (isset($d['name'])) {
857                        $name = $d['name'];
858                    } else {
859                        $name = '';
860                    }
861
862                    if (isset($d['version'])) {
863                        $version = $d['version'];
864                    } else {
865                        $version = '';
866                    }
867
868                    $data['data'][] = array($req, $type, $name, $rel, $version);
869                }
870            } else { // package.xml 2.0 dependencies display
871                require_once 'PEAR/Dependency2.php';
872                $deps = $info->getDependencies();
873                $reg = &$this->config->getRegistry();
874                if (is_array($deps)) {
875                    $data = array(
876                        'caption' => 'Dependencies for ' . $info->getPackage(),
877                        'border' => true,
878                        'headline' => array("Required?", "Type", "Name", 'Versioning', 'Group'),
879                        );
880                    foreach ($deps as $type => $subd) {
881                        $req = ($type == 'required') ? 'Yes' : 'No';
882                        if ($type == 'group' && isset($subd['attribs']['name'])) {
883                            $group = $subd['attribs']['name'];
884                        } else {
885                            $group = '';
886                        }
887
888                        if (!isset($subd[0])) {
889                            $subd = array($subd);
890                        }
891
892                        foreach ($subd as $groupa) {
893                            foreach ($groupa as $deptype => $depinfo) {
894                                if ($deptype == 'attribs') {
895                                    continue;
896                                }
897
898                                if ($deptype == 'pearinstaller') {
899                                    $deptype = 'pear Installer';
900                                }
901
902                                if (!isset($depinfo[0])) {
903                                    $depinfo = array($depinfo);
904                                }
905
906                                foreach ($depinfo as $inf) {
907                                    $name = '';
908                                    if (isset($inf['channel'])) {
909                                        $alias = $reg->channelAlias($inf['channel']);
910                                        if (!$alias) {
911                                            $alias = '(channel?) ' .$inf['channel'];
912                                        }
913                                        $name = $alias . '/';
914
915                                    }
916                                    if (isset($inf['name'])) {
917                                        $name .= $inf['name'];
918                                    } elseif (isset($inf['pattern'])) {
919                                        $name .= $inf['pattern'];
920                                    } else {
921                                        $name .= '';
922                                    }
923
924                                    if (isset($inf['uri'])) {
925                                        $name .= ' [' . $inf['uri'] .  ']';
926                                    }
927
928                                    if (isset($inf['conflicts'])) {
929                                        $ver = 'conflicts';
930                                    } else {
931                                        $ver = PEAR_Dependency2::_getExtraString($inf);
932                                    }
933
934                                    $data['data'][] = array($req, ucfirst($deptype), $name,
935                                        $ver, $group);
936                                }
937                            }
938                        }
939                    }
940                }
941            }
942
943            $this->ui->outputData($data, $command);
944            return true;
945        }
946
947        // Fallback
948        $this->ui->outputData("This package does not have any dependencies.", $command);
949    }
950
951    function doSign($command, $options, $params)
952    {
953        // should move most of this code into PEAR_Packager
954        // so it'll be easy to implement "pear package --sign"
955        if (count($params) !== 1) {
956            return $this->raiseError("bad parameter(s), try \"help $command\"");
957        }
958
959        require_once 'System.php';
960        require_once 'Archive/Tar.php';
961
962        if (!file_exists($params[0])) {
963            return $this->raiseError("file does not exist: $params[0]");
964        }
965
966        $obj = $this->getPackageFile($this->config, $this->_debug);
967        $info = $obj->fromTgzFile($params[0], PEAR_VALIDATE_NORMAL);
968        if (PEAR::isError($info)) {
969            return $this->raiseError($info);
970        }
971
972        $tar = new Archive_Tar($params[0]);
973
974        $tmpdir = $this->config->get('temp_dir');
975        $tmpdir = System::mktemp(' -t "' . $tmpdir . '" -d pearsign');
976        if (!$tar->extractList('package2.xml package.xml package.sig', $tmpdir)) {
977            return $this->raiseError("failed to extract tar file");
978        }
979
980        if (file_exists("$tmpdir/package.sig")) {
981            return $this->raiseError("package already signed");
982        }
983
984        $packagexml = 'package.xml';
985        if (file_exists("$tmpdir/package2.xml")) {
986            $packagexml = 'package2.xml';
987        }
988
989        if (file_exists("$tmpdir/package.sig")) {
990            unlink("$tmpdir/package.sig");
991        }
992
993        if (!file_exists("$tmpdir/$packagexml")) {
994            return $this->raiseError("Extracted file $tmpdir/$packagexml not found.");
995        }
996
997        $input = $this->ui->userDialog($command,
998                                       array('GnuPG Passphrase'),
999                                       array('password'));
1000        if (!isset($input[0])) {
1001            //use empty passphrase
1002            $input[0] = '';
1003        }
1004
1005        $devnull = (isset($options['verbose'])) ? '' : ' 2>/dev/null';
1006        $gpg = popen("gpg --batch --passphrase-fd 0 --armor --detach-sign --output $tmpdir/package.sig $tmpdir/$packagexml" . $devnull, "w");
1007        if (!$gpg) {
1008            return $this->raiseError("gpg command failed");
1009        }
1010
1011        fwrite($gpg, "$input[0]\n");
1012        if (pclose($gpg) || !file_exists("$tmpdir/package.sig")) {
1013            return $this->raiseError("gpg sign failed");
1014        }
1015
1016        if (!$tar->addModify("$tmpdir/package.sig", '', $tmpdir)) {
1017            return $this->raiseError('failed adding signature to file');
1018        }
1019
1020        $this->ui->outputData("Package signed.", $command);
1021        return true;
1022    }
1023
1024    /**
1025     * For unit testing purposes
1026     */
1027    function &getInstaller(&$ui)
1028    {
1029        if (!class_exists('PEAR_Installer')) {
1030            require_once 'PEAR/Installer.php';
1031        }
1032        $a = new PEAR_Installer($ui);
1033        return $a;
1034    }
1035
1036    /**
1037     * For unit testing purposes
1038     */
1039    function &getCommandPackaging(&$ui, &$config)
1040    {
1041        if (!class_exists('PEAR_Command_Packaging')) {
1042            if ($fp = @fopen('PEAR/Command/Packaging.php', 'r', true)) {
1043                fclose($fp);
1044                include_once 'PEAR/Command/Packaging.php';
1045            }
1046        }
1047
1048        if (class_exists('PEAR_Command_Packaging')) {
1049            $a = new PEAR_Command_Packaging($ui, $config);
1050        } else {
1051            $a = null;
1052        }
1053
1054        return $a;
1055    }
1056
1057    function doMakeRPM($command, $options, $params)
1058    {
1059
1060        // Check to see if PEAR_Command_Packaging is installed, and
1061        // transparently switch to use the "make-rpm-spec" command from it
1062        // instead, if it does. Otherwise, continue to use the old version
1063        // of "makerpm" supplied with this package (PEAR).
1064        $packaging_cmd = $this->getCommandPackaging($this->ui, $this->config);
1065        if ($packaging_cmd !== null) {
1066            $this->ui->outputData('PEAR_Command_Packaging is installed; using '.
1067                'newer "make-rpm-spec" command instead');
1068            return $packaging_cmd->run('make-rpm-spec', $options, $params);
1069        }
1070
1071        $this->ui->outputData('WARNING: "pear makerpm" is no longer available; an '.
1072          'improved version is available via "pear make-rpm-spec", which '.
1073          'is available by installing PEAR_Command_Packaging');
1074        return true;
1075    }
1076
1077    function doConvert($command, $options, $params)
1078    {
1079        $packagexml    = isset($params[0]) ? $params[0] : 'package.xml';
1080        $newpackagexml = isset($params[1]) ? $params[1] : dirname($packagexml) .
1081            DIRECTORY_SEPARATOR . 'package2.xml';
1082        $pkg = &$this->getPackageFile($this->config, $this->_debug);
1083        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
1084        $pf = $pkg->fromPackageFile($packagexml, PEAR_VALIDATE_NORMAL);
1085        PEAR::staticPopErrorHandling();
1086        if (PEAR::isError($pf)) {
1087            if (is_array($pf->getUserInfo())) {
1088                foreach ($pf->getUserInfo() as $warning) {
1089                    $this->ui->outputData($warning['message']);
1090                }
1091            }
1092            return $this->raiseError($pf);
1093        }
1094
1095        if (is_a($pf, 'PEAR_PackageFile_v2')) {
1096            $this->ui->outputData($packagexml . ' is already a package.xml version 2.0');
1097            return true;
1098        }
1099
1100        $gen   = &$pf->getDefaultGenerator();
1101        $newpf = &$gen->toV2();
1102        $newpf->setPackagefile($newpackagexml);
1103        $gen = &$newpf->getDefaultGenerator();
1104        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
1105        $state = (isset($options['flat']) ? PEAR_VALIDATE_PACKAGING : PEAR_VALIDATE_NORMAL);
1106        $saved = $gen->toPackageFile(dirname($newpackagexml), $state, basename($newpackagexml));
1107        PEAR::staticPopErrorHandling();
1108        if (PEAR::isError($saved)) {
1109            if (is_array($saved->getUserInfo())) {
1110                foreach ($saved->getUserInfo() as $warning) {
1111                    $this->ui->outputData($warning['message']);
1112                }
1113            }
1114
1115            $this->ui->outputData($saved->getMessage());
1116            return true;
1117        }
1118
1119        $this->ui->outputData('Wrote new version 2.0 package.xml to "' . $saved . '"');
1120        return true;
1121    }
1122}
1123