root/branches/1.5/wp-includes/rss-functions.php

Revision 2192, 20.7 kB (checked in by saxmatt, 4 years ago)

Dashboard and option tweaks

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