3  * PEAR_ChannelFile, the channel handling class
 
   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
 
  18  * Needed for error handling
 
  20 require_once 'PEAR/ErrorStack.php';
 
  21 require_once 'PEAR/XMLParser.php';
 
  22 require_once 'PEAR/Common.php';
 
  25  * Error code if the channel.xml <channel> tag does not contain a valid version
 
  27 define('PEAR_CHANNELFILE_ERROR_NO_VERSION', 1);
 
  29  * Error code if the channel.xml <channel> tag version is not supported (version 1.0 is the only supported version,
 
  32 define('PEAR_CHANNELFILE_ERROR_INVALID_VERSION', 2);
 
  35  * Error code if parsing is attempted with no xml extension
 
  37 define('PEAR_CHANNELFILE_ERROR_NO_XML_EXT', 3);
 
  40  * Error code if creating the xml parser resource fails
 
  42 define('PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER', 4);
 
  45  * Error code used for all sax xml parsing errors
 
  47 define('PEAR_CHANNELFILE_ERROR_PARSER_ERROR', 5);
 
  53  * Error code when channel name is missing
 
  55 define('PEAR_CHANNELFILE_ERROR_NO_NAME', 6);
 
  57  * Error code when channel name is invalid
 
  59 define('PEAR_CHANNELFILE_ERROR_INVALID_NAME', 7);
 
  61  * Error code when channel summary is missing
 
  63 define('PEAR_CHANNELFILE_ERROR_NO_SUMMARY', 8);
 
  65  * Error code when channel summary is multi-line
 
  67 define('PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY', 9);
 
  69  * Error code when channel server is missing for protocol
 
  71 define('PEAR_CHANNELFILE_ERROR_NO_HOST', 10);
 
  73  * Error code when channel server is invalid for protocol
 
  75 define('PEAR_CHANNELFILE_ERROR_INVALID_HOST', 11);
 
  77  * Error code when a mirror name is invalid
 
  79 define('PEAR_CHANNELFILE_ERROR_INVALID_MIRROR', 21);
 
  81  * Error code when a mirror type is invalid
 
  83 define('PEAR_CHANNELFILE_ERROR_INVALID_MIRRORTYPE', 22);
 
  85  * Error code when an attempt is made to generate xml, but the parsed content is invalid
 
  87 define('PEAR_CHANNELFILE_ERROR_INVALID', 23);
 
  89  * Error code when an empty package name validate regex is passed in
 
  91 define('PEAR_CHANNELFILE_ERROR_EMPTY_REGEX', 24);
 
  93  * Error code when a <function> tag has no version
 
  95 define('PEAR_CHANNELFILE_ERROR_NO_FUNCTIONVERSION', 25);
 
  97  * Error code when a <function> tag has no name
 
  99 define('PEAR_CHANNELFILE_ERROR_NO_FUNCTIONNAME', 26);
 
 101  * Error code when a <validatepackage> tag has no name
 
 103 define('PEAR_CHANNELFILE_ERROR_NOVALIDATE_NAME', 27);
 
 105  * Error code when a <validatepackage> tag has no version attribute
 
 107 define('PEAR_CHANNELFILE_ERROR_NOVALIDATE_VERSION', 28);
 
 109  * Error code when a mirror does not exist but is called for in one of the set*
 
 112 define('PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND', 32);
 
 114  * Error code when a server port is not numeric
 
 116 define('PEAR_CHANNELFILE_ERROR_INVALID_PORT', 33);
 
 118  * Error code when <static> contains no version attribute
 
 120 define('PEAR_CHANNELFILE_ERROR_NO_STATICVERSION', 34);
 
 122  * Error code when <baseurl> contains no type attribute in a <rest> protocol definition
 
 124 define('PEAR_CHANNELFILE_ERROR_NOBASEURLTYPE', 35);
 
 126  * Error code when a mirror is defined and the channel.xml represents the __uri pseudo-channel
 
 128 define('PEAR_CHANNELFILE_URI_CANT_MIRROR', 36);
 
 130  * Error code when ssl attribute is present and is not "yes"
 
 132 define('PEAR_CHANNELFILE_ERROR_INVALID_SSL', 37);
 
 136  * Mirror types allowed.  Currently only internet servers are recognized.
 
 138 $GLOBALS['_PEAR_CHANNELS_MIRROR_TYPES'] =  array('server');
 
 142  * The Channel handling class
 
 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
 
 153 class PEAR_ChannelFile
 
 157      * @var PEAR_ErrorStack
 
 163      * Supported channel.xml versions, for parsing
 
 167     var $_supportedVersions = array('1.0');
 
 170      * Parsed channel information
 
 177      * index into the subchannels array, used for parsing xml
 
 181     var $_subchannelIndex;
 
 184      * index into the mirrors array, used for parsing xml
 
 191      * Flag used to determine the validity of parsed content
 
 195     var $_isValid = false;
 
 197     function PEAR_ChannelFile()
 
 199         $this->_stack = &new PEAR_ErrorStack('PEAR_ChannelFile');
 
 200         $this->_stack->setErrorMessageTemplate($this->_getErrorMessage());
 
 201         $this->_isValid = false;
 
 208     function _getErrorMessage()
 
 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 =>
 
 218                 PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER =>
 
 219                     'Unable to create XML parser',
 
 220                 PEAR_CHANNELFILE_ERROR_PARSER_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',
 
 266      * @param string contents of package.xml file
 
 267      * @return bool success of parsing
 
 269     function fromXmlString($data)
 
 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]));
 
 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()));
 
 284                     $this->_stack->push(PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER, 'error');
 
 288             $this->_channelInfo = $parser->getData();
 
 291             $this->_stack->push(PEAR_CHANNELFILE_ERROR_NO_VERSION, 'error', array('xml' => $data));
 
 301         if (!$this->_isValid && !$this->validate()) {
 
 304         return $this->_channelInfo;
 
 310      * @return PEAR_ChannelFile|false false if invalid
 
 312     function &fromArray($data, $compatibility = false, $stackClass = 'PEAR_ErrorStack')
 
 314         $a = new PEAR_ChannelFile($compatibility, $stackClass);
 
 315         $a->_fromArray($data);
 
 316         if (!$a->validate()) {
 
 324      * Unlike {@link fromArray()} this does not do any validation
 
 327      * @return PEAR_ChannelFile
 
 329     function &fromArrayWithErrors($data, $compatibility = false,
 
 330                                   $stackClass = 'PEAR_ErrorStack')
 
 332         $a = new PEAR_ChannelFile($compatibility, $stackClass);
 
 333         $a->_fromArray($data);
 
 341     function _fromArray($data)
 
 343         $this->_channelInfo = $data;
 
 347      * Wrapper to {@link PEAR_ErrorStack::getErrors()}
 
 348      * @param boolean determines whether to purge the error stack after retrieving
 
 351     function getErrors($purge = false)
 
 353         return $this->_stack->getErrors($purge);
 
 357      * Unindent given string (?)
 
 359      * @param string $str The string that has to be unindented.
 
 363     function _unIndent($str)
 
 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);
 
 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";
 
 381      * Parse a channel.xml file.  Expects the name of
 
 382      * a channel xml file as input.
 
 384      * @param string  $descfile  name of channel xml file
 
 385      * @return bool success of parsing
 
 387     function fromXmlFile($descfile)
 
 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");
 
 395         // read the whole thing so we only get one cdata callback
 
 396         // for each block of cdata
 
 398         $data = file_get_contents($descfile);
 
 399         return $this->fromXmlString($data);
 
 403      * Parse channel information from different sources
 
 405      * This method is able to extract information about a channel
 
 406      * from an .xml file or a string
 
 409      * @param  string Filename of the source or the source itself
 
 412     function fromAny($info)
 
 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);
 
 419                 $fp = fopen($info, "r");
 
 420                 $test = fread($fp, 5);
 
 422                 if ($test == "<?xml") {
 
 423                     $info = $this->fromXmlFile($info);
 
 426             if (PEAR::isError($info)) {
 
 427                 require_once 'PEAR.php';
 
 428                 return PEAR::raiseError($info);
 
 431         if (is_string($info)) {
 
 432             $info = $this->fromXmlString($info);
 
 438      * Return an XML document based on previous parsing and modifications
 
 440      * @return string XML data
 
 446         if (!$this->_isValid && !$this->validate()) {
 
 447             $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID);
 
 450         if (!isset($this->_channelInfo['attribs']['version'])) {
 
 451             $this->_channelInfo['attribs']['version'] = '1.0';
 
 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>
 
 464         if (isset($channelInfo['suggestedalias'])) {
 
 465             $ret .= ' <suggestedalias>' . $channelInfo['suggestedalias'] . "</suggestedalias>\n";
 
 467         if (isset($channelInfo['validatepackage'])) {
 
 468             $ret .= ' <validatepackage version="' .
 
 469                 $channelInfo['validatepackage']['attribs']['version']. '">' .
 
 470                 htmlspecialchars($channelInfo['validatepackage']['_content']) .
 
 471                 "</validatepackage>\n";
 
 473         $ret .= " <servers>\n";
 
 475         if (isset($channelInfo['servers']['primary']['attribs']['ssl'])) {
 
 476             $ret .= ' ssl="' . $channelInfo['servers']['primary']['attribs']['ssl'] . '"';
 
 478         if (isset($channelInfo['servers']['primary']['attribs']['port'])) {
 
 479             $ret .= ' port="' . $channelInfo['servers']['primary']['attribs']['port'] . '"';
 
 482         if (isset($channelInfo['servers']['primary']['rest'])) {
 
 483             $ret .= $this->_makeRestXml($channelInfo['servers']['primary']['rest'], '   ');
 
 485         $ret .= "  </primary>\n";
 
 486         if (isset($channelInfo['servers']['mirror'])) {
 
 487             $ret .= $this->_makeMirrorsXml($channelInfo);
 
 489         $ret .= " </servers>\n";
 
 490         $ret .= "</channel>";
 
 491         return str_replace("\r", "\n", str_replace("\r\n", "\n", $ret));
 
 495      * Generate the <rest> tag
 
 498     function _makeRestXml($info, $indent)
 
 500         $ret = $indent . "<rest>\n";
 
 501         if (isset($info['baseurl']) && !isset($info['baseurl'][0])) {
 
 502             $info['baseurl'] = array($info['baseurl']);
 
 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";
 
 511         $ret .= $indent . "</rest>\n";
 
 516      * Generate the <mirrors> tag
 
 519     function _makeMirrorsXml($channelInfo)
 
 522         if (!isset($channelInfo['servers']['mirror'][0])) {
 
 523             $channelInfo['servers']['mirror'] = array($channelInfo['servers']['mirror']);
 
 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'] . '"';
 
 530             if (isset($mirror['attribs']['ssl'])) {
 
 531                 $ret .= ' ssl="' . $mirror['attribs']['ssl'] . '"';
 
 534             if (isset($mirror['rest'])) {
 
 535                 if (isset($mirror['rest'])) {
 
 536                     $ret .= $this->_makeRestXml($mirror['rest'], '   ');
 
 538                 $ret .= "  </mirror>\n";
 
 547      * Generate the <functions> tag
 
 550     function _makeFunctionsXml($functions, $indent, $rest = false)
 
 553         if (!isset($functions[0])) {
 
 554             $functions = array($functions);
 
 556         foreach ($functions as $function) {
 
 557             $ret .= "$indent<function version=\"" . $function['attribs']['version'] . "\"";
 
 559                 $ret .= ' uri="' . $function['attribs']['uri'] . '"';
 
 561             $ret .= ">" . $function['_content'] . "</function>\n";
 
 567      * Validation error.  Also marks the object contents as invalid
 
 569      * @param array error information
 
 572     function _validateError($code, $params = array())
 
 574         $this->_stack->push($code, 'error', $params);
 
 575         $this->_isValid = false;
 
 579      * Validation warning.  Does not mark the object contents invalid.
 
 581      * @param array error information
 
 584     function _validateWarning($code, $params = array())
 
 586         $this->_stack->push($code, 'warning', $params);
 
 590      * Validate parsed file.
 
 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']));
 
 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']));
 
 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']));
 
 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']));
 
 625         if (isset($info['validatepackage'])) {
 
 626             if (!isset($info['validatepackage']['_content'])) {
 
 627                 $this->_validateError(PEAR_CHANNELFILE_ERROR_NOVALIDATE_NAME);
 
 629             if (!isset($info['validatepackage']['attribs']['version'])) {
 
 630                 $content = isset($info['validatepackage']['_content']) ?
 
 631                     $info['validatepackage']['_content'] :
 
 633                 $this->_validateError(PEAR_CHANNELFILE_ERROR_NOVALIDATE_VERSION,
 
 634                     array('package' => $content));
 
 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']));
 
 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']));
 
 651         if (isset($info['servers']['primary']['rest']) &&
 
 652               isset($info['servers']['primary']['rest']['baseurl'])) {
 
 653             $this->_validateFunctions('rest', $info['servers']['primary']['rest']['baseurl']);
 
 655         if (isset($info['servers']['mirror'])) {
 
 656             if ($this->_channelInfo['name'] == '__uri') {
 
 657                 $this->_validateError(PEAR_CHANNELFILE_URI_CANT_MIRROR);
 
 659             if (!isset($info['servers']['mirror'][0])) {
 
 660                 $info['servers']['mirror'] = array($info['servers']['mirror']);
 
 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'));
 
 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']));
 
 674                 if (isset($mirror['rest'])) {
 
 675                     $this->_validateFunctions('rest', $mirror['rest']['baseurl'],
 
 676                         $mirror['attribs']['host']);
 
 680         return $this->_isValid;
 
 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)
 
 688     function _validateFunctions($protocol, $functions, $parent = '')
 
 690         if (!isset($functions[0])) {
 
 691             $functions = array($functions);
 
 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));
 
 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));
 
 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));
 
 717      * Test whether a string contains a valid channel server.
 
 718      * @param string $ver the package version to test
 
 721     function validChannelServer($server)
 
 723         if ($server == '__uri') {
 
 726         return (bool) preg_match(PEAR_CHANNELS_SERVER_PREG, $server);
 
 730      * @return string|false
 
 734         if (isset($this->_channelInfo['name'])) {
 
 735             return $this->_channelInfo['name'];
 
 742      * @return string|false
 
 746         if (isset($this->_channelInfo['name'])) {
 
 747             return $this->_channelInfo['name'];
 
 754      * @return int|80 port number to connect to
 
 756     function getPort($mirror = false)
 
 759             if ($mir = $this->getMirror($mirror)) {
 
 760                 if (isset($mir['attribs']['port'])) {
 
 761                     return $mir['attribs']['port'];
 
 764                 if ($this->getSSL($mirror)) {
 
 774         if (isset($this->_channelInfo['servers']['primary']['attribs']['port'])) {
 
 775             return $this->_channelInfo['servers']['primary']['attribs']['port'];
 
 778         if ($this->getSSL()) {
 
 786      * @return bool Determines whether secure sockets layer (SSL) is used to connect to this channel
 
 788     function getSSL($mirror = false)
 
 791             if ($mir = $this->getMirror($mirror)) {
 
 792                 if (isset($mir['attribs']['ssl'])) {
 
 802         if (isset($this->_channelInfo['servers']['primary']['attribs']['ssl'])) {
 
 810      * @return string|false
 
 812     function getSummary()
 
 814         if (isset($this->_channelInfo['summary'])) {
 
 815             return $this->_channelInfo['summary'];
 
 822      * @param string protocol type
 
 823      * @param string Mirror name
 
 824      * @return array|false
 
 826     function getFunctions($protocol, $mirror = false)
 
 828         if ($this->getName() == '__uri') {
 
 832         $function = $protocol == 'rest' ? 'baseurl' : 'function';
 
 834             if ($mir = $this->getMirror($mirror)) {
 
 835                 if (isset($mir[$protocol][$function])) {
 
 836                     return $mir[$protocol][$function];
 
 843         if (isset($this->_channelInfo['servers']['primary'][$protocol][$function])) {
 
 844             return $this->_channelInfo['servers']['primary'][$protocol][$function];
 
 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
 
 857      function getFunction($type, $name = null, $mirror = false)
 
 859         $protocols = $this->getFunctions($type, $mirror);
 
 864         foreach ($protocols as $protocol) {
 
 865             if ($name === null) {
 
 869             if ($protocol['_content'] != $name) {
 
 880      * @param string protocol type
 
 881      * @param string protocol name
 
 882      * @param string version
 
 883      * @param string mirror name
 
 886     function supports($type, $name = null, $mirror = false, $version = '1.0')
 
 888         $protocols = $this->getFunctions($type, $mirror);
 
 893         foreach ($protocols as $protocol) {
 
 894             if ($protocol['attribs']['version'] != $version) {
 
 898             if ($name === null) {
 
 902             if ($protocol['_content'] != $name) {
 
 913      * Determines whether a channel supports Representational State Transfer (REST) protocols
 
 914      * for retrieving channel information
 
 918     function supportsREST($mirror = false)
 
 920         if ($mirror == $this->_channelInfo['name']) {
 
 925             if ($mir = $this->getMirror($mirror)) {
 
 926                 return isset($mir['rest']);
 
 932         return isset($this->_channelInfo['servers']['primary']['rest']);
 
 936      * Get the URL to access a base resource.
 
 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
 
 942     function getBaseURL($resourceType, $mirror = false)
 
 944         if ($mirror == $this->_channelInfo['name']) {
 
 949             $mir = $this->getMirror($mirror);
 
 954             $rest = $mir['rest'];
 
 956             $rest = $this->_channelInfo['servers']['primary']['rest'];
 
 959         if (!isset($rest['baseurl'][0])) {
 
 960             $rest['baseurl'] = array($rest['baseurl']);
 
 963         foreach ($rest['baseurl'] as $baseurl) {
 
 964             if (strtolower($baseurl['attribs']['type']) == strtolower($resourceType)) {
 
 965                 return $baseurl['_content'];
 
 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
 
 977     function resetREST($mirror = false)
 
 979         return $this->resetFunctions('rest', $mirror);
 
 983      * Empty all protocol definitions
 
 984      * @param string protocol type
 
 985      * @param string|false mirror name, if any
 
 987     function resetFunctions($type, $mirror = false)
 
 990             if (isset($this->_channelInfo['servers']['mirror'])) {
 
 991                 $mirrors = $this->_channelInfo['servers']['mirror'];
 
 992                 if (!isset($mirrors[0])) {
 
 993                     $mirrors = array($mirrors);
 
 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]);
 
1012         if (isset($this->_channelInfo['servers']['primary'][$type])) {
 
1013             unset($this->_channelInfo['servers']['primary'][$type]);
 
1020      * Set a channel's protocols to the protocols supported by pearweb
 
1022     function setDefaultPEARProtocols($version = '1.0', $mirror = false)
 
1026                 $this->resetREST($mirror);
 
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());
 
1046     function getMirrors()
 
1048         if (isset($this->_channelInfo['servers']['mirror'])) {
 
1049             $mirrors = $this->_channelInfo['servers']['mirror'];
 
1050             if (!isset($mirrors[0])) {
 
1051                 $mirrors = array($mirrors);
 
1061      * Get the unserialized XML representing a mirror
 
1062      * @return array|false
 
1064     function getMirror($server)
 
1066         foreach ($this->getMirrors() as $mirror) {
 
1067             if ($mirror['attribs']['host'] == $server) {
 
1077      * @return string|false
 
1078      * @error PEAR_CHANNELFILE_ERROR_NO_NAME
 
1079      * @error PEAR_CHANNELFILE_ERROR_INVALID_NAME
 
1081     function setName($name)
 
1083         return $this->setServer($name);
 
1087      * Set the socket number (port) that is used to connect to this channel
 
1089      * @param string|false name of the mirror server, or false for the primary
 
1091     function setPort($port, $mirror = false)
 
1094             if (!isset($this->_channelInfo['servers']['mirror'])) {
 
1095                 $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
 
1096                     array('mirror' => $mirror));
 
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;
 
1109             } elseif ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) {
 
1110                 $this->_channelInfo['servers']['mirror']['attribs']['port'] = $port;
 
1111                 $this->_isValid = false;
 
1116         $this->_channelInfo['servers']['primary']['attribs']['port'] = $port;
 
1117         $this->_isValid = false;
 
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
 
1126     function setSSL($ssl = true, $mirror = false)
 
1129             if (!isset($this->_channelInfo['servers']['mirror'])) {
 
1130                 $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
 
1131                     array('mirror' => $mirror));
 
1135             if (isset($this->_channelInfo['servers']['mirror'][0])) {
 
1136                 foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) {
 
1137                     if ($mirror == $mir['attribs']['host']) {
 
1139                             if (isset($this->_channelInfo['servers']['mirror'][$i]
 
1140                                   ['attribs']['ssl'])) {
 
1141                                 unset($this->_channelInfo['servers']['mirror'][$i]['attribs']['ssl']);
 
1144                             $this->_channelInfo['servers']['mirror'][$i]['attribs']['ssl'] = 'yes';
 
1152             } elseif ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) {
 
1154                     if (isset($this->_channelInfo['servers']['mirror']['attribs']['ssl'])) {
 
1155                         unset($this->_channelInfo['servers']['mirror']['attribs']['ssl']);
 
1158                     $this->_channelInfo['servers']['mirror']['attribs']['ssl'] = 'yes';
 
1161                 $this->_isValid = false;
 
1167             $this->_channelInfo['servers']['primary']['attribs']['ssl'] = 'yes';
 
1169             if (isset($this->_channelInfo['servers']['primary']['attribs']['ssl'])) {
 
1170                 unset($this->_channelInfo['servers']['primary']['attribs']['ssl']);
 
1174         $this->_isValid = false;
 
1180      * @return string|false
 
1181      * @error PEAR_CHANNELFILE_ERROR_NO_SERVER
 
1182      * @error PEAR_CHANNELFILE_ERROR_INVALID_SERVER
 
1184     function setServer($server, $mirror = false)
 
1186         if (empty($server)) {
 
1187             $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_SERVER);
 
1189         } elseif (!$this->validChannelServer($server)) {
 
1190             $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME,
 
1191                 array('tag' => 'name', 'name' => $server));
 
1197             foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) {
 
1198                 if ($mirror == $mir['attribs']['host']) {
 
1205                 $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
 
1206                     array('mirror' => $mirror));
 
1210             $this->_channelInfo['mirror'][$i]['attribs']['host'] = $server;
 
1214         $this->_channelInfo['name'] = $server;
 
1220      * @return boolean success
 
1221      * @error PEAR_CHANNELFILE_ERROR_NO_SUMMARY
 
1222      * @warning PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY
 
1224     function setSummary($summary)
 
1226         if (empty($summary)) {
 
1227             $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_SUMMARY);
 
1229         } elseif (strpos(trim($summary), "\n") !== false) {
 
1230             $this->_validateWarning(PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY,
 
1231                 array('summary' => $summary));
 
1234         $this->_channelInfo['summary'] = $summary;
 
1240      * @param boolean determines whether the alias is in channel.xml or local
 
1241      * @return boolean success
 
1243     function setAlias($alias, $local = false)
 
1245         if (!$this->validChannelServer($alias)) {
 
1246             $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME,
 
1247                 array('tag' => 'suggestedalias', 'name' => $alias));
 
1252             $this->_channelInfo['localalias'] = $alias;
 
1254             $this->_channelInfo['suggestedalias'] = $alias;
 
1265         if (isset($this->_channelInfo['localalias'])) {
 
1266             return $this->_channelInfo['localalias'];
 
1268         if (isset($this->_channelInfo['suggestedalias'])) {
 
1269             return $this->_channelInfo['suggestedalias'];
 
1271         if (isset($this->_channelInfo['name'])) {
 
1272             return $this->_channelInfo['name'];
 
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
 
1284     function setValidationPackage($validateclass, $version)
 
1286         if (empty($validateclass)) {
 
1287             unset($this->_channelInfo['validatepackage']);
 
1289         $this->_channelInfo['validatepackage'] = array('_content' => $validateclass);
 
1290         $this->_channelInfo['validatepackage']['attribs'] = array('version' => $version);
 
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
 
1301     function addFunction($type, $version, $name = '', $mirror = false)
 
1304             return $this->addMirrorFunction($mirror, $type, $version, $name);
 
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());
 
1316             $this->_channelInfo['servers']['primary'][$type]['function'] = $set;
 
1317             $this->_isValid = false;
 
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']);
 
1324         $this->_channelInfo['servers']['primary'][$type]['function'][] = $set;
 
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
 
1334     function addMirrorFunction($mirror, $type, $version, $name = '')
 
1336         if (!isset($this->_channelInfo['servers']['mirror'])) {
 
1337             $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
 
1338                 array('mirror' => $mirror));
 
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];
 
1351             if ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) {
 
1352                 $setmirror = &$this->_channelInfo['servers']['mirror'];
 
1357             $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
 
1358                 array('mirror' => $mirror));
 
1362         $set = array('attribs' => array('version' => $version), '_content' => $name);
 
1363         if (!isset($setmirror[$type]['function'])) {
 
1364             $setmirror[$type]['function'] = $set;
 
1365             $this->_isValid = false;
 
1367         } elseif (!isset($setmirror[$type]['function'][0])) {
 
1368             $setmirror[$type]['function'] = array($setmirror[$type]['function']);
 
1371         $setmirror[$type]['function'][] = $set;
 
1372         $this->_isValid = false;
 
1377      * @param string Resource Type this url links to
 
1379      * @param string|false mirror name, if this is not a primary server REST base URL
 
1381     function setBaseURL($resourceType, $url, $mirror = false)
 
1384             if (!isset($this->_channelInfo['servers']['mirror'])) {
 
1385                 $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
 
1386                     array('mirror' => $mirror));
 
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];
 
1399                 if ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) {
 
1400                     $setmirror = &$this->_channelInfo['servers']['mirror'];
 
1404             $setmirror = &$this->_channelInfo['servers']['primary'];
 
1407         $set = array('attribs' => array('type' => $resourceType), '_content' => $url);
 
1408         if (!isset($setmirror['rest'])) {
 
1409             $setmirror['rest'] = array();
 
1412         if (!isset($setmirror['rest']['baseurl'])) {
 
1413             $setmirror['rest']['baseurl'] = $set;
 
1414             $this->_isValid = false;
 
1416         } elseif (!isset($setmirror['rest']['baseurl'][0])) {
 
1417             $setmirror['rest']['baseurl'] = array($setmirror['rest']['baseurl']);
 
1420         foreach ($setmirror['rest']['baseurl'] as $i => $url) {
 
1421             if ($url['attribs']['type'] == $resourceType) {
 
1422                 $this->_isValid = false;
 
1423                 $setmirror['rest']['baseurl'][$i] = $set;
 
1428         $setmirror['rest']['baseurl'][] = $set;
 
1429         $this->_isValid = false;
 
1434      * @param string mirror server
 
1435      * @param int mirror http port
 
1438     function addMirror($server, $port = null)
 
1440         if ($this->_channelInfo['name'] == '__uri') {
 
1441             return false; // the __uri channel cannot have mirrors by definition
 
1444         $set = array('attribs' => array('host' => $server));
 
1445         if (is_numeric($port)) {
 
1446             $set['attribs']['port'] = $port;
 
1449         if (!isset($this->_channelInfo['servers']['mirror'])) {
 
1450             $this->_channelInfo['servers']['mirror'] = $set;
 
1454         if (!isset($this->_channelInfo['servers']['mirror'][0])) {
 
1455             $this->_channelInfo['servers']['mirror'] =
 
1456                 array($this->_channelInfo['servers']['mirror']);
 
1459         $this->_channelInfo['servers']['mirror'][] = $set;
 
1464      * Retrieve the name of the validation package for this channel
 
1465      * @return string|false
 
1467     function getValidationPackage()
 
1469         if (!$this->_isValid && !$this->validate()) {
 
1473         if (!isset($this->_channelInfo['validatepackage'])) {
 
1474             return array('attribs' => array('version' => 'default'),
 
1475                 '_content' => 'PEAR_Validate');
 
1478         return $this->_channelInfo['validatepackage'];
 
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
 
1488     function &getValidationObject($package = false)
 
1490         if (!class_exists('PEAR_Validate')) {
 
1491             require_once 'PEAR/Validate.php';
 
1494         if (!$this->_isValid) {
 
1495             if (!$this->validate()) {
 
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;
 
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;
 
1522                 $vclass = str_replace('.', '_',
 
1523                     $this->_channelInfo['validatepackage']['_content']);
 
1524                 $val = &new $vclass;
 
1527             $val = &new PEAR_Validate;
 
1533     function isIncludeable($path)
 
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)) {
 
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
 
1551     function lastModified()
 
1553         if (isset($this->_channelInfo['_lastmodified'])) {
 
1554             return $this->_channelInfo['_lastmodified'];