PluginProbe
FeedWordPress / 0.91
FeedWordPress v0.91
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.91, at OPTIONAL/wp-includes/rss-functions.php

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