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