PluginProbe
FeedWordPress / 2008.1214
FeedWordPress v2008.1214
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 2008.1214, at MagpieRSS-upgrade/rss.php

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