PluginProbe
FeedWordPress / 0.96
FeedWordPress v0.96
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 / OPTIONAL / wp-includes / rss-functions.php

rss-functions.php in FeedWordPress 0.96, at OPTIONAL/wp-includes/rss-functions.php

1,248 lines 40.0 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.7wp (2005.05.07)
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 a modification of 0.7. In addition to improved handling of character
15 * encoding and other updates, this branch of MagpieRSS 0.7 also supports
16 * multiple categorization of posts (using <dc:subject> or <category>). The
17 * file is, therefore, derived from four sources: (1) Kellan's MagpieRSS 0.51,
18 * (2) the WordPress development team's modifications to MagpieRSS 0.51,
19 * (3) Kellan's MagpieRSS 0.7, and (4) Charles Johnson's modifications to
20 * MagpieRSS 0.7. All possible because of the GPL. Yay for free software!
21 *
22 * Differences from the main branch of MagpieRSS:
23 *
24 * 1. Everything in rss_parse.inc, rss_fetch.inc, rss_cache.inc, and
25 * rss_utils.inc is included in one file.
26 *
27 * 2. MagpieRSS returns the WordPress version as the user agent, rather than
28 * Magpie
29 *
30 * 3. class RSSCache is a modified version by WordPress developers, which
31 * caches feeds in the WordPress database (in the options table), rather
32 * than writing external files directly.
33 *
34 * 4. There are two WordPress-specific functions, get_rss() and wp_rss()
35 *
36 * 5. New cases added to MagpieRSS::feed_start_element(),
37 * MagpieRSS::feed_end_element(), and MagpieRSS::normalize() to handle:
38 *
39 * (a) Multiple categories
40 * (b) RSS 2.0 and Atom 0.6+ enclosures
41 *
42 * Categories are stored in $item['category'], $item['categories'],
43 * $item['dc']['subject'], and $item['dc']['subjects'] (the singular keys
44 * point to string values for the first category used; the plural keys
45 * point to an array of all the categories the item is in)
46 *
47 * Enclosures are stored in the array $item['enclosure'] as suggested
48 * at <http://magpie.laughingmeme.org/blog/?p=101>. So the URL of the first
49 * enclosure is $item['enclosure'][0]['url']; the length is
50 * $item['enclosure'][0]['length']; and the type is
51 * $item['enclosure'][0]['type']
52 *
53 * Note that these are hacked-in solutions for inherited problems with
54 * MagpieRSS as of version 0.7. They are not guaranteed to be
55 * forward-compatible when/if Kellan solves these problems in the official
56 * Magpie branch in the future. If you have filters (for example) that
57 * depend on either categories or enclosures working as they currently do,
58 * keep an eye on the ChangeLog in future releases.
59 */
60
61 define('RSS', 'RSS');
62 define('ATOM', 'Atom');
63 define('MAGPIE_USER_AGENT', 'WordPress/' . $wp_version);
64
65 # UPDATED: rss_parse.inc: class MagpieRSS, function map_attrs
66 # --- cut here ---
67 /**
68 * Hybrid parser, and object, takes RSS as a string and returns a simple object.
69 *
70 * see: rss_fetch.inc for a simpler interface with integrated caching support
71 *
72 */
73 class MagpieRSS {
74 var $parser;
75
76 var $current_item = array(); // item currently being parsed
77 var $items = array(); // collection of parsed items
78 var $channel = array(); // hash of channel fields
79 var $textinput = array();
80 var $image = array();
81 var $feed_type;
82 var $feed_version;
83 var $encoding = ''; // output encoding of parsed rss
84
85 var $_source_encoding = ''; // only set if we have to parse xml prolog
86
87 var $ERROR = "";
88 var $WARNING = "";
89
90 // define some constants
91
92 var $_CONTENT_CONSTRUCTS = array('content', 'summary', 'info', 'title', 'tagline', 'copyright');
93 var $_KNOWN_ENCODINGS = array('UTF-8', 'US-ASCII', 'ISO-8859-1');
94
95 // parser variables, useless if you're not a parser, treat as private
96 var $stack = array(); // parser stack
97 var $inchannel = false;
98 var $initem = false;
99 var $incontent = false; // if in Atom <content mode="xml"> field
100 var $intextinput = false;
101 var $inimage = false;
102 var $current_namespace = false;
103
104 var $incategory = false;
105 var $current_category = 0;
106
107 /**
108 * Set up XML parser, parse source, and return populated RSS object..
109 *
110 * @param string $source string containing the RSS to be parsed
111 *
112 * NOTE: Probably a good idea to leave the encoding options alone unless
113 * you know what you're doing as PHP's character set support is
114 * a little weird.
115 *
116 * NOTE: A lot of this is unnecessary but harmless with PHP5
117 *
118 *
119 * @param string $output_encoding output the parsed RSS in this character
120 * set defaults to ISO-8859-1 as this is PHP's
121 * default.
122 *
123 * NOTE: might be changed to UTF-8 in future
124 * versions.
125 *
126 * @param string $input_encoding the character set of the incoming RSS source.
127 * Leave blank and Magpie will try to figure it
128 * out.
129 *
130 *
131 * @param bool $detect_encoding if false Magpie won't attempt to detect
132 * source encoding. (caveat emptor)
133 *
134 */
135 function MagpieRSS ($source, $output_encoding='ISO-8859-1',
136 $input_encoding=null, $detect_encoding=true)
137 {
138 # if PHP xml isn't compiled in, die
139 #
140 if (!function_exists('xml_parser_create')) {
141 $this->error( "Failed to load PHP's XML Extension. " .
142 "http://www.php.net/manual/en/ref.xml.php",
143 E_USER_ERROR );
144 }
145
146 list($parser, $source) = $this->create_parser($source,
147 $output_encoding, $input_encoding, $detect_encoding);
148
149
150 if (!is_resource($parser)) {
151 $this->error( "Failed to create an instance of PHP's XML parser. " .
152 "http://www.php.net/manual/en/ref.xml.php",
153 E_USER_ERROR );
154 }
155
156
157 $this->parser = $parser;
158
159 # pass in parser, and a reference to this object
160 # setup handlers
161 #
162 xml_set_object( $this->parser, $this );
163 xml_set_element_handler($this->parser,
164 'feed_start_element', 'feed_end_element' );
165
166 xml_set_character_data_handler( $this->parser, 'feed_cdata' );
167
168 $status = xml_parse( $this->parser, $source );
169
170 if (! $status ) {
171 $errorcode = xml_get_error_code( $this->parser );
172 if ( $errorcode != XML_ERROR_NONE ) {
173 $xml_error = xml_error_string( $errorcode );
174 $error_line = xml_get_current_line_number($this->parser);
175 $error_col = xml_get_current_column_number($this->parser);
176 $errormsg = "$xml_error at line $error_line, column $error_col";
177
178 $this->error( $errormsg );
179 }
180 }
181
182 xml_parser_free( $this->parser );
183
184 $this->normalize();
185 }
186
187 function feed_start_element($p, $element, &$attrs) {
188 $el = $element = strtolower($element);
189 $attrs = array_change_key_case($attrs, CASE_LOWER);
190
191 // check for a namespace, and split if found
192 $ns = false;
193 if ( strpos( $element, ':' ) ) {
194 list($ns, $el) = split( ':', $element, 2);
195 }
196 if ( $ns and $ns != 'rdf' ) {
197 $this->current_namespace = $ns;
198 }
199
200 # if feed type isn't set, then this is first element of feed
201 # identify feed from root element
202 #
203 if (!isset($this->feed_type) ) {
204 if ( $el == 'rdf' ) {
205 $this->feed_type = RSS;
206 $this->feed_version = '1.0';
207 }
208 elseif ( $el == 'rss' ) {
209 $this->feed_type = RSS;
210 $this->feed_version = $attrs['version'];
211 }
212 elseif ( $el == 'feed' ) {
213 $this->feed_type = ATOM;
214 $this->feed_version = $attrs['version'];
215 $this->inchannel = true;
216 }
217 return;
218 }
219
220 if ( $el == 'channel' )
221 {
222 $this->inchannel = true;
223 }
224 elseif ($el == 'item' or $el == 'entry' )
225 {
226 $this->initem = true;
227 if ( isset($attrs['rdf:about']) ) {
228 $this->current_item['about'] = $attrs['rdf:about'];
229 }
230 }
231
232 elseif ($this->initem and ($el == 'category' or ($this->current_namespace == 'dc' and $el == 'subject'))) {
233 $this->incategory = true;
234 array_unshift( $this->stack, $el );
235 }
236
237 // if we're in the default namespace of an RSS feed,
238 // record textinput or image fields
239 elseif (
240 $this->feed_type == RSS and
241 $this->current_namespace == '' and
242 $el == 'textinput' )
243 {
244 $this->intextinput = true;
245 }
246
247 elseif (
248 $this->feed_type == RSS and
249 $this->current_namespace == '' and
250 $el == 'image' )
251 {
252 $this->inimage = true;
253 }
254
255 # -- Handle RSS 2 enclosures. Suggested by <http://magpie.laughingmeme.org/blog/?p=101>
256 elseif (
257 $this->feed_type == RSS and
258 $el == 'enclosure' )
259 {
260 $this->current_item[$el][] = $attrs;
261 $this->incontent = $el;
262 }
263
264 # handle atom content constructs
265 elseif ( $this->feed_type == ATOM and in_array($el, $this->_CONTENT_CONSTRUCTS) )
266 {
267 // avoid clashing w/ RSS mod_content
268 if ($el == 'content' ) {
269 $el = 'atom_content';
270 }
271
272 $this->incontent = $el;
273
274
275 }
276
277 // if inside an Atom content construct (e.g. content or summary) field treat tags as text
278 elseif ($this->feed_type == ATOM and $this->incontent )
279 {
280 // if tags are inlined, then flatten
281 $attrs_str = join(' ',
282 array_map('map_attrs',
283 array_keys($attrs),
284 array_values($attrs) ) );
285
286 $this->append_content( "<$element $attrs_str>" );
287
288 array_unshift( $this->stack, $el );
289 }
290
291 // Atom support many links per containging element.
292 // Magpie treats link elements of type rel='alternate'
293 // as being equivalent to RSS's simple link element.
294 //
295 elseif ($this->feed_type == ATOM and $el == 'link' )
296 {
297 # -- CWJ: Treat <link> elements without explicit rel as rel="alternate"
298 if ( !isset($attrs['rel']) or isset($attrs['rel']) and $attrs['rel'] == 'alternate' )
299 {
300 $link_el = 'link';
301 }
302 # -- CWJ: support Atom 0.6+ enclosures
303 elseif ( isset($attrs['rel']) and $attrs['rel'] == 'enclosure' )
304 {
305 $link_el = 'link_' . $attrs['rel'];
306
307 # -- CWJ: Normalize to RSS 2.0 enclosure handling
308 $n = count($this->current_item[$attrs['rel']]);
309 $this->current_item[$attrs['rel']][$n] = $attrs;
310 $this->current_item[$attrs['rel']][$n]['url'] =
311 $this->current_item[$attrs['rel']][$n]['href'];
312 }
313 else {
314 $link_el = 'link_' . $attrs['rel'];
315 }
316
317 $this->append($link_el, $attrs['href']);
318 }
319
320 // set stack[0] to current element
321 else {
322 array_unshift($this->stack, $el);
323 }
324 }
325
326
327
328 function feed_cdata ($p, $text) {
329 if ($this->feed_type == ATOM and $this->incontent)
330 {
331 $this->append_content( $text );
332 }
333 else {
334 $current_el = join('_', array_reverse($this->stack));
335 $this->append($current_el, $text);
336 }
337 }
338
339 function feed_end_element ($p, $el) {
340 $el = strtolower($el);
341
342 if ( $el == 'item' or $el == 'entry' )
343 {
344 $this->items[] = $this->current_item;
345 $this->current_item = array();
346 $this->initem = false;
347
348 $this->current_category = 0;
349 }
350 elseif ($this->initem and ($el == 'category' or $el == 'dc:subject')) {
351 $this->incategory = false;
352 $this->current_category = $this->current_category + 1;
353 array_shift( $this->stack );
354 }
355 elseif ($this->feed_type == RSS and $this->current_namespace == '' and $el == 'textinput' )
356 {
357 $this->intextinput = false;
358 }
359 elseif ($this->feed_type == RSS and $this->current_namespace == '' and $el == 'image' )
360 {
361 $this->inimage = false;
362 }
363 elseif ($this->feed_type == ATOM and in_array($el, $this->_CONTENT_CONSTRUCTS) )
364 {
365 $this->incontent = false;
366 }
367 elseif ($el == 'channel' or $el == 'feed' )
368 {
369 $this->inchannel = false;
370 }
371 elseif ($this->feed_type == ATOM and $this->incontent ) {
372 // balance tags properly
373 // note: i don't think this is actually neccessary
374 if ( $this->stack[0] == $el )
375 {
376 $this->append_content("</$el>");
377 }
378 else {
379 $this->append_content("<$el />");
380 }
381
382 array_shift( $this->stack );
383 }
384 else {
385 array_shift( $this->stack );
386 }
387
388 $this->current_namespace = false;
389 }
390
391 function concat (&$str1, $str2="") {
392 if (!isset($str1) ) {
393 $str1="";
394 }
395 $str1 .= $str2;
396 }
397
398
399
400 function append_content($text) {
401 if ( $this->initem ) {
402 $this->concat( $this->current_item[ $this->incontent ], $text );
403 }
404 elseif ( $this->inchannel ) {
405 $this->concat( $this->channel[ $this->incontent ], $text );
406 }
407 }
408
409 // smart append - field and namespace aware
410 function append($el, $text) {
411 if (!$el) {
412 return;
413 }
414 if ( $this->current_namespace )
415 {
416 if ( $this->incategory ) {
417 $this->concat( $this->current_item['categories'][$this->current_category], $text );
418 }
419 elseif ( $this->initem ) {
420 $this->concat(
421 $this->current_item[ $this->current_namespace ][ $el ], $text );
422 }
423 elseif ($this->inchannel) {
424 $this->concat(
425 $this->channel[ $this->current_namespace][ $el ], $text );
426 }
427 elseif ($this->intextinput) {
428 $this->concat(
429 $this->textinput[ $this->current_namespace][ $el ], $text );
430 }
431 elseif ($this->inimage) {
432 $this->concat(
433 $this->image[ $this->current_namespace ][ $el ], $text );
434 }
435 }
436 else {
437 if ( $this->incategory ) {
438 $this->concat( $this->current_item['categories'][$this->current_category], $text );
439 }
440 elseif ( $this->initem ) {
441 $this->concat(
442 $this->current_item[ $el ], $text);
443 }
444 elseif ($this->intextinput) {
445 $this->concat(
446 $this->textinput[ $el ], $text );
447 }
448 elseif ($this->inimage) {
449 $this->concat(
450 $this->image[ $el ], $text );
451 }
452 elseif ($this->inchannel) {
453 $this->concat(
454 $this->channel[ $el ], $text );
455 }
456
457 }
458 }
459
460 function normalize () {
461 // if atom populate rss fields
462 if ( $this->is_atom() ) {
463 $this->channel['description'] = $this->channel['tagline'];
464 for ( $i = 0; $i < count($this->items); $i++) {
465 $item = $this->items[$i];
466 if ( isset($item['summary']) )
467 $item['description'] = $item['summary'];
468 if ( isset($item['atom_content']))
469 $item['content']['encoded'] = $item['atom_content'];
470
471 $atom_date = (isset($item['issued']) ) ? $item['issued'] : $item['modified'];
472 if ( $atom_date ) {
473 $epoch = @parse_w3cdtf($item['modified']);
474 if ($epoch and $epoch > 0) {
475 $item['date_timestamp'] = $epoch;
476 }
477 }
478
479 if ( is_array($item['categories']) ) {
480 $item['category'] = $item['categories'][0];
481 $item['dc']['subjects'] = $item['categories'];
482 $item['dc']['subject'] = $item['category'];
483 }
484
485 $this->items[$i] = $item;
486 }
487 }
488 elseif ( $this->is_rss() ) {
489 $this->channel['tagline'] = $this->channel['description'];
490 for ( $i = 0; $i < count($this->items); $i++) {
491 $item = $this->items[$i];
492 if ( isset($item['description']))
493 $item['summary'] = $item['description'];
494 if ( isset($item['content']['encoded'] ) )
495 $item['atom_content'] = $item['content']['encoded'];
496
497 if ( $this->is_rss() == '1.0' and isset($item['dc']['date']) ) {
498 $epoch = @parse_w3cdtf($item['dc']['date']);
499 if ($epoch and $epoch > 0) {
500 $item['date_timestamp'] = $epoch;
501 }
502 }
503 elseif ( isset($item['pubdate']) ) {
504 $epoch = @strtotime($item['pubdate']);
505 if ($epoch > 0) {
506 $item['date_timestamp'] = $epoch;
507 }
508 }
509
510 if ( is_array($item['categories']) ) {
511 $item['category'] = $item['categories'][0];
512 $item['dc']['subjects'] = $item['categories'];
513 $item['dc']['subject'] = $item['category'];
514 }
515
516 $this->items[$i] = $item;
517 }
518 }
519 }
520
521
522 function is_rss () {
523 if ( $this->feed_type == RSS ) {
524 return $this->feed_version;
525 }
526 else {
527 return false;
528 }
529 }
530
531 function is_atom() {
532 if ( $this->feed_type == ATOM ) {
533 return $this->feed_version;
534 }
535 else {
536 return false;
537 }
538 }
539
540 /**
541 * return XML parser, and possibly re-encoded source
542 *
543 */
544 function create_parser($source, $out_enc, $in_enc, $detect) {
545 if ( substr(phpversion(),0,1) == 5) {
546 $parser = $this->php5_create_parser($in_enc, $detect);
547 }
548 else {
549 list($parser, $source) = $this->php4_create_parser($source, $in_enc, $detect);
550 }
551 if ($out_enc) {
552 $this->encoding = $out_enc;
553 xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $out_enc);
554 }
555
556 return array($parser, $source);
557 }
558
559 /**
560 * Instantiate an XML parser under PHP5
561 *
562 * PHP5 will do a fine job of detecting input encoding
563 * if passed an empty string as the encoding.
564 *
565 * All hail libxml2!
566 *
567 */
568 function php5_create_parser($in_enc, $detect) {
569 // by default php5 does a fine job of detecting input encodings
570 if(!$detect && $in_enc) {
571 return xml_parser_create($in_enc);
572 }
573 else {
574 return xml_parser_create('');
575 }
576 }
577
578 /**
579 * Instaniate an XML parser under PHP4
580 *
581 * Unfortunately PHP4's support for character encodings
582 * and especially XML and character encodings sucks. As
583 * long as the documents you parse only contain characters
584 * from the ISO-8859-1 character set (a superset of ASCII,
585 * and a subset of UTF-8) you're fine. However once you
586 * step out of that comfy little world things get mad, bad,
587 * and dangerous to know.
588 *
589 * The following code is based on SJM's work with FoF
590 * @see http://minutillo.com/steve/weblog/2004/6/17/php-xml-and-character-encodings-a-tale-of-sadness-rage-and-data-loss
591 *
592 */
593 function php4_create_parser($source, $in_enc, $detect) {
594 if ( !$detect ) {
595 return array(xml_parser_create($in_enc), $source);
596 }
597
598 if (!$in_enc) {
599 if (preg_match('/<?xml.*encoding=[\'"](.*?)[\'"].*?>/m', $source, $m)) {
600 $in_enc = strtoupper($m[1]);
601 $this->source_encoding = $in_enc;
602 }
603 else {
604 $in_enc = 'UTF-8';
605 }
606 }
607
608 if ($this->known_encoding($in_enc)) {
609 return array(xml_parser_create($in_enc), $source);
610 }
611
612 // the dectected encoding is not one of the simple encodings PHP knows
613
614 // attempt to use the iconv extension to
615 // cast the XML to a known encoding
616 // @see http://php.net/iconv
617
618 if (function_exists('iconv')) {
619 $encoded_source = iconv($in_enc,'UTF-8', $source);
620 if ($encoded_source) {
621 return array(xml_parser_create('UTF-8'), $encoded_source);
622 }
623 }
624
625 // iconv didn't work, try mb_convert_encoding
626 // @see http://php.net/mbstring
627 if(function_exists('mb_convert_encoding')) {
628 $encoded_source = mb_convert_encoding($source, 'UTF-8', $in_enc );
629 if ($encoded_source) {
630 return array(xml_parser_create('UTF-8'), $encoded_source);
631 }
632 }
633
634 // else
635 $this->error("Feed is in an unsupported character encoding. ($in_enc) " .
636 "You may see strange artifacts, and mangled characters.",
637 E_USER_NOTICE);
638
639 return array(xml_parser_create(), $source);
640 }
641
642 function known_encoding($enc) {
643 $enc = strtoupper($enc);
644 if ( in_array($enc, $this->_KNOWN_ENCODINGS) ) {
645 return $enc;
646 }
647 else {
648 return false;
649 }
650 }
651
652 function error ($errormsg, $lvl=E_USER_WARNING) {
653 // append PHP's error message if track_errors enabled
654 if ( $php_errormsg ) {
655 $errormsg .= " ($php_errormsg)";
656 }
657 if ( MAGPIE_DEBUG ) {
658 trigger_error( $errormsg, $lvl);
659 }
660 else {
661 error_log( $errormsg, 0);
662 }
663
664 $notices = E_USER_NOTICE|E_NOTICE;
665 if ( $lvl&$notices ) {
666 $this->WARNING = $errormsg;
667 } else {
668 $this->ERROR = $errormsg;
669 }
670 }
671 } // end class RSS
672
673 function map_attrs($k, $v) {
674 return "$k=\"$v\"";
675 }
676 # ---- cut here ----
677
678 require_once( dirname(__FILE__) . '/class-snoopy.php');
679
680 # -- UPDATED from rss_fetch.inc: fetch_rss, error, debug, magpie_error
681 # --- cut here ---
682 function fetch_rss ($url) {
683 // initialize constants
684 init();
685
686 if ( !isset($url) ) {
687 error("fetch_rss called without a url");
688 return false;
689 }
690
691 // if cache is disabled
692 if ( !MAGPIE_CACHE_ON ) {
693 // fetch file, and parse it
694 $resp = _fetch_remote_file( $url );
695 if ( is_success( $resp->status ) ) {
696 return _response_to_rss( $resp );
697 }
698 else {
699 error("Failed to fetch $url and cache is off");
700 return false;
701 }
702 }
703 // else cache is ON
704 else {
705 // Flow
706 // 1. check cache
707 // 2. if there is a hit, make sure its fresh
708 // 3. if cached obj fails freshness check, fetch remote
709 // 4. if remote fails, return stale object, or error
710
711 $cache = new RSSCache( MAGPIE_CACHE_DIR, MAGPIE_CACHE_AGE );
712
713 if (MAGPIE_DEBUG and $cache->ERROR) {
714 debug($cache->ERROR, E_USER_WARNING);
715 }
716
717
718 $cache_status = 0; // response of check_cache
719 $request_headers = array(); // HTTP headers to send with fetch
720 $rss = 0; // parsed RSS object
721 $errormsg = 0; // errors, if any
722
723 // store parsed XML by desired output encoding
724 // as character munging happens at parse time
725 $cache_key = $url . MAGPIE_OUTPUT_ENCODING;
726
727 if (!$cache->ERROR) {
728 // return cache HIT, MISS, or STALE
729 $cache_status = $cache->check_cache( $cache_key);
730 }
731
732 // if object cached, and cache is fresh, return cached obj
733 if ( $cache_status == 'HIT' ) {
734 $rss = $cache->get( $cache_key );
735 if ( isset($rss) and $rss ) {
736 // should be cache age
737 $rss->from_cache = 1;
738 if ( MAGPIE_DEBUG > 1) {
739 debug("MagpieRSS: Cache HIT", E_USER_NOTICE);
740 }
741 return $rss;
742 }
743 }
744
745 // else attempt a conditional get
746
747 // setup headers
748 if ( $cache_status == 'STALE' ) {
749 $rss = $cache->get( $cache_key );
750 if ( $rss and $rss->etag and $rss->last_modified ) {
751 $request_headers['If-None-Match'] = $rss->etag;
752 $request_headers['If-Last-Modified'] = $rss->last_modified;
753 }
754 }
755
756 $resp = _fetch_remote_file( $url, $request_headers );
757
758 if (isset($resp) and $resp) {
759 if ($resp->status == '304' ) {
760 // we have the most current copy
761 if ( MAGPIE_DEBUG > 1) {
762 debug("Got 304 for $url");
763 }
764 // reset cache on 304 (at minutillo insistent prodding)
765 $cache->set($cache_key, $rss);
766 return $rss;
767 }
768 elseif ( is_success( $resp->status ) ) {
769 $rss = _response_to_rss( $resp );
770 if ( $rss ) {
771 if (MAGPIE_DEBUG > 1) {
772 debug("Fetch successful");
773 }
774 // add object to cache
775 $cache->set( $cache_key, $rss );
776 return $rss;
777 }
778 }
779 else {
780 $errormsg = "Failed to fetch $url ";
781 if ( $resp->status == '-100' ) {
782 $errormsg .= "(Request timed out after " . MAGPIE_FETCH_TIME_OUT . " seconds)";
783 }
784 elseif ( $resp->error ) {
785 # compensate for Snoopy's annoying habbit to tacking
786 # on '\n'
787 $http_error = substr($resp->error, 0, -2);
788 $errormsg .= "(HTTP Error: $http_error)";
789 }
790 else {
791 $errormsg .= "(HTTP Response: " . $resp->response_code .')';
792 }
793 }
794 }
795 else {
796 $errormsg = "Unable to retrieve RSS file for unknown reasons.";
797 }
798
799 // else fetch failed
800
801 // attempt to return cached object
802 if ($rss) {
803 if ( MAGPIE_DEBUG ) {
804 debug("Returning STALE object for $url");
805 }
806 return $rss;
807 }
808
809 // else we totally failed
810 error( $errormsg );
811
812 return false;
813
814 } // end if ( !MAGPIE_CACHE_ON ) {
815 } // end fetch_rss()
816
817 /*=======================================================================*\
818 Function: error
819 Purpose: set MAGPIE_ERROR, and trigger error
820 \*=======================================================================*/
821
822 function error ($errormsg, $lvl=E_USER_WARNING) {
823 global $MAGPIE_ERROR;
824
825 // append PHP's error message if track_errors enabled
826 if ( isset($php_errormsg) ) {
827 $errormsg .= " ($php_errormsg)";
828 }
829 if ( $errormsg ) {
830 $errormsg = "MagpieRSS: $errormsg";
831 $MAGPIE_ERROR = $errormsg;
832 trigger_error( $errormsg, $lvl);
833 }
834 }
835
836 function debug ($debugmsg, $lvl=E_USER_NOTICE) {
837 trigger_error("MagpieRSS [debug] $debugmsg", $lvl);
838 }
839
840 /*=======================================================================*\
841 Function: magpie_error
842 Purpose: accessor for the magpie error variable
843 \*=======================================================================*/
844 function magpie_error ($errormsg="") {
845 global $MAGPIE_ERROR;
846
847 if ( isset($errormsg) and $errormsg ) {
848 $MAGPIE_ERROR = $errormsg;
849 }
850
851 return $MAGPIE_ERROR;
852 }
853 # --- cut here ---
854
855 # UPDATED FROM: rss_fetch.inc: _fetch_remote_file, _response_to_rss, init
856 # --- cut here ---
857 /*=======================================================================*\
858 Function: _fetch_remote_file
859 Purpose: retrieve an arbitrary remote file
860 Input: url of the remote file
861 headers to send along with the request (optional)
862 Output: an HTTP response object (see Snoopy.class.inc)
863 \*=======================================================================*/
864 function _fetch_remote_file ($url, $headers = "" ) {
865 // Snoopy is an HTTP client in PHP
866 $client = new Snoopy();
867 $client->agent = MAGPIE_USER_AGENT;
868 $client->read_timeout = MAGPIE_FETCH_TIME_OUT;
869 $client->use_gzip = MAGPIE_USE_GZIP;
870 if (is_array($headers) ) {
871 $client->rawheaders = $headers;
872 }
873
874 @$client->fetch($url);
875 return $client;
876
877 }
878
879 /*=======================================================================*\
880 Function: _response_to_rss
881 Purpose: parse an HTTP response object into an RSS object
882 Input: an HTTP response object (see Snoopy)
883 Output: parsed RSS object (see rss_parse)
884 \*=======================================================================*/
885 function _response_to_rss ($resp) {
886 $rss = new MagpieRSS( $resp->results, MAGPIE_OUTPUT_ENCODING, MAGPIE_INPUT_ENCODING, MAGPIE_DETECT_ENCODING );
887
888 // if RSS parsed successfully
889 if ( $rss and !$rss->ERROR) {
890
891 // find Etag, and Last-Modified
892 foreach($resp->headers as $h) {
893 // 2003-03-02 - Nicola Asuni (www.tecnick.com) - fixed bug "Undefined offset: 1"
894 if (strpos($h, ": ")) {
895 list($field, $val) = explode(": ", $h, 2);
896 }
897 else {
898 $field = $h;
899 $val = "";
900 }
901
902 if ( $field == 'ETag' ) {
903 $rss->etag = $val;
904 }
905
906 if ( $field == 'Last-Modified' ) {
907 $rss->last_modified = $val;
908 }
909 }
910
911 return $rss;
912 } // else construct error message
913 else {
914 $errormsg = "Failed to parse RSS file.";
915
916 if ($rss) {
917 $errormsg .= " (" . $rss->ERROR . ")";
918 }
919 error($errormsg);
920
921 return false;
922 } // end if ($rss and !$rss->error)
923 }
924
925 /*=======================================================================*\
926 Function: init
927 Purpose: setup constants with default values
928 check for user overrides
929 \*=======================================================================*/
930 function init () {
931 if ( defined('MAGPIE_INITALIZED') ) {
932 return;
933 }
934 else {
935 define('MAGPIE_INITALIZED', true);
936 }
937
938 if ( !defined('MAGPIE_CACHE_ON') ) {
939 define('MAGPIE_CACHE_ON', true);
940 }
941
942 if ( !defined('MAGPIE_CACHE_DIR') ) {
943 define('MAGPIE_CACHE_DIR', './cache');
944 }
945
946 if ( !defined('MAGPIE_CACHE_AGE') ) {
947 define('MAGPIE_CACHE_AGE', 60*60); // one hour
948 }
949
950 if ( !defined('MAGPIE_CACHE_FRESH_ONLY') ) {
951 define('MAGPIE_CACHE_FRESH_ONLY', false);
952 }
953
954 if ( !defined('MAGPIE_OUTPUT_ENCODING') ) {
955 # WORDPRESS MODIFICATION: use whatever charset the blog is using
956 # --- cut here ---
957 $wp_encoding = get_settings('blog_charset');
958 define('MAGPIE_OUTPUT_ENCODING', ($wp_encoding?$wp_encoding:'ISO-8859-1'));
959 # --- cut here ---
960 }
961
962 if ( !defined('MAGPIE_INPUT_ENCODING') ) {
963 define('MAGPIE_INPUT_ENCODING', null);
964 }
965
966 if ( !defined('MAGPIE_DETECT_ENCODING') ) {
967 define('MAGPIE_DETECT_ENCODING', true);
968 }
969
970 if ( !defined('MAGPIE_DEBUG') ) {
971 define('MAGPIE_DEBUG', 0);
972 }
973
974 if ( !defined('MAGPIE_USER_AGENT') ) {
975 # WORDPRESS MODIFICATION: send WordPress as user-agent
976 # --- cut here ---
977 $ua = 'WordPress/'. $wp_version . ' (+http://www.wordpress.org';
978 # --- cut here ---
979
980 if ( MAGPIE_CACHE_ON ) {
981 $ua = $ua . ')';
982 }
983 else {
984 $ua = $ua . '; No cache)';
985 }
986
987 define('MAGPIE_USER_AGENT', $ua);
988 }
989
990 if ( !defined('MAGPIE_FETCH_TIME_OUT') ) {
991 define('MAGPIE_FETCH_TIME_OUT', 5); // 5 second timeout
992 }
993
994 // use gzip encoding to fetch rss files if supported?
995 if ( !defined('MAGPIE_USE_GZIP') ) {
996 define('MAGPIE_USE_GZIP', true);
997 }
998 }
999 # --- cut here ---
1000
1001 function is_info ($sc) {
1002 return $sc >= 100 && $sc < 200;
1003 }
1004
1005 function is_success ($sc) {
1006 return $sc >= 200 && $sc < 300;
1007 }
1008
1009 function is_redirect ($sc) {
1010 return $sc >= 300 && $sc < 400;
1011 }
1012
1013 function is_error ($sc) {
1014 return $sc >= 400 && $sc < 600;
1015 }
1016
1017 function is_client_error ($sc) {
1018 return $sc >= 400 && $sc < 500;
1019 }
1020
1021 function is_server_error ($sc) {
1022 return $sc >= 500 && $sc < 600;
1023 }
1024
1025 # WORDPRESS-SPECIFIC: class RSSCache (modified to use WP database)
1026 # --- cut here ---
1027 class RSSCache {
1028 var $BASE_CACHE = 'wp-content/cache'; // where the cache files are stored
1029 var $MAX_AGE = 43200; // when are files stale, default twelve hours
1030 var $ERROR = ''; // accumulate error messages
1031
1032 function RSSCache ($base='', $age='') {
1033 if ( $base ) {
1034 $this->BASE_CACHE = $base;
1035 }
1036 if ( $age ) {
1037 $this->MAX_AGE = $age;
1038 }
1039
1040 }
1041
1042 /*=======================================================================*\
1043 Function: set
1044 Purpose: add an item to the cache, keyed on url
1045 Input: url from wich the rss file was fetched
1046 Output: true on sucess
1047 \*=======================================================================*/
1048 function set ($url, $rss) {
1049 global $wpdb;
1050 $cache_option = 'rss_' . $this->file_name( $url );
1051 $cache_timestamp = 'rss_' . $this->file_name( $url ) . '_ts';
1052
1053 if ( !$wpdb->get_var("SELECT option_name FROM $wpdb->options WHERE option_name = '$cache_option'") )
1054 add_option($cache_option, '', '', 'no');
1055 if ( !$wpdb->get_var("SELECT option_name FROM $wpdb->options WHERE option_name = '$cache_timestamp'") )
1056 add_option($cache_timestamp, '', '', 'no');
1057
1058 update_option($cache_option, $rss);
1059 update_option($cache_timestamp, time() );
1060
1061 return $cache_option;
1062 }
1063
1064 /*=======================================================================*\
1065 Function: get
1066 Purpose: fetch an item from the cache
1067 Input: url from wich the rss file was fetched
1068 Output: cached object on HIT, false on MISS
1069 \*=======================================================================*/
1070 function get ($url) {
1071 $this->ERROR = "";
1072 $cache_option = 'rss_' . $this->file_name( $url );
1073
1074 if ( ! get_option( $cache_option ) ) {
1075 $this->debug(
1076 "Cache doesn't contain: $url (cache option: $cache_option)"
1077 );
1078 return 0;
1079 }
1080
1081 $rss = get_option( $cache_option );
1082
1083 return $rss;
1084 }
1085
1086 /*=======================================================================*\
1087 Function: check_cache
1088 Purpose: check a url for membership in the cache
1089 and whether the object is older then MAX_AGE (ie. STALE)
1090 Input: url from wich the rss file was fetched
1091 Output: cached object on HIT, false on MISS
1092 \*=======================================================================*/
1093 function check_cache ( $url ) {
1094 $this->ERROR = "";
1095 $cache_option = $this->file_name( $url );
1096 $cache_timestamp = 'rss_' . $this->file_name( $url ) . '_ts';
1097
1098 if ( $mtime = get_option($cache_timestamp) ) {
1099 // find how long ago the file was added to the cache
1100 // and whether that is longer then MAX_AGE
1101 $age = time() - $mtime;
1102 if ( $this->MAX_AGE > $age ) {
1103 // object exists and is current
1104 return 'HIT';
1105 }
1106 else {
1107 // object exists but is old
1108 return 'STALE';
1109 }
1110 }
1111 else {
1112 // object does not exist
1113 return 'MISS';
1114 }
1115 }
1116
1117 /*=======================================================================*\
1118 Function: serialize
1119 \*=======================================================================*/
1120 function serialize ( $rss ) {
1121 return serialize( $rss );
1122 }
1123
1124 /*=======================================================================*\
1125 Function: unserialize
1126 \*=======================================================================*/
1127 function unserialize ( $data ) {
1128 return unserialize( $data );
1129 }
1130
1131 /*=======================================================================*\
1132 Function: file_name
1133 Purpose: map url to location in cache
1134 Input: url from wich the rss file was fetched
1135 Output: a file name
1136 \*=======================================================================*/
1137 function file_name ($url) {
1138 return md5( $url );
1139 }
1140
1141 /*=======================================================================*\
1142 Function: error
1143 Purpose: register error
1144 \*=======================================================================*/
1145 function error ($errormsg, $lvl=E_USER_WARNING) {
1146 // append PHP's error message if track_errors enabled
1147 if ( isset($php_errormsg) ) {
1148 $errormsg .= " ($php_errormsg)";
1149 }
1150 $this->ERROR = $errormsg;
1151 if ( MAGPIE_DEBUG ) {
1152 trigger_error( $errormsg, $lvl);
1153 }
1154 else {
1155 error_log( $errormsg, 0);
1156 }
1157 }
1158 function debug ($debugmsg, $lvl=E_USER_NOTICE) {
1159 if ( MAGPIE_DEBUG ) {
1160 $this->error("MagpieRSS [debug] $debugmsg", $lvl);
1161 }
1162 }
1163 }
1164 # --- cut here ---
1165
1166 function parse_w3cdtf ( $date_str ) {
1167
1168 # regex to match wc3dtf
1169 $pat = "/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(:(\d{2}))?(?:([-+])(\d{2}):?(\d{2})|(Z))?/";
1170
1171 if ( preg_match( $pat, $date_str, $match ) ) {
1172 list( $year, $month, $day, $hours, $minutes, $seconds) =
1173 array( $match[1], $match[2], $match[3], $match[4], $match[5], $match[6]);
1174
1175 # calc epoch for current date assuming GMT
1176 $epoch = gmmktime( $hours, $minutes, $seconds, $month, $day, $year);
1177
1178 $offset = 0;
1179 if ( $match[10] == 'Z' ) {
1180 # zulu time, aka GMT
1181 }
1182 else {
1183 list( $tz_mod, $tz_hour, $tz_min ) =
1184 array( $match[8], $match[9], $match[10]);
1185
1186 # zero out the variables
1187 if ( ! $tz_hour ) { $tz_hour = 0; }
1188 if ( ! $tz_min ) { $tz_min = 0; }
1189
1190 $offset_secs = (($tz_hour*60)+$tz_min)*60;
1191
1192 # is timezone ahead of GMT? then subtract offset
1193 #
1194 if ( $tz_mod == '+' ) {
1195 $offset_secs = $offset_secs * -1;
1196 }
1197
1198 $offset = $offset_secs;
1199 }
1200 $epoch = $epoch + $offset;
1201 return $epoch;
1202 }
1203 else {
1204 return -1;
1205 }
1206 }
1207
1208 # WORDPRESS-SPECIFIC: wp_rss (), get_rss ()
1209 # --- cut here ---
1210 function wp_rss ($url, $num) {
1211 //ini_set("display_errors", false); uncomment to suppress php errors thrown if the feed is not returned.
1212 $num_items = $num;
1213 $rss = fetch_rss($url);
1214 if ( $rss ) {
1215 echo "<ul>";
1216 $rss->items = array_slice($rss->items, 0, $num_items);
1217 foreach ($rss->items as $item ) {
1218 echo "<li>\n";
1219 echo "<a href='$item[link]' title='$item[description]'>";
1220 echo htmlentities($item['title']);
1221 echo "</a><br />\n";
1222 echo "</li>\n";
1223 }
1224 echo "</ul>";
1225 }
1226 else {
1227 echo "an error has occured the feed is probably down, try again later.";
1228 }
1229 }
1230
1231 function get_rss ($uri, $num = 5) { // Like get posts, but for RSS
1232 $rss = fetch_rss($url);
1233 if ( $rss ) {
1234 $rss->items = array_slice($rss->items, 0, $num_items);
1235 foreach ($rss->items as $item ) {
1236 echo "<li>\n";
1237 echo "<a href='$item[link]' title='$item[description]'>";
1238 echo htmlentities($item['title']);
1239 echo "</a><br />\n";
1240 echo "</li>\n";
1241 }
1242 return $posts;
1243 } else {
1244 return false;
1245 }
1246 }
1247 # --- cut here ---
1248 ?>