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'];