1<?php
2/**
3 * PEAR_ChannelFile, the channel handling class
4 *
5 * PHP versions 4 and 5
6 *
7 * LICENSE: This source file is subject to version 3.0 of the PHP license
8 * that is available through the world-wide-web at the following URI:
9 * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
10 * the PHP License and are unable to obtain it through the web, please
11 * send a note to license@php.net so we can mail you a copy immediately.
12 *
13 * @category   pear
14 * @package    PEAR
15 * @author     Greg Beaver <cellog@php.net>
16 * @copyright  1997-2006 The PHP Group
17 * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
18 * @version    CVS: $Id: ChannelFile.php,v 1.75 2006/03/02 18:14:12 cellog Exp $
19 * @link       http://pear.php.net/package/PEAR
20 * @since      File available since Release 1.4.0a1
21 */
22
23/**
24 * Needed for error handling
25 */
26require_once 'PEAR/ErrorStack.php';
27require_once 'PEAR/XMLParser.php';
28require_once 'PEAR/Common.php';
29
30/**
31 * Error code if the channel.xml <channel> tag does not contain a valid version
32 */
33define('PEAR_CHANNELFILE_ERROR_NO_VERSION', 1);
34/**
35 * Error code if the channel.xml <channel> tag version is not supported (version 1.0 is the only supported version,
36 * currently
37 */
38define('PEAR_CHANNELFILE_ERROR_INVALID_VERSION', 2);
39
40/**
41 * Error code if parsing is attempted with no xml extension
42 */
43define('PEAR_CHANNELFILE_ERROR_NO_XML_EXT', 3);
44
45/**
46 * Error code if creating the xml parser resource fails
47 */
48define('PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER', 4);
49
50/**
51 * Error code used for all sax xml parsing errors
52 */
53define('PEAR_CHANNELFILE_ERROR_PARSER_ERROR', 5);
54
55/**#@+
56 * Validation errors
57 */
58/**
59 * Error code when channel name is missing
60 */
61define('PEAR_CHANNELFILE_ERROR_NO_NAME', 6);
62/**
63 * Error code when channel name is invalid
64 */
65define('PEAR_CHANNELFILE_ERROR_INVALID_NAME', 7);
66/**
67 * Error code when channel summary is missing
68 */
69define('PEAR_CHANNELFILE_ERROR_NO_SUMMARY', 8);
70/**
71 * Error code when channel summary is multi-line
72 */
73define('PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY', 9);
74/**
75 * Error code when channel server is missing for xmlrpc or soap protocol
76 */
77define('PEAR_CHANNELFILE_ERROR_NO_HOST', 10);
78/**
79 * Error code when channel server is invalid for xmlrpc or soap protocol
80 */
81define('PEAR_CHANNELFILE_ERROR_INVALID_HOST', 11);
82/**
83 * Error code when a mirror name is invalid
84 */
85define('PEAR_CHANNELFILE_ERROR_INVALID_MIRROR', 21);
86/**
87 * Error code when a mirror type is invalid
88 */
89define('PEAR_CHANNELFILE_ERROR_INVALID_MIRRORTYPE', 22);
90/**
91 * Error code when an attempt is made to generate xml, but the parsed content is invalid
92 */
93define('PEAR_CHANNELFILE_ERROR_INVALID', 23);
94/**
95 * Error code when an empty package name validate regex is passed in
96 */
97define('PEAR_CHANNELFILE_ERROR_EMPTY_REGEX', 24);
98/**
99 * Error code when a <function> tag has no version
100 */
101define('PEAR_CHANNELFILE_ERROR_NO_FUNCTIONVERSION', 25);
102/**
103 * Error code when a <function> tag has no name
104 */
105define('PEAR_CHANNELFILE_ERROR_NO_FUNCTIONNAME', 26);
106/**
107 * Error code when a <validatepackage> tag has no name
108 */
109define('PEAR_CHANNELFILE_ERROR_NOVALIDATE_NAME', 27);
110/**
111 * Error code when a <validatepackage> tag has no version attribute
112 */
113define('PEAR_CHANNELFILE_ERROR_NOVALIDATE_VERSION', 28);
114/**
115 * Error code when a mirror does not exist but is called for in one of the set*
116 * methods.
117 */
118define('PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND', 32);
119/**
120 * Error code when a server port is not numeric
121 */
122define('PEAR_CHANNELFILE_ERROR_INVALID_PORT', 33);
123/**
124 * Error code when <static> contains no version attribute
125 */
126define('PEAR_CHANNELFILE_ERROR_NO_STATICVERSION', 34);
127/**
128 * Error code when <baseurl> contains no type attribute in a <rest> protocol definition
129 */
130define('PEAR_CHANNELFILE_ERROR_NOBASEURLTYPE', 35);
131/**
132 * Error code when a mirror is defined and the channel.xml represents the __uri pseudo-channel
133 */
134define('PEAR_CHANNELFILE_URI_CANT_MIRROR', 36);
135/**
136 * Error code when ssl attribute is present and is not "yes"
137 */
138define('PEAR_CHANNELFILE_ERROR_INVALID_SSL', 37);
139/**#@-*/
140
141/**
142 * Mirror types allowed.  Currently only internet servers are recognized.
143 */
144$GLOBALS['_PEAR_CHANNELS_MIRROR_TYPES'] =  array('server');
145
146
147/**
148 * The Channel handling class
149 *
150 * @category   pear
151 * @package    PEAR
152 * @author     Greg Beaver <cellog@php.net>
153 * @copyright  1997-2006 The PHP Group
154 * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
155 * @version    Release: 1.4.11
156 * @link       http://pear.php.net/package/PEAR
157 * @since      Class available since Release 1.4.0a1
158 */
159class PEAR_ChannelFile {
160    /**
161     * @access private
162     * @var PEAR_ErrorStack
163     * @access private
164     */
165    var $_stack;
166
167    /**
168     * Supported channel.xml versions, for parsing
169     * @var array
170     * @access private
171     */
172    var $_supportedVersions = array('1.0');
173
174    /**
175     * Parsed channel information
176     * @var array
177     * @access private
178     */
179    var $_channelInfo;
180
181    /**
182     * index into the subchannels array, used for parsing xml
183     * @var int
184     * @access private
185     */
186    var $_subchannelIndex;
187
188    /**
189     * index into the mirrors array, used for parsing xml
190     * @var int
191     * @access private
192     */
193    var $_mirrorIndex;
194
195    /**
196     * Flag used to determine the validity of parsed content
197     * @var boolean
198     * @access private
199     */
200    var $_isValid = false;
201
202    function PEAR_ChannelFile()
203    {
204        $this->_stack = &new PEAR_ErrorStack('PEAR_ChannelFile');
205        $this->_stack->setErrorMessageTemplate($this->_getErrorMessage());
206        $this->_isValid = false;
207    }
208
209    /**
210     * @return array
211     * @access protected
212     */
213    function _getErrorMessage()
214    {
215        return
216            array(
217                PEAR_CHANNELFILE_ERROR_INVALID_VERSION =>
218                    'While parsing channel.xml, an invalid version number "%version% was passed in, expecting one of %versions%',
219                PEAR_CHANNELFILE_ERROR_NO_VERSION =>
220                    'No version number found in <channel> tag',
221                PEAR_CHANNELFILE_ERROR_NO_XML_EXT =>
222                    '%error%',
223                PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER =>
224                    'Unable to create XML parser',
225                PEAR_CHANNELFILE_ERROR_PARSER_ERROR =>
226                    '%error%',
227                PEAR_CHANNELFILE_ERROR_NO_NAME =>
228                    'Missing channel name',
229                PEAR_CHANNELFILE_ERROR_INVALID_NAME =>
230                    'Invalid channel %tag% "%name%"',
231                PEAR_CHANNELFILE_ERROR_NO_SUMMARY =>
232                    'Missing channel summary',
233                PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY =>
234                    'Channel summary should be on one line, but is multi-line',
235                PEAR_CHANNELFILE_ERROR_NO_HOST =>
236                    'Missing channel server for %type% server',
237                PEAR_CHANNELFILE_ERROR_INVALID_HOST =>
238                    'Server name "%server%" is invalid for %type% server',
239                PEAR_CHANNELFILE_ERROR_INVALID_MIRROR =>
240                    'Invalid mirror name "%name%", mirror type %type%',
241                PEAR_CHANNELFILE_ERROR_INVALID_MIRRORTYPE =>
242                    'Invalid mirror type "%type%"',
243                PEAR_CHANNELFILE_ERROR_INVALID =>
244                    'Cannot generate xml, contents are invalid',
245                PEAR_CHANNELFILE_ERROR_EMPTY_REGEX =>
246                    'packagenameregex cannot be empty',
247                PEAR_CHANNELFILE_ERROR_NO_FUNCTIONVERSION =>
248                    '%parent% %protocol% function has no version',
249                PEAR_CHANNELFILE_ERROR_NO_FUNCTIONNAME =>
250                    '%parent% %protocol% function has no name',
251                PEAR_CHANNELFILE_ERROR_NOBASEURLTYPE =>
252                    '%parent% rest baseurl has no type',
253                PEAR_CHANNELFILE_ERROR_NOVALIDATE_NAME =>
254                    'Validation package has no name in <validatepackage> tag',
255                PEAR_CHANNELFILE_ERROR_NOVALIDATE_VERSION =>
256                    'Validation package "%package%" has no version',
257                PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND =>
258                    'Mirror "%mirror%" does not exist',
259                PEAR_CHANNELFILE_ERROR_INVALID_PORT =>
260                    'Port "%port%" must be numeric',
261                PEAR_CHANNELFILE_ERROR_NO_STATICVERSION =>
262                    '<static> tag must contain version attribute',
263                PEAR_CHANNELFILE_URI_CANT_MIRROR =>
264                    'The __uri pseudo-channel cannot have mirrors',
265                PEAR_CHANNELFILE_ERROR_INVALID_SSL =>
266                    '%server% has invalid ssl attribute "%ssl%" can only be yes or not present',
267            );
268    }
269
270    /**
271     * @param string contents of package.xml file
272     * @return bool success of parsing
273     */
274    function fromXmlString($data)
275    {
276        if (preg_match('/<channel\s+version="([0-9]+\.[0-9]+)"/', $data, $channelversion)) {
277            if (!in_array($channelversion[1], $this->_supportedVersions)) {
278                $this->_stack->push(PEAR_CHANNELFILE_ERROR_INVALID_VERSION, 'error',
279                    array('version' => $channelversion[1]));
280                return false;
281            }
282            $parser = new PEAR_XMLParser;
283            $result = $parser->parse($data);
284            if ($result !== true) {
285                if ($result->getCode() == 1) {
286                    $this->_stack->push(PEAR_CHANNELFILE_ERROR_NO_XML_EXT, 'error',
287                        array('error' => $error));
288                } else {
289                    $this->_stack->push(PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER, 'error');
290                }
291                return false;
292            }
293            $this->_channelInfo = $parser->getData();
294            return true;
295        } else {
296            $this->_stack->push(PEAR_CHANNELFILE_ERROR_NO_VERSION, 'error', array('xml' => $data));
297            return false;
298        }
299    }
300
301    /**
302     * @return array
303     */
304    function toArray()
305    {
306        if (!$this->_isValid && !$this->validate()) {
307            return false;
308        }
309        return $this->_channelInfo;
310    }
311
312    /**
313     * @param array
314     * @static
315     * @return PEAR_ChannelFile|false false if invalid
316     */
317    function &fromArray($data, $compatibility = false, $stackClass = 'PEAR_ErrorStack')
318    {
319        $a = new PEAR_ChannelFile($compatibility, $stackClass);
320        $a->_fromArray($data);
321        if (!$a->validate()) {
322            $a = false;
323            return $a;
324        }
325        return $a;
326    }
327
328    /**
329     * Unlike {@link fromArray()} this does not do any validation
330     * @param array
331     * @static
332     * @return PEAR_ChannelFile
333     */
334    function &fromArrayWithErrors($data, $compatibility = false,
335                                  $stackClass = 'PEAR_ErrorStack')
336    {
337        $a = new PEAR_ChannelFile($compatibility, $stackClass);
338        $a->_fromArray($data);
339        return $a;
340    }
341
342    /**
343     * @param array
344     * @access private
345     */
346    function _fromArray($data)
347    {
348        $this->_channelInfo = $data;
349    }
350
351    /**
352     * Wrapper to {@link PEAR_ErrorStack::getErrors()}
353     * @param boolean determines whether to purge the error stack after retrieving
354     * @return array
355     */
356    function getErrors($purge = false)
357    {
358        return $this->_stack->getErrors($purge);
359    }
360
361    /**
362     * Unindent given string (?)
363     *
364     * @param string $str The string that has to be unindented.
365     * @return string
366     * @access private
367     */
368    function _unIndent($str)
369    {
370        // remove leading newlines
371        $str = preg_replace('/^[\r\n]+/', '', $str);
372        // find whitespace at the beginning of the first line
373        $indent_len = strspn($str, " \t");
374        $indent = substr($str, 0, $indent_len);
375        $data = '';
376        // remove the same amount of whitespace from following lines
377        foreach (explode("\n", $str) as $line) {
378            if (substr($line, 0, $indent_len) == $indent) {
379                $data .= substr($line, $indent_len) . "\n";
380            }
381        }
382        return $data;
383    }
384
385    /**
386     * Parse a channel.xml file.  Expects the name of
387     * a channel xml file as input.
388     *
389     * @param string  $descfile  name of channel xml file
390     * @return bool success of parsing
391     */
392    function fromXmlFile($descfile)
393    {
394        if (!@is_file($descfile) || !is_readable($descfile) ||
395             (!$fp = @fopen($descfile, 'r'))) {
396            require_once 'PEAR.php';
397            return PEAR::raiseError("Unable to open $descfile");
398        }
399
400        // read the whole thing so we only get one cdata callback
401        // for each block of cdata
402        if (function_exists('file_get_contents')) {
403            fclose($fp);
404            $data = file_get_contents($descfile);
405        } else {
406            $data = fread($fp, filesize($descfile));
407            fclose($fp);
408        }
409        return $this->fromXmlString($data);
410    }
411
412    /**
413     * Parse channel information from different sources
414     *
415     * This method is able to extract information about a channel
416     * from an .xml file or a string
417     *
418     * @access public
419     * @param  string Filename of the source or the source itself
420     * @return bool
421     */
422    function fromAny($info)
423    {
424        if (is_string($info) && file_exists($info) && strlen($info) < 255) {
425            $tmp = substr($info, -4);
426            if ($tmp == '.xml') {
427                $info = $this->fromXmlFile($info);
428            } else {
429                $fp = fopen($info, "r");
430                $test = fread($fp, 5);
431                fclose($fp);
432                if ($test == "<?xml") {
433                    $info = $this->fromXmlFile($info);
434                }
435            }
436            if (PEAR::isError($info)) {
437                require_once 'PEAR.php';
438                return PEAR::raiseError($info);
439            }
440        }
441        if (is_string($info)) {
442            $info = $this->fromXmlString($info);
443        }
444        return $info;
445    }
446
447    /**
448     * Return an XML document based on previous parsing and modifications
449     *
450     * @return string XML data
451     *
452     * @access public
453     */
454    function toXml()
455    {
456        if (!$this->_isValid && !$this->validate()) {
457            $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID);
458            return false;
459        }
460        if (!isset($this->_channelInfo['attribs']['version'])) {
461            $this->_channelInfo['attribs']['version'] = '1.0';
462        }
463        $channelInfo = $this->_channelInfo;
464        $ret = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?>\n";
465        $ret .= "<channel version=\"" .
466            $channelInfo['attribs']['version'] . "\" xmlns=\"http://pear.php.net/channel-1.0\"
467  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"
468  xsi:schemaLocation=\"http://pear.php.net/dtd/channel-"
469            . $channelInfo['attribs']['version'] . " http://pear.php.net/dtd/channel-" .
470            $channelInfo['attribs']['version'] . ".xsd\">
471 <name>$channelInfo[name]</name>
472 <summary>" . htmlspecialchars($channelInfo['summary'])."</summary>
473";
474        if (isset($channelInfo['suggestedalias'])) {
475            $ret .= ' <suggestedalias>' . $channelInfo['suggestedalias'] . "</suggestedalias>\n";
476        }
477        if (isset($channelInfo['validatepackage'])) {
478            $ret .= ' <validatepackage version="' .
479                $channelInfo['validatepackage']['attribs']['version']. '">' .
480                htmlspecialchars($channelInfo['validatepackage']['_content']) .
481                "</validatepackage>\n";
482        }
483        $ret .= " <servers>\n";
484        $ret .= '  <primary';
485        if (isset($channelInfo['servers']['primary']['attribs']['ssl'])) {
486            $ret .= ' ssl="' . $channelInfo['servers']['primary']['attribs']['ssl'] . '"';
487        }
488        if (isset($channelInfo['servers']['primary']['attribs']['port'])) {
489            $ret .= ' port="' . $channelInfo['servers']['primary']['attribs']['port'] . '"';
490        }
491        $ret .= ">\n";
492        if (isset($channelInfo['servers']['primary']['xmlrpc'])) {
493            $ret .= $this->_makeXmlrpcXml($channelInfo['servers']['primary']['xmlrpc'], '   ');
494        }
495        if (isset($channelInfo['servers']['primary']['rest'])) {
496            $ret .= $this->_makeRestXml($channelInfo['servers']['primary']['rest'], '   ');
497        }
498        if (isset($channelInfo['servers']['primary']['soap'])) {
499            $ret .= $this->_makeSoapXml($channelInfo['servers']['primary']['soap'], '   ');
500        }
501        $ret .= "  </primary>\n";
502        if (isset($channelInfo['servers']['mirror'])) {
503            $ret .= $this->_makeMirrorsXml($channelInfo);
504        }
505        $ret .= " </servers>\n";
506        $ret .= "</channel>";
507        return str_replace("\r", "\n", str_replace("\r\n", "\n", $ret));
508    }
509
510    /**
511     * Generate the <xmlrpc> tag
512     * @access private
513     */
514    function _makeXmlrpcXml($info, $indent)
515    {
516        $ret = $indent . "<xmlrpc";
517        if (isset($info['attribs']['path'])) {
518            $ret .= ' path="' . htmlspecialchars($info['attribs']['path']) . '"';
519        }
520        $ret .= ">\n";
521        $ret .= $this->_makeFunctionsXml($info['function'], "$indent ");
522        $ret .= $indent . "</xmlrpc>\n";
523        return $ret;
524    }
525
526    /**
527     * Generate the <soap> tag
528     * @access private
529     */
530    function _makeSoapXml($info, $indent)
531    {
532        $ret = $indent . "<soap";
533        if (isset($info['attribs']['path'])) {
534            $ret .= ' path="' . htmlspecialchars($info['attribs']['path']) . '"';
535        }
536        $ret .= ">\n";
537        $ret .= $this->_makeFunctionsXml($info['function'], "$indent ");
538        $ret .= $indent . "</soap>\n";
539        return $ret;
540    }
541
542    /**
543     * Generate the <rest> tag
544     * @access private
545     */
546    function _makeRestXml($info, $indent)
547    {
548        $ret = $indent . "<rest>\n";
549        if (!isset($info['baseurl'][0])) {
550            $info['baseurl'] = array($info['baseurl']);
551        }
552        foreach ($info['baseurl'] as $url) {
553            $ret .= "$indent <baseurl type=\"" . $url['attribs']['type'] . "\"";
554            $ret .= ">" . $url['_content'] . "</baseurl>\n";
555        }
556        $ret .= $indent . "</rest>\n";
557        return $ret;
558    }
559
560    /**
561     * Generate the <mirrors> tag
562     * @access private
563     */
564    function _makeMirrorsXml($channelInfo)
565    {
566        $ret = "";
567        if (!isset($channelInfo['servers']['mirror'][0])) {
568            $channelInfo['servers']['mirror'] = array($channelInfo['servers']['mirror']);
569        }
570        foreach ($channelInfo['servers']['mirror'] as $mirror) {
571            $ret .= '  <mirror host="' . $mirror['attribs']['host'] . '"';
572            if (isset($mirror['attribs']['port'])) {
573                $ret .= ' port="' . $mirror['attribs']['port'] . '"';
574            }
575            if (isset($mirror['attribs']['ssl'])) {
576                $ret .= ' ssl="' . $mirror['attribs']['ssl'] . '"';
577            }
578            $ret .= ">\n";
579            if (isset($mirror['xmlrpc']) || isset($mirror['soap'])) {
580                if (isset($mirror['xmlrpc'])) {
581                    $ret .= $this->_makeXmlrpcXml($mirror['xmlrpc'], '   ');
582                }
583                if (isset($mirror['rest'])) {
584                    $ret .= $this->_makeRestXml($mirror['rest'], '   ');
585                }
586                if (isset($mirror['soap'])) {
587                    $ret .= $this->_makeSoapXml($mirror['soap'], '   ');
588                }
589                $ret .= "  </mirror>\n";
590            } else {
591                $ret .= "/>\n";
592            }
593        }
594        return $ret;
595    }
596
597    /**
598     * Generate the <functions> tag
599     * @access private
600     */
601    function _makeFunctionsXml($functions, $indent, $rest = false)
602    {
603        $ret = '';
604        if (!isset($functions[0])) {
605            $functions = array($functions);
606        }
607        foreach ($functions as $function) {
608            $ret .= "$indent<function version=\"" . $function['attribs']['version'] . "\"";
609            if ($rest) {
610                $ret .= ' uri="' . $function['attribs']['uri'] . '"';
611            }
612            $ret .= ">" . $function['_content'] . "</function>\n";
613        }
614        return $ret;
615    }
616
617    /**
618     * Validation error.  Also marks the object contents as invalid
619     * @param error code
620     * @param array error information
621     * @access private
622     */
623    function _validateError($code, $params = array())
624    {
625        $this->_stack->push($code, 'error', $params);
626        $this->_isValid = false;
627    }
628
629    /**
630     * Validation warning.  Does not mark the object contents invalid.
631     * @param error code
632     * @param array error information
633     * @access private
634     */
635    function _validateWarning($code, $params = array())
636    {
637        $this->_stack->push($code, 'warning', $params);
638    }
639
640    /**
641     * Validate parsed file.
642     *
643     * @access public
644     * @return boolean
645     */
646    function validate()
647    {
648        $this->_isValid = true;
649        $info = $this->_channelInfo;
650        if (empty($info['name'])) {
651            $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_NAME);
652        } elseif (!$this->validChannelServer($info['name'])) {
653            if ($info['name'] != '__uri') {
654                $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME, array('tag' => 'name',
655                    'name' => $info['name']));
656            }
657        }
658        if (empty($info['summary'])) {
659            $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_SUMMARY);
660        } elseif (strpos(trim($info['summary']), "\n") !== false) {
661            $this->_validateWarning(PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY,
662                array('summary' => $info['summary']));
663        }
664        if (isset($info['suggestedalias'])) {
665            if (!$this->validChannelServer($info['suggestedalias'])) {
666                $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME,
667                    array('tag' => 'suggestedalias', 'name' =>$info['suggestedalias']));
668            }
669        }
670        if (isset($info['localalias'])) {
671            if (!$this->validChannelServer($info['localalias'])) {
672                $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME,
673                    array('tag' => 'localalias', 'name' =>$info['localalias']));
674            }
675        }
676        if (isset($info['validatepackage'])) {
677            if (!isset($info['validatepackage']['_content'])) {
678                $this->_validateError(PEAR_CHANNELFILE_ERROR_NOVALIDATE_NAME);
679            }
680            if (!isset($info['validatepackage']['attribs']['version'])) {
681                $this->_validateError(PEAR_CHANNELFILE_ERROR_NOVALIDATE_VERSION,
682                    array('package' => @$info['validatepackage']['_content']));
683            }
684        }
685        if (isset($info['servers']['primary']['attribs']['port']) &&
686              !is_numeric($info['servers']['primary']['attribs']['port'])) {
687            $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_PORT,
688                array('port' => $info['servers']['primary']['attribs']['port']));
689        }
690        if (isset($info['servers']['primary']['attribs']['ssl']) &&
691              $info['servers']['primary']['attribs']['ssl'] != 'yes') {
692            $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_SSL,
693                array('ssl' => $info['servers']['primary']['attribs']['ssl'],
694                    'server' => $info['name']));
695        }
696
697        if (isset($info['servers']['primary']['xmlrpc']) &&
698              isset($info['servers']['primary']['xmlrpc']['function'])) {
699            $this->_validateFunctions('xmlrpc', $info['servers']['primary']['xmlrpc']['function']);
700        }
701        if (isset($info['servers']['primary']['soap']) &&
702              isset($info['servers']['primary']['soap']['function'])) {
703            $this->_validateFunctions('soap', $info['servers']['primary']['soap']['function']);
704        }
705        if (isset($info['servers']['primary']['rest']) &&
706              isset($info['servers']['primary']['rest']['baseurl'])) {
707            $this->_validateFunctions('rest', $info['servers']['primary']['rest']['baseurl']);
708        }
709        if (isset($info['servers']['mirror'])) {
710            if ($this->_channelInfo['name'] == '__uri') {
711                $this->_validateError(PEAR_CHANNELFILE_URI_CANT_MIRROR);
712            }
713            if (!isset($info['servers']['mirror'][0])) {
714                $info['servers']['mirror'] = array($info['servers']['mirror']);
715            }
716            $i = 0;
717            foreach ($info['servers']['mirror'] as $mirror) {
718                if (!isset($mirror['attribs']['host'])) {
719                    $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_HOST,
720                      array('type' => 'mirror'));
721                } elseif (!$this->validChannelServer($mirror['attribs']['host'])) {
722                    $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_HOST,
723                        array('server' => $mirror['attribs']['host'], 'type' => 'mirror'));
724                }
725                if (isset($mirror['attribs']['ssl']) && $mirror['attribs']['ssl'] != 'yes') {
726                    $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_SSL,
727                        array('ssl' => $info['ssl'], 'server' => $mirror['attribs']['host']));
728                }
729                if (isset($mirror['xmlrpc'])) {
730                    $this->_validateFunctions('xmlrpc',
731                        $mirror['xmlrpc']['function'], $mirror['attribs']['host']);
732                }
733                if (isset($mirror['soap'])) {
734                    $this->_validateFunctions('soap', $mirror['soap']['function'],
735                        $mirror['attribs']['host']);
736                }
737                if (isset($mirror['rest'])) {
738                    $this->_validateFunctions('rest', $mirror['rest']['baseurl'],
739                        $mirror['attribs']['host']);
740                }
741            }
742        }
743        return $this->_isValid;
744    }
745
746    /**
747     * @param string xmlrpc or soap - protocol name this function applies to
748     * @param array the functions
749     * @param string the name of the parent element (mirror name, for instance)
750     */
751    function _validateFunctions($protocol, $functions, $parent = '')
752    {
753        if (!isset($functions[0])) {
754            $functions = array($functions);
755        }
756        foreach ($functions as $function) {
757            if (!isset($function['_content']) || empty($function['_content'])) {
758                $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_FUNCTIONNAME,
759                    array('parent' => $parent, 'protocol' => $protocol));
760            }
761            if ($protocol == 'rest') {
762                if (!isset($function['attribs']['type']) ||
763                      empty($function['attribs']['type'])) {
764                    $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_BASEURLTYPE,
765                        array('parent' => $parent, 'protocol' => $protocol));
766                }
767            } else {
768                if (!isset($function['attribs']['version']) ||
769                      empty($function['attribs']['version'])) {
770                    $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_FUNCTIONVERSION,
771                        array('parent' => $parent, 'protocol' => $protocol));
772                }
773            }
774        }
775    }
776
777    /**
778     * Test whether a string contains a valid channel server.
779     * @param string $ver the package version to test
780     * @return bool
781     */
782    function validChannelServer($server)
783    {
784        if ($server == '__uri') {
785            return true;
786        }
787        return (bool) preg_match(PEAR_CHANNELS_SERVER_PREG, $server);
788    }
789
790    /**
791     * @return string|false
792     */
793    function getName()
794    {
795        if (isset($this->_channelInfo['name'])) {
796            return $this->_channelInfo['name'];
797        } else {
798            return false;
799        }
800    }
801
802    /**
803     * @return string|false
804     */
805    function getServer()
806    {
807        if (isset($this->_channelInfo['name'])) {
808            return $this->_channelInfo['name'];
809        } else {
810            return false;
811        }
812    }
813
814    /**
815     * @return int|80 port number to connect to
816     */
817    function getPort($mirror = false)
818    {
819        if ($mirror) {
820            if ($mir = $this->getMirror($mirror)) {
821                if (isset($mir['attribs']['port'])) {
822                    return $mir['attribs']['port'];
823                } else {
824                    if ($this->getSSL($mirror)) {
825                        return 443;
826                    }
827                    return 80;
828                }
829            }
830            return false;
831        }
832        if (isset($this->_channelInfo['servers']['primary']['attribs']['port'])) {
833            return $this->_channelInfo['servers']['primary']['attribs']['port'];
834        }
835        if ($this->getSSL()) {
836            return 443;
837        }
838        return 80;
839    }
840
841    /**
842     * @return bool Determines whether secure sockets layer (SSL) is used to connect to this channel
843     */
844    function getSSL($mirror = false)
845    {
846        if ($mirror) {
847            if ($mir = $this->getMirror($mirror)) {
848                if (isset($mir['attribs']['ssl'])) {
849                    return true;
850                } else {
851                    return false;
852                }
853            }
854            return false;
855        }
856        if (isset($this->_channelInfo['servers']['primary']['attribs']['ssl'])) {
857            return true;
858        }
859        return false;
860    }
861
862    /**
863     * @return string|false
864     */
865    function getSummary()
866    {
867        if (isset($this->_channelInfo['summary'])) {
868            return $this->_channelInfo['summary'];
869        } else {
870            return false;
871        }
872    }
873
874    /**
875     * @param string xmlrpc or soap
876     * @param string|false mirror name or false for primary server
877     */
878    function getPath($protocol, $mirror = false)
879    {
880        if (!in_array($protocol, array('xmlrpc', 'soap'))) {
881            return false;
882        }
883        if ($mirror) {
884            if (!($mir = $this->getMirror($mirror))) {
885                return false;
886            }
887            if (isset($mir[$protocol]['attribs']['path'])) {
888                return $mir[$protocol]['attribs']['path'];
889            } else {
890                return $protocol . '.php';
891            }
892        } elseif (isset($this->_channelInfo['servers']['primary'][$protocol]['attribs']['path'])) {
893            return $this->_channelInfo['servers']['primary'][$protocol]['attribs']['path'];
894        }
895        return $protocol . '.php';
896    }
897
898    /**
899     * @param string protocol type (xmlrpc, soap)
900     * @param string Mirror name
901     * @return array|false
902     */
903    function getFunctions($protocol, $mirror = false)
904    {
905        if ($this->getName() == '__uri') {
906            return false;
907        }
908        if ($protocol == 'rest') {
909            $function = 'baseurl';
910        } else {
911            $function = 'function';
912        }
913        if ($mirror) {
914            if ($mir = $this->getMirror($mirror)) {
915                if (isset($mir[$protocol][$function])) {
916                    return $mir[$protocol][$function];
917                }
918            }
919            return false;
920        }
921        if (isset($this->_channelInfo['servers']['primary'][$protocol][$function])) {
922            return $this->_channelInfo['servers']['primary'][$protocol][$function];
923        } else {
924            return false;
925        }
926    }
927
928    /**
929     * @param string Protocol type
930     * @param string Function name (null to return the
931     *               first protocol of the type requested)
932     * @param string Mirror name, if any
933     * @return array
934     */
935     function getFunction($type, $name = null, $mirror = false)
936     {
937        $protocols = $this->getFunctions($type, $mirror);
938        if (!$protocols) {
939            return false;
940        }
941        foreach ($protocols as $protocol) {
942            if ($name === null) {
943                return $protocol;
944            }
945            if ($protocol['_content'] != $name) {
946                continue;
947            }
948            return $protocol;
949        }
950        return false;
951     }
952
953    /**
954     * @param string protocol type
955     * @param string protocol name
956     * @param string version
957     * @param string mirror name
958     * @return boolean
959     */
960    function supports($type, $name = null, $mirror = false, $version = '1.0')
961    {
962        $protocols = $this->getFunctions($type, $mirror);
963        if (!$protocols) {
964            return false;
965        }
966        foreach ($protocols as $protocol) {
967            if ($protocol['attribs']['version'] != $version) {
968                continue;
969            }
970            if ($name === null) {
971                return true;
972            }
973            if ($protocol['_content'] != $name) {
974                continue;
975            }
976            return true;
977        }
978        return false;
979    }
980
981    /**
982     * Determines whether a channel supports Representational State Transfer (REST) protocols
983     * for retrieving channel information
984     * @param string
985     * @return bool
986     */
987    function supportsREST($mirror = false)
988    {
989        if ($mirror == $this->_channelInfo['name']) {
990            $mirror = false;
991        }
992        if ($mirror) {
993            if ($mir = $this->getMirror($mirror)) {
994                return isset($mir['rest']);
995            }
996            return false;
997        }
998        return isset($this->_channelInfo['servers']['primary']['rest']);
999    }
1000
1001    /**
1002     * Get the URL to access a base resource.
1003     *
1004     * Hyperlinks in the returned xml will be used to retrieve the proper information
1005     * needed.  This allows extreme extensibility and flexibility in implementation
1006     * @param string Resource Type to retrieve
1007     */
1008    function getBaseURL($resourceType, $mirror = false)
1009    {
1010        if ($mirror == $this->_channelInfo['name']) {
1011            $mirror = false;
1012        }
1013        if ($mirror) {
1014            if ($mir = $this->getMirror($mirror)) {
1015                $rest = $mir['rest'];
1016            } else {
1017                return false;
1018            }
1019            $server = $mirror;
1020        } else {
1021            $rest = $this->_channelInfo['servers']['primary']['rest'];
1022            $server = $this->getServer();
1023        }
1024        if (!isset($rest['baseurl'][0])) {
1025            $rest['baseurl'] = array($rest['baseurl']);
1026        }
1027        foreach ($rest['baseurl'] as $baseurl) {
1028            if (strtolower($baseurl['attribs']['type']) == strtolower($resourceType)) {
1029                return $baseurl['_content'];
1030            }
1031        }
1032        return false;
1033    }
1034
1035    /**
1036     * Since REST does not implement RPC, provide this as a logical wrapper around
1037     * resetFunctions for REST
1038     * @param string|false mirror name, if any
1039     */
1040    function resetREST($mirror = false)
1041    {
1042        return $this->resetFunctions('rest', $mirror);
1043    }
1044
1045    /**
1046     * Empty all protocol definitions
1047     * @param string protocol type (xmlrpc, soap)
1048     * @param string|false mirror name, if any
1049     */
1050    function resetFunctions($type, $mirror = false)
1051    {
1052        if ($mirror) {
1053            if (isset($this->_channelInfo['servers']['mirror'])) {
1054                $mirrors = $this->_channelInfo['servers']['mirror'];
1055                if (!isset($mirrors[0])) {
1056                    $mirrors = array($mirrors);
1057                }
1058                foreach ($mirrors as $i => $mir) {
1059                    if ($mir['attribs']['host'] == $mirror) {
1060                        if (isset($this->_channelInfo['servers']['mirror'][$i][$type])) {
1061                            unset($this->_channelInfo['servers']['mirror'][$i][$type]);
1062                        }
1063                        return true;
1064                    }
1065                }
1066                return false;
1067            } else {
1068                return false;
1069            }
1070        } else {
1071            if (isset($this->_channelInfo['servers']['primary'][$type])) {
1072                unset($this->_channelInfo['servers']['primary'][$type]);
1073            }
1074            return true;
1075        }
1076    }
1077
1078    /**
1079     * Set a channel's protocols to the protocols supported by pearweb
1080     */
1081    function setDefaultPEARProtocols($version = '1.0', $mirror = false)
1082    {
1083        switch ($version) {
1084            case '1.0' :
1085                $this->resetFunctions('xmlrpc', $mirror);
1086                $this->resetFunctions('soap', $mirror);
1087                $this->resetREST($mirror);
1088                $this->addFunction('xmlrpc', '1.0', 'logintest', $mirror);
1089                $this->addFunction('xmlrpc', '1.0', 'package.listLatestReleases', $mirror);
1090                $this->addFunction('xmlrpc', '1.0', 'package.listAll', $mirror);
1091                $this->addFunction('xmlrpc', '1.0', 'package.info', $mirror);
1092                $this->addFunction('xmlrpc', '1.0', 'package.getDownloadURL', $mirror);
1093                $this->addFunction('xmlrpc', '1.1', 'package.getDownloadURL', $mirror);
1094                $this->addFunction('xmlrpc', '1.0', 'package.getDepDownloadURL', $mirror);
1095                $this->addFunction('xmlrpc', '1.1', 'package.getDepDownloadURL', $mirror);
1096                $this->addFunction('xmlrpc', '1.0', 'package.search', $mirror);
1097                $this->addFunction('xmlrpc', '1.0', 'channel.listAll', $mirror);
1098                return true;
1099            break;
1100            default :
1101                return false;
1102            break;
1103        }
1104    }
1105
1106    /**
1107     * @return array
1108     */
1109    function getMirrors()
1110    {
1111        if (isset($this->_channelInfo['servers']['mirror'])) {
1112            $mirrors = $this->_channelInfo['servers']['mirror'];
1113            if (!isset($mirrors[0])) {
1114                $mirrors = array($mirrors);
1115            }
1116            return $mirrors;
1117        } else {
1118            return array();
1119        }
1120    }
1121
1122    /**
1123     * Get the unserialized XML representing a mirror
1124     * @return array|false
1125     */
1126    function getMirror($server)
1127    {
1128        foreach ($this->getMirrors() as $mirror) {
1129            if ($mirror['attribs']['host'] == $server) {
1130                return $mirror;
1131            }
1132        }
1133        return false;
1134    }
1135
1136    /**
1137     * @param string
1138     * @return string|false
1139     * @error PEAR_CHANNELFILE_ERROR_NO_NAME
1140     * @error PEAR_CHANNELFILE_ERROR_INVALID_NAME
1141     */
1142    function setName($name)
1143    {
1144        return $this->setServer($name);
1145    }
1146
1147    /**
1148     * Set the socket number (port) that is used to connect to this channel
1149     * @param integer
1150     * @param string|false name of the mirror server, or false for the primary
1151     */
1152    function setPort($port, $mirror = false)
1153    {
1154        if ($mirror) {
1155            if (!isset($this->_channelInfo['servers']['mirror'])) {
1156                $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
1157                    array('mirror' => $mirror));
1158                return false;
1159            }
1160            $setmirror = false;
1161            if (isset($this->_channelInfo['servers']['mirror'][0])) {
1162                foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) {
1163                    if ($mirror == $mir['attribs']['host']) {
1164                        $this->_channelInfo['servers']['mirror'][$i]['attribs']['port'] = $port;
1165                        return true;
1166                    }
1167                }
1168                return false;
1169            } elseif ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) {
1170                $this->_channelInfo['servers']['mirror']['attribs']['port'] = $port;
1171                $this->_isValid = false;
1172                return true;
1173            }
1174        }
1175        $this->_channelInfo['servers']['primary']['attribs']['port'] = $port;
1176        $this->_isValid = false;
1177        return true;
1178    }
1179
1180    /**
1181     * Set the socket number (port) that is used to connect to this channel
1182     * @param bool Determines whether to turn on SSL support or turn it off
1183     * @param string|false name of the mirror server, or false for the primary
1184     */
1185    function setSSL($ssl = true, $mirror = false)
1186    {
1187        if ($mirror) {
1188            if (!isset($this->_channelInfo['servers']['mirror'])) {
1189                $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
1190                    array('mirror' => $mirror));
1191                return false;
1192            }
1193            $setmirror = false;
1194            if (isset($this->_channelInfo['servers']['mirror'][0])) {
1195                foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) {
1196                    if ($mirror == $mir['attribs']['host']) {
1197                        if (!$ssl) {
1198                            if (isset($this->_channelInfo['servers']['mirror'][$i]
1199                                  ['attribs']['ssl'])) {
1200                                unset($this->_channelInfo['servers']['mirror'][$i]['attribs']['ssl']);
1201                            }
1202                        } else {
1203                            $this->_channelInfo['servers']['mirror'][$i]['attribs']['ssl'] = 'yes';
1204                        }
1205                        return true;
1206                    }
1207                }
1208                return false;
1209            } elseif ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) {
1210                if (!$ssl) {
1211                    if (isset($this->_channelInfo['servers']['mirror']['attribs']['ssl'])) {
1212                        unset($this->_channelInfo['servers']['mirror']['attribs']['ssl']);
1213                    }
1214                } else {
1215                    $this->_channelInfo['servers']['mirror']['attribs']['ssl'] = 'yes';
1216                }
1217                $this->_isValid = false;
1218                return true;
1219            }
1220        }
1221        if ($ssl) {
1222            $this->_channelInfo['servers']['primary']['attribs']['ssl'] = 'yes';
1223        } else {
1224            if (isset($this->_channelInfo['servers']['primary']['attribs']['ssl'])) {
1225                unset($this->_channelInfo['servers']['primary']['attribs']['ssl']);
1226            }
1227        }
1228        $this->_isValid = false;
1229        return true;
1230    }
1231
1232    /**
1233     * Set the socket number (port) that is used to connect to this channel
1234     * @param integer
1235     * @param string|false name of the mirror server, or false for the primary
1236     */
1237    function setPath($protocol, $path, $mirror = false)
1238    {
1239        if (!in_array($protocol, array('xmlrpc', 'soap'))) {
1240            return false;
1241        }
1242        if ($mirror) {
1243            if (!isset($this->_channelInfo['servers']['mirror'])) {
1244                $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
1245                    array('mirror' => $mirror));
1246                return false;
1247            }
1248            $setmirror = false;
1249            if (isset($this->_channelInfo['servers']['mirror'][0])) {
1250                foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) {
1251                    if ($mirror == $mir['attribs']['host']) {
1252                        $this->_channelInfo['servers']['mirror'][$i][$protocol]['attribs']['path'] =
1253                            $path;
1254                        return true;
1255                    }
1256                }
1257                $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
1258                    array('mirror' => $mirror));
1259                return false;
1260            } elseif ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) {
1261                $this->_channelInfo['servers']['mirror'][$protocol]['attribs']['path'] = $path;
1262                $this->_isValid = false;
1263                return true;
1264            }
1265        }
1266        $this->_channelInfo['servers']['primary'][$protocol]['attribs']['path'] = $path;
1267        $this->_isValid = false;
1268        return true;
1269    }
1270
1271    /**
1272     * @param string
1273     * @return string|false
1274     * @error PEAR_CHANNELFILE_ERROR_NO_SERVER
1275     * @error PEAR_CHANNELFILE_ERROR_INVALID_SERVER
1276     */
1277    function setServer($server, $mirror = false)
1278    {
1279        if (empty($server)) {
1280            $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_SERVER);
1281            return false;
1282        } elseif (!$this->validChannelServer($server)) {
1283            $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME,
1284                array('tag' => 'name', 'name' => $server));
1285            return false;
1286        }
1287        if ($mirror) {
1288            $found = false;
1289            foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) {
1290                if ($mirror == $mir['attribs']['host']) {
1291                    $found = true;
1292                    break;
1293                }
1294            }
1295            if (!$found) {
1296                $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
1297                    array('mirror' => $mirror));
1298                return false;
1299            }
1300            $this->_channelInfo['mirror'][$i]['attribs']['host'] = $server;
1301            return true;
1302        }
1303        $this->_channelInfo['name'] = $server;
1304        return true;
1305    }
1306
1307    /**
1308     * @param string
1309     * @return boolean success
1310     * @error PEAR_CHANNELFILE_ERROR_NO_SUMMARY
1311     * @warning PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY
1312     */
1313    function setSummary($summary)
1314    {
1315        if (empty($summary)) {
1316            $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_SUMMARY);
1317            return false;
1318        } elseif (strpos(trim($summary), "\n") !== false) {
1319            $this->_validateWarning(PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY,
1320                array('summary' => $summary));
1321        }
1322        $this->_channelInfo['summary'] = $summary;
1323        return true;
1324    }
1325
1326    /**
1327     * @param string
1328     * @param boolean determines whether the alias is in channel.xml or local
1329     * @return boolean success
1330     */
1331    function setAlias($alias, $local = false)
1332    {
1333        if (!$this->validChannelServer($alias)) {
1334            $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME,
1335                array('tag' => 'suggestedalias', 'name' => $alias));
1336            return false;
1337        }
1338        if ($local) {
1339            $this->_channelInfo['localalias'] = $alias;
1340        } else {
1341            $this->_channelInfo['suggestedalias'] = $alias;
1342        }
1343        return true;
1344    }
1345
1346    /**
1347     * @return string
1348     */
1349    function getAlias()
1350    {
1351        if (isset($this->_channelInfo['localalias'])) {
1352            return $this->_channelInfo['localalias'];
1353        }
1354        if (isset($this->_channelInfo['suggestedalias'])) {
1355            return $this->_channelInfo['suggestedalias'];
1356        }
1357        if (isset($this->_channelInfo['name'])) {
1358            return $this->_channelInfo['name'];
1359        }
1360    }
1361
1362    /**
1363     * Set the package validation object if it differs from PEAR's default
1364     * The class must be includeable via changing _ in the classname to path separator,
1365     * but no checking of this is made.
1366     * @param string|false pass in false to reset to the default packagename regex
1367     * @return boolean success
1368     */
1369    function setValidationPackage($validateclass, $version)
1370    {
1371        if (empty($validateclass)) {
1372            unset($this->_channelInfo['validatepackage']);
1373        }
1374        $this->_channelInfo['validatepackage'] = array('_content' => $validateclass);
1375        $this->_channelInfo['validatepackage']['attribs'] = array('version' => $version);
1376    }
1377
1378    /**
1379     * Add a protocol to the provides section
1380     * @param string protocol type
1381     * @param string protocol version
1382     * @param string protocol name, if any
1383     * @param string mirror name, if this is a mirror's protocol
1384     * @return bool
1385     */
1386    function addFunction($type, $version, $name = '', $mirror = false)
1387    {
1388        if ($mirror) {
1389            return $this->addMirrorFunction($mirror, $type, $version, $name);
1390        }
1391        $set = array('attribs' => array('version' => $version), '_content' => $name);
1392        if (!isset($this->_channelInfo['servers']['primary'][$type]['function'])) {
1393            $this->_channelInfo['servers']['primary'][$type]['function'] = $set;
1394            $this->_isValid = false;
1395            return true;
1396        } elseif (!isset($this->_channelInfo['servers']['primary'][$type]['function'][0])) {
1397            $this->_channelInfo['servers']['primary'][$type]['function'] = array(
1398                $this->_channelInfo['servers']['primary'][$type]['function']);
1399        }
1400        $this->_channelInfo['servers']['primary'][$type]['function'][] = $set;
1401        return true;
1402    }
1403    /**
1404     * Add a protocol to a mirror's provides section
1405     * @param string mirror name (server)
1406     * @param string protocol type
1407     * @param string protocol version
1408     * @param string protocol name, if any
1409     */
1410    function addMirrorFunction($mirror, $type, $version, $name = '')
1411    {
1412        $found = false;
1413        if (!isset($this->_channelInfo['servers']['mirror'])) {
1414            $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
1415                array('mirror' => $mirror));
1416            return false;
1417        }
1418        $setmirror = false;
1419        if (isset($this->_channelInfo['servers']['mirror'][0])) {
1420            foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) {
1421                if ($mirror == $mir['attribs']['host']) {
1422                    $setmirror = &$this->_channelInfo['servers']['mirror'][$i];
1423                    break;
1424                }
1425            }
1426        } else {
1427            if ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) {
1428                $setmirror = &$this->_channelInfo['servers']['mirror'];
1429            }
1430        }
1431        if (!$setmirror) {
1432            $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
1433                array('mirror' => $mirror));
1434            return false;
1435        }
1436        $set = array('attribs' => array('version' => $version), '_content' => $name);
1437        if (!isset($setmirror[$type]['function'])) {
1438            $setmirror[$type]['function'] = $set;
1439            $this->_isValid = false;
1440            return true;
1441        } elseif (!isset($setmirror[$type]['function'][0])) {
1442            $setmirror[$type]['function'] = array($setmirror[$type]['function']);
1443        }
1444        $setmirror[$type]['function'][] = $set;
1445        $this->_isValid = false;
1446        return true;
1447    }
1448
1449    /**
1450     * @param string Resource Type this url links to
1451     * @param string URL
1452     * @param string|false mirror name, if this is not a primary server REST base URL
1453     */
1454    function setBaseURL($resourceType, $url, $mirror = false)
1455    {
1456        if ($mirror) {
1457            $found = false;
1458            if (!isset($this->_channelInfo['servers']['mirror'])) {
1459                $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
1460                    array('mirror' => $mirror));
1461                return false;
1462            }
1463            $setmirror = false;
1464            if (isset($this->_channelInfo['servers']['mirror'][0])) {
1465                foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) {
1466                    if ($mirror == $mir['attribs']['host']) {
1467                        $setmirror = &$this->_channelInfo['servers']['mirror'][$i];
1468                        break;
1469                    }
1470                }
1471            } else {
1472                if ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) {
1473                    $setmirror = &$this->_channelInfo['servers']['mirror'];
1474                }
1475            }
1476        } else {
1477            $setmirror = &$this->_channelInfo['servers']['primary'];
1478        }
1479        $set = array('attribs' => array('type' => $resourceType), '_content' => $url);
1480        if (!isset($setmirror['rest']['baseurl'])) {
1481            $setmirror['rest']['baseurl'] = $set;
1482            $this->_isValid = false;
1483            return true;
1484        } elseif (!isset($setmirror['rest']['baseurl'][0])) {
1485            $setmirror['rest']['baseurl'] = array($setmirror['rest']['baseurl']);
1486        }
1487        foreach ($setmirror['rest']['baseurl'] as $i => $url) {
1488            if ($url['attribs']['type'] == $resourceType) {
1489                $this->_isValid = false;
1490                $setmirror['rest']['baseurl'][$i] = $set;
1491                return true;
1492            }
1493        }
1494        $setmirror['rest']['baseurl'][] = $set;
1495        $this->_isValid = false;
1496        return true;
1497    }
1498
1499    /**
1500     * @param string mirror server
1501     * @param int mirror http port
1502     * @return boolean
1503     */
1504    function addMirror($server, $port = null)
1505    {
1506        if ($this->_channelInfo['name'] == '__uri') {
1507            return false; // the __uri channel cannot have mirrors by definition
1508        }
1509        $set = array('attribs' => array('host' => $server));
1510        if (is_numeric($port)) {
1511            $set['attribs']['port'] = $port;
1512        }
1513        if (!isset($this->_channelInfo['servers']['mirror'])) {
1514            $this->_channelInfo['servers']['mirror'] = $set;
1515            return true;
1516        } else {
1517            if (!isset($this->_channelInfo['servers']['mirror'][0])) {
1518                $this->_channelInfo['servers']['mirror'] =
1519                    array($this->_channelInfo['servers']['mirror']);
1520            }
1521        }
1522        $this->_channelInfo['servers']['mirror'][] = $set;
1523        return true;
1524    }
1525
1526    /**
1527     * Retrieve the name of the validation package for this channel
1528     * @return string|false
1529     */
1530    function getValidationPackage()
1531    {
1532        if (!$this->_isValid && !$this->validate()) {
1533            return false;
1534        }
1535        if (!isset($this->_channelInfo['validatepackage'])) {
1536            return array('attribs' => array('version' => 'default'),
1537                '_content' => 'PEAR_Validate');
1538        }
1539        return $this->_channelInfo['validatepackage'];
1540    }
1541
1542    /**
1543     * Retrieve the object that can be used for custom validation
1544     * @param string|false the name of the package to validate.  If the package is
1545     *                     the channel validation package, PEAR_Validate is returned
1546     * @return PEAR_Validate|false false is returned if the validation package
1547     *         cannot be located
1548     */
1549    function &getValidationObject($package = false)
1550    {
1551        if (!class_exists('PEAR_Validate')) {
1552            require_once 'PEAR/Validate.php';
1553        }
1554        if (!$this->_isValid) {
1555            if (!$this->validate()) {
1556                $a = false;
1557                return $a;
1558            }
1559        }
1560        if (isset($this->_channelInfo['validatepackage'])) {
1561            if ($package == $this->_channelInfo['validatepackage']) {
1562                // channel validation packages are always validated by PEAR_Validate
1563                $val = &new PEAR_Validate;
1564                return $val;
1565            }
1566            if (!class_exists(str_replace('.', '_',
1567                  $this->_channelInfo['validatepackage']['_content']))) {
1568                if ($this->isIncludeable(str_replace('_', '/',
1569                      $this->_channelInfo['validatepackage']['_content']) . '.php')) {
1570                    include_once str_replace('_', '/',
1571                        $this->_channelInfo['validatepackage']['_content']) . '.php';
1572                    $vclass = str_replace('.', '_',
1573                        $this->_channelInfo['validatepackage']['_content']);
1574                    $val = &new $vclass;
1575                } else {
1576                    $a = false;
1577                    return $a;
1578                }
1579            } else {
1580                $vclass = str_replace('.', '_',
1581                    $this->_channelInfo['validatepackage']['_content']);
1582                $val = &new $vclass;
1583            }
1584        } else {
1585            $val = &new PEAR_Validate;
1586        }
1587        return $val;
1588    }
1589
1590    function isIncludeable($path)
1591    {
1592        $possibilities = explode(PATH_SEPARATOR, ini_get('include_path'));
1593        foreach ($possibilities as $dir) {
1594            if (file_exists($dir . DIRECTORY_SEPARATOR . $path)
1595                  && is_readable($dir . DIRECTORY_SEPARATOR . $path)) {
1596                return true;
1597            }
1598        }
1599        return false;
1600    }
1601
1602    /**
1603     * This function is used by the channel updater and retrieves a value set by
1604     * the registry, or the current time if it has not been set
1605     * @return string
1606     */
1607    function lastModified()
1608    {
1609        if (isset($this->_channelInfo['_lastmodified'])) {
1610            return $this->_channelInfo['_lastmodified'];
1611        }
1612        return time();
1613    }
1614}
1615?>
1616