root/tags/1_0-rc1/wp-includes/rss.php

Revision 599, 32.7 kB (checked in by donncha, 3 years ago)

WP Merge

Line 
1 <?php
2
3 /* Much of the code in this file was taken from MagpieRSS
4  * by Kellan Elliott-McCrea <kellan@protest.net> which is
5  * released under the GPL license.
6  *
7  * The lastest version of MagpieRSS can be obtained from:
8  * http://magpierss.sourceforge.net
9  */
10
11 function fetch_rss($url) {
12     $url = apply_filters('fetch_rss_url', $url);
13
14     $feeder = new WP_Feeder();
15
16     $feed = $feeder->get($url);
17
18     $magpie = $feed->to_magpie();
19
20     return $magpie;
21 }
22
23 class WP_Feeder {
24     var $url, $http_client, $last_fetch, $wp_object_cache, $cache;
25     var $redirects = 0;
26     var $max_redirects = 3;
27     var $cache_redirects = true;
28
29     function WP_Feeder () {
30         global $wp_object_cache;
31         
32         if ( $wp_object_cache->cache_enabled ) {
33             $this->wp_object_cache = true;
34         } else {
35             $this->wp_object_cache = false;
36             $this->cache = new RSSCache();
37         }
38     }
39
40     function get ($url) {
41         $cached = false;
42
43         $feed = $this->cache_get($url);
44
45         if ( is_object($feed) ) {
46             $cached = true;
47         } else {
48             unset($feed);
49
50             $this->fetch($url);
51
52             $feed = new WP_Feed($this->http_client);
53         }
54
55         // Handle redirects
56         if ( $feed->status >= 300 && $feed->status < 400 && $this->redirects < $this->max_redirects ) {
57             ++$this->redirects;
58
59             if ( $this->cache_redirects && !$cached )
60                 $this->cache_set($url, $feed);
61
62             return $this->get($feed->redirect_location);
63         }
64
65         if ( !$cached )
66             $this->cache_set($url, $feed);
67
68         return $feed;
69     }
70
71     function fetch ($url) {
72         $this->last_fetch = $url;
73         $parts = parse_url($url);
74         $url = ($parts['path'] ? $parts['path'] : '/') . ($parts['query'] ? '?'.$parts['query'] : '');
75         $this->http_client = new HttpClient('', 80);
76         $this->http_client->handle_redirects = false;
77         $this->http_client->host = $parts['host'];
78         $this->http_client->port = $parts['port'] ? $parts['port'] : 80;
79         $this->http_client->user_agent = 'WordPress ' . $GLOBALS['wp_version'] . ' Feed Client';
80         $this->http_client->get($url);
81     }
82     
83     function cache_get ($url) {
84         if ( $this->wp_object_cache )
85             return unserialize(wp_cache_get($url, 'rss'));
86
87         return $this->cache->get($url);
88     }
89     
90     function cache_set ($url, $object) {
91         if ( $this->wp_object_cache )
92             return wp_cache_set($url, serialize($object), 'rss', 3600);
93         
94         return $this->cache->set($url, $object);
95     }
96 }
97
98 class RSSCache {
99     var $BASE_CACHE = 'wp-content/cache';    // where the cache files are stored
100     var $MAX_AGE    = 43200;          // when are files stale, default twelve hours
101     var $ERROR         = '';            // accumulate error messages
102
103     function RSSCache ($base='', $age='') {
104         if ( $base ) {
105             $this->BASE_CACHE = $base;
106         }
107         if ( $age ) {
108             $this->MAX_AGE = $age;
109         }
110
111     }
112
113     function set ($url, $rss) {
114         global $wpdb;
115         $cache_option = 'rss_' . $this->file_name( $url );
116         $cache_timestamp = 'rss_' . $this->file_name( $url ) . '_ts';
117
118         if ( !$wpdb->get_var("SELECT option_name FROM $wpdb->options WHERE option_name = '$cache_option'") )
119             add_option($cache_option, '', '', 'no');
120         if ( !$wpdb->get_var("SELECT option_name FROM $wpdb->options WHERE option_name = '$cache_timestamp'") )
121             add_option($cache_timestamp, '', '', 'no');
122
123         update_option($cache_option, $rss);
124         update_option($cache_timestamp, time() );
125
126         return $cache_option;
127     }
128
129     function get ($url) {
130         $this->ERROR = "";
131         $cache_option = 'rss_' . $this->file_name( $url );
132
133         if ( ! get_option( $cache_option ) ) {
134             $this->debug(
135                 "Cache doesn't contain: $url (cache option: $cache_option)"
136             );
137             return 0;
138         }
139
140         $rss = get_option( $cache_option );
141
142         return $rss;
143     }
144
145     function check_cache ( $url ) {
146         $this->ERROR = "";
147         $cache_option = $this->file_name( $url );
148         $cache_timestamp = 'rss_' . $this->file_name( $url ) . '_ts';
149
150         if ( $mtime = get_option($cache_timestamp) ) {
151             // find how long ago the file was added to the cache
152             // and whether that is longer then MAX_AGE
153             $age = time() - $mtime;
154             if ( $this->MAX_AGE > $age ) {
155                 // object exists and is current
156                 return 'HIT';
157             }
158             else {
159                 // object exists but is old
160                 return 'STALE';
161             }
162         }
163         else {
164             // object does not exist
165             return 'MISS';
166         }
167     }
168
169     function serialize ( $rss ) {
170         return serialize( $rss );
171     }
172
173     function unserialize ( $data ) {
174         return unserialize( $data );
175     }
176
177     function file_name ($url) {
178         return md5( $url );
179     }
180
181     function error ($errormsg, $lvl=E_USER_WARNING) {
182         // append PHP's error message if track_errors enabled
183         if ( isset($php_errormsg) ) {
184             $errormsg .= " ($php_errormsg)";
185         }
186         $this->ERROR = $errormsg;
187         if ( MAGPIE_DEBUG ) {
188             trigger_error( $errormsg, $lvl);
189         }
190         else {
191             error_log( $errormsg, 0);
192         }
193     }
194             function debug ($debugmsg, $lvl=E_USER_NOTICE) {
195         if ( MAGPIE_DEBUG ) {
196             $this->error("MagpieRSS [debug] $debugmsg", $lvl);
197         }
198     }
199 }
200
201 class WP_Feed {
202     var $status;
203     var $raw_xml;
204     var $last_updated;
205     var $tree;
206     var $items;
207     var $children;
208
209     var $parser;
210     var $feed_type;
211     var $feed_version;
212     var $stack = array();
213
214     function WP_Feed ($source)
215     {
216         # if PHP xml isn't compiled in, die
217         #
218         if (!function_exists('xml_parser_create')) {
219             $this->error( "Failed to load PHP's XML Extension. " .
220             "http://www.php.net/manual/en/ref.xml.php",
221             E_USER_ERROR );
222         }
223
224         // Handle overloaded arg (string or HttpClient object)
225         if ( is_object($source) ) {
226             if ( $source->status >= 200 && $source->status < 300) {
227                 $this->etag = $source->headers['etag'];
228                 $this->last_modified = $source->headers['last-modified'];
229                 $source = $source->content;
230             } else {
231                 $this->scour();
232                 $this->status = $source->status;
233                 $this->redirect_location = $source->headers->location;
234                 $this->bathe();
235                 return;
236             }
237         }
238
239         list($parser, $source) = $this->create_parser($source, 'UTF-8', null, true);
240
241         if (!is_resource($parser)) {
242             $this->error( "Failed to create an instance of PHP's XML parser. " .
243             "http://www.php.net/manual/en/ref.xml.php",
244             E_USER_ERROR );
245         }
246
247         $this->parser = $parser;
248
249         # pass in parser, and a reference to this object
250         # setup handlers
251         #
252         xml_set_object($this->parser, $this);
253         xml_set_element_handler($this->parser, 'start_element', 'end_element');
254         xml_set_character_data_handler( $this->parser, 'cdata');
255
256         $status = xml_parse( $this->parser, $source );
257
258         if (! $status ) {
259             $errorcode = xml_get_error_code( $this->parser );
260             if ( $errorcode != XML_ERROR_NONE ) {
261                 $xml_error = xml_error_string( $errorcode );
262                 $error_line = xml_get_current_line_number($this->parser);
263                 $error_col = xml_get_current_column_number($this->parser);
264                 $errormsg = "$xml_error at line $error_line, column $error_col";
265
266                 $this->error( $errormsg );
267             }
268         }
269
270         // SUPER SLOPPY FEED DISCOVERY!! TO-DO: AXE THIS CRAP!!
271         if ( !is_object($this->feed) || !method_exists($this->feed, 'to_xml') ) {
272             if ( preg_match_all('/<link [^>]*href=([^ >]+)[^>]+>/i', $source, $matches) ) {
273                 $types = array('rss', 'atom');
274                 foreach ( $types as $type )
275                     foreach ( $matches[0] as $key => $link )
276                         if ( preg_match('/rel=.alternate./', $link) && preg_match("/type=[^ >]*{$type}[^ >]*/", $link) )
277                             break 2;
278                 $this->scour();
279                 $this->redirect_location = 'http://xml.wordpress.com/get/' . trim($matches[1][$key], '\'"');
280                 $this->status = 301;
281                 return;
282             } else {
283                 $this->scour();
284                 $this->status = 404;
285                 return;
286             }
287         } else {
288             $this->status = 200;
289         }
290
291         xml_parser_free( $this->parser );
292         unset($this->parser);
293
294         $this->bathe();
295     }
296
297     function to_xml() {
298         if ( is_object($this->feed) && method_exists($this->feed, 'to_xml') )
299             return $this->feed->to_xml();
300
301         return false;
302     }
303
304     // Called internally by xml_parse(). We create an object and call its start_element method.
305     function start_element($p, $element, &$attrs) {
306         $el = $element;// = strtolower($element);
307         // $attrs = array_change_key_case($attrs, CASE_LOWER);
308
309         // If there is an extended class for this element, use it.
310         $class = 'element';
311
312         $maybe_class = $test_class = strtolower(str_replace(':', '_', $el));
313         if ( class_exists($maybe_class) ) {
314             for ($classes[] = $test_class; $test_class = get_parent_class ($test_class); $classes[] = $test_class);
315             if ( in_array($class, $classes) )
316                 $class = $maybe_class;
317         }
318
319         // Instantiate an object for this element.
320         $object = new $class();
321
322         // Tell the element to start itself.
323         $object->start_element($p, $element, $attrs, $this);
324     }
325
326     function cdata ($p, $data) {
327         $this->stack[0]->cdata($p, $data, $this);
328     }
329
330     function end_element ($p, $el) {
331         $this->stack[0]->end_element($p, $el, $this);
332     }
333
334     function create_parser($source, $out_enc, $in_enc, $detect) {
335         if ( substr(phpversion(),0,1) == 5) {
336             $parser = $this->php5_create_parser($in_enc, $detect);
337         }
338         else {
339             list($parser, $source) = $this->php4_create_parser($source, $in_enc, $detect);
340         }
341         if ($out_enc) {
342             $this->encoding = $out_enc;
343             xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $out_enc);
344             xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
345         }
346
347         return array($parser, $source);
348     }
349
350     function php5_create_parser($in_enc, $detect) {
351         // by default php5 does a fine job of detecting input encodings
352         if(!$detect && $in_enc) {
353             return xml_parser_create($in_enc);
354         }
355         else {
356             return xml_parser_create('');
357         }
358     }
359
360     /**
361     * Instaniate an XML parser under PHP4
362     *
363     * Unfortunately PHP4's support for character encodings
364     * and especially XML and character encodings sucks.  As
365     * long as the documents you parse only contain characters
366     * from the ISO-8859-1 character set (a superset of ASCII,
367     * and a subset of UTF-8) you're fine.  However once you
368     * step out of that comfy little world things get mad, bad,
369     * and dangerous to know.
370     *
371     * The following code is based on SJM's work with FoF
372     * @see http://minutillo.com/steve/weblog/2004/6/17/php-xml-and-character-encodings-a-tale-of-sadness-rage-and-data-loss
373     *
374     */
375     function php4_create_parser($source, $in_enc, $detect) {
376         if ( !$detect ) {
377             return array(xml_parser_create($in_enc), $source);
378         }
379
380         if (!$in_enc) {
381             if (preg_match('/<?xml.*encoding=[\'"](.*?)[\'"].*?>/m', $source, $m)) {
382                 $in_enc = strtoupper($m[1]);
383                 $this->source_encoding = $in_enc;
384             }
385             else {
386                 $in_enc = 'UTF-8';
387             }
388         }
389
390         if ($this->known_encoding($in_enc)) {
391             return array(xml_parser_create($in_enc), $source);
392         }
393
394         // the dectected encoding is not one of the simple encodings PHP knows
395
396         // attempt to use the iconv extension to
397         // cast the XML to a known encoding
398         // @see http://php.net/iconv
399
400         if (function_exists('iconv'))  {
401             $encoded_source = iconv($in_enc,'UTF-8', $source);
402             if ($encoded_source) {
403                 return array(xml_parser_create('UTF-8'), $encoded_source);
404             }
405         }
406
407         // iconv didn't work, try mb_convert_encoding
408         // @see http://php.net/mbstring
409         if(function_exists('mb_convert_encoding')) {
410             $encoded_source = mb_convert_encoding($source, 'UTF-8', $in_enc );
411             if ($encoded_source) {
412                 return array(xml_parser_create('UTF-8'), $encoded_source);
413             }
414         }
415
416         // else
417         $this->error("Feed is in an unsupported character encoding. ($in_enc) " .
418         "You may see strange artifacts, and mangled characters.",
419         E_USER_NOTICE);
420
421         return array(xml_parser_create(), $source);
422     }
423
424     function known_encoding($enc) {
425         $enc = strtoupper($enc);
426         if ( in_array($enc, array('UTF-8', 'US-ASCII', 'ISO-8859-1')) ) {
427             return $enc;
428         }
429         else {
430             return false;
431         }
432     }
433
434     function error ($errormsg, $lvl=E_USER_WARNING) {
435         // append PHP's error message if track_errors enabled
436         if ( isset($php_errormsg) ) {
437             $errormsg .= " ($php_errormsg)";
438         }
439         if ( MAGPIE_DEBUG ) {
440         //    trigger_error( $errormsg, $lvl);
441         }
442         else {
443             error_log( $errormsg, 0);
444         }
445
446         $notices = E_USER_NOTICE|E_NOTICE;
447         if ( $lvl&$notices ) {
448             $this->WARNING = $errormsg;
449         } else {
450             $this->ERROR = $errormsg;
451         }
452     }
453
454     // Remove empty and |^_.*| object vars
455     function bathe() {
456         foreach ( get_object_vars($this) as $key => $data )
457             if ( empty($this->$key) || substr($key, 0, 1) == '_' )
458                 unset($this->$key);
459     }
460
461     // Remove ALL object vars
462     function scour() {
463         foreach ( get_object_vars($this) as $key => $data )
464             unset($this->$key);
465     }
466     
467     function to_magpie() {
468         $magpie = new stdClass();
469
470         foreach ( $this as $var => $value ) {
471             if ( $var == 'feed' ) {
472                 continue;
473             } else {
474                 $magpie->$var = $this->$var;
475             }
476         }
477
478         $magpie->items = array();
479
480         if ( is_object($this->feed) && method_exists($this->feed, 'to_magpie') ) {
481             $feed = $this->feed->to_magpie();
482
483             if ( is_array($feed) ) {
484                 foreach ( $this->feed->to_magpie() as $var => $val ) {
485                     if ( $var == 'items' )
486                         $magpie->items = $val;
487                     else
488                         $magpie->channel["$var"] = $val;
489                 }
490             }
491         }
492
493         return $magpie;
494     }
495 }
496
497
498 class element {
499     function element() {
500     }
501
502     function start_element($p, $el, $attr, &$mag) {
503         $this->name = $el;
504         $this->attributes = $attr;
505
506         array_unshift($mag->stack, $this);
507     }
508     function cdata($p, $data