root/tags/2.5/wp-includes/rss.php

Revision 6585, 21.0 kB (checked in by ryan, 8 months ago)

phpdoc tuneup from darkdragon. see #5611

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
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  * Hook to use another RSS object instead of MagpieRSS
17  */
18 do_action('load_feed_engine');
19
20
21 define('RSS', 'RSS');
22 define('ATOM', 'Atom');
23 define('MAGPIE_USER_AGENT', 'WordPress/' . $GLOBALS['wp_version']);
24
25 class MagpieRSS {
26     var $parser;
27     var $current_item    = array();    // item currently being parsed
28     var $items            = array();    // collection of parsed items
29     var $channel        = array();    // hash of channel fields
30     var $textinput        = array();
31     var $image            = array();
32     var $feed_type;
33     var $feed_version;
34
35     // parser variables
36     var $stack                = array(); // parser stack
37     var $inchannel            = false;
38     var $initem             = false;
39     var $incontent            = false; // if in Atom <content mode="xml"> field
40     var $intextinput        = false;
41     var $inimage             = false;
42     var $current_field        = '';
43     var $current_namespace    = false;
44
45     //var $ERROR = "";
46
47     var $_CONTENT_CONSTRUCTS = array('content', 'summary', 'info', 'title', 'tagline', 'copyright');
48
49     function MagpieRSS ($source) {
50
51         # if PHP xml isn't compiled in, die
52         #
53         if ( !function_exists('xml_parser_create') )
54             trigger_error( "Failed to load PHP's XML Extension. http://www.php.net/manual/en/ref.xml.php" );
55
56         $parser = @xml_parser_create();
57
58         if ( !is_resource($parser) )
59             trigger_error( "Failed to create an instance of PHP's XML parser. http://www.php.net/manual/en/ref.xml.php");
60
61
62         $this->parser = $parser;
63
64         # pass in parser, and a reference to this object
65         # setup handlers
66         #
67         xml_set_object( $this->parser, $this );
68         xml_set_element_handler($this->parser,
69                 'feed_start_element', 'feed_end_element' );
70
71         xml_set_character_data_handler( $this->parser, 'feed_cdata' );
72
73         $status = xml_parse( $this->parser, $source );
74
75         if (! $status ) {
76             $errorcode = xml_get_error_code( $this->parser );
77             if ( $errorcode != XML_ERROR_NONE ) {
78                 $xml_error = xml_error_string( $errorcode );
79                 $error_line = xml_get_current_line_number($this->parser);
80                 $error_col = xml_get_current_column_number($this->parser);
81                 $errormsg = "$xml_error at line $error_line, column $error_col";
82
83                 $this->error( $errormsg );
84             }
85         }
86
87         xml_parser_free( $this->parser );
88
89         $this->normalize();
90     }
91
92     function feed_start_element($p, $element, &$attrs) {
93         $el = $element = strtolower($element);
94         $attrs = array_change_key_case($attrs, CASE_LOWER);
95
96         // check for a namespace, and split if found
97         $ns    = false;
98         if ( strpos( $element, ':' ) ) {
99             list($ns, $el) = split( ':', $element, 2);
100         }
101         if ( $ns and $ns != 'rdf' ) {
102             $this->current_namespace = $ns;
103         }
104
105         # if feed type isn't set, then this is first element of feed
106         # identify feed from root element
107         #
108         if (!isset($this->feed_type) ) {
109             if ( $el == 'rdf' ) {
110                 $this->feed_type = RSS;
111                 $this->feed_version = '1.0';
112             }
113             elseif ( $el == 'rss' ) {
114                 $this->feed_type = RSS;
115                 $this->feed_version = $attrs['version'];
116             }
117             elseif ( $el == 'feed' ) {
118                 $this->feed_type = ATOM;
119                 $this->feed_version = $attrs['version'];
120                 $this->inchannel = true;
121             }
122             return;
123         }
124
125         if ( $el == 'channel' )
126         {
127             $this->inchannel = true;
128         }
129         elseif ($el == 'item' or $el == 'entry' )
130         {
131             $this->initem = true;
132             if ( isset($attrs['rdf:about']) ) {
133                 $this->current_item['about'] = $attrs['rdf:about'];
134             }
135         }
136
137         // if we're in the default namespace of an RSS feed,
138         //  record textinput or image fields
139         elseif (
140             $this->feed_type == RSS and
141             $this->current_namespace == '' and
142             $el == 'textinput' )
143         {
144             $this->intextinput = true;
145         }
146
147         elseif (
148             $this->feed_type == RSS and
149             $this->current_namespace == '' and
150             $el == 'image' )
151         {
152             $this->inimage = true;
153         }
154
155         # handle atom content constructs
156         elseif ( $this->feed_type == ATOM and in_array($el, $this->_CONTENT_CONSTRUCTS) )
157         {
158             // avoid clashing w/ RSS mod_content
159             if ($el == 'content' ) {
160                 $el = 'atom_content';
161             }
162
163             $this->incontent = $el;
164
165
166         }
167
168         // if inside an Atom content construct (e.g. content or summary) field treat tags as text
169         elseif ($this->feed_type == ATOM and $this->incontent )
170         {
171             // if tags are inlined, then flatten
172             $attrs_str = join(' ',
173                     array_map('map_attrs',
174                     array_keys($attrs),
175                     array_values($attrs) ) );
176
177             $this->append_content( "<$element $attrs_str>"  );
178
179             array_unshift( $this->stack, $el );
180         }
181
182         // Atom support many links per containging element.
183         // Magpie treats link elements of type rel='alternate'
184         // as being equivalent to RSS's simple link element.
185         //
186         elseif ($this->feed_type == ATOM and $el == 'link' )
187         {
188             if ( isset($attrs['rel']) and $attrs['rel'] == 'alternate' )
189             {
190                 $link_el = 'link';
191             }
192             else {
193                 $link_el = 'link_' . $attrs['rel'];
194             }
195
196             $this->append($link_el, $attrs['href']);
197         }
198         // set stack[0] to current element
199         else {
200             array_unshift($this->stack, $el);
201         }
202     }
203
204
205
206     function feed_cdata ($p, $text) {
207
208         if ($this->feed_type == ATOM and $this->incontent)
209         {
210             $this->append_content( $text );
211         }
212         else {
213             $current_el = join('_', array_reverse($this->stack));
214             $this->append($current_el, $text);
215         }
216     }
217
218     function feed_end_element ($p, $el) {
219         $el = strtolower($el);
220
221         if ( $el == 'item' or $el == 'entry' )
222         {
223             $this->items[] = $this->current_item;
224             $this->current_item = array();
225             $this->initem = false;
226         }
227         elseif ($this->feed_type == RSS and $this->current_namespace == '' and $el == 'textinput' )
228         {
229             $this->intextinput = false;
230         }
231         elseif ($this->feed_type == RSS and $this->current_namespace == '' and $el == 'image' )
232         {
233             $this->inimage = false;
234         }
235         elseif ($this->feed_type == ATOM and in_array($el, $this->_CONTENT_CONSTRUCTS) )
236         {
237             $this->incontent = false;
238         }
239         elseif ($el == 'channel' or $el == 'feed' )
240         {
241             $this->inchannel = false;
242         }
243         elseif ($this->feed_type == ATOM and $this->incontent  ) {
244             // balance tags properly
245             // note:  i don't think this is actually neccessary
246             if ( $this->stack[0] == $el )
247             {
248                 $this->append_content("</$el>");
249             }
250             else {
251                 $this->append_content("<$el />");
252             }
253
254             array_shift( $this->stack );
255         }
256         else {
257             array_shift( $this->stack );
258         }
259
260         $this->current_namespace = false;
261     }
262
263     function concat (&$str1, $str2="") {
264         if (!isset($str1) ) {
265             $str1="";
266         }
267         $str1 .= $str2;
268     }
269
270     function append_content($text) {
271         if ( $this->initem ) {
272             $this->concat( $this->current_item[ $this->incontent ], $text );
273         }
274         elseif ( $this->inchannel ) {
275             $this->concat( $this->channel[ $this->incontent ], $text );
276         }
277     }
278
279     // smart append - field and namespace aware
280     function append($el, $text) {
281         if (!$el) {
282             return;
283         }
284         if ( $this->current_namespace )
285         {
286             if ( $this->initem ) {
287                 $this->concat(
288                     $this->current_item[ $this->current_namespace ][ $el ], $text);
289             }
290             elseif ($this->inchannel) {
291                 $this->concat(
292                     $this->channel[ $this->current_namespace][ $el ], $text );
293             }
294             elseif ($this->intextinput) {
295                 $this->concat(
296                     $this->textinput[ $this->current_namespace][ $el ], $text );
297             }
298             elseif ($this->inimage) {
299                 $this->concat(
300                     $this->image[ $this->current_namespace ][ $el ], $text );
301             }
302         }
303         else {
304             if ( $this->initem ) {
305                 $this->concat(
306                     $this->current_item[ $el ], $text);
307             }
308             elseif ($this->intextinput) {
309                 $this->concat(
310                     $this->textinput[ $el ], $text );
311             }
312             elseif ($this->inimage) {
313                 $this->concat(
314                     $this->image[ $el ], $text );
315             }
316             elseif ($this->inchannel) {
317                 $this->concat(
318                     $this->channel[ $el ], $text );
319             }
320
321         }
322     }
323
324     function normalize () {
325         // if atom populate rss fields
326         if ( $this->is_atom() ) {
327             $this->channel['descripton'] = $this->channel['tagline'];
328             for ( $i = 0; $i < count($this->items); $i++) {
329                 $item = $this->items[$i];
330                 if ( isset($item['summary']) )
331                     $item['description'] = $item['summary'];
332                 if ( isset($item['atom_content']))
333                     $item['content']['encoded'] = $item['atom_content'];
334
335                 $this->items[$i] = $item;
336             }
337         }
338         elseif ( $this->is_rss() ) {
339             $this->channel['tagline'] = $this->channel['description'];
340             for ( $i = 0; $i < count($this->items); $i++) {
341                 $item = $this->items[$i];
342                 if ( isset($item['description']))
343                     $item['summary'] = $item['description'];
344                 if ( isset($item['content']['encoded'] ) )
345                     $item['atom_content'] = $item['content']['encoded'];
346
347                 $this->items[$i] = $item;
348             }
349         }
350     }
351
352     function is_rss () {
353         if ( $this->feed_type == RSS ) {
354             return $this->feed_version;
355         }
356         else {
357             return false;
358         }
359     }
360
361     function is_atom() {
362         if ( $this->feed_type == ATOM ) {
363             return $this->feed_version;
364         }
365         else {
366             return false;
367         }
368     }
369
370     function map_attrs($k, $v) {
371         return "$k=\"$v\"";
372     }
373
374     function error( $errormsg, $lvl = E_USER_WARNING ) {
375         // append PHP's error message if track_errors enabled
376         if ( isset($php_errormsg) ) {
377             $errormsg .= " ($php_errormsg)";
378         }
379         if ( MAGPIE_DEBUG ) {
380             trigger_error( $errormsg, $lvl);
381         } else {
382             error_log( $errormsg, 0);
383         }
384     }
385
386 }
387 require_once( dirname(__FILE__) . '/class-snoopy.php');
388
389 if ( !function_exists('fetch_rss') ) :
390 function fetch_rss ($url) {
391     // initialize constants
392     init();
393
394     if ( !isset($url) ) {
395         // error("fetch_rss called without a url");
396         return false;
397     }
398
399     // if cache is disabled
400     if ( !MAGPIE_CACHE_ON ) {
401         // fetch file, and parse it
402         $resp = _fetch_remote_file( $url );
403         if ( is_success( $resp->status ) ) {
404             return _response_to_rss( $resp );
405         }
406         else {
407             // error("Failed to fetch $url and cache is off");
408             return false;
409         }
410     }
411     // else cache is ON
412     else {
413         // Flow
414         // 1. check cache
415         // 2. if there is a hit, make sure its fresh
416         // 3. if cached obj fails freshness check, fetch remote
417         // 4. if remote fails, return stale object, or error
418
419         $cache = new RSSCache( MAGPIE_CACHE_DIR, MAGPIE_CACHE_AGE );
420
421         if (MAGPIE_DEBUG and $cache->ERROR) {
422             debug($cache->ERROR, E_USER_WARNING);
423         }
424
425
426         $cache_status      = 0;        // response of check_cache
427         $request_headers = array(); // HTTP headers to send with fetch
428         $rss              = 0;        // parsed RSS object
429         $errormsg         = 0;        // errors, if any
430
431         if (!$cache->ERROR) {
432             // return cache HIT, MISS, or STALE
433             $cache_status = $cache->check_cache( $url );
434         }
435
436         // if object cached, and cache is fresh, return cached obj
437         if ( $cache_status == 'HIT' ) {
438             $rss = $cache->get( $url );
439             if ( isset($rss) and $rss ) {
440                 $rss->from_cache = 1;
441                 if ( MAGPIE_DEBUG > 1) {
442                 debug("MagpieRSS: Cache HIT", E_USER_NOTICE);
443             }
444                 return $rss;
445             }
446         }
447
448         // else attempt a conditional get
449
450         // setup headers
451         if ( $cache_status == 'STALE' ) {
452             $rss = $cache->get( $url );
453             if ( $rss->etag and $rss->last_modified ) {
454                 $request_headers['If-None-Match'] = $rss->etag;
455                 $request_headers['If-Last-Modified'] = $rss->last_modified;
456             }
457         }
458
459         $resp = _fetch_remote_file( $url, $request_headers );
460
461         if (isset($resp) and $resp) {
462             if ($resp->status == '304' ) {
463                 // we have the most current copy
464                 if ( MAGPIE_DEBUG > 1) {
465                     debug("Got 304 for $url");
466                 }
467                 // reset cache on 304 (at minutillo insistent prodding)
468                 $cache->set($url, $rss);
469                 return $rss;
470             }
471             elseif ( is_success( $resp->status ) ) {
472                 $rss = _response_to_rss( $resp );
473                 if ( $rss ) {
474                     if (MAGPIE_DEBUG > 1) {
475                         debug("Fetch successful");
476                     }
477                     // add object to cache
478                     $cache->set( $url, $rss );
479                     return $rss;
480                 }
481             }
482             else {
483                 $errormsg = "Failed to fetch $url. ";
484                 if ( $resp->error ) {
485                     # compensate for Snoopy's annoying habbit to tacking
486                     # on '\n'
487                     $http_error = substr($resp->error, 0, -2);
488                     $errormsg .= "(HTTP Error: $http_error)";
489                 }
490                 else {
491                     $errormsg .=  "(HTTP Response: " . $resp->response_code .')';
492                 }
493             }
494         }
495         else {
496             $errormsg = "Unable to retrieve RSS file for unknown reasons.";
497         }
498
499         // else fetch failed
500
501         // attempt to return cached object
502         if ($rss) {
503             if ( MAGPIE_DEBUG ) {
504                 debug("Returning STALE object for $url");
505             }
506             return $rss;
507         }
508
509         // else we totally failed
510         // error( $errormsg );
511
512         return false;
513
514     } // end if ( !MAGPIE_CACHE_ON ) {
515 } // end fetch_rss()
516 endif;
517
518 function _fetch_remote_file