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  * @link       http://pear.php.net/package/PEAR
 
  13  * @since      File available since Release 1.4.0a1
 
  17  * Needed for error handling
 
  19 require_once 'PEAR/ErrorStack.php';
 
  20 require_once 'PEAR/XMLParser.php';
 
  21 require_once 'PEAR/Common.php';
 
  24  * Error code if the channel.xml <channel> tag does not contain a valid version
 
  26 define('PEAR_CHANNELFILE_ERROR_NO_VERSION', 1);
 
  28  * Error code if the channel.xml <channel> tag version is not supported (version 1.0 is the only supported version,
 
  31 define('PEAR_CHANNELFILE_ERROR_INVALID_VERSION', 2);
 
  34  * Error code if parsing is attempted with no xml extension
 
  36 define('PEAR_CHANNELFILE_ERROR_NO_XML_EXT', 3);
 
  39  * Error code if creating the xml parser resource fails
 
  41 define('PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER', 4);
 
  44  * Error code used for all sax xml parsing errors
 
  46 define('PEAR_CHANNELFILE_ERROR_PARSER_ERROR', 5);
 
  52  * Error code when channel name is missing
 
  54 define('PEAR_CHANNELFILE_ERROR_NO_NAME', 6);
 
  56  * Error code when channel name is invalid
 
  58 define('PEAR_CHANNELFILE_ERROR_INVALID_NAME', 7);
 
  60  * Error code when channel summary is missing
 
  62 define('PEAR_CHANNELFILE_ERROR_NO_SUMMARY', 8);
 
  64  * Error code when channel summary is multi-line
 
  66 define('PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY', 9);
 
  68  * Error code when channel server is missing for protocol
 
  70 define('PEAR_CHANNELFILE_ERROR_NO_HOST', 10);
 
  72  * Error code when channel server is invalid for protocol
 
  74 define('PEAR_CHANNELFILE_ERROR_INVALID_HOST', 11);
 
  76  * Error code when a mirror name is invalid
 
  78 define('PEAR_CHANNELFILE_ERROR_INVALID_MIRROR', 21);
 
  80  * Error code when a mirror type is invalid
 
  82 define('PEAR_CHANNELFILE_ERROR_INVALID_MIRRORTYPE', 22);
 
  84  * Error code when an attempt is made to generate xml, but the parsed content is invalid
 
  86 define('PEAR_CHANNELFILE_ERROR_INVALID', 23);
 
  88  * Error code when an empty package name validate regex is passed in
 
  90 define('PEAR_CHANNELFILE_ERROR_EMPTY_REGEX', 24);
 
  92  * Error code when a <function> tag has no version
 
  94 define('PEAR_CHANNELFILE_ERROR_NO_FUNCTIONVERSION', 25);
 
  96  * Error code when a <function> tag has no name
 
  98 define('PEAR_CHANNELFILE_ERROR_NO_FUNCTIONNAME', 26);
 
 100  * Error code when a <validatepackage> tag has no name
 
 102 define('PEAR_CHANNELFILE_ERROR_NOVALIDATE_NAME', 27);
 
 104  * Error code when a <validatepackage> tag has no version attribute
 
 106 define('PEAR_CHANNELFILE_ERROR_NOVALIDATE_VERSION', 28);
 
 108  * Error code when a mirror does not exist but is called for in one of the set*
 
 111 define('PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND', 32);
 
 113  * Error code when a server port is not numeric
 
 115 define('PEAR_CHANNELFILE_ERROR_INVALID_PORT', 33);
 
 117  * Error code when <static> contains no version attribute
 
 119 define('PEAR_CHANNELFILE_ERROR_NO_STATICVERSION', 34);
 
 121  * Error code when <baseurl> contains no type attribute in a <rest> protocol definition
 
 123 define('PEAR_CHANNELFILE_ERROR_NOBASEURLTYPE', 35);
 
 125  * Error code when a mirror is defined and the channel.xml represents the __uri pseudo-channel
 
 127 define('PEAR_CHANNELFILE_URI_CANT_MIRROR', 36);
 
 129  * Error code when ssl attribute is present and is not "yes"
 
 131 define('PEAR_CHANNELFILE_ERROR_INVALID_SSL', 37);
 
 135  * Mirror types allowed.  Currently only internet servers are recognized.
 
 137 $GLOBALS['_PEAR_CHANNELS_MIRROR_TYPES'] =  array('server');
 
 141  * The Channel handling class
 
 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
 
 152 class PEAR_ChannelFile
 
 156      * @var PEAR_ErrorStack
 
 162      * Supported channel.xml versions, for parsing
 
 166     var $_supportedVersions = array('1.0');
 
 169      * Parsed channel information
 
 176      * index into the subchannels array, used for parsing xml
 
 180     var $_subchannelIndex;
 
 183      * index into the mirrors array, used for parsing xml
 
 190      * Flag used to determine the validity of parsed content
 
 194     var $_isValid = false;
 
 196     function __construct()
 
 198         $this->_stack = new PEAR_ErrorStack('PEAR_ChannelFile');
 
 199         $this->_stack->setErrorMessageTemplate($this->_getErrorMessage());
 
 200         $this->_isValid = false;
 
 207     function _getErrorMessage()
 
 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 =>
 
 217                 PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER =>
 
 218                     'Unable to create XML parser',
 
 219                 PEAR_CHANNELFILE_ERROR_PARSER_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',
 
 265      * @param string contents of package.xml file
 
 266      * @return bool success of parsing
 
 268     function fromXmlString($data)
 
 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]));
 
 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()));
 
 283                     $this->_stack->push(PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER, 'error');
 
 287             $this->_channelInfo = $parser->getData();
 
 290             $this->_stack->push(PEAR_CHANNELFILE_ERROR_NO_VERSION, 'error', array('xml' => $data));
 
 300         if (!$this->_isValid && !$this->validate()) {
 
 303         return $this->_channelInfo;
 
 309      * @return PEAR_ChannelFile|false false if invalid
 
 311     public static function &fromArray(
 
 312         $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
 
 328      * @return PEAR_ChannelFile
 
 330     public static function &fromArrayWithErrors(
 
 331         $data, $compatibility = false, $stackClass = 'PEAR_ErrorStack'
 
 333         $a = new PEAR_ChannelFile($compatibility, $stackClass);
 
 334         $a->_fromArray($data);
 
 342     function _fromArray($data)
 
 344         $this->_channelInfo = $data;
 
 348      * Wrapper to {@link PEAR_ErrorStack::getErrors()}
 
 349      * @param boolean determines whether to purge the error stack after retrieving
 
 352     function getErrors($purge = false)
 
 354         return $this->_stack->getErrors($purge);
 
 358      * Unindent given string (?)
 
 360      * @param string $str The string that has to be unindented.
 
 364     function _unIndent($str)
 
 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);
 
 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";
 
 382      * Parse a channel.xml file.  Expects the name of
 
 383      * a channel xml file as input.
 
 385      * @param string  $descfile  name of channel xml file
 
 386      * @return bool success of parsing
 
 388     function fromXmlFile($descfile)
 
 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");
 
 396         // read the whole thing so we only get one cdata callback
 
 397         // for each block of cdata
 
 399         $data = file_get_contents($descfile);
 
 400         return $this->fromXmlString($data);
 
 404      * Parse channel information from different sources
 
 406      * This method is able to extract information about a channel
 
 407      * from an .xml file or a string
 
 410      * @param  string Filename of the source or the source itself
 
 413     function fromAny($info)
 
 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);
 
 420                 $fp = fopen($info, "r");
 
 421                 $test = fread($fp, 5);
 
 423                 if ($test == "<?xml") {
 
 424                     $info = $this->fromXmlFile($info);
 
 427             if (PEAR::isError($info)) {
 
 428                 require_once 'PEAR.php';
 
 429                 return PEAR::raiseError($info);
 
 432         if (is_string($info)) {
 
 433             $info = $this->fromXmlString($info);
 
 439      * Return an XML document based on previous parsing and modifications
 
 441      * @return string XML data
 
 447         if (!$this->_isValid && !$this->validate()) {
 
 448             $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID);
 
 451         if (!isset($this->_channelInfo['attribs']['version'])) {
 
 452             $this->_channelInfo['attribs']['version'] = '1.0';
 
 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>
 
 465         if (isset($channelInfo['suggestedalias'])) {
 
 466             $ret .= ' <suggestedalias>' . $channelInfo['suggestedalias'] . "</suggestedalias>\n";
 
 468         if (isset($channelInfo['validatepackage'])) {
 
 469             $ret .= ' <validatepackage version="' .
 
 470                 $channelInfo['validatepackage']['attribs']['version']. '">' .
 
 471                 htmlspecialchars($channelInfo['validatepackage']['_content']) .
 
 472                 "</validatepackage>\n";
 
 474         $ret .= " <servers>\n";
 
 476         if (isset($channelInfo['servers']['primary']['attribs']['ssl'])) {
 
 477             $ret .= ' ssl="' . $channelInfo['servers']['primary']['attribs']['ssl'] . '"';
 
 479         if (isset($channelInfo['servers']['primary']['attribs']['port'])) {
 
 480             $ret .= ' port="' . $channelInfo['servers']['primary']['attribs']['port'] . '"';
 
 483         if (isset($channelInfo['servers']['primary']['rest'])) {
 
 484             $ret .= $this->_makeRestXml($channelInfo['servers']['primary']['rest'], '   ');
 
 486         $ret .= "  </primary>\n";
 
 487         if (isset($channelInfo['servers']['mirror'])) {
 
 488             $ret .= $this->_makeMirrorsXml($channelInfo);
 
 490         $ret .= " </servers>\n";
 
 491         $ret .= "</channel>";
 
 492         return str_replace("\r", "\n", str_replace("\r\n", "\n", $ret));
 
 496      * Generate the <rest> tag
 
 499     function _makeRestXml($info, $indent)
 
 501         $ret = $indent . "<rest>\n";
 
 502         if (isset($info['baseurl']) && !isset($info['baseurl'][0])) {
 
 503             $info['baseurl'] = array($info['baseurl']);
 
 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";
 
 512         $ret .= $indent . "</rest>\n";
 
 517      * Generate the <mirrors> tag
 
 520     function _makeMirrorsXml($channelInfo)
 
 523         if (!isset($channelInfo['servers']['mirror'][0])) {
 
 524             $channelInfo['servers']['mirror'] = array($channelInfo['servers']['mirror']);
 
 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'] . '"';
 
 531             if (isset($mirror['attribs']['ssl'])) {
 
 532                 $ret .= ' ssl="' . $mirror['attribs']['ssl'] . '"';
 
 535             if (isset($mirror['rest'])) {
 
 536                 if (isset($mirror['rest'])) {
 
 537                     $ret .= $this->_makeRestXml($mirror['rest'], '   ');
 
 539                 $ret .= "  </mirror>\n";
 
 548      * Generate the <functions> tag
 
 551     function _makeFunctionsXml($functions, $indent, $rest = false)
 
 554         if (!isset($functions[0])) {
 
 555             $functions = array($functions);
 
 557         foreach ($functions as $function) {
 
 558             $ret .= "$indent<function version=\"" . $function['attribs']['version'] . "\"";
 
 560                 $ret .= ' uri="' . $function['attribs']['uri'] . '"';
 
 562             $ret .= ">" . $function['_content'] . "</function>\n";
 
 568      * Validation error.  Also marks the object contents as invalid
 
 570      * @param array error information
 
 573     function _validateError($code, $params = array())
 
 575         $this->_stack->push($code, 'error', $params);
 
 576         $this->_isValid = false;
 
 580      * Validation warning.  Does not mark the object contents invalid.
 
 582      * @param array error information
 
 585     function _validateWarning($code, $params = array())
 
 587         $this->_stack->push($code, 'warning', $params);
 
 591      * Validate parsed file.
 
 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']));
 
 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']));
 
 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']));
 
 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']));
 
 626         if (isset($info['validatepackage'])) {
 
 627             if (!isset($info['validatepackage']['_content'])) {
 
 628                 $this->_validateError(PEAR_CHANNELFILE_ERROR_NOVALIDATE_NAME);
 
 630             if (!isset($info['validatepackage']['attribs']['version'])) {
 
 631                 $content = isset($info['validatepackage']['_content']) ?
 
 632                     $info['validatepackage']['_content'] :
 
 634                 $this->_validateError(PEAR_CHANNELFILE_ERROR_NOVALIDATE_VERSION,
 
 635                     array('package' => $content));
 
 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']));
 
 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']));
 
 652         if (isset($info['servers']['primary']['rest']) &&
 
 653               isset($info['servers']['primary']['rest']['baseurl'])) {
 
 654             $this->_validateFunctions('rest', $info['servers']['primary']['rest']['baseurl']);
 
 656         if (isset($info['servers']['mirror'])) {
 
 657             if ($this->_channelInfo['name'] == '__uri') {
 
 658                 $this->_validateError(PEAR_CHANNELFILE_URI_CANT_MIRROR);
 
 660             if (!isset($info['servers']['mirror'][0])) {
 
 661                 $info['servers']['mirror'] = array($info['servers']['mirror']);
 
 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'));
 
 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']));
 
 675                 if (isset($mirror['rest'])) {
 
 676                     $this->_validateFunctions('rest', $mirror['rest']['baseurl'],
 
 677                         $mirror['attribs']['host']);
 
 681         return $this->_isValid;
 
 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)
 
 689     function _validateFunctions($protocol, $functions, $parent = '')
 
 691         if (!isset($functions[0])) {
 
 692             $functions = array($functions);
 
 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));
 
 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));
 
 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));
 
 718      * Test whether a string contains a valid channel server.
 
 719      * @param string $ver the package version to test
 
 722     function validChannelServer($server)
 
 724         if ($server == '__uri') {
 
 727         return (bool) preg_match(PEAR_CHANNELS_SERVER_PREG, $server);
 
 731      * @return string|false
 
 735         if (isset($this->_channelInfo['name'])) {
 
 736             return $this->_channelInfo['name'];
 
 743      * @return string|false
 
 747         if (isset($this->_channelInfo['name'])) {
 
 748             return $this->_channelInfo['name'];
 
 755      * @return int|80 port number to connect to
 
 757     function getPort($mirror = false)
 
 760             if ($mir = $this->getMirror($mirror)) {
 
 761                 if (isset($mir['attribs']['port'])) {
 
 762                     return $mir['attribs']['port'];
 
 765                 if ($this->getSSL($mirror)) {
 
 775         if (isset($this->_channelInfo['servers']['primary']['attribs']['port'])) {
 
 776             return $this->_channelInfo['servers']['primary']['attribs']['port'];
 
 779         if ($this->getSSL()) {
 
 787      * @return bool Determines whether secure sockets layer (SSL) is used to connect to this channel
 
 789     function getSSL($mirror = false)
 
 792             if ($mir = $this->getMirror($mirror)) {
 
 793                 if (isset($mir['attribs']['ssl'])) {
 
 803         if (isset($this->_channelInfo['servers']['primary']['attribs']['ssl'])) {
 
 811      * @return string|false
 
 813     function getSummary()
 
 815         if (isset($this->_channelInfo['summary'])) {
 
 816             return $this->_channelInfo['summary'];
 
 823      * @param string protocol type
 
 824      * @param string Mirror name
 
 825      * @return array|false
 
 827     function getFunctions($protocol, $mirror = false)
 
 829         if ($this->getName() == '__uri') {
 
 833         $function = $protocol == 'rest' ? 'baseurl' : 'function';
 
 835             if ($mir = $this->getMirror($mirror)) {
 
 836                 if (isset($mir[$protocol][$function])) {
 
 837                     return $mir[$protocol][$function];
 
 844         if (isset($this->_channelInfo['servers']['primary'][$protocol][$function])) {
 
 845             return $this->_channelInfo['servers']['primary'][$protocol][$function];
 
 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
 
 858      function getFunction($type, $name = null, $mirror = false)
 
 860         $protocols = $this->getFunctions($type, $mirror);
 
 865         foreach ($protocols as $protocol) {
 
 866             if ($name === null) {
 
 870             if ($protocol['_content'] != $name) {
 
 881      * @param string protocol type
 
 882      * @param string protocol name
 
 883      * @param string version
 
 884      * @param string mirror name
 
 887     function supports($type, $name = null, $mirror = false, $version = '1.0')
 
 889         $protocols = $this->getFunctions($type, $mirror);
 
 894         foreach ($protocols as $protocol) {
 
 895             if ($protocol['attribs']['version'] != $version) {
 
 899             if ($name === null) {
 
 903             if ($protocol['_content'] != $name) {
 
 914      * Determines whether a channel supports Representational State Transfer (REST) protocols
 
 915      * for retrieving channel information
 
 919     function supportsREST($mirror = false)
 
 921         if ($mirror == $this->_channelInfo['name']) {
 
 926             if ($mir = $this->getMirror($mirror)) {
 
 927                 return isset($mir['rest']);
 
 933         return isset($this->_channelInfo['servers']['primary']['rest']);
 
 937      * Get the URL to access a base resource.
 
 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
 
 943     function getBaseURL($resourceType, $mirror = false)
 
 945         if ($mirror == $this->_channelInfo['name']) {
 
 950             $mir = $this->getMirror($mirror);
 
 955             $rest = $mir['rest'];
 
 957             $rest = $this->_channelInfo['servers']['primary']['rest'];
 
 960         if (!isset($rest['baseurl'][0])) {
 
 961             $rest['baseurl'] = array($rest['baseurl']);
 
 964         foreach ($rest['baseurl'] as $baseurl) {
 
 965             if (strtolower($baseurl['attribs']['type']) == strtolower($resourceType)) {
 
 966                 return $baseurl['_content'];
 
 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
 
 978     function resetREST($mirror = false)
 
 980         return $this->resetFunctions('rest', $mirror);
 
 984      * Empty all protocol definitions
 
 985      * @param string protocol type
 
 986      * @param string|false mirror name, if any
 
 988     function resetFunctions($type, $mirror = false)
 
 991             if (isset($this->_channelInfo['servers']['mirror'])) {
 
 992                 $mirrors = $this->_channelInfo['servers']['mirror'];
 
 993                 if (!isset($mirrors[0])) {
 
 994                     $mirrors = array($mirrors);
 
 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]);
 
1013         if (isset($this->_channelInfo['servers']['primary'][$type])) {
 
1014             unset($this->_channelInfo['servers']['primary'][$type]);
 
1021      * Set a channel's protocols to the protocols supported by pearweb
 
1023     function setDefaultPEARProtocols($version = '1.0', $mirror = false)
 
1027                 $this->resetREST($mirror);
 
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());
 
1047     function getMirrors()
 
1049         if (isset($this->_channelInfo['servers']['mirror'])) {
 
1050             $mirrors = $this->_channelInfo['servers']['mirror'];
 
1051             if (!isset($mirrors[0])) {
 
1052                 $mirrors = array($mirrors);
 
1062      * Get the unserialized XML representing a mirror
 
1063      * @return array|false
 
1065     function getMirror($server)
 
1067         foreach ($this->getMirrors() as $mirror) {
 
1068             if ($mirror['attribs']['host'] == $server) {
 
1078      * @return string|false
 
1079      * @error PEAR_CHANNELFILE_ERROR_NO_NAME
 
1080      * @error PEAR_CHANNELFILE_ERROR_INVALID_NAME
 
1082     function setName($name)
 
1084         return $this->setServer($name);
 
1088      * Set the socket number (port) that is used to connect to this channel
 
1090      * @param string|false name of the mirror server, or false for the primary
 
1092     function setPort($port, $mirror = false)
 
1095             if (!isset($this->_channelInfo['servers']['mirror'])) {
 
1096                 $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
 
1097                     array('mirror' => $mirror));
 
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;
 
1110             } elseif ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) {
 
1111                 $this->_channelInfo['servers']['mirror']['attribs']['port'] = $port;
 
1112                 $this->_isValid = false;
 
1117         $this->_channelInfo['servers']['primary']['attribs']['port'] = $port;
 
1118         $this->_isValid = false;
 
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
 
1127     function setSSL($ssl = true, $mirror = false)
 
1130             if (!isset($this->_channelInfo['servers']['mirror'])) {
 
1131                 $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
 
1132                     array('mirror' => $mirror));
 
1136             if (isset($this->_channelInfo['servers']['mirror'][0])) {
 
1137                 foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) {
 
1138                     if ($mirror == $mir['attribs']['host']) {
 
1140                             if (isset($this->_channelInfo['servers']['mirror'][$i]
 
1141                                   ['attribs']['ssl'])) {
 
1142                                 unset($this->_channelInfo['servers']['mirror'][$i]['attribs']['ssl']);
 
1145                             $this->_channelInfo['servers']['mirror'][$i]['attribs']['ssl'] = 'yes';
 
1153             } elseif ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) {
 
1155                     if (isset($this->_channelInfo['servers']['mirror']['attribs']['ssl'])) {
 
1156                         unset($this->_channelInfo['servers']['mirror']['attribs']['ssl']);
 
1159                     $this->_channelInfo['servers']['mirror']['attribs']['ssl'] = 'yes';
 
1162                 $this->_isValid = false;
 
1168             $this->_channelInfo['servers']['primary']['attribs']['ssl'] = 'yes';
 
1170             if (isset($this->_channelInfo['servers']['primary']['attribs']['ssl'])) {
 
1171                 unset($this->_channelInfo['servers']['primary']['attribs']['ssl']);
 
1175         $this->_isValid = false;
 
1181      * @return string|false
 
1182      * @error PEAR_CHANNELFILE_ERROR_NO_SERVER
 
1183      * @error PEAR_CHANNELFILE_ERROR_INVALID_SERVER
 
1185     function setServer($server, $mirror = false)
 
1187         if (empty($server)) {
 
1188             $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_SERVER);
 
1190         } elseif (!$this->validChannelServer($server)) {
 
1191             $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME,
 
1192                 array('tag' => 'name', 'name' => $server));
 
1198             foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) {
 
1199                 if ($mirror == $mir['attribs']['host']) {
 
1206                 $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
 
1207                     array('mirror' => $mirror));
 
1211             $this->_channelInfo['mirror'][$i]['attribs']['host'] = $server;
 
1215         $this->_channelInfo['name'] = $server;
 
1221      * @return boolean success
 
1222      * @error PEAR_CHANNELFILE_ERROR_NO_SUMMARY
 
1223      * @warning PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY
 
1225     function setSummary($summary)
 
1227         if (empty($summary)) {
 
1228             $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_SUMMARY);
 
1230         } elseif (strpos(trim($summary), "\n") !== false) {
 
1231             $this->_validateWarning(PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY,
 
1232                 array('summary' => $summary));
 
1235         $this->_channelInfo['summary'] = $summary;
 
1241      * @param boolean determines whether the alias is in channel.xml or local
 
1242      * @return boolean success
 
1244     function setAlias($alias, $local = false)
 
1246         if (!$this->validChannelServer($alias)) {
 
1247             $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME,
 
1248                 array('tag' => 'suggestedalias', 'name' => $alias));
 
1253             $this->_channelInfo['localalias'] = $alias;
 
1255             $this->_channelInfo['suggestedalias'] = $alias;
 
1266         if (isset($this->_channelInfo['localalias'])) {
 
1267             return $this->_channelInfo['localalias'];
 
1269         if (isset($this->_channelInfo['suggestedalias'])) {
 
1270             return $this->_channelInfo['suggestedalias'];
 
1272         if (isset($this->_channelInfo['name'])) {
 
1273             return $this->_channelInfo['name'];
 
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
 
1285     function setValidationPackage($validateclass, $version)
 
1287         if (empty($validateclass)) {
 
1288             unset($this->_channelInfo['validatepackage']);
 
1290         $this->_channelInfo['validatepackage'] = array('_content' => $validateclass);
 
1291         $this->_channelInfo['validatepackage']['attribs'] = array('version' => $version);
 
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
 
1302     function addFunction($type, $version, $name = '', $mirror = false)
 
1305             return $this->addMirrorFunction($mirror, $type, $version, $name);
 
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());
 
1317             $this->_channelInfo['servers']['primary'][$type]['function'] = $set;
 
1318             $this->_isValid = false;
 
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']);
 
1325         $this->_channelInfo['servers']['primary'][$type]['function'][] = $set;
 
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
 
1335     function addMirrorFunction($mirror, $type, $version, $name = '')
 
1337         if (!isset($this->_channelInfo['servers']['mirror'])) {
 
1338             $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
 
1339                 array('mirror' => $mirror));
 
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];
 
1352             if ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) {
 
1353                 $setmirror = &$this->_channelInfo['servers']['mirror'];
 
1358             $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
 
1359                 array('mirror' => $mirror));
 
1363         $set = array('attribs' => array('version' => $version), '_content' => $name);
 
1364         if (!isset($setmirror[$type]['function'])) {
 
1365             $setmirror[$type]['function'] = $set;
 
1366             $this->_isValid = false;
 
1368         } elseif (!isset($setmirror[$type]['function'][0])) {
 
1369             $setmirror[$type]['function'] = array($setmirror[$type]['function']);
 
1372         $setmirror[$type]['function'][] = $set;
 
1373         $this->_isValid = false;
 
1378      * @param string Resource Type this url links to
 
1380      * @param string|false mirror name, if this is not a primary server REST base URL
 
1382     function setBaseURL($resourceType, $url, $mirror = false)
 
1385             if (!isset($this->_channelInfo['servers']['mirror'])) {
 
1386                 $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
 
1387                     array('mirror' => $mirror));
 
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];
 
1400                 if ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) {
 
1401                     $setmirror = &$this->_channelInfo['servers']['mirror'];
 
1405             $setmirror = &$this->_channelInfo['servers']['primary'];
 
1408         $set = array('attribs' => array('type' => $resourceType), '_content' => $url);
 
1409         if (!isset($setmirror['rest'])) {
 
1410             $setmirror['rest'] = array();
 
1413         if (!isset($setmirror['rest']['baseurl'])) {
 
1414             $setmirror['rest']['baseurl'] = $set;
 
1415             $this->_isValid = false;
 
1417         } elseif (!isset($setmirror['rest']['baseurl'][0])) {
 
1418             $setmirror['rest']['baseurl'] = array($setmirror['rest']['baseurl']);
 
1421         foreach ($setmirror['rest']['baseurl'] as $i => $url) {
 
1422             if ($url['attribs']['type'] == $resourceType) {
 
1423                 $this->_isValid = false;
 
1424                 $setmirror['rest']['baseurl'][$i] = $set;
 
1429         $setmirror['rest']['baseurl'][] = $set;
 
1430         $this->_isValid = false;
 
1435      * @param string mirror server
 
1436      * @param int mirror http port
 
1439     function addMirror($server, $port = null)
 
1441         if ($this->_channelInfo['name'] == '__uri') {
 
1442             return false; // the __uri channel cannot have mirrors by definition
 
1445         $set = array('attribs' => array('host' => $server));
 
1446         if (is_numeric($port)) {
 
1447             $set['attribs']['port'] = $port;
 
1450         if (!isset($this->_channelInfo['servers']['mirror'])) {
 
1451             $this->_channelInfo['servers']['mirror'] = $set;
 
1455         if (!isset($this->_channelInfo['servers']['mirror'][0])) {
 
1456             $this->_channelInfo['servers']['mirror'] =
 
1457                 array($this->_channelInfo['servers']['mirror']);
 
1460         $this->_channelInfo['servers']['mirror'][] = $set;
 
1465      * Retrieve the name of the validation package for this channel
 
1466      * @return string|false
 
1468     function getValidationPackage()
 
1470         if (!$this->_isValid && !$this->validate()) {
 
1474         if (!isset($this->_channelInfo['validatepackage'])) {
 
1475             return array('attribs' => array('version' => 'default'),
 
1476                 '_content' => 'PEAR_Validate');
 
1479         return $this->_channelInfo['validatepackage'];
 
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
 
1489     function &getValidationObject($package = false)
 
1491         if (!class_exists('PEAR_Validate')) {
 
1492             require_once 'PEAR/Validate.php';
 
1495         if (!$this->_isValid) {
 
1496             if (!$this->validate()) {
 
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;
 
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']);
 
1523                 $vclass = str_replace('.', '_',
 
1524                     $this->_channelInfo['validatepackage']['_content']);
 
1528             $val = new PEAR_Validate;
 
1534     function isIncludeable($path)
 
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)) {
 
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
 
1552     function lastModified()
 
1554         if (isset($this->_channelInfo['_lastmodified'])) {
 
1555             return $this->_channelInfo['_lastmodified'];