Updated PEAR and PEAR packages.
[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  * @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  */
19 require_once 'PEAR/ErrorStack.php';
20 require_once 'PEAR/XMLParser.php';
21 require_once 'PEAR/Common.php';
22
23 /**
24  * Error code if the channel.xml <channel> tag does not contain a valid version
25  */
26 define('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  */
31 define('PEAR_CHANNELFILE_ERROR_INVALID_VERSION', 2);
32
33 /**
34  * Error code if parsing is attempted with no xml extension
35  */
36 define('PEAR_CHANNELFILE_ERROR_NO_XML_EXT', 3);
37
38 /**
39  * Error code if creating the xml parser resource fails
40  */
41 define('PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER', 4);
42
43 /**
44  * Error code used for all sax xml parsing errors
45  */
46 define('PEAR_CHANNELFILE_ERROR_PARSER_ERROR', 5);
47
48 /**#@+
49  * Validation errors
50  */
51 /**
52  * Error code when channel name is missing
53  */
54 define('PEAR_CHANNELFILE_ERROR_NO_NAME', 6);
55 /**
56  * Error code when channel name is invalid
57  */
58 define('PEAR_CHANNELFILE_ERROR_INVALID_NAME', 7);
59 /**
60  * Error code when channel summary is missing
61  */
62 define('PEAR_CHANNELFILE_ERROR_NO_SUMMARY', 8);
63 /**
64  * Error code when channel summary is multi-line
65  */
66 define('PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY', 9);
67 /**
68  * Error code when channel server is missing for protocol
69  */
70 define('PEAR_CHANNELFILE_ERROR_NO_HOST', 10);
71 /**
72  * Error code when channel server is invalid for protocol
73  */
74 define('PEAR_CHANNELFILE_ERROR_INVALID_HOST', 11);
75 /**
76  * Error code when a mirror name is invalid
77  */
78 define('PEAR_CHANNELFILE_ERROR_INVALID_MIRROR', 21);
79 /**
80  * Error code when a mirror type is invalid
81  */
82 define('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  */
86 define('PEAR_CHANNELFILE_ERROR_INVALID', 23);
87 /**
88  * Error code when an empty package name validate regex is passed in
89  */
90 define('PEAR_CHANNELFILE_ERROR_EMPTY_REGEX', 24);
91 /**
92  * Error code when a <function> tag has no version
93  */
94 define('PEAR_CHANNELFILE_ERROR_NO_FUNCTIONVERSION', 25);
95 /**
96  * Error code when a <function> tag has no name
97  */
98 define('PEAR_CHANNELFILE_ERROR_NO_FUNCTIONNAME', 26);
99 /**
100  * Error code when a <validatepackage> tag has no name
101  */
102 define('PEAR_CHANNELFILE_ERROR_NOVALIDATE_NAME', 27);
103 /**
104  * Error code when a <validatepackage> tag has no version attribute
105  */
106 define('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  */
111 define('PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND', 32);
112 /**
113  * Error code when a server port is not numeric
114  */
115 define('PEAR_CHANNELFILE_ERROR_INVALID_PORT', 33);
116 /**
117  * Error code when <static> contains no version attribute
118  */
119 define('PEAR_CHANNELFILE_ERROR_NO_STATICVERSION', 34);
120 /**
121  * Error code when <baseurl> contains no type attribute in a <rest> protocol definition
122  */
123 define('PEAR_CHANNELFILE_ERROR_NOBASEURLTYPE', 35);
124 /**
125  * Error code when a mirror is defined and the channel.xml represents the __uri pseudo-channel
126  */
127 define('PEAR_CHANNELFILE_URI_CANT_MIRROR', 36);
128 /**
129  * Error code when ssl attribute is present and is not "yes"
130  */
131 define('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: 1.10.1
149  * @link       http://pear.php.net/package/PEAR
150  * @since      Class available since Release 1.4.0a1
151  */
152 class 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 }