Updated PEAR and PEAR packages.
[timetracker.git] / WEB-INF / lib / pear / PEAR / Command / Package.php
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  */
22 require_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
39 class 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]
57 Creates a PEAR package from its description file (usually called
58 package.xml).  If a second packagefile is passed in, then
59 the packager will check to make sure that one is a package.xml
60 version 1.0, and the other is a package.xml version 2.0.  The
61 package.xml version 1.0 will be saved as "package.xml" in the archive,
62 and 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>
130 Compares all the files in a package.  Without any options, this
131 command will compare the current code with the last checked-in code.
132 Using the -r or -R option you may compare the current code with that
133 of 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...]
196 Sets a CVS tag on all files in a package.  Use this command after you have
197 packaged a distribution tarball with the "package" command to tag what
198 revisions of what files were in that release.  If need to fix something
199 after running cvstag once, but before the tarball is released to the public,
200 use the "slide" option to move the release tag.
201
202 to include files (such as a second package.xml, or tests not included in the
203 release), 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>
212 List all dependencies the package has.
213 Can 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>
226 Signs 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
242 by the PEAR package name, defaults to "PEAR::%s".',
243                     ),
244                 ),
245             'doc' => '<package-file>
246
247 Creates an RPM .spec file for wrapping a PEAR package inside an RPM
248 package.  Intended to be used from the SPECS directory, with the PEAR
249 package tarball in the SOURCES directory:
250
251 $ pear makerpm ../SOURCES/Net_Socket-1.0.tgz
252 Wrote RPM spec file PEAR::Net_Geo-1.0.spec
253 $ rpm -bb PEAR::Net_Socket-1.0.spec
254 ...
255 Wrote: /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]
269 Converts a package.xml in 1.0 format into a package.xml
270 in 2.0 format.  The new file will be named package2.xml by default,
271 and package.xml will be used as the old file by default.
272 This is not the most intelligent conversion, and should only be
273 used 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                     $d = new PEAR_Dependency2($this->config, array(), '');
876                     $data = array(
877                         'caption' => 'Dependencies for ' . $info->getPackage(),
878                         'border' => true,
879                         'headline' => array("Required?", "Type", "Name", 'Versioning', 'Group'),
880                         );
881                     foreach ($deps as $type => $subd) {
882                         $req = ($type == 'required') ? 'Yes' : 'No';
883                         if ($type == 'group' && isset($subd['attribs']['name'])) {
884                             $group = $subd['attribs']['name'];
885                         } else {
886                             $group = '';
887                         }
888
889                         if (!isset($subd[0])) {
890                             $subd = array($subd);
891                         }
892
893                         foreach ($subd as $groupa) {
894                             foreach ($groupa as $deptype => $depinfo) {
895                                 if ($deptype == 'attribs') {
896                                     continue;
897                                 }
898
899                                 if ($deptype == 'pearinstaller') {
900                                     $deptype = 'pear Installer';
901                                 }
902
903                                 if (!isset($depinfo[0])) {
904                                     $depinfo = array($depinfo);
905                                 }
906
907                                 foreach ($depinfo as $inf) {
908                                     $name = '';
909                                     if (isset($inf['channel'])) {
910                                         $alias = $reg->channelAlias($inf['channel']);
911                                         if (!$alias) {
912                                             $alias = '(channel?) ' .$inf['channel'];
913                                         }
914                                         $name = $alias . '/';
915
916                                     }
917                                     if (isset($inf['name'])) {
918                                         $name .= $inf['name'];
919                                     } elseif (isset($inf['pattern'])) {
920                                         $name .= $inf['pattern'];
921                                     } else {
922                                         $name .= '';
923                                     }
924
925                                     if (isset($inf['uri'])) {
926                                         $name .= ' [' . $inf['uri'] .  ']';
927                                     }
928
929                                     if (isset($inf['conflicts'])) {
930                                         $ver = 'conflicts';
931                                     } else {
932                                         $ver = $d->_getExtraString($inf);
933                                     }
934
935                                     $data['data'][] = array($req, ucfirst($deptype), $name,
936                                         $ver, $group);
937                                 }
938                             }
939                         }
940                     }
941                 }
942             }
943
944             $this->ui->outputData($data, $command);
945             return true;
946         }
947
948         // Fallback
949         $this->ui->outputData("This package does not have any dependencies.", $command);
950     }
951
952     function doSign($command, $options, $params)
953     {
954         // should move most of this code into PEAR_Packager
955         // so it'll be easy to implement "pear package --sign"
956         if (count($params) !== 1) {
957             return $this->raiseError("bad parameter(s), try \"help $command\"");
958         }
959
960         require_once 'System.php';
961         require_once 'Archive/Tar.php';
962
963         if (!file_exists($params[0])) {
964             return $this->raiseError("file does not exist: $params[0]");
965         }
966
967         $obj = $this->getPackageFile($this->config, $this->_debug);
968         $info = $obj->fromTgzFile($params[0], PEAR_VALIDATE_NORMAL);
969         if (PEAR::isError($info)) {
970             return $this->raiseError($info);
971         }
972
973         $tar = new Archive_Tar($params[0]);
974
975         $tmpdir = $this->config->get('temp_dir');
976         $tmpdir = System::mktemp(' -t "' . $tmpdir . '" -d pearsign');
977         if (!$tar->extractList('package2.xml package.xml package.sig', $tmpdir)) {
978             return $this->raiseError("failed to extract tar file");
979         }
980
981         if (file_exists("$tmpdir/package.sig")) {
982             return $this->raiseError("package already signed");
983         }
984
985         $packagexml = 'package.xml';
986         if (file_exists("$tmpdir/package2.xml")) {
987             $packagexml = 'package2.xml';
988         }
989
990         if (file_exists("$tmpdir/package.sig")) {
991             unlink("$tmpdir/package.sig");
992         }
993
994         if (!file_exists("$tmpdir/$packagexml")) {
995             return $this->raiseError("Extracted file $tmpdir/$packagexml not found.");
996         }
997
998         $input = $this->ui->userDialog($command,
999                                        array('GnuPG Passphrase'),
1000                                        array('password'));
1001         if (!isset($input[0])) {
1002             //use empty passphrase
1003             $input[0] = '';
1004         }
1005
1006         $devnull = (isset($options['verbose'])) ? '' : ' 2>/dev/null';
1007         $gpg = popen("gpg --batch --passphrase-fd 0 --armor --detach-sign --output $tmpdir/package.sig $tmpdir/$packagexml" . $devnull, "w");
1008         if (!$gpg) {
1009             return $this->raiseError("gpg command failed");
1010         }
1011
1012         fwrite($gpg, "$input[0]\n");
1013         if (pclose($gpg) || !file_exists("$tmpdir/package.sig")) {
1014             return $this->raiseError("gpg sign failed");
1015         }
1016
1017         if (!$tar->addModify("$tmpdir/package.sig", '', $tmpdir)) {
1018             return $this->raiseError('failed adding signature to file');
1019         }
1020
1021         $this->ui->outputData("Package signed.", $command);
1022         return true;
1023     }
1024
1025     /**
1026      * For unit testing purposes
1027      */
1028     function &getInstaller(&$ui)
1029     {
1030         if (!class_exists('PEAR_Installer')) {
1031             require_once 'PEAR/Installer.php';
1032         }
1033         $a = new PEAR_Installer($ui);
1034         return $a;
1035     }
1036
1037     /**
1038      * For unit testing purposes
1039      */
1040     function &getCommandPackaging(&$ui, &$config)
1041     {
1042         if (!class_exists('PEAR_Command_Packaging')) {
1043             if ($fp = @fopen('PEAR/Command/Packaging.php', 'r', true)) {
1044                 fclose($fp);
1045                 include_once 'PEAR/Command/Packaging.php';
1046             }
1047         }
1048
1049         if (class_exists('PEAR_Command_Packaging')) {
1050             $a = new PEAR_Command_Packaging($ui, $config);
1051         } else {
1052             $a = null;
1053         }
1054
1055         return $a;
1056     }
1057
1058     function doMakeRPM($command, $options, $params)
1059     {
1060
1061         // Check to see if PEAR_Command_Packaging is installed, and
1062         // transparently switch to use the "make-rpm-spec" command from it
1063         // instead, if it does. Otherwise, continue to use the old version
1064         // of "makerpm" supplied with this package (PEAR).
1065         $packaging_cmd = $this->getCommandPackaging($this->ui, $this->config);
1066         if ($packaging_cmd !== null) {
1067             $this->ui->outputData('PEAR_Command_Packaging is installed; using '.
1068                 'newer "make-rpm-spec" command instead');
1069             return $packaging_cmd->run('make-rpm-spec', $options, $params);
1070         }
1071
1072         $this->ui->outputData('WARNING: "pear makerpm" is no longer available; an '.
1073           'improved version is available via "pear make-rpm-spec", which '.
1074           'is available by installing PEAR_Command_Packaging');
1075         return true;
1076     }
1077
1078     function doConvert($command, $options, $params)
1079     {
1080         $packagexml    = isset($params[0]) ? $params[0] : 'package.xml';
1081         $newpackagexml = isset($params[1]) ? $params[1] : dirname($packagexml) .
1082             DIRECTORY_SEPARATOR . 'package2.xml';
1083         $pkg = &$this->getPackageFile($this->config, $this->_debug);
1084         PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
1085         $pf = $pkg->fromPackageFile($packagexml, PEAR_VALIDATE_NORMAL);
1086         PEAR::staticPopErrorHandling();
1087         if (PEAR::isError($pf)) {
1088             if (is_array($pf->getUserInfo())) {
1089                 foreach ($pf->getUserInfo() as $warning) {
1090                     $this->ui->outputData($warning['message']);
1091                 }
1092             }
1093             return $this->raiseError($pf);
1094         }
1095
1096         if (is_a($pf, 'PEAR_PackageFile_v2')) {
1097             $this->ui->outputData($packagexml . ' is already a package.xml version 2.0');
1098             return true;
1099         }
1100
1101         $gen   = &$pf->getDefaultGenerator();
1102         $newpf = &$gen->toV2();
1103         $newpf->setPackagefile($newpackagexml);
1104         $gen = &$newpf->getDefaultGenerator();
1105         PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
1106         $state = (isset($options['flat']) ? PEAR_VALIDATE_PACKAGING : PEAR_VALIDATE_NORMAL);
1107         $saved = $gen->toPackageFile(dirname($newpackagexml), $state, basename($newpackagexml));
1108         PEAR::staticPopErrorHandling();
1109         if (PEAR::isError($saved)) {
1110             if (is_array($saved->getUserInfo())) {
1111                 foreach ($saved->getUserInfo() as $warning) {
1112                     $this->ui->outputData($warning['message']);
1113                 }
1114             }
1115
1116             $this->ui->outputData($saved->getMessage());
1117             return true;
1118         }
1119
1120         $this->ui->outputData('Wrote new version 2.0 package.xml to "' . $saved . '"');
1121         return true;
1122     }
1123 }