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

1,203 lines 38.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
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 define('MAGPIE_OUTPUT_ENCODING', 'ISO-8859-1');
915 }
916
917 if ( !defined('MAGPIE_INPUT_ENCODING') ) {
918 define('MAGPIE_INPUT_ENCODING', null);
919 }
920
921 if ( !defined('MAGPIE_DETECT_ENCODING') ) {
922 define('MAGPIE_DETECT_ENCODING', true);
923 }
924
925 if ( !defined('MAGPIE_DEBUG') ) {
926 define('MAGPIE_DEBUG', 0);
927 }
928
929 if ( !defined('MAGPIE_USER_AGENT') ) {
930 # WORDPRESS MODIFICATION: send WordPress as user-agent
931 # --- cut here ---
932 $ua = 'WordPress/'. $wp_version . ' (+http://www.wordpress.org';
933 # --- cut here ---
934
935 if ( MAGPIE_CACHE_ON ) {
936 $ua = $ua . ')';
937 }
938 else {
939 $ua = $ua . '; No cache)';
940 }
941
942 define('MAGPIE_USER_AGENT', $ua);
943 }
944
945 if ( !defined('MAGPIE_FETCH_TIME_OUT') ) {
946 define('MAGPIE_FETCH_TIME_OUT', 5); // 5 second timeout
947 }
948
949 // use gzip encoding to fetch rss files if supported?
950 if ( !defined('MAGPIE_USE_GZIP') ) {
951 define('MAGPIE_USE_GZIP', true);
952 }
953 }
954 # --- cut here ---
955
956 function is_info ($sc) {
957 return $sc >= 100 && $sc < 200;
958 }
959
960 function is_success ($sc) {
961 return $sc >= 200 && $sc < 300;
962 }
963
964 function is_redirect ($sc) {
965 return $sc >= 300 && $sc < 400;
966 }
967
968 function is_error ($sc) {
969 return $sc >= 400 && $sc < 600;
970 }
971
972 function is_client_error ($sc) {
973 return $sc >= 400 && $sc < 500;
974 }
975
976 function is_server_error ($sc) {
977 return $sc >= 500 && $sc < 600;
978 }
979
980 # WORDPRESS-SPECIFIC: class RSSCache (modified to use WP database)
981 # --- cut here ---
982 class RSSCache {
983 var $BASE_CACHE = 'wp-content/cache'; // where the cache files are stored
984 var $MAX_AGE = 43200; // when are files stale, default twelve hours
985 var $ERROR = ''; // accumulate error messages
986
987 function RSSCache ($base='', $age='') {
988 if ( $base ) {
989 $this->BASE_CACHE = $base;
990 }
991 if ( $age ) {
992 $this->MAX_AGE = $age;
993 }
994
995 }
996
997 /*=======================================================================*\
998 Function: set
999 Purpose: add an item to the cache, keyed on url
1000 Input: url from wich the rss file was fetched
1001 Output: true on sucess
1002 \*=======================================================================*/
1003 function set ($url, $rss) {
1004 global $wpdb;
1005 $cache_option = 'rss_' . $this->file_name( $url );
1006 $cache_timestamp = 'rss_' . $this->file_name( $url ) . '_ts';
1007
1008 if ( !$wpdb->get_var("SELECT option_name FROM $wpdb->options WHERE option_name = '$cache_option'") )
1009 add_option($cache_option, '', '', 'no');
1010 if ( !$wpdb->get_var("SELECT option_name FROM $wpdb->options WHERE option_name = '$cache_timestamp'") )
1011 add_option($cache_timestamp, '', '', 'no');
1012
1013 update_option($cache_option, $rss);
1014 update_option($cache_timestamp, time() );
1015
1016 return $cache_option;
1017 }
1018
1019 /*=======================================================================*\
1020 Function: get
1021 Purpose: fetch an item from the cache
1022 Input: url from wich the rss file was fetched
1023 Output: cached object on HIT, false on MISS
1024 \*=======================================================================*/
1025 function get ($url) {
1026 $this->ERROR = "";
1027 $cache_option = 'rss_' . $this->file_name( $url );
1028
1029 if ( ! get_option( $cache_option ) ) {
1030 $this->debug(
1031 "Cache doesn't contain: $url (cache option: $cache_option)"
1032 );
1033 return 0;
1034 }
1035
1036 $rss = get_option( $cache_option );
1037
1038 return $rss;
1039 }
1040
1041 /*=======================================================================*\
1042 Function: check_cache
1043 Purpose: check a url for membership in the cache
1044 and whether the object is older then MAX_AGE (ie. STALE)
1045 Input: url from wich the rss file was fetched
1046 Output: cached object on HIT, false on MISS
1047 \*=======================================================================*/
1048 function check_cache ( $url ) {
1049 $this->ERROR = "";
1050 $cache_option = $this->file_name( $url );
1051 $cache_timestamp = 'rss_' . $this->file_name( $url ) . '_ts';
1052
1053 if ( $mtime = get_option($cache_timestamp) ) {
1054 // find how long ago the file was added to the cache
1055 // and whether that is longer then MAX_AGE
1056 $age = time() - $mtime;
1057 if ( $this->MAX_AGE > $age ) {
1058 // object exists and is current
1059 return 'HIT';
1060 }
1061 else {
1062 // object exists but is old
1063 return 'STALE';
1064 }
1065 }
1066 else {
1067 // object does not exist
1068 return 'MISS';
1069 }
1070 }
1071
1072 /*=======================================================================*\
1073 Function: serialize
1074 \*=======================================================================*/
1075 function serialize ( $rss ) {
1076 return serialize( $rss );
1077 }
1078
1079 /*=======================================================================*\
1080 Function: unserialize
1081 \*=======================================================================*/
1082 function unserialize ( $data ) {
1083 return unserialize( $data );
1084 }
1085
1086 /*=======================================================================*\
1087 Function: file_name
1088 Purpose: map url to location in cache
1089 Input: url from wich the rss file was fetched
1090 Output: a file name
1091 \*=======================================================================*/
1092 function file_name ($url) {
1093 return md5( $url );
1094 }
1095
1096 /*=======================================================================*\
1097 Function: error
1098 Purpose: register error
1099 \*=======================================================================*/
1100 function error ($errormsg, $lvl=E_USER_WARNING) {
1101 // append PHP's error message if track_errors enabled
1102 if ( isset($php_errormsg) ) {
1103 $errormsg .= " ($php_errormsg)";
1104 }
1105 $this->ERROR = $errormsg;
1106 if ( MAGPIE_DEBUG ) {
1107 trigger_error( $errormsg, $lvl);
1108 }
1109 else {
1110 error_log( $errormsg, 0);
1111 }
1112 }
1113 function debug ($debugmsg, $lvl=E_USER_NOTICE) {
1114 if ( MAGPIE_DEBUG ) {
1115 $this->error("MagpieRSS [debug] $debugmsg", $lvl);
1116 }
1117 }
1118 }
1119 # --- cut here ---
1120
1121 function parse_w3cdtf ( $date_str ) {
1122
1123 # regex to match wc3dtf
1124 $pat = "/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(:(\d{2}))?(?:([-+])(\d{2}):?(\d{2})|(Z))?/";
1125
1126 if ( preg_match( $pat, $date_str, $match ) ) {
1127 list( $year, $month, $day, $hours, $minutes, $seconds) =
1128 array( $match[1], $match[2], $match[3], $match[4], $match[5], $match[6]);
1129
1130 # calc epoch for current date assuming GMT
1131 $epoch = gmmktime( $hours, $minutes, $seconds, $month, $day, $year);
1132
1133 $offset = 0;
1134 if ( $match[10] == 'Z' ) {
1135 # zulu time, aka GMT
1136 }
1137 else {
1138 list( $tz_mod, $tz_hour, $tz_min ) =
1139 array( $match[8], $match[9], $match[10]);
1140
1141 # zero out the variables
1142 if ( ! $tz_hour ) { $tz_hour = 0; }
1143 if ( ! $tz_min ) { $tz_min = 0; }
1144
1145 $offset_secs = (($tz_hour*60)+$tz_min)*60;
1146
1147 # is timezone ahead of GMT? then subtract offset
1148 #
1149 if ( $tz_mod == '+' ) {
1150 $offset_secs = $offset_secs * -1;
1151 }
1152
1153 $offset = $offset_secs;
1154 }
1155 $epoch = $epoch + $offset;
1156 return $epoch;
1157 }
1158 else {
1159 return -1;
1160 }
1161 }
1162
1163 # WORDPRESS-SPECIFIC: wp_rss (), get_rss ()
1164 # --- cut here ---
1165 function wp_rss ($url, $num) {
1166 //ini_set("display_errors", false); uncomment to suppress php errors thrown if the feed is not returned.
1167 $num_items = $num;
1168 $rss = fetch_rss($url);
1169 if ( $rss ) {
1170 echo "<ul>";
1171 $rss->items = array_slice($rss->items, 0, $num_items);
1172 foreach ($rss->items as $item ) {
1173 echo "<li>\n";
1174 echo "<a href='$item[link]' title='$item[description]'>";
1175 echo htmlentities($item['title']);
1176 echo "</a><br />\n";
1177 echo "</li>\n";
1178 }
1179 echo "</ul>";
1180 }
1181 else {
1182 echo "an error has occured the feed is probably down, try again later.";
1183 }
1184 }
1185
1186 function get_rss ($uri, $num = 5) { // Like get posts, but for RSS
1187 $rss = fetch_rss($url);
1188 if ( $rss ) {
1189 $rss->items = array_slice($rss->items, 0, $num_items);
1190 foreach ($rss->items as $item ) {
1191 echo "<li>\n";
1192 echo "<a href='$item[link]' title='$item[description]'>";
1193 echo htmlentities($item['title']);
1194 echo "</a><br />\n";
1195 echo "</li>\n";
1196 }
1197 return $posts;
1198 } else {
1199 return false;
1200 }
1201 }
1202 # --- cut here ---
1203 ?>