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