PluginProbe
FeedWordPress / 2010.0127
FeedWordPress v2010.0127
trunk 0.8 0.9 0.91 0.95 0.96 0.97 0.98 0.981 0.99 0.991 0.992 0.993 2008.1030 2008.1101 2008.1105 2008.1214 2009.0612 2009.0613 2009.0618 2009.0707 2009.1111 2009.1112 2010.0127 2010.0528 All 65 releases
feedwordpress / MagpieRSS-upgrade / rss.php

rss.php in FeedWordPress 2010.0127, at MagpieRSS-upgrade/rss.php

2,045 lines 71.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /* Project: MagpieRSS: a simple RSS integration tool
3 * File: A compiled file for RSS syndication
4 * Author: Kellan Elliot-McCrea <kellan@protest.net>
5 * WordPress development team <http://www.wordpress.org/>
6 * Charles Johnson <technophilia@radgeek.com>
7 * Version: 2010.0122
8 * License: GPL
9 *
10 * Provenance:
11 *
12 * This is a drop-in replacement for the `rss-functions.php` provided with the
13 * WordPress 1.5 distribution, which upgrades the version of MagpieRSS from 0.51
14 * to 0.8a. The update improves handling of character encoding, supports
15 * multiple categories for posts (using <dc:subject> or <category>), supports
16 * Atom 1.0, and implements many other useful features. The file is derived from
17 * a combination of (1) the WordPress development team's modifications to
18 * MagpieRSS 0.51 and (2) the latest bleeding-edge updates to the "official"
19 * MagpieRSS software, including Kellan's original work and some substantial
20 * updates by Charles Johnson. All possible through the magic of the GPL. Yay
21 * for free software!
22 *
23 * Differences from the main branch of MagpieRSS:
24 *
25 * 1. Everything in rss_parse.inc, rss_fetch.inc, rss_cache.inc, and
26 * rss_utils.inc is included in one file.
27 *
28 * 2. MagpieRSS returns the WordPress version as the user agent, rather than
29 * Magpie
30 *
31 * 3. class RSSCache is a modified version by WordPress developers, which
32 * caches feeds in the WordPress database (in the options table), rather
33 * than writing external files directly.
34 *
35 * 4. There are two WordPress-specific functions, get_rss() and wp_rss()
36 *
37 * Differences from the version of MagpieRSS packaged with WordPress:
38 *
39 * 1. Support for translation between multiple character encodings. Under
40 * PHP 5 this is very nicely handled by the XML parsing library. Under PHP
41 * 4 we need to do a little bit of work ourselves, using either iconv or
42 * mb_convert_encoding if it is not one of the (extremely limited) number
43 * of character sets that PHP 4's XML module can handle natively.
44 *
45 * 2. Numerous bug fixes.
46 *
47 * 3. The parser class MagpieRSS has been substantially revised to better
48 * support popular features such as enclosures and multiple categories,
49 * and to support the new Atom 1.0 IETF standard. (Atom feeds are
50 * normalized so as to make the data available using terminology from
51 * either Atom 0.3 or Atom 1.0. Atom 0.3 backward-compatibility is provided
52 * to allow existing software to easily begin accepting Atom 1.0 data; new
53 * software SHOULD NOT depend on the 0.3 terminology, but rather use the
54 * normalization as a convenient way to keep supporting 0.3 feeds while
55 * they linger in the world.)
56 *
57 * The upgraded MagpieRSS can also now handle some content constructs that
58 * had not been handled well by previous versions of Magpie (such as the
59 * use of namespaced XHTML in <xhtml:body> or <xhtml:div> elements to
60 * provide the full content of posts in RSS 2.0 feeds).
61 *
62 * Unlike previous versions of MagpieRSS, this version can parse multiple
63 * instances of the same child element in item/entry and channel/feed
64 * containers. This is done using simple counters next to the element
65 * names: the first <category> element on an RSS item, for example, can be
66 * found in $item['category'] (thus preserving backward compatibility); the
67 * second in $item['category#2'], the third in $item['category#3'], and so
68 * on. The number of categories applied to the item can be found in
69 * $item['category#']
70 *
71 * Also unlike previous versions of MagpieRSS, this version allows you to
72 * access the values of elements' attributes as well as the content they
73 * contain. This can be done using a simple syntax inspired by XPath: to
74 * access the type attribute of an RSS 2.0 enclosure, for example, you
75 * need only access `$item['enclosure@type']`. A comma-separated list of
76 * attributes for the enclosure element is stored in `$item['enclosure@']`.
77 * (This syntax interacts easily with the syntax for multiple categories;
78 * for example, the value of the `scheme` attribute for the fourth category
79 * element on a particular item is stored in `$item['category#4@scheme']`.)
80 *
81 * Note also that this implementation IS NOT backward-compatible with the
82 * kludges that were used to hack in support for multiple categories and
83 * for enclosures in upgraded versions of MagpieRSS distributed with
84 * previous versions of FeedWordPress. If your hacks or filter plugins
85 * depended on the old way of doing things... well, I warned you that they
86 * might not be permanent. Sorry!
87 */
88
89 define('RSS', 'RSS');
90 define('ATOM', 'Atom');
91
92 ################################################################################
93 ## WordPress: make some settings WordPress-appropriate #########################
94 ################################################################################
95
96 define('MAGPIE_USER_AGENT', 'WordPress/' . $wp_version . '(+http://www.wordpress.org)');
97
98 $wp_encoding = get_option('blog_charset', /*default=*/ 'ISO-8859-1');
99 define('MAGPIE_OUTPUT_ENCODING', ($wp_encoding?$wp_encoding:'ISO-8859-1'));
100
101 ################################################################################
102 ## rss_parse.inc: from MagpieRSS 0.85 ##########################################
103 ################################################################################
104
105 /**
106 * Hybrid parser, and object, takes RSS as a string and returns a simple object.
107 *
108 * see: rss_fetch.inc for a simpler interface with integrated caching support
109 *
110 */
111 class MagpieRSS {
112 var $parser;
113
114 var $current_item = array(); // item currently being parsed
115 var $items = array(); // collection of parsed items
116 var $channel = array(); // hash of channel fields
117 var $textinput = array();
118 var $image = array();
119 var $feed_type;
120 var $feed_version;
121 var $encoding = ''; // output encoding of parsed rss
122
123 var $_source_encoding = ''; // only set if we have to parse xml prolog
124
125 var $ERROR = "";
126 var $WARNING = "";
127
128 // define some constants
129 var $_XMLNS_FAMILIAR = array (
130 'http://www.w3.org/2005/Atom' => 'atom' /* 1.0 */,
131 'http://purl.org/atom/ns#' => 'atom' /* pre-1.0 */,
132 'http://purl.org/rss/1.0/' => 'rss' /* 1.0 */,
133 'http://backend.userland.com/RSS2' => 'rss' /* 2.0 */,
134 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' => 'rdf',
135 'http://www.w3.org/1999/xhtml' => 'xhtml',
136 'http://purl.org/dc/elements/1.1/' => 'dc',
137 'http://purl.org/dc/terms/' => 'dcterms',
138 'http://purl.org/rss/1.0/modules/content/' => 'content',
139 'http://purl.org/rss/1.0/modules/syndication/' => 'sy',
140 'http://purl.org/rss/1.0/modules/taxonomy/' => 'taxo',
141 'http://purl.org/rss/1.0/modules/dc/' => 'dc',
142 'http://wellformedweb.org/CommentAPI/' => 'wfw',
143 'http://webns.net/mvcb/' => 'admin',
144 'http://purl.org/rss/1.0/modules/annotate/' => 'annotate',
145 'http://xmlns.com/foaf/0.1/' => 'foaf',
146 'http://madskills.com/public/xml/rss/module/trackback/' => 'trackback',
147 'http://web.resource.org/cc/' => 'cc',
148 'http://search.yahoo.com/mrss' => 'media',
149 'http://search.yahoo.com/mrss/' => 'media',
150 'http://video.search.yahoo.com/mrss' => 'media',
151 'http://video.search.yahoo.com/mrss/' => 'media',
152 );
153
154 var $_XMLBASE_RESOLVE = array (
155 // Atom 0.3 and 1.0 xml:base support
156 'atom' => array (
157 'link' => array ('href' => true),
158 'content' => array ('src' => true, '*xml' => true, '*html' => true),
159 'summary' => array ('*xml' => true, '*html' => true),
160 'title' => array ('*xml' => true, '*html' => true),
161 'rights' => array ('*xml' => true, '*html' => true),
162 'subtitle' => array ('*xml' => true, '*html' => true),
163 'info' => array('*xml' => true, '*html' => true),
164 'tagline' => array('*xml' => true, '*html' => true),
165 'copyright' => array ('*xml' => true, '*html' => true),
166 'generator' => array ('uri' => true, 'url' => true),
167 'uri' => array ('*content' => true),
168 'url' => array ('*content' => true),
169 'icon' => array ('*content' => true),
170 'logo' => array ('*content' => true),
171 ),
172
173 // for inline namespaced XHTML
174 'xhtml' => array (
175 'a' => array ('href' => true),
176 'applet' => array('codebase' => true),
177 'area' => array('href' => true),
178 'blockquote' => array('cite' => true),
179 'body' => array('background' => true),
180 'del' => array('cite' => true),
181 'form' => array('action' => true),
182 'frame' => array('longdesc' => true, 'src' => true),
183 'iframe' => array('longdesc' => true, 'iframe' => true, 'src' => true),
184 'head' => array('profile' => true),
185 'img' => array('longdesc' => true, 'src' => true, 'usemap' => true),
186 'input' => array('src' => true, 'usemap' => true),
187 'ins' => array('cite' => true),
188 'link' => array('href' => true),
189 'object' => array('classid' => true, 'codebase' => true, 'data' => true, 'usemap' => true),
190 'q' => array('cite' => true),
191 'script' => array('src' => true),
192 ),
193 );
194
195 var $_ATOM_CONTENT_CONSTRUCTS = array(
196 'content', 'summary', 'title', /* common */
197 'info', 'tagline', 'copyright', /* Atom 0.3 */
198 'rights', 'subtitle', /* Atom 1.0 */
199 );
200 var $_XHTML_CONTENT_CONSTRUCTS = array('body', 'div');
201 var $_KNOWN_ENCODINGS = array('UTF-8', 'US-ASCII', 'ISO-8859-1');
202
203 // parser variables, useless if you're not a parser, treat as private
204 var $stack = array('element' => array (), 'ns' => array (), 'xmlns' => array (), 'xml:base' => array ()); // stack of XML data
205
206 var $inchannel = false;
207 var $initem = false;
208
209 var $incontent = array(); // non-empty if in namespaced XML content field
210 var $xml_escape = false; // true when accepting namespaced XML
211 var $exclude_top = false; // true when Atom 1.0 type="xhtml"
212
213 var $intextinput = false;
214 var $inimage = false;
215 var $root_namespaces = array();
216 var $current_namespace = false;
217 var $working_namespace_table = array();
218
219 /**
220 * Set up XML parser, parse source, and return populated RSS object..
221 *
222 * @param string $source string containing the RSS to be parsed
223 *
224 * NOTE: Probably a good idea to leave the encoding options alone unless
225 * you know what you're doing as PHP's character set support is
226 * a little weird.
227 *
228 * NOTE: A lot of this is unnecessary but harmless with PHP5
229 *
230 *
231 * @param string $output_encoding output the parsed RSS in this character
232 * set defaults to ISO-8859-1 as this is PHP's
233 * default.
234 *
235 * NOTE: might be changed to UTF-8 in future
236 * versions.
237 *
238 * @param string $input_encoding the character set of the incoming RSS source.
239 * Leave blank and Magpie will try to figure it
240 * out.
241 *
242 *
243 * @param bool $detect_encoding if false Magpie won't attempt to detect
244 * source encoding. (caveat emptor)
245 *
246 */
247 function MagpieRSS ($source, $output_encoding='ISO-8859-1',
248 $input_encoding=null, $detect_encoding=true, $base_uri=null)
249 {
250 # if PHP xml isn't compiled in, die
251 #
252 if (!function_exists('xml_parser_create')) {
253 $this->error( "Failed to load PHP's XML Extension. " .
254 "http://www.php.net/manual/en/ref.xml.php",
255 E_USER_ERROR );
256 }
257
258 list($parser, $source) = $this->create_parser($source,
259 $output_encoding, $input_encoding, $detect_encoding);
260
261
262 if (!is_resource($parser)) {
263 $this->error( "Failed to create an instance of PHP's XML parser. " .
264 "http://www.php.net/manual/en/ref.xml.php",
265 E_USER_ERROR );
266 }
267
268
269 $this->parser = $parser;
270
271 # pass in parser, and a reference to this object
272 # setup handlers
273 #
274 xml_set_object( $this->parser, $this );
275 xml_set_element_handler($this->parser,
276 'feed_start_element', 'feed_end_element' );
277
278 xml_set_character_data_handler( $this->parser, 'feed_cdata' );
279
280 $this->stack['xml:base'] = array($base_uri);
281
282 $status = xml_parse( $this->parser, $source );
283
284 if (! $status ) {
285 $errorcode = xml_get_error_code( $this->parser );
286 if ( $errorcode != XML_ERROR_NONE ) {
287 $xml_error = xml_error_string( $errorcode );
288 $error_line = xml_get_current_line_number($this->parser);
289 $error_col = xml_get_current_column_number($this->parser);
290 $errormsg = "$xml_error at line $error_line, column $error_col";
291
292 $this->error( $errormsg );
293 }
294 }
295
296 xml_parser_free( $this->parser );
297
298 $this->normalize();
299 }
300
301 function feed_start_element($p, $element, &$attributes) {
302 $el = strtolower($element);
303
304 $namespaces = end($this->stack['xmlns']);
305 $baseuri = end($this->stack['xml:base']);
306
307 if (isset($attributes['xml:base'])) {
308 $baseuri = Relative_URI::resolve($attributes['xml:base'], $baseuri);
309 }
310 array_push($this->stack['xml:base'], $baseuri);
311
312 // scan for xml namespace declarations. ugly ugly ugly.
313 // theoretically we could use xml_set_start_namespace_decl_handler and
314 // xml_set_end_namespace_decl_handler to handle this more elegantly, but
315 // support for these is buggy
316 foreach ($attributes as $attr => $value) {
317 if ( preg_match('/^xmlns(\:([A-Z_a-z].*))?$/', $attr, $match) ) {
318 $ns = (isset($match[2]) ? $match[2] : '');
319 $namespaces[$ns] = $value;
320 }
321 }
322
323 array_push($this->stack['xmlns'], $namespaces);
324
325 // check for a namespace, and split if found
326 // Don't munge content tags
327 $ns = $this->xmlns($element);
328 if ( empty($this->incontent) ) {
329 $el = strtolower($ns['element']);
330 $this->current_namespace = $ns['effective'];
331 array_push($this->stack['ns'], $ns['effective']);
332 }
333
334 $nsc = $ns['canonical']; $nse = $ns['element'];
335 if ( isset($this->_XMLBASE_RESOLVE[$nsc][$nse]) ) {
336 if (isset($this->_XMLBASE_RESOLVE[$nsc][$nse]['*xml'])) {
337 $attributes['xml:base'] = $baseuri;
338 }
339 foreach ($attributes as $key => $value) {
340 if (isset($this->_XMLBASE_RESOLVE[$nsc][$nse][strtolower($key)])) {
341 $attributes[$key] = Relative_URI::resolve($attributes[$key], $baseuri);
342 }
343 }
344 }
345
346 $attrs = array_change_key_case($attributes, CASE_LOWER);
347
348 # if feed type isn't set, then this is first element of feed
349 # identify feed from root element
350 #
351 if (!isset($this->feed_type) ) {
352 if ( $el == 'rdf' ) {
353 $this->feed_type = RSS;
354 $this->root_namespaces = array('rss', 'rdf');
355 $this->feed_version = '1.0';
356 }
357 elseif ( $el == 'rss' ) {
358 $this->feed_type = RSS;
359 $this->root_namespaces = array('rss');
360 $this->feed_version = $attrs['version'];
361 }
362 elseif ( $el == 'feed' ) {
363 $this->feed_type = ATOM;
364 $this->root_namespaces = array('atom');
365 if ($ns['uri'] == 'http://www.w3.org/2005/Atom') { // Atom 1.0
366 $this->feed_version = '1.0';
367 }
368 else { // Atom 0.3, probably.
369 $this->feed_version = $attrs['version'];
370 }
371 $this->inchannel = true;
372 }
373 return;
374 }
375
376 // if we're inside a namespaced content construct, treat tags as text
377 if ( !empty($this->incontent) )
378 {
379 if ((count($this->incontent) > 1) or !$this->exclude_top) {
380 if ($ns['effective']=='xhtml') {
381 $tag = $ns['element'];
382 }
383 else {
384 $tag = $element;
385 $xmlns = 'xmlns';
386 if (strlen($ns['prefix'])>0) {
387 $xmlns = $xmlns . ':' . $ns['prefix'];
388 }
389 $attributes[$xmlns] = $ns['uri']; // make sure it's visible
390 }
391
392 // if tags are inlined, then flatten
393 $attrs_str = join(' ',
394 array_map(array($this, 'map_attrs'),
395 array_keys($attributes),
396 array_values($attributes) )
397 );
398
399 if (strlen($attrs_str) > 0) { $attrs_str = ' '.$attrs_str; }
400 $this->append_content( "<{$tag}{$attrs_str}>" );
401 }
402 array_push($this->incontent, $ns); // stack for parsing content XML
403 }
404
405 elseif ( $el == 'channel' ) {
406 $this->inchannel = true;
407 }
408
409 elseif ($el == 'item' or $el == 'entry' )
410 {
411 $this->initem = true;
412 if ( isset($attrs['rdf:about']) ) {
413 $this->current_item['about'] = $attrs['rdf:about'];
414 }
415 }
416
417 // if we're in the default namespace of an RSS feed,
418 // record textinput or image fields
419 elseif (
420 $this->feed_type == RSS and
421 $this->current_namespace == '' and
422 $el == 'textinput' )
423 {
424 $this->intextinput = true;
425 }
426
427 elseif (
428 $this->feed_type == RSS and
429 $this->current_namespace == '' and
430 $el == 'image' )
431 {
432 $this->inimage = true;
433 }
434
435 // set stack[0] to current element
436 else {
437 // Atom support many links per containing element.
438 // Magpie treats link elements of type rel='alternate'
439 // as being equivalent to RSS's simple link element.
440
441 $atom_link = false;
442 if ( ($ns['canonical']=='atom') and $el == 'link') {
443 $atom_link = true;
444 if (isset($attrs['rel']) and $attrs['rel'] != 'alternate') {
445 $el = $el . "_" . $attrs['rel']; // pseudo-element names for Atom link elements
446 }
447 }
448 # handle atom content constructs
449 elseif ( ($ns['canonical']=='atom') and in_array($el, $this->_ATOM_CONTENT_CONSTRUCTS) )
450 {
451 // avoid clashing w/ RSS mod_content
452 if ($el == 'content' ) {
453 $el = 'atom_content';
454 }
455
456 // assume that everything accepts namespaced XML
457 // (that will pass through some non-validating feeds;
458 // but so what? this isn't a validating parser)
459 $this->incontent = array();
460 array_push($this->incontent, $ns); // start a stack
461
462 $this->xml_escape = $this->accepts_namespaced_xml($attrs);
463
464 if ( isset($attrs['type']) and trim(strtolower($attrs['type']))=='xhtml') {
465 $this->exclude_top = true;
466 } else {
467 $this->exclude_top = false;
468 }
469 }
470 # Handle inline XHTML body elements --CWJ
471 elseif ($ns['effective']=='xhtml' and in_array($el, $this->_XHTML_CONTENT_CONSTRUCTS)) {
472 $this->current_namespace = 'xhtml';
473 $this->incontent = array();
474 array_push($this->incontent, $ns); // start a stack
475
476 $this->xml_escape = true;
477 $this->exclude_top = false;
478 }
479
480 array_unshift($this->stack['element'], $el);
481 $elpath = join('_', array_reverse($this->stack['element']));
482
483 $n = $this->element_count($elpath);
484 $this->element_count($elpath, $n+1);
485
486 if ($n > 0) {
487 array_shift($this->stack['element']);
488 array_unshift($this->stack['element'], $el.'#'.($n+1));
489 $elpath = join('_', array_reverse($this->stack['element']));
490 }
491
492 // this makes the baby Jesus cry, but we can't do it in normalize()
493 // because we've made the element name for Atom links unpredictable
494 // by tacking on the relation to the end. -CWJ
495 if ($atom_link and isset($attrs['href'])) {
496 $this->append($elpath, $attrs['href']);
497 }
498
499 // add attributes
500 if (count($attrs) > 0) {
501 $this->append($elpath.'@', join(',', array_keys($attrs)));
502 foreach ($attrs as $attr => $value) {
503 $this->append($elpath.'@'.$attr, $value);
504 }
505 }
506 }
507 }
508
509 function feed_cdata ($p, $text) {
510 if ($this->incontent) {
511 if ($this->xml_escape) { $text = htmlspecialchars($text, ENT_COMPAT, $this->encoding); }
512 $this->append_content( $text );
513 } else {
514 $current_el = join('_', array_reverse($this->stack['element']));
515 $this->append($current_el, $text);
516 }
517 }
518
519 function feed_end_element ($p, $el) {
520 $closer = $this->xmlns($el);
521
522 if ( $this->incontent ) {
523 $opener = array_pop($this->incontent);
524
525 // balance tags properly
526 // note: i don't think this is actually neccessary
527 if ($opener != $closer) {
528 array_push($this->incontent, $opener);
529 $this->append_content("<$el />");
530 } elseif ($this->incontent) { // are we in the content construct still?
531 if ((count($this->incontent) > 1) or !$this->exclude_top) {
532 if ($closer['effective']=='xhtml') {
533 $tag = $closer['element'];
534 }
535 else {
536 $tag = $el;
537 }
538 $this->append_content("</$tag>");
539 }
540 } else { // if we're done with the content construct, shift the opening of the content construct off the normal stack
541 array_shift( $this->stack['element'] );
542 }
543 }
544 elseif ($closer['effective'] == '') {
545 $el = strtolower($closer['element']);
546 if ( $el == 'item' or $el == 'entry' ) {
547 $this->items[] = $this->current_item;
548 $this->current_item = array();
549 $this->initem = false;
550 $this->current_category = 0;
551 }
552 elseif ($this->feed_type == RSS and $el == 'textinput' ) {
553 $this->intextinput = false;
554 }
555 elseif ($this->feed_type == RSS and $el == 'image' ) {
556 $this->inimage = false;
557 }
558 elseif ($el == 'channel' or $el == 'feed' ) {
559 $this->inchannel = false;
560 } else {
561 $nsc = $closer['canonical']; $nse = $closer['element'];
562 if (isset($this->_XMLBASE_RESOLVE[$nsc][$nse]['*content'])) {
563 // Resolve relative URI in content of tag
564 $this->dereference_current_element();
565 }
566 array_shift( $this->stack['element'] );
567 }
568 } else {
569 $nsc = $closer['canonical']; $nse = strtolower($closer['element']);
570 if (isset($this->_XMLBASE_RESOLVE[$nsc][$nse]['*content'])) {
571 // Resolve relative URI in content of tag
572 $this->dereference_current_element();
573 }
574 array_shift( $this->stack['element'] );
575 }
576
577 if ( !$this->incontent ) { // Don't munge the namespace after finishing with elements in namespaced content constructs -CWJ
578 $this->current_namespace = array_pop($this->stack['ns']);
579 }
580 array_pop($this->stack['xmlns']);
581 array_pop($this->stack['xml:base']);
582 }
583
584 // Namespace handling functions
585 function xmlns ($element) {
586 $namespaces = end($this->stack['xmlns']);
587 $ns = '';
588 if ( strpos( $element, ':' ) ) {
589 list($ns, $element) = split( ':', $element, 2);
590 }
591
592 $uri = (isset($namespaces[$ns]) ? $namespaces[$ns] : null);
593
594 if (!is_null($uri)) {
595 $canonical = (
596 isset($this->_XMLNS_FAMILIAR[$uri])
597 ? $this->_XMLNS_FAMILIAR[$uri]
598 : $uri
599 );
600 } else {
601 $canonical = $ns;
602 }
603
604 if (in_array($canonical, $this->root_namespaces)) {
605 $effective = '';
606 } else {
607 $effective = $canonical;
608 }
609
610 return array('effective' => $effective, 'canonical' => $canonical, 'prefix' => $ns, 'uri' => $uri, 'element' => $element);
611 }
612
613 // Utility functions for accessing data structure
614
615 // for smart, namespace-aware methods...
616 function magpie_data ($el, $method, $text = NULL) {
617 $ret = NULL;
618 if ($el) {
619 if (is_array($method)) {
620 $el = $this->{$method['key']}($el);
621 $method = $method['value'];
622 }
623
624 if ( $this->current_namespace ) {
625 if ( $this->initem ) {
626 $ret = $this->{$method} (
627 $this->current_item[ $this->current_namespace ][ $el ],
628 $text
629 );
630 }
631 elseif ($this->inchannel) {
632 $ret = $this->{$method} (
633 $this->channel[ $this->current_namespace][ $el ],
634 $text
635 );
636 }
637 elseif ($this->intextinput) {
638 $ret = $this->{$method} (
639 $this->textinput[ $this->current_namespace][ $el ],
640 $text
641 );
642 }
643 elseif ($this->inimage) {
644 $ret = $this->{$method} (
645 $this->image[ $this->current_namespace ][ $el ], $text );
646 }
647 }
648 else {
649 if ( $this->initem ) {
650 $ret = $this->{$method} (
651 $this->current_item[ $el ], $text);
652 }
653 elseif ($this->intextinput) {
654 $ret = $this->{$method} (
655 $this->textinput[ $el ], $text );
656 }
657 elseif ($this->inimage) {
658 $ret = $this->{$method} (
659 $this->image[ $el ], $text );
660 }
661 elseif ($this->inchannel) {
662 $ret = $this->{$method} (
663 $this->channel[ $el ], $text );
664 }
665 }
666 }
667 return $ret;
668 }
669
670 function concat (&$str1, $str2="") {
671 if (!isset($str1) ) {
672 $str1="";
673 }
674 $str1 .= $str2;
675 }
676
677 function retrieve_value (&$el, $text /*ignore*/) {
678 return $el;
679 }
680 function replace_value (&$el, $text) {
681 $el = $text;
682 }
683 function counter_key ($el) {
684 return $el.'#';
685 }
686
687
688 function append_content($text) {
689 $construct = reset($this->incontent);
690 $ns = $construct['effective'];
691
692 // Keeping data about parent elements is necessary to
693 // properly handle atom:source and its children elements
694 $tag = join('_', array_reverse($this->stack['element']));
695
696 if ( $this->initem ) {
697 if ($ns) {
698 $this->concat( $this->current_item[$ns][$tag], $text );
699 } else {
700 $this->concat( $this->current_item[$tag], $text );
701 }
702 }
703 elseif ( $this->inchannel ) {
704 if ($this->current_namespace) {
705 $this->concat( $this->channel[$ns][$tag], $text );
706 } else {
707 $this->concat( $this->channel[$tag], $text );
708 }
709 }
710 }
711
712 // smart append - field and namespace aware
713 function append($el, $text) {
714 $this->magpie_data($el, 'concat', $text);
715 }
716
717 function dereference_current_element () {
718 $el = join('_', array_reverse($this->stack['element']));
719 $base = end($this->stack['xml:base']);
720 $uri = $this->magpie_data($el, 'retrieve_value');
721 $this->magpie_data($el, 'replace_value', Relative_URI::resolve($uri, $base));
722 }
723
724 // smart count - field and namespace aware
725 function element_count ($el, $set = NULL) {
726 if (!is_null($set)) {
727 $ret = $this->magpie_data($el, array('key' => 'counter_key', 'value' => 'replace_value'), $set);
728 }
729 $ret = $this->magpie_data($el, array('key' => 'counter_key', 'value' => 'retrieve_value'));
730 return ($ret ? $ret : 0);
731 }
732
733 function normalize_enclosure (&$source, $from, &$dest, $to, $i) {
734 $id_from = $this->element_id($from, $i);
735 $id_to = $this->element_id($to, $i);
736 if (isset($source["{$id_from}@"])) {
737 foreach (explode(',', $source["{$id_from}@"]) as $attr) {
738 if ($from=='link_enclosure' and $attr=='href') { // from Atom
739 $dest["{$id_to}@url"] = $source["{$id_from}@{$attr}"];
740 $dest["{$id_to}"] = $source["{$id_from}@{$attr}"];
741 }
742 elseif ($from=='enclosure' and $attr=='url') { // from RSS
743 $dest["{$id_to}@href"] = $source["{$id_from}@{$attr}"];
744 $dest["{$id_to}"] = $source["{$id_from}@{$attr}"];
745 }
746 else {
747 $dest["{$id_to}@{$attr}"] = $source["{$id_from}@{$attr}"];
748 }
749 }
750 }
751 }
752
753 function normalize_atom_person (&$source, $person, &$dest, $to, $i) {
754 $id = $this->element_id($person, $i);
755 $id_to = $this->element_id($to, $i);
756
757 // Atom 0.3 <=> Atom 1.0
758 if ($this->feed_version >= 1.0) { $used = 'uri'; $norm = 'url'; }
759 else { $used = 'url'; $norm = 'uri'; }
760
761 if (isset($source["{$id}_{$used}"])) {
762 $dest["{$id_to}_{$norm}"] = $source["{$id}_{$used}"];
763 }
764
765 // Atom to RSS 2.0 and Dublin Core
766 // RSS 2.0 person strings should be valid e-mail addresses if possible.
767 if (isset($source["{$id}_email"])) {
768 $rss_author = $source["{$id}_email"];
769 }
770 if (isset($source["{$id}_name"])) {
771 $rss_author = $source["{$id}_name"]
772 . (isset($rss_author) ? " <$rss_author>" : '');
773 }
774 if (isset($rss_author)) {
775 $source[$id] = $rss_author; // goes to top-level author or contributor
776 $dest[$id_to] = $rss_author; // goes to dc:creator or dc:contributor
777 }
778 }
779
780 // Normalize Atom 1.0 and RSS 2.0 categories to Dublin Core...
781 function normalize_category (&$source, $from, &$dest, $to, $i) {
782 $cat_id = $this->element_id($from, $i);
783 $dc_id = $this->element_id($to, $i);
784
785 // first normalize category elements: Atom 1.0 <=> RSS 2.0
786 if ( isset($source["{$cat_id}@term"]) ) { // category identifier
787 $source[$cat_id] = $source["{$cat_id}@term"];
788 } elseif ( $this->feed_type == RSS ) {
789 $source["{$cat_id}@term"] = $source[$cat_id];
790 }
791
792 if ( isset($source["{$cat_id}@scheme"]) ) { // URI to taxonomy
793 $source["{$cat_id}@domain"] = $source["{$cat_id}@scheme"];
794 } elseif ( isset($source["{$cat_id}@domain"]) ) {
795 $source["{$cat_id}@scheme"] = $source["{$cat_id}@domain"];
796 }
797
798 // Now put the identifier into dc:subject
799 $dest[$dc_id] = $source[$cat_id];
800 }
801
802 // ... or vice versa
803 function normalize_dc_subject (&$source, $from, &$dest, $to, $i) {
804 $dc_id = $this->element_id($from, $i);
805 $cat_id = $this->element_id($to, $i);
806
807 $dest[$cat_id] = $source[$dc_id]; // RSS 2.0
808 $dest["{$cat_id}@term"] = $source[$dc_id]; // Atom 1.0
809 }
810
811 // simplify the logic for normalize(). Makes sure that count of elements and
812 // each of multiple elements is normalized properly. If you need to mess
813 // with things like attributes or change formats or the like, pass it a
814 // callback to handle each element.
815 function normalize_element (&$source, $from, &$dest, $to, $via = NULL) {
816 if (isset($source[$from]) or isset($source["{$from}#"])) {
817 if (isset($source["{$from}#"])) {
818 $n = $source["{$from}#"];
819 $dest["{$to}#"] = $source["{$from}#"];
820 }
821 else { $n = 1; }
822
823 for ($i = 1; $i <= $n; $i++) {
824 if (isset($via)) { // custom callback for ninja attacks
825 $this->{$via}($source, $from, $dest, $to, $i);
826 }
827 else { // just make it the same
828 $from_id = $this->element_id($from, $i);
829 $to_id = $this->element_id($to, $i);
830 $dest[$to_id] = $source[$from_id];
831 }
832 }
833 }
834 }
835
836 function normalize () {
837 // if atom populate rss fields and normalize 0.3 and 1.0 feeds
838 if ( $this->is_atom() ) {
839 // Atom 1.0 elements <=> Atom 0.3 elements (Thanks, o brilliant wordsmiths of the Atom 1.0 standard!)
840 if ($this->feed_version < 1.0) {
841 $this->normalize_element($this->channel, 'tagline', $this->channel, 'subtitle');
842 $this->normalize_element($this->channel, 'copyright', $this->channel, 'rights');
843 $this->normalize_element($this->channel, 'modified', $this->channel, 'updated');
844 } else {
845 $this->normalize_element($this->channel, 'subtitle', $this->channel, 'tagline');
846 $this->normalize_element($this->channel, 'rights', $this->channel, 'copyright');
847 $this->normalize_element($this->channel, 'updated', $this->channel, 'modified');
848 }
849 $this->normalize_element($this->channel, 'author', $this->channel['dc'], 'creator', 'normalize_atom_person');
850 $this->normalize_element($this->channel, 'contributor', $this->channel['dc'], 'contributor', 'normalize_atom_person');
851
852 // Atom elements to RSS elements
853 $this->normalize_element($this->channel, 'subtitle', $this->channel, 'description');
854
855 if ( isset($this->channel['logo']) ) {
856 $this->normalize_element($this->channel, 'logo', $this->image, 'url');
857 $this->normalize_element($this->channel, 'link', $this->image, 'link');
858 $this->normalize_element($this->channel, 'title', $this->image, 'title');
859 }
860
861 for ( $i = 0; $i < count($this->items); $i++) {
862 $item = $this->items[$i];
863
864 // Atom 1.0 elements <=> Atom 0.3 elements
865 if ($this->feed_version < 1.0) {
866 $this->normalize_element($item, 'modified', $item, 'updated');
867 $this->normalize_element($item, 'issued', $item, 'published');
868 } else {
869 $this->normalize_element($item, 'updated', $item, 'modified');
870 $this->normalize_element($item, 'published', $item, 'issued');
871 }
872
873 // "If an atom:entry element does not contain
874 // atom:author elements, then the atom:author elements
875 // of the contained atom:source element are considered
876 // to apply. In an Atom Feed Document, the atom:author
877 // elements of the containing atom:feed element are
878 // considered to apply to the entry if there are no
879 // atom:author elements in the locations described
880 // above." <http://atompub.org/2005/08/17/draft-ietf-atompub-format-11.html#rfc.section.4.2.1>
881 if (!isset($item["author#"])) {
882 if (isset($item["source_author#"])) { // from aggregation source
883 $source = $item;
884 $author = "source_author";
885 } elseif (isset($this->channel["author#"])) { // from containing feed
886 $source = $this->channel;
887 $author = "author";
888 } else {
889 $author = null;
890 }
891
892 if (!is_null($author)) {
893 $item["author#"] = $source["{$author}#"];
894 for ($au = 1; $au <= $item["author#"]; $au++) {
895 $id_to = $this->element_id('author', $au);
896 $id_from = $this->element_id($author, $au);
897
898 $item[$id_to] = $source[$id_from];
899 foreach (array('name', 'email', 'uri', 'url') as $what) {
900 if (isset($source["{$id_from}_{$what}"])) {
901 $item["{$id_to}_{$what}"] = $source["{$id_from}_{$what}"];
902 }
903 }
904 }
905 }
906 }
907
908 // Atom elements to RSS elements
909 $this->normalize_element($item, 'author', $item['dc'], 'creator', 'normalize_atom_person');
910 $this->normalize_element($item, 'contributor', $item['dc'], 'contributor', 'normalize_atom_person');
911 $this->normalize_element($item, 'summary', $item, 'description');
912 $this->normalize_element($item, 'atom_content', $item['content'], 'encoded');
913 $this->normalize_element($item, 'link_enclosure', $item, 'enclosure', 'normalize_enclosure');
914
915 // Categories
916 if ( isset($item['category#']) ) { // Atom 1.0 categories to dc:subject and RSS 2.0 categories
917 $this->normalize_element($item, 'category', $item['dc'], 'subject', 'normalize_category');
918 }
919 elseif ( isset($item['dc']['subject#']) ) { // dc:subject to Atom 1.0 and RSS 2.0 categories
920 $this->normalize_element($item['dc'], 'subject', $item, 'category', 'normalize_dc_subject');
921 }
922
923 // Normalized item timestamp
924 $atom_date = (isset($item['published']) ) ? $item['published'] : $item['updated'];
925 if ( $atom_date ) {
926 $epoch = @parse_w3cdtf($atom_date);
927 if ($epoch and $epoch > 0) {
928 $item['date_timestamp'] = $epoch;
929 }
930 }
931
932 $this->items[$i] = $item;
933 }
934 }
935 elseif ( $this->is_rss() ) {
936 // RSS elements to Atom elements
937 $this->normalize_element($this->channel, 'description', $this->channel, 'tagline'); // Atom 0.3
938 $this->normalize_element($this->channel, 'description', $this->channel, 'subtitle'); // Atom 1.0 (yay wordsmithing!)
939 $this->normalize_element($this->image, 'url', $this->channel, 'logo');
940
941 for ( $i = 0; $i < count($this->items); $i++) {
942 $item = $this->items[$i];
943
944 // RSS elements to Atom elements
945 $this->normalize_element($item, 'description', $item, 'summary');
946 $this->normalize_element($item, 'enclosure', $item, 'link_enclosure', 'normalize_enclosure');
947
948 // Categories
949 if ( isset($item['category#']) ) { // RSS 2.0 categories to dc:subject and Atom 1.0 categories
950 $this->normalize_element($item, 'category', $item['dc'], 'subject', 'normalize_category');
951 }
952 elseif ( isset($item['dc']['subject#']) ) { // dc:subject to Atom 1.0 and RSS 2.0 categories
953 $this->normalize_element($item['dc'], 'subject', $item, 'category', 'normalize_dc_subject');
954 }
955
956 // Normalized item timestamp
957 if ( $this->is_rss() == '1.0' and isset($item['dc']['date']) ) {
958 $epoch = @parse_w3cdtf($item['dc']['date']);
959 if ($epoch and $epoch > 0) {
960 $item['date_timestamp'] = $epoch;
961 }
962 }
963 elseif ( isset($item['pubdate']) ) {
964 $epoch = @strtotime($item['pubdate']);
965 if ($epoch > 0) {
966 $item['date_timestamp'] = $epoch;
967 }
968 }
969
970 $this->items[$i] = $item;
971 }
972 }
973 }
974
975
976 function is_rss () {
977 if ( $this->feed_type == RSS ) {
978 return $this->feed_version;
979 }
980 else {
981 return false;
982 }
983 }
984
985 function is_atom() {
986 if ( $this->feed_type == ATOM ) {
987 return $this->feed_version;
988 }
989 else {
990 return false;
991 }
992 }
993
994 /**
995 * return XML parser, and possibly re-encoded source
996 *
997 */
998 function create_parser($source, $out_enc, $in_enc, $detect) {
999 if ( substr(phpversion(),0,1) == 5) {
1000 $parser = $this->php5_create_parser($in_enc, $detect);
1001 }
1002 else {
1003 list($parser, $source) = $this->php4_create_parser($source, $in_enc, $detect);
1004 }
1005 if ($out_enc) {
1006 $this->encoding = $out_enc;
1007 xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $out_enc);
1008 }
1009 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
1010 return array($parser, $source);
1011 }
1012
1013 /**
1014 * Instantiate an XML parser under PHP5
1015 *
1016 * PHP5 will do a fine job of detecting input encoding
1017 * if passed an empty string as the encoding.
1018 *
1019 * All hail libxml2!
1020 *
1021 */
1022 function php5_create_parser($in_enc, $detect) {
1023 // by default php5 does a fine job of detecting input encodings
1024 if(!$detect && $in_enc) {
1025 return xml_parser_create($in_enc);
1026 }
1027 else {
1028 return xml_parser_create('');
1029 }
1030 }
1031
1032 /**
1033 * Instaniate an XML parser under PHP4
1034 *
1035 * Unfortunately PHP4's support for character encodings
1036 * and especially XML and character encodings sucks. As
1037 * long as the documents you parse only contain characters
1038 * from the ISO-8859-1 character set (a superset of ASCII,
1039 * and a subset of UTF-8) you're fine. However once you
1040 * step out of that comfy little world things get mad, bad,
1041 * and dangerous to know.
1042 *
1043 * The following code is based on SJM's work with FoF
1044 * @see http://minutillo.com/steve/weblog/2004/6/17/php-xml-and-character-encodings-a-tale-of-sadness-rage-and-data-loss
1045 *
1046 */
1047 function php4_create_parser($source, $in_enc, $detect) {
1048 if ( !$detect ) {
1049 return array(xml_parser_create($in_enc), $source);
1050 }
1051
1052 if (!$in_enc) {
1053 if (preg_match('/<?xml.*encoding=[\'"](.*?)[\'"].*?>/m', $source, $m)) {
1054 $in_enc = strtoupper($m[1]);
1055 $this->source_encoding = $in_enc;
1056 }
1057 else {
1058 $in_enc = 'UTF-8';
1059 }
1060 }
1061
1062 if ($this->known_encoding($in_enc)) {
1063 return array(xml_parser_create($in_enc), $source);
1064 }
1065
1066 // the dectected encoding is not one of the simple encodings PHP knows
1067
1068 // attempt to use the iconv extension to
1069 // cast the XML to a known encoding
1070 // @see http://php.net/iconv
1071
1072 if (function_exists('iconv')) {
1073 $encoded_source = iconv($in_enc,'UTF-8', $source);
1074 if ($encoded_source) {
1075 return array(xml_parser_create('UTF-8'), $encoded_source);
1076 }
1077 }
1078
1079 // iconv didn't work, try mb_convert_encoding
1080 // @see http://php.net/mbstring
1081 if(function_exists('mb_convert_encoding')) {
1082 $encoded_source = mb_convert_encoding($source, 'UTF-8', $in_enc );
1083 if ($encoded_source) {
1084 return array(xml_parser_create('UTF-8'), $encoded_source);
1085 }
1086 }
1087
1088 // else
1089 $this->error("Feed is in an unsupported character encoding. ($in_enc) " .
1090 "You may see strange artifacts, and mangled characters.",
1091 E_USER_NOTICE);
1092
1093 return array(xml_parser_create(), $source);
1094 }
1095
1096 function known_encoding($enc) {
1097 $enc = strtoupper($enc);
1098 if ( in_array($enc, $this->_KNOWN_ENCODINGS) ) {
1099 return $enc;
1100 }
1101 else {
1102 return false;
1103 }
1104 }
1105
1106 function error ($errormsg, $lvl=E_USER_WARNING) {
1107 // append PHP's error message if track_errors enabled
1108 if ( isset($php_errormsg) ) {
1109 $errormsg .= " ($php_errormsg)";
1110 }
1111 if ( MAGPIE_DEBUG ) {
1112 trigger_error( $errormsg, $lvl);
1113 }
1114 else {
1115 error_log( $errormsg, 0);
1116 }
1117
1118 $notices = E_USER_NOTICE|E_NOTICE;
1119 if ( $lvl&$notices ) {
1120 $this->WARNING = $errormsg;
1121 } else {
1122 $this->ERROR = $errormsg;
1123 }
1124 }
1125
1126 // magic ID function for multiple elemenets.
1127 // can be called as static MagpieRSS::element_id()
1128 function element_id ($el, $counter) {
1129 return $el . (($counter > 1) ? '#'.$counter : '');
1130 }
1131
1132 function map_attrs($k, $v) {
1133 return $k.'="'.htmlspecialchars($v, ENT_COMPAT, $this->encoding).'"';
1134 }
1135
1136 function accepts_namespaced_xml ($attrs) {
1137 $mode = (isset($attrs['mode']) ? trim(strtolower($attrs['mode'])) : 'xml');
1138 $type = (isset($attrs['type']) ? trim(strtolower($attrs['type'])) : null);
1139 if ($this->feed_type == ATOM and $this->feed_version < 1.0) {
1140 if ($mode=='xml' and preg_match(':[/+](html|xml)$:i', $type)) {
1141 $ret = true;
1142 } else {
1143 $ret = false;
1144 }
1145 } elseif ($this->feed_type == ATOM and $this->feed_version >= 1.0) {
1146 if ($type=='xhtml' or preg_match(':[/+]xml$:i', $type)) {
1147 $ret = true;
1148 } else {
1149 $ret = false;
1150 }
1151 } else {
1152 $ret = false; // Don't munge unless you're sure
1153 }
1154 return $ret;
1155 }
1156 } // end class RSS
1157
1158
1159 // patch to support medieval versions of PHP4.1.x,
1160 // courtesy, Ryan Currie, ryan@digibliss.com
1161
1162 if (!function_exists('array_change_key_case')) {
1163 define("CASE_UPPER",1);
1164 define("CASE_LOWER",0);
1165
1166
1167 function array_change_key_case($array,$case=CASE_LOWER) {
1168 if ($case==CASE_LOWER) $cmd='strtolower';
1169 elseif ($case==CASE_UPPER) $cmd='strtoupper';
1170 foreach($array as $key=>$value) {
1171 $output[$cmd($key)]=$value;
1172 }
1173 return $output;
1174 }
1175
1176 }
1177
1178 ################################################################################
1179 ## WordPress: Load in Snoopy from wp-includes ##################################
1180 ################################################################################
1181
1182 if (!function_exists('wp_remote_request')) :
1183 require_once( dirname(__FILE__) . '/class-snoopy.php');
1184 endif;
1185
1186 ################################################################################
1187 ## rss_fetch.inc: from MagpieRSS 0.8a ##########################################
1188 ################################################################################
1189
1190 /*=======================================================================*\
1191 Function: fetch_rss:
1192 Purpose: return RSS object for the give url
1193 maintain the cache
1194 Input: url of RSS file
1195 Output: parsed RSS object (see rss_parse.inc)
1196
1197 NOTES ON CACHEING:
1198 If caching is on (MAGPIE_CACHE_ON) fetch_rss will first check the cache.
1199
1200 NOTES ON RETRIEVING REMOTE FILES:
1201 If conditional gets are on (MAGPIE_CONDITIONAL_GET_ON) fetch_rss will
1202 return a cached object, and touch the cache object upon recieving a
1203 304.
1204
1205 NOTES ON FAILED REQUESTS:
1206 If there is an HTTP error while fetching an RSS object, the cached
1207 version will be return, if it exists (and if MAGPIE_CACHE_FRESH_ONLY is off)
1208 \*=======================================================================*/
1209
1210 define('MAGPIE_VERSION', '2010.0122');
1211
1212 $MAGPIE_ERROR = "";
1213
1214 function fetch_rss ($url) {
1215 // initialize constants
1216 init();
1217
1218 if ( !isset($url) ) {
1219 error("fetch_rss called without a url");
1220 return false;
1221 }
1222
1223 // if cache is disabled
1224 if ( !MAGPIE_CACHE_ON ) {
1225 // fetch file, and parse it
1226 $resp = _fetch_remote_file( $url );
1227 if ( is_success( $resp->status ) ) {
1228 return _response_to_rss( $resp, $url );
1229 }
1230 else {
1231 error("Failed to fetch $url and cache is off");
1232 return false;
1233 }
1234 }
1235 // else cache is ON
1236 else {
1237 // Flow
1238 // 1. check cache
1239 // 2. if there is a hit, make sure its fresh
1240 // 3. if cached obj fails freshness check, fetch remote
1241 // 4. if remote fails, return stale object, or error
1242
1243 $cache = new RSSCache( MAGPIE_CACHE_DIR, MAGPIE_CACHE_AGE );
1244
1245 if (MAGPIE_DEBUG and $cache->ERROR) {
1246 debug($cache->ERROR, E_USER_WARNING);
1247 }
1248
1249
1250 $cache_status = 0; // response of check_cache
1251 $request_headers = array(); // HTTP headers to send with fetch
1252 $rss = 0; // parsed RSS object
1253 $errormsg = 0; // errors, if any
1254
1255 // store parsed XML by desired output encoding
1256 // as character munging happens at parse time
1257 $cache_key = $url . MAGPIE_OUTPUT_ENCODING;
1258
1259 if (!$cache->ERROR) {
1260 // return cache HIT, MISS, or STALE
1261 $cache_status = $cache->check_cache( $cache_key);
1262 }
1263
1264 // if object cached, and cache is fresh, return cached obj
1265 if ( $cache_status == 'HIT' ) {
1266 $rss = $cache->get( $cache_key );
1267 if ( isset($rss) and $rss ) {
1268 // should be cache age
1269 $rss->from_cache = 1;
1270 if ( MAGPIE_DEBUG > 1) {
1271 debug("MagpieRSS: Cache HIT", E_USER_NOTICE);
1272 }
1273 return $rss;
1274 }
1275 }
1276
1277 // else attempt a conditional get
1278
1279 // setup headers
1280 if ( $cache_status == 'STALE' ) {
1281 $rss = $cache->get( $cache_key );
1282 if ( $rss and isset($rss->etag) and $rss->last_modified ) {
1283 $request_headers['If-None-Match'] = $rss->etag;
1284 $request_headers['If-Last-Modified'] = $rss->last_modified;
1285 }
1286 }
1287
1288 $resp = _fetch_remote_file( $url, $request_headers );
1289
1290 if (isset($resp) and $resp) {
1291 if ($resp->status == '304' ) {
1292 // we have the most current copy
1293 if ( MAGPIE_DEBUG > 1) {
1294 debug("Got 304 for $url");
1295 }
1296 // reset cache on 304 (at minutillo insistent prodding)
1297 $cache->set($cache_key, $rss);
1298 return $rss;
1299 }
1300 elseif ( is_success( $resp->status ) ) {
1301 $rss = _response_to_rss( $resp, $url );
1302 if ( $rss ) {
1303 if (MAGPIE_DEBUG > 1) {
1304 debug("Fetch successful");
1305 }
1306 // add object to cache
1307 $cache->set( $cache_key, $rss );
1308 return $rss;
1309 }
1310 }
1311 else {
1312 $errormsg = "Failed to fetch $url ";
1313 if ( $resp->status == '-100' ) {
1314 $errormsg .= "(Request timed out after " . MAGPIE_FETCH_TIME_OUT . " seconds)";
1315 }
1316 elseif ( $resp->error ) {
1317 # compensate for Snoopy's annoying habbit to tacking
1318 # on '\n'
1319 $http_error = substr($resp->error, 0, -2);
1320 $errormsg .= "(HTTP Error: $http_error)";
1321 }
1322 else {
1323 $errormsg .= "(HTTP Response: " . $resp->response_code .')';
1324 }
1325 }
1326 }
1327 else {
1328 $errormsg = "Unable to retrieve RSS file for unknown reasons.";
1329 }
1330
1331 // else fetch failed
1332 debug("MagpieRSS fetch failed [$errormsg]");
1333
1334 // attempt to return cached object
1335 if ($rss) {
1336 if ( MAGPIE_DEBUG ) {
1337 debug("Returning STALE object for $url");
1338 }
1339 return $rss;
1340 }
1341
1342 // else we totally failed
1343 error( $errormsg );
1344
1345 return false;
1346
1347 } // end if ( !MAGPIE_CACHE_ON ) {
1348 } // end fetch_rss()
1349
1350 /*=======================================================================*\
1351 Function: error
1352 Purpose: set MAGPIE_ERROR, and trigger error
1353 \*=======================================================================*/
1354
1355 function error ($errormsg, $lvl=E_USER_WARNING) {
1356 global $MAGPIE_ERROR;
1357
1358 // append PHP's error message if track_errors enabled
1359 if ( isset($php_errormsg) ) {
1360 $errormsg .= " ($php_errormsg)";
1361 }
1362 if ( $errormsg ) {
1363 $errormsg = "MagpieRSS: $errormsg";
1364 $MAGPIE_ERROR = $errormsg;
1365 if ( MAGPIE_DEBUG ) {
1366 trigger_error( $errormsg, $lvl);
1367 } else {
1368 error_log($errormsg, 0);
1369 }
1370 }
1371 }
1372
1373 function debug ($debugmsg, $lvl=E_USER_NOTICE) {
1374 trigger_error("MagpieRSS [debug] $debugmsg", $lvl);
1375 }
1376
1377 /*=======================================================================*\
1378 Function: magpie_error
1379 Purpose: accessor for the magpie error variable
1380 \*=======================================================================*/
1381 function magpie_error ($errormsg="") {
1382 global $MAGPIE_ERROR;
1383
1384 if ( isset($errormsg) and $errormsg ) {
1385 $MAGPIE_ERROR = $errormsg;
1386 }
1387
1388 return $MAGPIE_ERROR;
1389 }
1390
1391 /*=======================================================================*\
1392 Function: _fetch_remote_file
1393 Purpose: retrieve an arbitrary remote file
1394 Input: url of the remote file
1395 headers to send along with the request (optional)
1396 Output: an HTTP response object (see Snoopy.class.inc)
1397 \*=======================================================================*/
1398 function _fetch_remote_file ($url, $headers = "" ) {
1399 // Ensure that we have constants set up, since they are used below.
1400 init();
1401
1402 // WordPress 2.7 has deprecated Snoopy. It's still there, for now, but
1403 // I'd rather not rely on it.
1404 if (function_exists('wp_remote_request')) :
1405 $resp = wp_remote_request($url, array(
1406 'headers' => $headers,
1407 'timeout' => MAGPIE_FETCH_TIME_OUT
1408 ));
1409
1410 if ( is_wp_error($resp) ) :
1411 $error = $resp->get_error_messages();
1412
1413 $client = new stdClass;
1414 $client->status = 500;
1415 $client->response_code = 500;
1416 $client->error = implode(" / ", $error). "\n"; //\n = Snoopy compatibility
1417 else :
1418 $client = new stdClass;
1419 $client->status = $resp['response']['code'];
1420 $client->response_code = $resp['response']['code'];
1421 $client->headers = $resp['headers'];
1422 $client->results = $resp['body'];
1423 endif;
1424 else :
1425 // Snoopy is an HTTP client in PHP
1426 $client = new Snoopy();
1427 $client->agent = MAGPIE_USER_AGENT;
1428 $client->read_timeout = MAGPIE_FETCH_TIME_OUT;
1429 $client->use_gzip = MAGPIE_USE_GZIP;
1430 if (is_array($headers) ) {
1431 $client->rawheaders = $headers;
1432 }
1433 @$client->fetch($url);
1434 endif;
1435 return $client;
1436 }
1437
1438 /*=======================================================================*\
1439 Function: _response_to_rss
1440 Purpose: parse an HTTP response object into an RSS object
1441 Input: an HTTP response object (see Snoopy)
1442 Output: parsed RSS object (see rss_parse)
1443 \*=======================================================================*/
1444 function _response_to_rss ($resp, $url = null) {
1445 $rss = new MagpieRSS( $resp->results, MAGPIE_OUTPUT_ENCODING, MAGPIE_INPUT_ENCODING, MAGPIE_DETECT_ENCODING, $url );
1446
1447 // if RSS parsed successfully
1448 if ( $rss and !$rss->ERROR) {
1449 $rss->http_status = $resp->status;
1450
1451 // find Etag, and Last-Modified
1452 foreach($resp->headers as $index => $h) {
1453 if (is_string($index)) :
1454 $field = $index;
1455 $val = $h;
1456 elseif (strpos($h, ": ")) :
1457 list($field, $val) = explode(": ", $h, 2);
1458 else :
1459 $field = $h; $val = '';
1460 endif;
1461
1462 $rss->header[$field] = $val;
1463
1464 if ( $field == 'ETag' ) :
1465 $rss->etag = $val;
1466 elseif ( $field == 'Last-Modified' ) :
1467 $rss->last_modified = $val;
1468 endif;
1469 }
1470
1471 return $rss;
1472 } // else construct error message
1473 else {
1474 $errormsg = "Failed to parse RSS file.";
1475
1476 if ($rss) {
1477 $errormsg .= " (" . $rss->ERROR . ")";
1478 }
1479 error($errormsg);
1480
1481 return false;
1482 } // end if ($rss and !$rss->error)
1483 }
1484
1485 /*=======================================================================*\
1486 Function: init
1487 Purpose: setup constants with default values
1488 check for user overrides
1489 \*=======================================================================*/
1490 function init () {
1491 if ( defined('MAGPIE_INITALIZED') ) {
1492 return;
1493 }
1494 else {
1495 define('MAGPIE_INITALIZED', true);
1496 }
1497
1498 if ( !defined('MAGPIE_CACHE_ON') ) {
1499 define('MAGPIE_CACHE_ON', true);
1500 }
1501
1502 if ( !defined('MAGPIE_CACHE_DIR') ) {
1503 define('MAGPIE_CACHE_DIR', './cache');
1504 }
1505
1506 if ( !defined('MAGPIE_CACHE_AGE') ) {
1507 define('MAGPIE_CACHE_AGE', 60*60); // one hour
1508 }
1509
1510 if ( !defined('MAGPIE_CACHE_FRESH_ONLY') ) {
1511 define('MAGPIE_CACHE_FRESH_ONLY', false);
1512 }
1513
1514 if ( !defined('MAGPIE_OUTPUT_ENCODING') ) {
1515 define('MAGPIE_OUTPUT_ENCODING', 'ISO-8859-1');
1516 }
1517
1518 if ( !defined('MAGPIE_INPUT_ENCODING') ) {
1519 define('MAGPIE_INPUT_ENCODING', null);
1520 }
1521
1522 if ( !defined('MAGPIE_DETECT_ENCODING') ) {
1523 define('MAGPIE_DETECT_ENCODING', true);
1524 }
1525
1526 if ( !defined('MAGPIE_DEBUG') ) {
1527 define('MAGPIE_DEBUG', 0);
1528 }
1529
1530 if ( !defined('MAGPIE_USER_AGENT') ) {
1531 $ua = 'MagpieRSS/'. MAGPIE_VERSION . ' (+http://magpierss.sf.net';
1532
1533 if ( MAGPIE_CACHE_ON ) {
1534 $ua = $ua . ')';
1535 }
1536 else {
1537 $ua = $ua . '; No cache)';
1538 }
1539
1540 define('MAGPIE_USER_AGENT', $ua);
1541 }
1542
1543 if ( !defined('MAGPIE_FETCH_TIME_OUT') ) {
1544 define('MAGPIE_FETCH_TIME_OUT', 5); // 5 second timeout
1545 }
1546
1547 // use gzip encoding to fetch rss files if supported?
1548 if ( !defined('MAGPIE_USE_GZIP') ) {
1549 define('MAGPIE_USE_GZIP', true);
1550 }
1551 }
1552
1553 // NOTE: the following code should really be in Snoopy, or at least
1554 // somewhere other then rss_fetch!
1555
1556 /*=======================================================================*\
1557 HTTP STATUS CODE PREDICATES
1558 These functions attempt to classify an HTTP status code
1559 based on RFC 2616 and RFC 2518.
1560
1561 All of them take an HTTP status code as input, and return true or false
1562
1563 All this code is adapted from LWP's HTTP::Status.
1564 \*=======================================================================*/
1565
1566
1567 /*=======================================================================*\
1568 Function: is_info
1569 Purpose: return true if Informational status code
1570 \*=======================================================================*/
1571 function is_info ($sc) {
1572 return $sc >= 100 && $sc < 200;
1573 }
1574
1575 /*=======================================================================*\
1576 Function: is_success
1577 Purpose: return true if Successful status code
1578 \*=======================================================================*/
1579 function is_success ($sc) {
1580 return $sc >= 200 && $sc < 300;
1581 }
1582
1583 /*=======================================================================*\
1584 Function: is_redirect
1585 Purpose: return true if Redirection status code
1586 \*=======================================================================*/
1587 function is_redirect ($sc) {
1588 return $sc >= 300 && $sc < 400;
1589 }
1590
1591 /*=======================================================================*\
1592 Function: is_error
1593 Purpose: return true if Error status code
1594 \*=======================================================================*/
1595 function is_error ($sc) {
1596 return $sc >= 400 && $sc < 600;
1597 }
1598
1599 /*=======================================================================*\
1600 Function: is_client_error
1601 Purpose: return true if Error status code, and its a client error
1602 \*=======================================================================*/
1603 function is_client_error ($sc) {
1604 return $sc >= 400 && $sc < 500;
1605 }
1606
1607 /*=======================================================================*\
1608 Function: is_client_error
1609 Purpose: return true if Error status code, and its a server error
1610 \*=======================================================================*/
1611 function is_server_error ($sc) {
1612 return $sc >= 500 && $sc < 600;
1613 }
1614
1615 ################################################################################
1616 ## rss_cache.inc: from WordPress 1.5 ###########################################
1617 ################################################################################
1618
1619 class RSSCache {
1620 var $BASE_CACHE = 'wp-content/cache'; // where the cache files are stored
1621 var $MAX_AGE = 43200; // when are files stale, default twelve hours
1622 var $ERROR = ''; // accumulate error messages
1623
1624 function RSSCache ($base='', $age='') {
1625 if ( $base ) {
1626 $this->BASE_CACHE = $base;
1627 }
1628 if ( $age ) {
1629 $this->MAX_AGE = $age;
1630 }
1631
1632 }
1633
1634 /*=======================================================================*\
1635 Function: set
1636 Purpose: add an item to the cache, keyed on url
1637 Input: url from wich the rss file was fetched
1638 Output: true on sucess
1639 \*=======================================================================*/
1640 function set ($url, $rss) {
1641 global $wpdb;
1642 $cache_option = 'rss_' . $this->file_name( $url );
1643 $cache_timestamp = 'rss_' . $this->file_name( $url ) . '_ts';
1644
1645 if ( !$wpdb->get_var("SELECT option_name FROM $wpdb->options WHERE option_name = '$cache_option'") )
1646 add_option($cache_option, '', '', 'no');
1647 if ( !$wpdb->get_var("SELECT option_name FROM $wpdb->options WHERE option_name = '$cache_timestamp'") )
1648 add_option($cache_timestamp, '', '', 'no');
1649
1650 update_option($cache_option, $rss);
1651 update_option($cache_timestamp, time() );
1652
1653 return $cache_option;
1654 }
1655
1656 /*=======================================================================*\
1657 Function: get
1658 Purpose: fetch an item from the cache
1659 Input: url from wich the rss file was fetched
1660 Output: cached object on HIT, false on MISS
1661 \*=======================================================================*/
1662 function get ($url) {
1663 $this->ERROR = "";
1664 $cache_option = 'rss_' . $this->file_name( $url );
1665
1666 if ( ! get_option( $cache_option ) ) {
1667 $this->debug(
1668 "Cache doesn't contain: $url (cache option: $cache_option)"
1669 );
1670 return 0;
1671 }
1672
1673 $rss = get_option( $cache_option );
1674
1675 // failsafe; seems to break at odd points in WP MU
1676 if (is_string($rss)) {
1677 $rss = $this->unserialize($rss);
1678 }
1679
1680 return $rss;
1681 }
1682
1683 /*=======================================================================*\
1684 Function: check_cache
1685 Purpose: check a url for membership in the cache
1686 and whether the object is older then MAX_AGE (ie. STALE)
1687 Input: url from wich the rss file was fetched
1688 Output: cached object on HIT, false on MISS
1689 \*=======================================================================*/
1690 function check_cache ( $url ) {
1691 $this->ERROR = "";
1692 $cache_option = $this->file_name( $url );
1693 $cache_timestamp = 'rss_' . $this->file_name( $url ) . '_ts';
1694
1695 if ( $mtime = get_option($cache_timestamp) ) {
1696 // find how long ago the file was added to the cache
1697 // and whether that is longer then MAX_AGE
1698 $age = time() - $mtime;
1699 if ( $this->MAX_AGE > $age ) {
1700 // object exists and is current
1701 return 'HIT';
1702 }
1703 else {
1704 // object exists but is old
1705 return 'STALE';
1706 }
1707 }
1708 else {
1709 // object does not exist
1710 return 'MISS';
1711 }
1712 }
1713
1714 /*=======================================================================*\
1715 Function: serialize
1716 \*=======================================================================*/
1717 function serialize ( $rss ) {
1718 return serialize( $rss );
1719 }
1720
1721 /*=======================================================================*\
1722 Function: unserialize
1723 \*=======================================================================*/
1724 function unserialize ( $data ) {
1725 return unserialize( $data );
1726 }
1727
1728 /*=======================================================================*\
1729 Function: file_name
1730 Purpose: map url to location in cache
1731 Input: url from wich the rss file was fetched
1732 Output: a file name
1733 \*=======================================================================*/
1734 function file_name ($url) {
1735 return md5( $url );
1736 }
1737
1738 /*=======================================================================*\
1739 Function: error
1740 Purpose: register error
1741 \*=======================================================================*/
1742 function error ($errormsg, $lvl=E_USER_WARNING) {
1743 // append PHP's error message if track_errors enabled
1744 if ( isset($php_errormsg) ) {
1745 $errormsg .= " ($php_errormsg)";
1746 }
1747 $this->ERROR = $errormsg;
1748 if ( MAGPIE_DEBUG ) {
1749 trigger_error( $errormsg, $lvl);
1750 }
1751 else {
1752 error_log( $errormsg, 0);
1753 }
1754 }
1755 function debug ($debugmsg, $lvl=E_USER_NOTICE) {
1756 if ( MAGPIE_DEBUG ) {
1757 $this->error("MagpieRSS [debug] $debugmsg", $lvl);
1758 }
1759 }
1760 }
1761
1762 ################################################################################
1763 ## rss_utils.inc: from MagpieRSS 0.8a ##########################################
1764 ################################################################################
1765
1766 /*======================================================================*\
1767 Function: parse_w3cdtf
1768 Purpose: parse a W3CDTF date into unix epoch
1769
1770 NOTE: http://www.w3.org/TR/NOTE-datetime
1771 \*======================================================================*/
1772
1773 function parse_w3cdtf ( $date_str ) {
1774
1775 # regex to match wc3dtf
1776 $pat = "/^\s*(\d{4})(-(\d{2})(-(\d{2})(T(\d{2}):(\d{2})(:(\d{2})(\.\d+)?)?(?:([-+])(\d{2}):?(\d{2})|(Z))?)?)?)?\s*\$/";
1777
1778 if ( preg_match( $pat, $date_str, $match ) ) {
1779 list( $year, $month, $day, $hours, $minutes, $seconds) =
1780 array( $match[1], $match[3], $match[5], $match[7], $match[8], $match[10]);
1781
1782 # W3C dates can omit the time, the day of the month, or even the month.
1783 # Fill in any blanks using information from the present moment. --CWJ
1784 $default['hr'] = (int) gmdate('H');
1785 $default['day'] = (int) gmdate('d');
1786 $default['month'] = (int) gmdate('m');
1787
1788 if (is_null($hours)) : $hours = $default['hr']; $minutes = 0; $seconds = 0; endif;
1789 if (is_null($day)) : $day = $default['day']; endif;
1790 if (is_null($month)) : $month = $default['month']; endif;
1791
1792 # calc epoch for current date assuming GMT
1793 $epoch = gmmktime( $hours, $minutes, $seconds, $month, $day, $year);
1794
1795 $offset = 0;
1796 if ( $match[15] == 'Z' ) {
1797 # zulu time, aka GMT
1798 }
1799 else {
1800 list( $tz_mod, $tz_hour, $tz_min ) =
1801 array( $match[12], $match[13], $match[14]);
1802
1803 # zero out the variables
1804 if ( ! $tz_hour ) { $tz_hour = 0; }
1805 if ( ! $tz_min ) { $tz_min = 0; }
1806
1807 $offset_secs = (($tz_hour*60)+$tz_min)*60;
1808
1809 # is timezone ahead of GMT? then subtract offset
1810 #
1811 if ( $tz_mod == '+' ) {
1812 $offset_secs = $offset_secs * -1;
1813 }
1814
1815 $offset = $offset_secs;
1816 }
1817 $epoch = $epoch + $offset;
1818 return $epoch;
1819 }
1820 else {
1821 return -1;
1822 }
1823 }
1824
1825 # Relative URI static class: PHP class for resolving relative URLs
1826 #
1827 # This class is derived (under the terms of the GPL) from URL Class 0.3 by
1828 # Keyvan Minoukadeh <keyvan@k1m.com>, which is great but more than we need
1829 # for MagpieRSS's purposes. The class has been stripped down to a single
1830 # public method: Relative_URI::resolve($url, $base), which resolves the URI in
1831 # $url relative to the URI in $base
1832 #
1833 # FeedWordPress also uses this class. So if we have it loaded in, don't load it
1834 # again.
1835 #
1836 # -- Charles Johnson <technophilia@radgeek.com>
1837 if (!class_exists('Relative_URI')) {
1838 class Relative_URI
1839 {
1840 // Resolve relative URI in $url against the base URI in $base. If $base
1841 // is not supplied, then we use the REQUEST_URI of this script.
1842 //
1843 // I'm hoping this method reflects RFC 2396 Section 5.2
1844 function resolve ($url, $base = NULL)
1845 {
1846 if (is_null($base)):
1847 $base = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
1848 endif;
1849
1850 $base = Relative_URI::_encode(trim($base));
1851 $uri_parts = Relative_URI::_parse_url($base);
1852
1853 $url = Relative_URI::_encode(trim($url));
1854 $parts = Relative_URI::_parse_url($url);
1855
1856 $uri_parts['fragment'] = (isset($parts['fragment']) ? $parts['fragment'] : null);
1857 $uri_parts['query'] = (isset($parts['query']) ? $parts['query'] : null);
1858
1859 // if path is empty, and scheme, host, and query are undefined,
1860 // the URL is referring the base URL
1861
1862 if (($parts['path'] == '') && !isset($parts['scheme']) && !isset($parts['host']) && !isset($parts['query'])) {
1863 // If the URI is empty or only a fragment, return the base URI
1864 return $base . (isset($parts['fragment']) ? '#'.$parts['fragment'] : '');
1865 } elseif (isset($parts['scheme'])) {
1866 // If the scheme is set, then the URI is absolute.
1867 return $url;
1868 } elseif (isset($parts['host'])) {
1869 $uri_parts['host'] = $parts['host'];
1870 $uri_parts['path'] = $parts['path'];
1871 } else {
1872 // We have a relative path but not a host.
1873
1874 // start ugly fix:
1875 // prepend slash to path if base host is set, base path is not set, and url path is not absolute
1876 if ($uri_parts['host'] && ($uri_parts['path'] == '')
1877 && (strlen($parts['path']) > 0)
1878 && (substr($parts['path'], 0, 1) != '/')) {
1879 $parts['path'] = '/'.$parts['path'];
1880 } // end ugly fix
1881
1882 if (substr($parts['path'], 0, 1) == '/') {
1883 $uri_parts['path'] = $parts['path'];
1884 } else {
1885 // copy base path excluding any characters after the last (right-most) slash character
1886 $buffer = substr($uri_parts['path'], 0, (int)strrpos($uri_parts['path'], '/')+1);
1887 // append relative path
1888 $buffer .= $parts['path'];
1889 // remove "./" where "." is a complete path segment.
1890 $buffer = str_replace('/./', '/', $buffer);
1891 if (substr($buffer, 0, 2) == './') {
1892 $buffer = substr($buffer, 2);
1893 }
1894 // if buffer ends with "." as a complete path segment, remove it
1895 if (substr($buffer, -2) == '/.') {
1896 $buffer = substr($buffer, 0, -1);
1897 }
1898 // remove "<segment>/../" where <segment> is a complete path segment not equal to ".."
1899 $search_finished = false;
1900 $segment = explode('/', $buffer);
1901 while (!$search_finished) {
1902 for ($x=0; $x+1 < count($segment);) {
1903 if (($segment[$x] != '') && ($segment[$x] != '..') && ($segment[$x+1] == '..')) {
1904 if ($x+2 == count($segment)) $segment[] = '';
1905 unset($segment[$x], $segment[$x+1]);
1906 $segment = array_values($segment);
1907 continue 2;
1908 } else {
1909 $x++;
1910 }
1911 }
1912 $search_finished = true;
1913 }
1914 $buffer = (count($segment) == 1) ? '/' : implode('/', $segment);
1915 $uri_parts['path'] = $buffer;
1916
1917 }
1918 }
1919
1920 // If we've gotten to this point, we can try to put the pieces
1921 // back together.
1922 $ret = '';
1923 if (isset($uri_parts['scheme'])) $ret .= $uri_parts['scheme'].':';
1924 if (isset($uri_parts['user'])) {
1925 $ret .= $uri_parts['user'];
1926 if (isset($uri_parts['pass'])) $ret .= ':'.$uri_parts['parts'];
1927 $ret .= '@';
1928 }
1929 if (isset($uri_parts['host'])) {
1930 $ret .= '//'.$uri_parts['host'];
1931 if (isset($uri_parts['port'])) $ret .= ':'.$uri_parts['port'];
1932 }
1933 $ret .= $uri_parts['path'];
1934 if (isset($uri_parts['query'])) $ret .= '?'.$uri_parts['query'];
1935 if (isset($uri_parts['fragment'])) $ret .= '#'.$uri_parts['fragment'];
1936
1937 return $ret;
1938 }
1939
1940 /**
1941 * Parse URL
1942 *
1943 * Regular expression grabbed from RFC 2396 Appendix B.
1944 * This is a replacement for PHPs builtin parse_url().
1945 * @param string $url
1946 * @access private
1947 * @return array
1948 */
1949 function _parse_url($url)
1950 {
1951 // I'm using this pattern instead of parse_url() as there's a few strings where parse_url()
1952 // generates a warning.
1953 if (preg_match('!^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?!', $url, $match)) {
1954 $parts = array();
1955 if ($match[1] != '') $parts['scheme'] = $match[2];
1956 if ($match[3] != '') $parts['auth'] = $match[4];
1957 // parse auth
1958 if (isset($parts['auth'])) {
1959 // store user info
1960 if (($at_pos = strpos($parts['auth'], '@')) !== false) {
1961 $userinfo = explode(':', substr($parts['auth'], 0, $at_pos), 2);
1962 $parts['user'] = $userinfo[0];
1963 if (isset($userinfo[1])) $parts['pass'] = $userinfo[1];
1964 $parts['auth'] = substr($parts['auth'], $at_pos+1);
1965 }
1966 // get port number
1967 if ($port_pos = strrpos($parts['auth'], ':')) {
1968 $parts['host'] = substr($parts['auth'], 0, $port_pos);
1969 $parts['port'] = (int)substr($parts['auth'], $port_pos+1);
1970 if ($parts['port'] < 1) $parts['port'] = null;
1971 } else {
1972 $parts['host'] = $parts['auth'];
1973 }
1974 }
1975 unset($parts['auth']);
1976 $parts['path'] = $match[5];
1977 if (isset($match[6]) && ($match[6] != '')) $parts['query'] = $match[7];
1978 if (isset($match[8]) && ($match[8] != '')) $parts['fragment'] = $match[9];
1979 return $parts;
1980 }
1981 // shouldn't reach here
1982 return array('path'=>'');
1983 }
1984
1985 function _encode($string)
1986 {
1987 static $replace = array();
1988 if (!count($replace)) {
1989 $find = array(32, 34, 60, 62, 123, 124, 125, 91, 92, 93, 94, 96, 127);
1990 $find = array_merge(range(0, 31), $find);
1991 $find = array_map('chr', $find);
1992 foreach ($find as $char) {
1993 $replace[$char] = '%'.bin2hex($char);
1994 }
1995 }
1996 // escape control characters and a few other characters
1997 $encoded = strtr($string, $replace);
1998 // remove any character outside the hex range: 21 - 7E (see www.asciitable.com)
1999 return preg_replace('/[^\x21-\x7e]/', '', $encoded);
2000 }
2001 } // class Relative_URI
2002 }
2003
2004 ################################################################################
2005 ## WordPress: wp_rss(), get_rss() ##############################################
2006 ################################################################################
2007
2008 function wp_rss ($url, $num) {
2009 //ini_set("display_errors", false); uncomment to suppress php errors thrown if the feed is not returned.
2010 $num_items = $num;
2011 $rss = fetch_rss($url);
2012 if ( $rss ) {
2013 echo "<ul>";
2014 $rss->items = array_slice($rss->items, 0, $num_items);
2015 foreach ($rss->items as $item ) {
2016 echo "<li>\n";
2017 echo "<a href='$item[link]' title='$item[description]'>";
2018 echo htmlentities($item['title']);
2019 echo "</a><br />\n";
2020 echo "</li>\n";
2021 }
2022 echo "</ul>";
2023 }
2024 else {
2025 echo "an error has occured the feed is probably down, try again later.";
2026 }
2027 }
2028
2029 function get_rss ($uri, $num = 5) { // Like get posts, but for RSS
2030 $rss = fetch_rss($url);
2031 if ( $rss ) {
2032 $rss->items = array_slice($rss->items, 0, $num_items);
2033 foreach ($rss->items as $item ) {
2034 echo "<li>\n";
2035 echo "<a href='$item[link]' title='$item[description]'>";
2036 echo htmlentities($item['title']);
2037 echo "</a><br />\n";
2038 echo "</li>\n";
2039 }
2040 return $posts;
2041 } else {
2042 return false;
2043 }
2044 }
2045 ?>