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