WordPress 4.7
[autoinstalls/wordpress.git] / wp-includes / rss.php
1 <?php
2 /**
3  * MagpieRSS: a simple RSS integration tool
4  *
5  * A compiled file for RSS syndication
6  *
7  * @author Kellan Elliott-McCrea <kellan@protest.net>
8  * @version 0.51
9  * @license GPL
10  *
11  * @package External
12  * @subpackage MagpieRSS
13  * @deprecated 3.0.0 Use SimplePie instead.
14  */
15
16 /**
17  * Deprecated. Use SimplePie (class-simplepie.php) instead.
18  */
19 _deprecated_file( basename( __FILE__ ), '3.0.0', WPINC . '/class-simplepie.php' );
20
21 /**
22  * Fires before MagpieRSS is loaded, to optionally replace it.
23  *
24  * @since 2.3.0
25  * @deprecated 3.0.0
26  */
27 do_action( 'load_feed_engine' );
28
29 /** RSS feed constant. */
30 define('RSS', 'RSS');
31 define('ATOM', 'Atom');
32 define('MAGPIE_USER_AGENT', 'WordPress/' . $GLOBALS['wp_version']);
33
34 class MagpieRSS {
35         var $parser;
36         var $current_item       = array();      // item currently being parsed
37         var $items                      = array();      // collection of parsed items
38         var $channel            = array();      // hash of channel fields
39         var $textinput          = array();
40         var $image                      = array();
41         var $feed_type;
42         var $feed_version;
43
44         // parser variables
45         var $stack                              = array(); // parser stack
46         var $inchannel                  = false;
47         var $initem                     = false;
48         var $incontent                  = false; // if in Atom <content mode="xml"> field
49         var $intextinput                = false;
50         var $inimage                    = false;
51         var $current_field              = '';
52         var $current_namespace  = false;
53
54         //var $ERROR = "";
55
56         var $_CONTENT_CONSTRUCTS = array('content', 'summary', 'info', 'title', 'tagline', 'copyright');
57
58         /**
59          * PHP5 constructor.
60          */
61         function __construct( $source ) {
62
63                 # Check if PHP xml isn't compiled
64                 #
65                 if ( ! function_exists('xml_parser_create') ) {
66                         return trigger_error( "PHP's XML extension is not available. Please contact your hosting provider to enable PHP's XML extension." );
67                 }
68
69                 $parser = xml_parser_create();
70
71                 $this->parser = $parser;
72
73                 # pass in parser, and a reference to this object
74                 # set up handlers
75                 #
76                 xml_set_object( $this->parser, $this );
77                 xml_set_element_handler($this->parser,
78                                 'feed_start_element', 'feed_end_element' );
79
80                 xml_set_character_data_handler( $this->parser, 'feed_cdata' );
81
82                 $status = xml_parse( $this->parser, $source );
83
84                 if (! $status ) {
85                         $errorcode = xml_get_error_code( $this->parser );
86                         if ( $errorcode != XML_ERROR_NONE ) {
87                                 $xml_error = xml_error_string( $errorcode );
88                                 $error_line = xml_get_current_line_number($this->parser);
89                                 $error_col = xml_get_current_column_number($this->parser);
90                                 $errormsg = "$xml_error at line $error_line, column $error_col";
91
92                                 $this->error( $errormsg );
93                         }
94                 }
95
96                 xml_parser_free( $this->parser );
97
98                 $this->normalize();
99         }
100
101         /**
102          * PHP4 constructor.
103          */
104         public function MagpieRSS( $source ) {
105                 self::__construct( $source );
106         }
107
108         function feed_start_element($p, $element, &$attrs) {
109                 $el = $element = strtolower($element);
110                 $attrs = array_change_key_case($attrs, CASE_LOWER);
111
112                 // check for a namespace, and split if found
113                 $ns     = false;
114                 if ( strpos( $element, ':' ) ) {
115                         list($ns, $el) = explode( ':', $element, 2);
116                 }
117                 if ( $ns and $ns != 'rdf' ) {
118                         $this->current_namespace = $ns;
119                 }
120
121                 # if feed type isn't set, then this is first element of feed
122                 # identify feed from root element
123                 #
124                 if (!isset($this->feed_type) ) {
125                         if ( $el == 'rdf' ) {
126                                 $this->feed_type = RSS;
127                                 $this->feed_version = '1.0';
128                         }
129                         elseif ( $el == 'rss' ) {
130                                 $this->feed_type = RSS;
131                                 $this->feed_version = $attrs['version'];
132                         }
133                         elseif ( $el == 'feed' ) {
134                                 $this->feed_type = ATOM;
135                                 $this->feed_version = $attrs['version'];
136                                 $this->inchannel = true;
137                         }
138                         return;
139                 }
140
141                 if ( $el == 'channel' )
142                 {
143                         $this->inchannel = true;
144                 }
145                 elseif ($el == 'item' or $el == 'entry' )
146                 {
147                         $this->initem = true;
148                         if ( isset($attrs['rdf:about']) ) {
149                                 $this->current_item['about'] = $attrs['rdf:about'];
150                         }
151                 }
152
153                 // if we're in the default namespace of an RSS feed,
154                 //  record textinput or image fields
155                 elseif (
156                         $this->feed_type == RSS and
157                         $this->current_namespace == '' and
158                         $el == 'textinput' )
159                 {
160                         $this->intextinput = true;
161                 }
162
163                 elseif (
164                         $this->feed_type == RSS and
165                         $this->current_namespace == '' and
166                         $el == 'image' )
167                 {
168                         $this->inimage = true;
169                 }
170
171                 # handle atom content constructs
172                 elseif ( $this->feed_type == ATOM and in_array($el, $this->_CONTENT_CONSTRUCTS) )
173                 {
174                         // avoid clashing w/ RSS mod_content
175                         if ($el == 'content' ) {
176                                 $el = 'atom_content';
177                         }
178
179                         $this->incontent = $el;
180
181                 }
182
183                 // if inside an Atom content construct (e.g. content or summary) field treat tags as text
184                 elseif ($this->feed_type == ATOM and $this->incontent )
185                 {
186                         // if tags are inlined, then flatten
187                         $attrs_str = join(' ',
188                                         array_map(array('MagpieRSS', 'map_attrs'),
189                                         array_keys($attrs),
190                                         array_values($attrs) ) );
191
192                         $this->append_content( "<$element $attrs_str>"  );
193
194                         array_unshift( $this->stack, $el );
195                 }
196
197                 // Atom support many links per containging element.
198                 // Magpie treats link elements of type rel='alternate'
199                 // as being equivalent to RSS's simple link element.
200                 //
201                 elseif ($this->feed_type == ATOM and $el == 'link' )
202                 {
203                         if ( isset($attrs['rel']) and $attrs['rel'] == 'alternate' )
204                         {
205                                 $link_el = 'link';
206                         }
207                         else {
208                                 $link_el = 'link_' . $attrs['rel'];
209                         }
210
211                         $this->append($link_el, $attrs['href']);
212                 }
213                 // set stack[0] to current element
214                 else {
215                         array_unshift($this->stack, $el);
216                 }
217         }
218
219         function feed_cdata ($p, $text) {
220
221                 if ($this->feed_type == ATOM and $this->incontent)
222                 {
223                         $this->append_content( $text );
224                 }
225                 else {
226                         $current_el = join('_', array_reverse($this->stack));
227                         $this->append($current_el, $text);
228                 }
229         }
230
231         function feed_end_element ($p, $el) {
232                 $el = strtolower($el);
233
234                 if ( $el == 'item' or $el == 'entry' )
235                 {
236                         $this->items[] = $this->current_item;
237                         $this->current_item = array();
238                         $this->initem = false;
239                 }
240                 elseif ($this->feed_type == RSS and $this->current_namespace == '' and $el == 'textinput' )
241                 {
242                         $this->intextinput = false;
243                 }
244                 elseif ($this->feed_type == RSS and $this->current_namespace == '' and $el == 'image' )
245                 {
246                         $this->inimage = false;
247                 }
248                 elseif ($this->feed_type == ATOM and in_array($el, $this->_CONTENT_CONSTRUCTS) )
249                 {
250                         $this->incontent = false;
251                 }
252                 elseif ($el == 'channel' or $el == 'feed' )
253                 {
254                         $this->inchannel = false;
255                 }
256                 elseif ($this->feed_type == ATOM and $this->incontent  ) {
257                         // balance tags properly
258                         // note: This may not actually be necessary
259                         if ( $this->stack[0] == $el )
260                         {
261                                 $this->append_content("</$el>");
262                         }
263                         else {
264                                 $this->append_content("<$el />");
265                         }
266
267                         array_shift( $this->stack );
268                 }
269                 else {
270                         array_shift( $this->stack );
271                 }
272
273                 $this->current_namespace = false;
274         }
275
276         function concat (&$str1, $str2="") {
277                 if (!isset($str1) ) {
278                         $str1="";
279                 }
280                 $str1 .= $str2;
281         }
282
283         function append_content($text) {
284                 if ( $this->initem ) {
285                         $this->concat( $this->current_item[ $this->incontent ], $text );
286                 }
287                 elseif ( $this->inchannel ) {
288                         $this->concat( $this->channel[ $this->incontent ], $text );
289                 }
290         }
291
292         // smart append - field and namespace aware
293         function append($el, $text) {
294                 if (!$el) {
295                         return;
296                 }
297                 if ( $this->current_namespace )
298                 {
299                         if ( $this->initem ) {
300                                 $this->concat(
301                                         $this->current_item[ $this->current_namespace ][ $el ], $text);
302                         }
303                         elseif ($this->inchannel) {
304                                 $this->concat(
305                                         $this->channel[ $this->current_namespace][ $el ], $text );
306                         }
307                         elseif ($this->intextinput) {
308                                 $this->concat(
309                                         $this->textinput[ $this->current_namespace][ $el ], $text );
310                         }
311                         elseif ($this->inimage) {
312                                 $this->concat(
313                                         $this->image[ $this->current_namespace ][ $el ], $text );
314                         }
315                 }
316                 else {
317                         if ( $this->initem ) {
318                                 $this->concat(
319                                         $this->current_item[ $el ], $text);
320                         }
321                         elseif ($this->intextinput) {
322                                 $this->concat(
323                                         $this->textinput[ $el ], $text );
324                         }
325                         elseif ($this->inimage) {
326                                 $this->concat(
327                                         $this->image[ $el ], $text );
328                         }
329                         elseif ($this->inchannel) {
330                                 $this->concat(
331                                         $this->channel[ $el ], $text );
332                         }
333
334                 }
335         }
336
337         function normalize () {
338                 // if atom populate rss fields
339                 if ( $this->is_atom() ) {
340                         $this->channel['descripton'] = $this->channel['tagline'];
341                         for ( $i = 0; $i < count($this->items); $i++) {
342                                 $item = $this->items[$i];
343                                 if ( isset($item['summary']) )
344                                         $item['description'] = $item['summary'];
345                                 if ( isset($item['atom_content']))
346                                         $item['content']['encoded'] = $item['atom_content'];
347
348                                 $this->items[$i] = $item;
349                         }
350                 }
351                 elseif ( $this->is_rss() ) {
352                         $this->channel['tagline'] = $this->channel['description'];
353                         for ( $i = 0; $i < count($this->items); $i++) {
354                                 $item = $this->items[$i];
355                                 if ( isset($item['description']))
356                                         $item['summary'] = $item['description'];
357                                 if ( isset($item['content']['encoded'] ) )
358                                         $item['atom_content'] = $item['content']['encoded'];
359
360                                 $this->items[$i] = $item;
361                         }
362                 }
363         }
364
365         function is_rss () {
366                 if ( $this->feed_type == RSS ) {
367                         return $this->feed_version;
368                 }
369                 else {
370                         return false;
371                 }
372         }
373
374         function is_atom() {
375                 if ( $this->feed_type == ATOM ) {
376                         return $this->feed_version;
377                 }
378                 else {
379                         return false;
380                 }
381         }
382
383         function map_attrs($k, $v) {
384                 return "$k=\"$v\"";
385         }
386
387         function error( $errormsg, $lvl = E_USER_WARNING ) {
388                 // append PHP's error message if track_errors enabled
389                 if ( isset($php_errormsg) ) {
390                         $errormsg .= " ($php_errormsg)";
391                 }
392                 if ( MAGPIE_DEBUG ) {
393                         trigger_error( $errormsg, $lvl);
394                 } else {
395                         error_log( $errormsg, 0);
396                 }
397         }
398
399 }
400
401 if ( !function_exists('fetch_rss') ) :
402 /**
403  * Build Magpie object based on RSS from URL.
404  *
405  * @since 1.5.0
406  * @package External
407  * @subpackage MagpieRSS
408  *
409  * @param string $url URL to retrieve feed
410  * @return bool|MagpieRSS false on failure or MagpieRSS object on success.
411  */
412 function fetch_rss ($url) {
413         // initialize constants
414         init();
415
416         if ( !isset($url) ) {
417                 // error("fetch_rss called without a url");
418                 return false;
419         }
420
421         // if cache is disabled
422         if ( !MAGPIE_CACHE_ON ) {
423                 // fetch file, and parse it
424                 $resp = _fetch_remote_file( $url );
425                 if ( is_success( $resp->status ) ) {
426                         return _response_to_rss( $resp );
427                 }
428                 else {
429                         // error("Failed to fetch $url and cache is off");
430                         return false;
431                 }
432         }
433         // else cache is ON
434         else {
435                 // Flow
436                 // 1. check cache
437                 // 2. if there is a hit, make sure it's fresh
438                 // 3. if cached obj fails freshness check, fetch remote
439                 // 4. if remote fails, return stale object, or error
440
441                 $cache = new RSSCache( MAGPIE_CACHE_DIR, MAGPIE_CACHE_AGE );
442
443                 if (MAGPIE_DEBUG and $cache->ERROR) {
444                         debug($cache->ERROR, E_USER_WARNING);
445                 }
446
447                 $cache_status    = 0;           // response of check_cache
448                 $request_headers = array(); // HTTP headers to send with fetch
449                 $rss                     = 0;           // parsed RSS object
450                 $errormsg                = 0;           // errors, if any
451
452                 if (!$cache->ERROR) {
453                         // return cache HIT, MISS, or STALE
454                         $cache_status = $cache->check_cache( $url );
455                 }
456
457                 // if object cached, and cache is fresh, return cached obj
458                 if ( $cache_status == 'HIT' ) {
459                         $rss = $cache->get( $url );
460                         if ( isset($rss) and $rss ) {
461                                 $rss->from_cache = 1;
462                                 if ( MAGPIE_DEBUG > 1) {
463                                 debug("MagpieRSS: Cache HIT", E_USER_NOTICE);
464                         }
465                                 return $rss;
466                         }
467                 }
468
469                 // else attempt a conditional get
470
471                 // set up headers
472                 if ( $cache_status == 'STALE' ) {
473                         $rss = $cache->get( $url );
474                         if ( isset($rss->etag) and $rss->last_modified ) {
475                                 $request_headers['If-None-Match'] = $rss->etag;
476                                 $request_headers['If-Last-Modified'] = $rss->last_modified;
477                         }
478                 }
479
480                 $resp = _fetch_remote_file( $url, $request_headers );
481
482                 if (isset($resp) and $resp) {
483                         if ($resp->status == '304' ) {
484                                 // we have the most current copy
485                                 if ( MAGPIE_DEBUG > 1) {
486                                         debug("Got 304 for $url");
487                                 }
488                                 // reset cache on 304 (at minutillo insistent prodding)
489                                 $cache->set($url, $rss);
490                                 return $rss;
491                         }
492                         elseif ( is_success( $resp->status ) ) {
493                                 $rss = _response_to_rss( $resp );
494                                 if ( $rss ) {
495                                         if (MAGPIE_DEBUG > 1) {
496                                                 debug("Fetch successful");
497                                         }
498                                         // add object to cache
499                                         $cache->set( $url, $rss );
500                                         return $rss;
501                                 }
502                         }
503                         else {
504                                 $errormsg = "Failed to fetch $url. ";
505                                 if ( $resp->error ) {
506                                         # compensate for Snoopy's annoying habbit to tacking
507                                         # on '\n'
508                                         $http_error = substr($resp->error, 0, -2);
509                                         $errormsg .= "(HTTP Error: $http_error)";
510                                 }
511                                 else {
512                                         $errormsg .=  "(HTTP Response: " . $resp->response_code .')';
513                                 }
514                         }
515                 }
516                 else {
517                         $errormsg = "Unable to retrieve RSS file for unknown reasons.";
518                 }
519
520                 // else fetch failed
521
522                 // attempt to return cached object
523                 if ($rss) {
524                         if ( MAGPIE_DEBUG ) {
525                                 debug("Returning STALE object for $url");
526                         }
527                         return $rss;
528                 }
529
530                 // else we totally failed
531                 // error( $errormsg );
532
533                 return false;
534
535         } // end if ( !MAGPIE_CACHE_ON ) {
536 } // end fetch_rss()
537 endif;
538
539 /**
540  * Retrieve URL headers and content using WP HTTP Request API.
541  *
542  * @since 1.5.0
543  * @package External
544  * @subpackage MagpieRSS
545  *
546  * @param string $url URL to retrieve
547  * @param array $headers Optional. Headers to send to the URL.
548  * @return Snoopy style response
549  */
550 function _fetch_remote_file($url, $headers = "" ) {
551         $resp = wp_safe_remote_request( $url, array( 'headers' => $headers, 'timeout' => MAGPIE_FETCH_TIME_OUT ) );
552         if ( is_wp_error($resp) ) {
553                 $error = array_shift($resp->errors);
554
555                 $resp = new stdClass;
556                 $resp->status = 500;
557                 $resp->response_code = 500;
558                 $resp->error = $error[0] . "\n"; //\n = Snoopy compatibility
559                 return $resp;
560         }
561
562         // Snoopy returns headers unprocessed.
563         // Also note, WP_HTTP lowercases all keys, Snoopy did not.
564         $return_headers = array();
565         foreach ( wp_remote_retrieve_headers( $resp ) as $key => $value ) {
566                 if ( !is_array($value) ) {
567                         $return_headers[] = "$key: $value";
568                 } else {
569                         foreach ( $value as $v )
570                                 $return_headers[] = "$key: $v";
571                 }
572         }
573
574         $response = new stdClass;
575         $response->status = wp_remote_retrieve_response_code( $resp );
576         $response->response_code = wp_remote_retrieve_response_code( $resp );
577         $response->headers = $return_headers;
578         $response->results = wp_remote_retrieve_body( $resp );
579
580         return $response;
581 }
582
583 /**
584  * Retrieve
585  *
586  * @since 1.5.0
587  * @package External
588  * @subpackage MagpieRSS
589  *
590  * @param array $resp
591  * @return MagpieRSS|bool
592  */
593 function _response_to_rss ($resp) {
594         $rss = new MagpieRSS( $resp->results );
595
596         // if RSS parsed successfully
597         if ( $rss && (!isset($rss->ERROR) || !$rss->ERROR) ) {
598
599                 // find Etag, and Last-Modified
600                 foreach ( (array) $resp->headers as $h) {
601                         // 2003-03-02 - Nicola Asuni (www.tecnick.com) - fixed bug "Undefined offset: 1"
602                         if (strpos($h, ": ")) {
603                                 list($field, $val) = explode(": ", $h, 2);
604                         }
605                         else {
606                                 $field = $h;
607                                 $val = "";
608                         }
609
610                         if ( $field == 'etag' ) {
611                                 $rss->etag = $val;
612                         }
613
614                         if ( $field == 'last-modified' ) {
615                                 $rss->last_modified = $val;
616                         }
617                 }
618
619                 return $rss;
620         } // else construct error message
621         else {
622                 $errormsg = "Failed to parse RSS file.";
623
624                 if ($rss) {
625                         $errormsg .= " (" . $rss->ERROR . ")";
626                 }
627                 // error($errormsg);
628
629                 return false;
630         } // end if ($rss and !$rss->error)
631 }
632
633 /**
634  * Set up constants with default values, unless user overrides.
635  *
636  * @since 1.5.0
637  * @package External
638  * @subpackage MagpieRSS
639  */
640 function init () {
641         if ( defined('MAGPIE_INITALIZED') ) {
642                 return;
643         }
644         else {
645                 define('MAGPIE_INITALIZED', 1);
646         }
647
648         if ( !defined('MAGPIE_CACHE_ON') ) {
649                 define('MAGPIE_CACHE_ON', 1);
650         }
651
652         if ( !defined('MAGPIE_CACHE_DIR') ) {
653                 define('MAGPIE_CACHE_DIR', './cache');
654         }
655
656         if ( !defined('MAGPIE_CACHE_AGE') ) {
657                 define('MAGPIE_CACHE_AGE', 60*60); // one hour
658         }
659
660         if ( !defined('MAGPIE_CACHE_FRESH_ONLY') ) {
661                 define('MAGPIE_CACHE_FRESH_ONLY', 0);
662         }
663
664                 if ( !defined('MAGPIE_DEBUG') ) {
665                 define('MAGPIE_DEBUG', 0);
666         }
667
668         if ( !defined('MAGPIE_USER_AGENT') ) {
669                 $ua = 'WordPress/' . $GLOBALS['wp_version'];
670
671                 if ( MAGPIE_CACHE_ON ) {
672                         $ua = $ua . ')';
673                 }
674                 else {
675                         $ua = $ua . '; No cache)';
676                 }
677
678                 define('MAGPIE_USER_AGENT', $ua);
679         }
680
681         if ( !defined('MAGPIE_FETCH_TIME_OUT') ) {
682                 define('MAGPIE_FETCH_TIME_OUT', 2);     // 2 second timeout
683         }
684
685         // use gzip encoding to fetch rss files if supported?
686         if ( !defined('MAGPIE_USE_GZIP') ) {
687                 define('MAGPIE_USE_GZIP', true);
688         }
689 }
690
691 function is_info ($sc) {
692         return $sc >= 100 && $sc < 200;
693 }
694
695 function is_success ($sc) {
696         return $sc >= 200 && $sc < 300;
697 }
698
699 function is_redirect ($sc) {
700         return $sc >= 300 && $sc < 400;
701 }
702
703 function is_error ($sc) {
704         return $sc >= 400 && $sc < 600;
705 }
706
707 function is_client_error ($sc) {
708         return $sc >= 400 && $sc < 500;
709 }
710
711 function is_server_error ($sc) {
712         return $sc >= 500 && $sc < 600;
713 }
714
715 class RSSCache {
716         var $BASE_CACHE;        // where the cache files are stored
717         var $MAX_AGE    = 43200;                // when are files stale, default twelve hours
718         var $ERROR              = '';                   // accumulate error messages
719
720         /**
721          * PHP5 constructor.
722          */
723         function __construct( $base = '', $age = '' ) {
724                 $this->BASE_CACHE = WP_CONTENT_DIR . '/cache';
725                 if ( $base ) {
726                         $this->BASE_CACHE = $base;
727                 }
728                 if ( $age ) {
729                         $this->MAX_AGE = $age;
730                 }
731
732         }
733
734         /**
735          * PHP4 constructor.
736          */
737         public function RSSCache( $base = '', $age = '' ) {
738                 self::__construct( $base, $age );
739         }
740
741 /*=======================================================================*\
742         Function:       set
743         Purpose:        add an item to the cache, keyed on url
744         Input:          url from which the rss file was fetched
745         Output:         true on success
746 \*=======================================================================*/
747         function set ($url, $rss) {
748                 $cache_option = 'rss_' . $this->file_name( $url );
749
750                 set_transient($cache_option, $rss, $this->MAX_AGE);
751
752                 return $cache_option;
753         }
754
755 /*=======================================================================*\
756         Function:       get
757         Purpose:        fetch an item from the cache
758         Input:          url from which the rss file was fetched
759         Output:         cached object on HIT, false on MISS
760 \*=======================================================================*/
761         function get ($url) {
762                 $this->ERROR = "";
763                 $cache_option = 'rss_' . $this->file_name( $url );
764
765                 if ( ! $rss = get_transient( $cache_option ) ) {
766                         $this->debug(
767                                 "Cache doesn't contain: $url (cache option: $cache_option)"
768                         );
769                         return 0;
770                 }
771
772                 return $rss;
773         }
774
775 /*=======================================================================*\
776         Function:       check_cache
777         Purpose:        check a url for membership in the cache
778                                 and whether the object is older then MAX_AGE (ie. STALE)
779         Input:          url from which the rss file was fetched
780         Output:         cached object on HIT, false on MISS
781 \*=======================================================================*/
782         function check_cache ( $url ) {
783                 $this->ERROR = "";
784                 $cache_option = 'rss_' . $this->file_name( $url );
785
786                 if ( get_transient($cache_option) ) {
787                         // object exists and is current
788                                 return 'HIT';
789                 } else {
790                         // object does not exist
791                         return 'MISS';
792                 }
793         }
794
795 /*=======================================================================*\
796         Function:       serialize
797 \*=======================================================================*/
798         function serialize ( $rss ) {
799                 return serialize( $rss );
800         }
801
802 /*=======================================================================*\
803         Function:       unserialize
804 \*=======================================================================*/
805         function unserialize ( $data ) {
806                 return unserialize( $data );
807         }
808
809 /*=======================================================================*\
810         Function:       file_name
811         Purpose:        map url to location in cache
812         Input:          url from which the rss file was fetched
813         Output:         a file name
814 \*=======================================================================*/
815         function file_name ($url) {
816                 return md5( $url );
817         }
818
819 /*=======================================================================*\
820         Function:       error
821         Purpose:        register error
822 \*=======================================================================*/
823         function error ($errormsg, $lvl=E_USER_WARNING) {
824                 // append PHP's error message if track_errors enabled
825                 if ( isset($php_errormsg) ) {
826                         $errormsg .= " ($php_errormsg)";
827                 }
828                 $this->ERROR = $errormsg;
829                 if ( MAGPIE_DEBUG ) {
830                         trigger_error( $errormsg, $lvl);
831                 }
832                 else {
833                         error_log( $errormsg, 0);
834                 }
835         }
836                         function debug ($debugmsg, $lvl=E_USER_NOTICE) {
837                 if ( MAGPIE_DEBUG ) {
838                         $this->error("MagpieRSS [debug] $debugmsg", $lvl);
839                 }
840         }
841 }
842
843 if ( !function_exists('parse_w3cdtf') ) :
844 function parse_w3cdtf ( $date_str ) {
845
846         # regex to match wc3dtf
847         $pat = "/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(:(\d{2}))?(?:([-+])(\d{2}):?(\d{2})|(Z))?/";
848
849         if ( preg_match( $pat, $date_str, $match ) ) {
850                 list( $year, $month, $day, $hours, $minutes, $seconds) =
851                         array( $match[1], $match[2], $match[3], $match[4], $match[5], $match[7]);
852
853                 # calc epoch for current date assuming GMT
854                 $epoch = gmmktime( $hours, $minutes, $seconds, $month, $day, $year);
855
856                 $offset = 0;
857                 if ( $match[11] == 'Z' ) {
858                         # zulu time, aka GMT
859                 }
860                 else {
861                         list( $tz_mod, $tz_hour, $tz_min ) =
862                                 array( $match[8], $match[9], $match[10]);
863
864                         # zero out the variables
865                         if ( ! $tz_hour ) { $tz_hour = 0; }
866                         if ( ! $tz_min ) { $tz_min = 0; }
867
868                         $offset_secs = (($tz_hour*60)+$tz_min)*60;
869
870                         # is timezone ahead of GMT?  then subtract offset
871                         #
872                         if ( $tz_mod == '+' ) {
873                                 $offset_secs = $offset_secs * -1;
874                         }
875
876                         $offset = $offset_secs;
877                 }
878                 $epoch = $epoch + $offset;
879                 return $epoch;
880         }
881         else {
882                 return -1;
883         }
884 }
885 endif;
886
887 if ( !function_exists('wp_rss') ) :
888 /**
889  * Display all RSS items in a HTML ordered list.
890  *
891  * @since 1.5.0
892  * @package External
893  * @subpackage MagpieRSS
894  *
895  * @param string $url URL of feed to display. Will not auto sense feed URL.
896  * @param int $num_items Optional. Number of items to display, default is all.
897  */
898 function wp_rss( $url, $num_items = -1 ) {
899         if ( $rss = fetch_rss( $url ) ) {
900                 echo '<ul>';
901
902                 if ( $num_items !== -1 ) {
903                         $rss->items = array_slice( $rss->items, 0, $num_items );
904                 }
905
906                 foreach ( (array) $rss->items as $item ) {
907                         printf(
908                                 '<li><a href="%1$s" title="%2$s">%3$s</a></li>',
909                                 esc_url( $item['link'] ),
910                                 esc_attr( strip_tags( $item['description'] ) ),
911                                 esc_html( $item['title'] )
912                         );
913                 }
914
915                 echo '</ul>';
916         } else {
917                 _e( 'An error has occurred, which probably means the feed is down. Try again later.' );
918         }
919 }
920 endif;
921
922 if ( !function_exists('get_rss') ) :
923 /**
924  * Display RSS items in HTML list items.
925  *
926  * You have to specify which HTML list you want, either ordered or unordered
927  * before using the function. You also have to specify how many items you wish
928  * to display. You can't display all of them like you can with wp_rss()
929  * function.
930  *
931  * @since 1.5.0
932  * @package External
933  * @subpackage MagpieRSS
934  *
935  * @param string $url URL of feed to display. Will not auto sense feed URL.
936  * @param int $num_items Optional. Number of items to display, default is all.
937  * @return bool False on failure.
938  */
939 function get_rss ($url, $num_items = 5) { // Like get posts, but for RSS
940         $rss = fetch_rss($url);
941         if ( $rss ) {
942                 $rss->items = array_slice($rss->items, 0, $num_items);
943                 foreach ( (array) $rss->items as $item ) {
944                         echo "<li>\n";
945                         echo "<a href='$item[link]' title='$item[description]'>";
946                         echo esc_html($item['title']);
947                         echo "</a><br />\n";
948                         echo "</li>\n";
949                 }
950         } else {
951                 return false;
952         }
953 }
954 endif;