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