Updated PEAR and PEAR packages.
[timetracker.git] / WEB-INF / lib / pear / PEAR / Command / Channels.php
1 <?php
2 // /* vim: set expandtab tabstop=4 shiftwidth=4: */
3 /**
4  * PEAR_Command_Channels (list-channels, update-channels, channel-delete, channel-add,
5  * channel-update, channel-info, channel-alias, channel-discover commands)
6  *
7  * PHP versions 4 and 5
8  *
9  * @category   pear
10  * @package    PEAR
11  * @author     Stig Bakken <ssb@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 1.4.0a1
17  */
18
19 /**
20  * base class
21  */
22 require_once 'PEAR/Command/Common.php';
23
24 define('PEAR_COMMAND_CHANNELS_CHANNEL_EXISTS', -500);
25
26 /**
27  * PEAR commands for managing channels.
28  *
29  * @category   pear
30  * @package    PEAR
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: 1.10.1
35  * @link       http://pear.php.net/package/PEAR
36  * @since      Class available since Release 1.4.0a1
37  */
38 class PEAR_Command_Channels extends PEAR_Command_Common
39 {
40     var $commands = array(
41         'list-channels' => array(
42             'summary' => 'List Available Channels',
43             'function' => 'doList',
44             'shortcut' => 'lc',
45             'options' => array(),
46             'doc' => '
47 List all available channels for installation.
48 ',
49             ),
50         'update-channels' => array(
51             'summary' => 'Update the Channel List',
52             'function' => 'doUpdateAll',
53             'shortcut' => 'uc',
54             'options' => array(),
55             'doc' => '
56 List all installed packages in all channels.
57 '
58             ),
59         'channel-delete' => array(
60             'summary' => 'Remove a Channel From the List',
61             'function' => 'doDelete',
62             'shortcut' => 'cde',
63             'options' => array(),
64             'doc' => '<channel name>
65 Delete a channel from the registry.  You may not
66 remove any channel that has installed packages.
67 '
68             ),
69         'channel-add' => array(
70             'summary' => 'Add a Channel',
71             'function' => 'doAdd',
72             'shortcut' => 'ca',
73             'options' => array(),
74             'doc' => '<channel.xml>
75 Add a private channel to the channel list.  Note that all
76 public channels should be synced using "update-channels".
77 Parameter may be either a local file or remote URL to a
78 channel.xml.
79 '
80             ),
81         'channel-update' => array(
82             'summary' => 'Update an Existing Channel',
83             'function' => 'doUpdate',
84             'shortcut' => 'cu',
85             'options' => array(
86                 'force' => array(
87                     'shortopt' => 'f',
88                     'doc' => 'will force download of new channel.xml if an existing channel name is used',
89                     ),
90                 'channel' => array(
91                     'shortopt' => 'c',
92                     'arg' => 'CHANNEL',
93                     'doc' => 'will force download of new channel.xml if an existing channel name is used',
94                     ),
95 ),
96             'doc' => '[<channel.xml>|<channel name>]
97 Update a channel in the channel list directly.  Note that all
98 public channels can be synced using "update-channels".
99 Parameter may be a local or remote channel.xml, or the name of
100 an existing channel.
101 '
102             ),
103         'channel-info' => array(
104             'summary' => 'Retrieve Information on a Channel',
105             'function' => 'doInfo',
106             'shortcut' => 'ci',
107             'options' => array(),
108             'doc' => '<package>
109 List the files in an installed package.
110 '
111             ),
112         'channel-alias' => array(
113             'summary' => 'Specify an alias to a channel name',
114             'function' => 'doAlias',
115             'shortcut' => 'cha',
116             'options' => array(),
117             'doc' => '<channel> <alias>
118 Specify a specific alias to use for a channel name.
119 The alias may not be an existing channel name or
120 alias.
121 '
122             ),
123         'channel-discover' => array(
124             'summary' => 'Initialize a Channel from its server',
125             'function' => 'doDiscover',
126             'shortcut' => 'di',
127             'options' => array(),
128             'doc' => '[<channel.xml>|<channel name>]
129 Initialize a channel from its server and create a local channel.xml.
130 If <channel name> is in the format "<username>:<password>@<channel>" then
131 <username> and <password> will be set as the login username/password for
132 <channel>. Use caution when passing the username/password in this way, as
133 it may allow other users on your computer to briefly view your username/
134 password via the system\'s process list.
135 '
136             ),
137         'channel-login' => array(
138             'summary' => 'Connects and authenticates to remote channel server',
139             'shortcut' => 'cli',
140             'function' => 'doLogin',
141             'options' => array(),
142             'doc' => '<channel name>
143 Log in to a remote channel server.  If <channel name> is not supplied,
144 the default channel is used. To use remote functions in the installer
145 that require any kind of privileges, you need to log in first.  The
146 username and password you enter here will be stored in your per-user
147 PEAR configuration (~/.pearrc on Unix-like systems).  After logging
148 in, your username and password will be sent along in subsequent
149 operations on the remote server.',
150             ),
151         'channel-logout' => array(
152             'summary' => 'Logs out from the remote channel server',
153             'shortcut' => 'clo',
154             'function' => 'doLogout',
155             'options' => array(),
156             'doc' => '<channel name>
157 Logs out from a remote channel server.  If <channel name> is not supplied,
158 the default channel is used. This command does not actually connect to the
159 remote server, it only deletes the stored username and password from your user
160 configuration.',
161             ),
162         );
163
164     /**
165      * PEAR_Command_Registry constructor.
166      *
167      * @access public
168      */
169     function __construct(&$ui, &$config)
170     {
171         parent::__construct($ui, $config);
172     }
173
174     function _sortChannels($a, $b)
175     {
176         return strnatcasecmp($a->getName(), $b->getName());
177     }
178
179     function doList($command, $options, $params)
180     {
181         $reg = &$this->config->getRegistry();
182         $registered = $reg->getChannels();
183         usort($registered, array(&$this, '_sortchannels'));
184         $i = $j = 0;
185         $data = array(
186             'caption' => 'Registered Channels:',
187             'border' => true,
188             'headline' => array('Channel', 'Alias', 'Summary')
189             );
190         foreach ($registered as $channel) {
191             $data['data'][] = array($channel->getName(),
192                                     $channel->getAlias(),
193                                     $channel->getSummary());
194         }
195
196         if (count($registered) === 0) {
197             $data = '(no registered channels)';
198         }
199         $this->ui->outputData($data, $command);
200         return true;
201     }
202
203     function doUpdateAll($command, $options, $params)
204     {
205         $reg = &$this->config->getRegistry();
206         $channels = $reg->getChannels();
207
208         $success = true;
209         foreach ($channels as $channel) {
210             if ($channel->getName() != '__uri') {
211                 PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
212                 $err = $this->doUpdate('channel-update',
213                                           $options,
214                                           array($channel->getName()));
215                 if (PEAR::isError($err)) {
216                     $this->ui->outputData($err->getMessage(), $command);
217                     $success = false;
218                 } else {
219                     $success &= $err;
220                 }
221             }
222         }
223         return $success;
224     }
225
226     function doInfo($command, $options, $params)
227     {
228         if (count($params) !== 1) {
229             return $this->raiseError("No channel specified");
230         }
231
232         $reg     = &$this->config->getRegistry();
233         $channel = strtolower($params[0]);
234         if ($reg->channelExists($channel)) {
235             $chan = $reg->getChannel($channel);
236             if (PEAR::isError($chan)) {
237                 return $this->raiseError($chan);
238             }
239         } else {
240             if (strpos($channel, '://')) {
241                 $downloader = &$this->getDownloader();
242                 $tmpdir = $this->config->get('temp_dir');
243                 PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
244                 $loc = $downloader->downloadHttp($channel, $this->ui, $tmpdir);
245                 PEAR::staticPopErrorHandling();
246                 if (PEAR::isError($loc)) {
247                     return $this->raiseError('Cannot open "' . $channel .
248                         '" (' . $loc->getMessage() . ')');
249                 } else {
250                     $contents = implode('', file($loc));
251                 }
252             } else {
253                 if (!file_exists($params[0])) {
254                     return $this->raiseError('Unknown channel "' . $channel . '"');
255                 }
256
257                 $fp = fopen($params[0], 'r');
258                 if (!$fp) {
259                     return $this->raiseError('Cannot open "' . $params[0] . '"');
260                 }
261
262                 $contents = '';
263                 while (!feof($fp)) {
264                     $contents .= fread($fp, 1024);
265                 }
266                 fclose($fp);
267             }
268
269             if (!class_exists('PEAR_ChannelFile')) {
270                 require_once 'PEAR/ChannelFile.php';
271             }
272
273             $chan = new PEAR_ChannelFile;
274             $chan->fromXmlString($contents);
275             $chan->validate();
276             if ($errs = $chan->getErrors(true)) {
277                 foreach ($errs as $err) {
278                     $this->ui->outputData($err['level'] . ': ' . $err['message']);
279                 }
280                 return $this->raiseError('Channel file "' . $params[0] . '" is not valid');
281             }
282         }
283
284         if (!$chan) {
285             return $this->raiseError('Serious error: Channel "' . $params[0] .
286                 '" has a corrupted registry entry');
287         }
288
289         $channel = $chan->getName();
290         $caption = 'Channel ' . $channel . ' Information:';
291         $data1 = array(
292             'caption' => $caption,
293             'border' => true);
294         $data1['data']['server'] = array('Name and Server', $chan->getName());
295         if ($chan->getAlias() != $chan->getName()) {
296             $data1['data']['alias'] = array('Alias', $chan->getAlias());
297         }
298
299         $data1['data']['summary'] = array('Summary', $chan->getSummary());
300         $validate = $chan->getValidationPackage();
301         $data1['data']['vpackage'] = array('Validation Package Name', $validate['_content']);
302         $data1['data']['vpackageversion'] =
303             array('Validation Package Version', $validate['attribs']['version']);
304         $d = array();
305         $d['main'] = $data1;
306
307         $data['data'] = array();
308         $data['caption'] = 'Server Capabilities';
309         $data['headline'] = array('Type', 'Version/REST type', 'Function Name/REST base');
310         if ($chan->supportsREST()) {
311             if ($chan->supportsREST()) {
312                 $funcs = $chan->getFunctions('rest');
313                 if (!isset($funcs[0])) {
314                     $funcs = array($funcs);
315                 }
316                 foreach ($funcs as $protocol) {
317                     $data['data'][] = array('rest', $protocol['attribs']['type'],
318                         $protocol['_content']);
319                 }
320             }
321         } else {
322             $data['data'][] = array('No supported protocols');
323         }
324
325         $d['protocols'] = $data;
326         $data['data'] = array();
327         $mirrors = $chan->getMirrors();
328         if ($mirrors) {
329             $data['caption'] = 'Channel ' . $channel . ' Mirrors:';
330             unset($data['headline']);
331             foreach ($mirrors as $mirror) {
332                 $data['data'][] = array($mirror['attribs']['host']);
333                 $d['mirrors'] = $data;
334             }
335
336             foreach ($mirrors as $i => $mirror) {
337                 $data['data'] = array();
338                 $data['caption'] = 'Mirror ' . $mirror['attribs']['host'] . ' Capabilities';
339                 $data['headline'] = array('Type', 'Version/REST type', 'Function Name/REST base');
340                 if ($chan->supportsREST($mirror['attribs']['host'])) {
341                     if ($chan->supportsREST($mirror['attribs']['host'])) {
342                         $funcs = $chan->getFunctions('rest', $mirror['attribs']['host']);
343                         if (!isset($funcs[0])) {
344                             $funcs = array($funcs);
345                         }
346
347                         foreach ($funcs as $protocol) {
348                             $data['data'][] = array('rest', $protocol['attribs']['type'],
349                                 $protocol['_content']);
350                         }
351                     }
352                 } else {
353                     $data['data'][] = array('No supported protocols');
354                 }
355                 $d['mirrorprotocols' . $i] = $data;
356             }
357         }
358         $this->ui->outputData($d, 'channel-info');
359     }
360
361     // }}}
362
363     function doDelete($command, $options, $params)
364     {
365         if (count($params) !== 1) {
366             return $this->raiseError('channel-delete: no channel specified');
367         }
368
369         $reg = &$this->config->getRegistry();
370         if (!$reg->channelExists($params[0])) {
371             return $this->raiseError('channel-delete: channel "' . $params[0] . '" does not exist');
372         }
373
374         $channel = $reg->channelName($params[0]);
375         if ($channel == 'pear.php.net') {
376             return $this->raiseError('Cannot delete the pear.php.net channel');
377         }
378
379         if ($channel == 'pecl.php.net') {
380             return $this->raiseError('Cannot delete the pecl.php.net channel');
381         }
382
383         if ($channel == 'doc.php.net') {
384             return $this->raiseError('Cannot delete the doc.php.net channel');
385         }
386
387         if ($channel == '__uri') {
388             return $this->raiseError('Cannot delete the __uri pseudo-channel');
389         }
390
391         if (PEAR::isError($err = $reg->listPackages($channel))) {
392             return $err;
393         }
394
395         if (count($err)) {
396             return $this->raiseError('Channel "' . $channel .
397                 '" has installed packages, cannot delete');
398         }
399
400         if (!$reg->deleteChannel($channel)) {
401             return $this->raiseError('Channel "' . $channel . '" deletion failed');
402         } else {
403             $this->config->deleteChannel($channel);
404             $this->ui->outputData('Channel "' . $channel . '" deleted', $command);
405         }
406     }
407
408     function doAdd($command, $options, $params)
409     {
410         if (count($params) !== 1) {
411             return $this->raiseError('channel-add: no channel file specified');
412         }
413
414         if (strpos($params[0], '://')) {
415             $downloader = &$this->getDownloader();
416             $tmpdir = $this->config->get('temp_dir');
417             if (!file_exists($tmpdir)) {
418                 require_once 'System.php';
419                 PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
420                 $err = System::mkdir(array('-p', $tmpdir));
421                 PEAR::staticPopErrorHandling();
422                 if (PEAR::isError($err)) {
423                     return $this->raiseError('channel-add: temp_dir does not exist: "' .
424                         $tmpdir .
425                         '" - You can change this location with "pear config-set temp_dir"');
426                 }
427             }
428
429             if (!is_writable($tmpdir)) {
430                 return $this->raiseError('channel-add: temp_dir is not writable: "' .
431                     $tmpdir .
432                     '" - You can change this location with "pear config-set temp_dir"');
433             }
434
435             PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
436             $loc = $downloader->downloadHttp($params[0], $this->ui, $tmpdir, null, false);
437             PEAR::staticPopErrorHandling();
438             if (PEAR::isError($loc)) {
439                 return $this->raiseError('channel-add: Cannot open "' . $params[0] .
440                     '" (' . $loc->getMessage() . ')');
441             }
442
443             list($loc, $lastmodified) = $loc;
444             $contents = implode('', file($loc));
445         } else {
446             $lastmodified = $fp = false;
447             if (file_exists($params[0])) {
448                 $fp = fopen($params[0], 'r');
449             }
450
451             if (!$fp) {
452                 return $this->raiseError('channel-add: cannot open "' . $params[0] . '"');
453             }
454
455             $contents = '';
456             while (!feof($fp)) {
457                 $contents .= fread($fp, 1024);
458             }
459             fclose($fp);
460         }
461
462         if (!class_exists('PEAR_ChannelFile')) {
463             require_once 'PEAR/ChannelFile.php';
464         }
465
466         $channel = new PEAR_ChannelFile;
467         PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
468         $result = $channel->fromXmlString($contents);
469         PEAR::staticPopErrorHandling();
470         if (!$result) {
471             $exit = false;
472             if (count($errors = $channel->getErrors(true))) {
473                 foreach ($errors as $error) {
474                     $this->ui->outputData(ucfirst($error['level'] . ': ' . $error['message']));
475                     if (!$exit) {
476                         $exit = $error['level'] == 'error' ? true : false;
477                     }
478                 }
479                 if ($exit) {
480                     return $this->raiseError('channel-add: invalid channel.xml file');
481                 }
482             }
483         }
484
485         $reg = &$this->config->getRegistry();
486         if ($reg->channelExists($channel->getName())) {
487             return $this->raiseError('channel-add: Channel "' . $channel->getName() .
488                 '" exists, use channel-update to update entry', PEAR_COMMAND_CHANNELS_CHANNEL_EXISTS);
489         }
490
491         $ret = $reg->addChannel($channel, $lastmodified);
492         if (PEAR::isError($ret)) {
493             return $ret;
494         }
495
496         if (!$ret) {
497             return $this->raiseError('channel-add: adding Channel "' . $channel->getName() .
498                 '" to registry failed');
499         }
500
501         $this->config->setChannels($reg->listChannels());
502         $this->config->writeConfigFile();
503         $this->ui->outputData('Adding Channel "' . $channel->getName() . '" succeeded', $command);
504     }
505
506     function doUpdate($command, $options, $params)
507     {
508         if (count($params) !== 1) {
509             return $this->raiseError("No channel file specified");
510         }
511
512         $tmpdir = $this->config->get('temp_dir');
513         if (!file_exists($tmpdir)) {
514             require_once 'System.php';
515             PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
516             $err = System::mkdir(array('-p', $tmpdir));
517             PEAR::staticPopErrorHandling();
518             if (PEAR::isError($err)) {
519                 return $this->raiseError('channel-add: temp_dir does not exist: "' .
520                     $tmpdir .
521                     '" - You can change this location with "pear config-set temp_dir"');
522             }
523         }
524
525         if (!is_writable($tmpdir)) {
526             return $this->raiseError('channel-add: temp_dir is not writable: "' .
527                 $tmpdir .
528                 '" - You can change this location with "pear config-set temp_dir"');
529         }
530
531         $reg = &$this->config->getRegistry();
532         $lastmodified = false;
533         if ((!file_exists($params[0]) || is_dir($params[0]))
534               && $reg->channelExists(strtolower($params[0]))) {
535             $c = $reg->getChannel(strtolower($params[0]));
536             if (PEAR::isError($c)) {
537                 return $this->raiseError($c);
538             }
539
540             $this->ui->outputData("Updating channel \"$params[0]\"", $command);
541             $dl = &$this->getDownloader(array());
542             // if force is specified, use a timestamp of "1" to force retrieval
543             $lastmodified = isset($options['force']) ? false : $c->lastModified();
544             PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
545             $contents = $dl->downloadHttp('http://' . $c->getName() . '/channel.xml',
546                 $this->ui, $tmpdir, null, $lastmodified);
547             PEAR::staticPopErrorHandling();
548             if (PEAR::isError($contents)) {
549                 // Attempt to fall back to https
550                 $this->ui->outputData("Channel \"$params[0]\" is not responding over http://, failed with message: " . $contents->getMessage());
551                 $this->ui->outputData("Trying channel \"$params[0]\" over https:// instead");
552                 PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
553                 $contents = $dl->downloadHttp('https://' . $c->getName() . '/channel.xml',
554                     $this->ui, $tmpdir, null, $lastmodified);
555                 PEAR::staticPopErrorHandling();
556                 if (PEAR::isError($contents)) {
557                     return $this->raiseError('Cannot retrieve channel.xml for channel "' .
558                         $c->getName() . '" (' . $contents->getMessage() . ')');
559                 }
560             }
561
562             list($contents, $lastmodified) = $contents;
563             if (!$contents) {
564                 $this->ui->outputData("Channel \"$params[0]\" is up to date");
565                 return;
566             }
567
568             $contents = implode('', file($contents));
569             if (!class_exists('PEAR_ChannelFile')) {
570                 require_once 'PEAR/ChannelFile.php';
571             }
572
573             $channel = new PEAR_ChannelFile;
574             $channel->fromXmlString($contents);
575             if (!$channel->getErrors()) {
576                 // security check: is the downloaded file for the channel we got it from?
577                 if (strtolower($channel->getName()) != strtolower($c->getName())) {
578                     if (!isset($options['force'])) {
579                         return $this->raiseError('ERROR: downloaded channel definition file' .
580                             ' for channel "' . $channel->getName() . '" from channel "' .
581                             strtolower($c->getName()) . '"');
582                     }
583
584                     $this->ui->log(0, 'WARNING: downloaded channel definition file' .
585                         ' for channel "' . $channel->getName() . '" from channel "' .
586                         strtolower($c->getName()) . '"');
587                 }
588             }
589         } else {
590             if (strpos($params[0], '://')) {
591                 $dl = &$this->getDownloader();
592                 PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
593                 $loc = $dl->downloadHttp($params[0],
594                     $this->ui, $tmpdir, null, $lastmodified);
595                 PEAR::staticPopErrorHandling();
596                 if (PEAR::isError($loc)) {
597                     return $this->raiseError("Cannot open " . $params[0] .
598                          ' (' . $loc->getMessage() . ')');
599                 }
600
601                 list($loc, $lastmodified) = $loc;
602                 $contents = implode('', file($loc));
603             } else {
604                 $fp = false;
605                 if (file_exists($params[0])) {
606                     $fp = fopen($params[0], 'r');
607                 }
608
609                 if (!$fp) {
610                     return $this->raiseError("Cannot open " . $params[0]);
611                 }
612
613                 $contents = '';
614                 while (!feof($fp)) {
615                     $contents .= fread($fp, 1024);
616                 }
617                 fclose($fp);
618             }
619
620             if (!class_exists('PEAR_ChannelFile')) {
621                 require_once 'PEAR/ChannelFile.php';
622             }
623
624             $channel = new PEAR_ChannelFile;
625             $channel->fromXmlString($contents);
626         }
627
628         $exit = false;
629         if (count($errors = $channel->getErrors(true))) {
630             foreach ($errors as $error) {
631                 $this->ui->outputData(ucfirst($error['level'] . ': ' . $error['message']));
632                 if (!$exit) {
633                     $exit = $error['level'] == 'error' ? true : false;
634                 }
635             }
636             if ($exit) {
637                 return $this->raiseError('Invalid channel.xml file');
638             }
639         }
640
641         if (!$reg->channelExists($channel->getName())) {
642             return $this->raiseError('Error: Channel "' . $channel->getName() .
643                 '" does not exist, use channel-add to add an entry');
644         }
645
646         $ret = $reg->updateChannel($channel, $lastmodified);
647         if (PEAR::isError($ret)) {
648             return $ret;
649         }
650
651         if (!$ret) {
652             return $this->raiseError('Updating Channel "' . $channel->getName() .
653                 '" in registry failed');
654         }
655
656         $this->config->setChannels($reg->listChannels());
657         $this->config->writeConfigFile();
658         $this->ui->outputData('Update of Channel "' . $channel->getName() . '" succeeded');
659     }
660
661     function &getDownloader()
662     {
663         if (!class_exists('PEAR_Downloader')) {
664             require_once 'PEAR/Downloader.php';
665         }
666         $a = new PEAR_Downloader($this->ui, array(), $this->config);
667         return $a;
668     }
669
670     function doAlias($command, $options, $params)
671     {
672         if (count($params) === 1) {
673             return $this->raiseError('No channel alias specified');
674         }
675
676         if (count($params) !== 2 || (!empty($params[1]) && $params[1]{0} == '-')) {
677             return $this->raiseError(
678                 'Invalid format, correct is: channel-alias channel alias');
679         }
680
681         $reg = &$this->config->getRegistry();
682         if (!$reg->channelExists($params[0], true)) {
683             $extra = '';
684             if ($reg->isAlias($params[0])) {
685                 $extra = ' (use "channel-alias ' . $reg->channelName($params[0]) . ' ' .
686                     strtolower($params[1]) . '")';
687             }
688
689             return $this->raiseError('"' . $params[0] . '" is not a valid channel' . $extra);
690         }
691
692         if ($reg->isAlias($params[1])) {
693             return $this->raiseError('Channel "' . $reg->channelName($params[1]) . '" is ' .
694                 'already aliased to "' . strtolower($params[1]) . '", cannot re-alias');
695         }
696
697         $chan = $reg->getChannel($params[0]);
698         if (PEAR::isError($chan)) {
699             return $this->raiseError('Corrupt registry?  Error retrieving channel "' . $params[0] .
700                 '" information (' . $chan->getMessage() . ')');
701         }
702
703         // make it a local alias
704         if (!$chan->setAlias(strtolower($params[1]), true)) {
705             return $this->raiseError('Alias "' . strtolower($params[1]) .
706                 '" is not a valid channel alias');
707         }
708
709         $reg->updateChannel($chan);
710         $this->ui->outputData('Channel "' . $chan->getName() . '" aliased successfully to "' .
711             strtolower($params[1]) . '"');
712     }
713
714     /**
715      * The channel-discover command
716      *
717      * @param string $command command name
718      * @param array  $options option_name => value
719      * @param array  $params  list of additional parameters.
720      *               $params[0] should contain a string with either:
721      *               - <channel name> or
722      *               - <username>:<password>@<channel name>
723      * @return null|PEAR_Error
724      */
725     function doDiscover($command, $options, $params)
726     {
727         if (count($params) !== 1) {
728             return $this->raiseError("No channel server specified");
729         }
730
731         // Look for the possible input format "<username>:<password>@<channel>"
732         if (preg_match('/^(.+):(.+)@(.+)\\z/', $params[0], $matches)) {
733             $username = $matches[1];
734             $password = $matches[2];
735             $channel  = $matches[3];
736         } else {
737             $channel = $params[0];
738         }
739
740         $reg = &$this->config->getRegistry();
741         if ($reg->channelExists($channel)) {
742             if (!$reg->isAlias($channel)) {
743                 return $this->raiseError("Channel \"$channel\" is already initialized", PEAR_COMMAND_CHANNELS_CHANNEL_EXISTS);
744             }
745
746             return $this->raiseError("A channel alias named \"$channel\" " .
747                 'already exists, aliasing channel "' . $reg->channelName($channel)
748                 . '"');
749         }
750
751         $this->pushErrorHandling(PEAR_ERROR_RETURN);
752         $err = $this->doAdd($command, $options, array('http://' . $channel . '/channel.xml'));
753         $this->popErrorHandling();
754         if (PEAR::isError($err)) {
755             if ($err->getCode() === PEAR_COMMAND_CHANNELS_CHANNEL_EXISTS) {
756                 return $this->raiseError("Discovery of channel \"$channel\" failed (" .
757                     $err->getMessage() . ')');
758             }
759             // Attempt fetch via https
760             $this->ui->outputData("Discovering channel $channel over http:// failed with message: " . $err->getMessage());
761             $this->ui->outputData("Trying to discover channel $channel over https:// instead");
762             $this->pushErrorHandling(PEAR_ERROR_RETURN);
763             $err = $this->doAdd($command, $options, array('https://' . $channel . '/channel.xml'));
764             $this->popErrorHandling();
765             if (PEAR::isError($err)) {
766                 return $this->raiseError("Discovery of channel \"$channel\" failed (" .
767                     $err->getMessage() . ')');
768             }
769         }
770
771         // Store username/password if they were given
772         // Arguably we should do a logintest on the channel here, but since
773         // that's awkward on a REST-based channel (even "pear login" doesn't
774         // do it for those), and XML-RPC is deprecated, it's fairly pointless.
775         if (isset($username)) {
776             $this->config->set('username', $username, 'user', $channel);
777             $this->config->set('password', $password, 'user', $channel);
778             $this->config->store();
779             $this->ui->outputData("Stored login for channel \"$channel\" using username \"$username\"", $command);
780         }
781
782         $this->ui->outputData("Discovery of channel \"$channel\" succeeded", $command);
783     }
784
785     /**
786      * Execute the 'login' command.
787      *
788      * @param string $command command name
789      * @param array $options option_name => value
790      * @param array $params list of additional parameters
791      *
792      * @return bool TRUE on success or
793      * a PEAR error on failure
794      *
795      * @access public
796      */
797     function doLogin($command, $options, $params)
798     {
799         $reg = &$this->config->getRegistry();
800
801         // If a parameter is supplied, use that as the channel to log in to
802         $channel = isset($params[0]) ? $params[0] : $this->config->get('default_channel');
803
804         $chan = $reg->getChannel($channel);
805         if (PEAR::isError($chan)) {
806             return $this->raiseError($chan);
807         }
808
809         $server   = $this->config->get('preferred_mirror', null, $channel);
810         $username = $this->config->get('username',         null, $channel);
811         if (empty($username)) {
812             $username = isset($_ENV['USER']) ? $_ENV['USER'] : null;
813         }
814         $this->ui->outputData("Logging in to $server.", $command);
815
816         list($username, $password) = $this->ui->userDialog(
817             $command,
818             array('Username', 'Password'),
819             array('text',     'password'),
820             array($username,  '')
821             );
822         $username = trim($username);
823         $password = trim($password);
824
825         $ourfile = $this->config->getConfFile('user');
826         if (!$ourfile) {
827             $ourfile = $this->config->getConfFile('system');
828         }
829
830         $this->config->set('username', $username, 'user', $channel);
831         $this->config->set('password', $password, 'user', $channel);
832
833         if ($chan->supportsREST()) {
834             $ok = true;
835         }
836
837         if ($ok !== true) {
838             return $this->raiseError('Login failed!');
839         }
840
841         $this->ui->outputData("Logged in.", $command);
842         // avoid changing any temporary settings changed with -d
843         $ourconfig = new PEAR_Config($ourfile, $ourfile);
844         $ourconfig->set('username', $username, 'user', $channel);
845         $ourconfig->set('password', $password, 'user', $channel);
846         $ourconfig->store();
847
848         return true;
849     }
850
851     /**
852      * Execute the 'logout' command.
853      *
854      * @param string $command command name
855      * @param array $options option_name => value
856      * @param array $params list of additional parameters
857      *
858      * @return bool TRUE on success or
859      * a PEAR error on failure
860      *
861      * @access public
862      */
863     function doLogout($command, $options, $params)
864     {
865         $reg     = &$this->config->getRegistry();
866
867         // If a parameter is supplied, use that as the channel to log in to
868         $channel = isset($params[0]) ? $params[0] : $this->config->get('default_channel');
869
870         $chan    = $reg->getChannel($channel);
871         if (PEAR::isError($chan)) {
872             return $this->raiseError($chan);
873         }
874
875         $server = $this->config->get('preferred_mirror', null, $channel);
876         $this->ui->outputData("Logging out from $server.", $command);
877         $this->config->remove('username', 'user', $channel);
878         $this->config->remove('password', 'user', $channel);
879         $this->config->store();
880         return true;
881     }
882 }