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

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