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