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