Make WordPress Core

Ticket #2864: rss.php

File rss.php, 32.9 KB (added by skeltoac, 19 years ago)
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
11function 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
23class 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
98class 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_cache_' . $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 clear_cache ( ) {
170                global $wpdb;
171               
172                $wpdb->query("DELETE FROM $wpdb->options WHERE option_name LIKE 'rss_cache_%'");
173               
174                return $wpdb->rows_affected;
175        }
176
177        function serialize ( $rss ) {
178                return serialize( $rss );
179        }
180
181        function unserialize ( $data ) {
182                return unserialize( $data );
183        }
184
185        function file_name ($url) {
186                return md5( $url );
187        }
188
189        function error ($errormsg, $lvl=E_USER_WARNING) {
190                // append PHP's error message if track_errors enabled
191                if ( isset($php_errormsg) ) { 
192                        $errormsg .= " ($php_errormsg)";
193                }
194                $this->ERROR = $errormsg;
195                if ( MAGPIE_DEBUG ) {
196                        trigger_error( $errormsg, $lvl);
197                }
198                else {
199                        error_log( $errormsg, 0);
200                }
201        }
202
203        function debug ($debugmsg, $lvl=E_USER_NOTICE) {
204                if ( MAGPIE_DEBUG ) {
205                        $this->error("MagpieRSS [debug] $debugmsg", $lvl);
206                }
207        }
208}
209
210class WP_Feed {
211        var $status;
212        var $raw_xml;
213        var $last_updated;
214        var $tree;
215        var $items;
216        var $children;
217
218        var $parser;
219        var $feed_type;
220        var $feed_version;
221        var $stack = array();
222
223        function WP_Feed ($source)
224        {
225                # if PHP xml isn't compiled in, die
226                #
227                if (!function_exists('xml_parser_create')) {
228                        $this->error( "Failed to load PHP's XML Extension. " .
229                        "http://www.php.net/manual/en/ref.xml.php",
230                        E_USER_ERROR );
231                }
232
233                // Handle overloaded arg (string or HttpClient object)
234                if ( is_object($source) ) {
235                        if ( $source->status >= 200 && $source->status < 300) {
236                                $this->etag = $source->headers['etag'];
237                                $this->last_modified = $source->headers['last-modified'];
238                                $source = $source->content;
239                        } else {
240                                $this->scour();
241                                $this->status = $source->status;
242                                $this->redirect_location = $source->headers->location;
243                                $this->bathe();
244                                return;
245                        }
246                }
247
248                list($parser, $source) = $this->create_parser($source, 'UTF-8', null, true);
249
250                if (!is_resource($parser)) {
251                        $this->error( "Failed to create an instance of PHP's XML parser. " .
252                        "http://www.php.net/manual/en/ref.xml.php",
253                        E_USER_ERROR );
254                }
255
256                $this->parser = $parser;
257
258                # pass in parser, and a reference to this object
259                # setup handlers
260                #
261                xml_set_object($this->parser, $this);
262                xml_set_element_handler($this->parser, 'start_element', 'end_element');
263                xml_set_character_data_handler( $this->parser, 'cdata');
264
265                $status = xml_parse( $this->parser, $source );
266
267                if (! $status ) {
268                        $errorcode = xml_get_error_code( $this->parser );
269                        if ( $errorcode != XML_ERROR_NONE ) {
270                                $xml_error = xml_error_string( $errorcode );
271                                $error_line = xml_get_current_line_number($this->parser);
272                                $error_col = xml_get_current_column_number($this->parser);
273                                $errormsg = "$xml_error at line $error_line, column $error_col";
274
275                                $this->error( $errormsg );
276                        }
277                }
278
279                // SUPER SLOPPY FEED DISCOVERY!! TO-DO: AXE THIS CRAP!!
280                if ( !is_object($this->feed) || !method_exists($this->feed, 'to_xml') ) {
281                        if ( preg_match_all('/<link [^>]*href=([^ >]+)[^>]+>/i', $source, $matches) ) {
282                                $types = array('rss', 'atom');
283                                foreach ( $types as $type )
284                                        foreach ( $matches[0] as $key => $link )
285                                                if ( preg_match('/rel=.alternate./', $link) && preg_match("/type=[^ >]*{$type}[^ >]*/", $link) )
286                                                        break 2;
287                                $this->scour();
288                                $this->redirect_location = 'http://xml.wordpress.com/get/' . trim($matches[1][$key], '\'"');
289                                $this->status = 301;
290                                return;
291                        } else {
292                                $this->scour();
293                                $this->status = 404;
294                                return;
295                        }
296                } else {
297                        $this->status = 200;
298                }
299
300                xml_parser_free( $this->parser );
301                unset($this->parser);
302
303                $this->bathe();
304        }
305
306        function to_xml() {
307                if ( is_object($this->feed) && method_exists($this->feed, 'to_xml') )
308                        return $this->feed->to_xml();
309
310                return false;
311        }
312
313        // Called internally by xml_parse(). We create an object and call its start_element method.
314        function start_element($p, $element, &$attrs) {
315                $el = $element;// = strtolower($element);
316                // $attrs = array_change_key_case($attrs, CASE_LOWER);
317
318                // If there is an extended class for this element, use it.
319                $class = 'element';
320
321                $maybe_class = $test_class = strtolower(str_replace(':', '_', $el));
322                if ( class_exists($maybe_class) ) {
323                        for ($classes[] = $test_class; $test_class = get_parent_class ($test_class); $classes[] = $test_class);
324                        if ( in_array($class, $classes) )
325                                $class = $maybe_class;
326                }
327
328                // Instantiate an object for this element.
329                $object = new $class();
330
331                // Tell the element to start itself.
332                $object->start_element($p, $element, $attrs, $this);
333        }
334
335        function cdata ($p, $data) {
336                $this->stack[0]->cdata($p, $data, $this);
337        }
338
339        function end_element ($p, $el) {
340                $this->stack[0]->end_element($p, $el, $this);
341        }
342
343        function create_parser($source, $out_enc, $in_enc, $detect) {
344                if ( substr(phpversion(),0,1) == 5) {
345                        $parser = $this->php5_create_parser($in_enc, $detect);
346                }
347                else {
348                        list($parser, $source) = $this->php4_create_parser($source, $in_enc, $detect);
349                }
350                if ($out_enc) {
351                        $this->encoding = $out_enc;
352                        xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $out_enc);
353                        xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
354                }
355
356                return array($parser, $source);
357        }
358
359        function php5_create_parser($in_enc, $detect) {
360                // by default php5 does a fine job of detecting input encodings
361                if(!$detect && $in_enc) {
362                        return xml_parser_create($in_enc);
363                }
364                else {
365                        return xml_parser_create('');
366                }
367        }
368
369        /**
370    * Instaniate an XML parser under PHP4
371    *
372    * Unfortunately PHP4's support for character encodings
373    * and especially XML and character encodings sucks.  As
374    * long as the documents you parse only contain characters
375    * from the ISO-8859-1 character set (a superset of ASCII,
376    * and a subset of UTF-8) you're fine.  However once you
377    * step out of that comfy little world things get mad, bad,
378    * and dangerous to know.
379    *
380    * The following code is based on SJM's work with FoF
381    * @see http://minutillo.com/steve/weblog/2004/6/17/php-xml-and-character-encodings-a-tale-of-sadness-rage-and-data-loss
382    *
383    */
384        function php4_create_parser($source, $in_enc, $detect) {
385                if ( !$detect ) {
386                        return array(xml_parser_create($in_enc), $source);
387                }
388
389                if (!$in_enc) {
390                        if (preg_match('/<?xml.*encoding=[\'"](.*?)[\'"].*?>/m', $source, $m)) {
391                                $in_enc = strtoupper($m[1]);
392                                $this->source_encoding = $in_enc;
393                        }
394                        else {
395                                $in_enc = 'UTF-8';
396                        }
397                }
398
399                if ($this->known_encoding($in_enc)) {
400                        return array(xml_parser_create($in_enc), $source);
401                }
402
403                // the dectected encoding is not one of the simple encodings PHP knows
404
405                // attempt to use the iconv extension to
406                // cast the XML to a known encoding
407                // @see http://php.net/iconv
408
409                if (function_exists('iconv'))  {
410                        $encoded_source = iconv($in_enc,'UTF-8', $source);
411                        if ($encoded_source) {
412                                return array(xml_parser_create('UTF-8'), $encoded_source);
413                        }
414                }
415
416                // iconv didn't work, try mb_convert_encoding
417                // @see http://php.net/mbstring
418                if(function_exists('mb_convert_encoding')) {
419                        $encoded_source = mb_convert_encoding($source, 'UTF-8', $in_enc );
420                        if ($encoded_source) {
421                                return array(xml_parser_create('UTF-8'), $encoded_source);
422                        }
423                }
424
425                // else
426                $this->error("Feed is in an unsupported character encoding. ($in_enc) " .
427                "You may see strange artifacts, and mangled characters.",
428                E_USER_NOTICE);
429
430                return array(xml_parser_create(), $source);
431        }
432
433        function known_encoding($enc) {
434                $enc = strtoupper($enc);
435                if ( in_array($enc, array('UTF-8', 'US-ASCII', 'ISO-8859-1')) ) {
436                        return $enc;
437                }
438                else {
439                        return false;
440                }
441        }
442
443        function error ($errormsg, $lvl=E_USER_WARNING) {
444                // append PHP's error message if track_errors enabled
445                if ( isset($php_errormsg) ) {
446                        $errormsg .= " ($php_errormsg)";
447                }
448                if ( MAGPIE_DEBUG ) {
449                //      trigger_error( $errormsg, $lvl);
450                }
451                else {
452                        error_log( $errormsg, 0);
453                }
454
455                $notices = E_USER_NOTICE|E_NOTICE;
456                if ( $lvl&$notices ) {
457                        $this->WARNING = $errormsg;
458                } else {
459                        $this->ERROR = $errormsg;
460                }
461        }
462
463        // Remove empty and |^_.*| object vars
464        function bathe() {
465                foreach ( get_object_vars($this) as $key => $data )
466                        if ( empty($this->$key) || substr($key, 0, 1) == '_' )
467                                unset($this->$key);
468        }
469
470        // Remove ALL object vars
471        function scour() {
472                foreach ( get_object_vars($this) as $key => $data )
473                        unset($this->$key);
474        }
475       
476        function to_magpie() {
477                $magpie = new stdClass();
478
479                foreach ( $this as $var => $value ) {
480                        if ( $var == 'feed' ) {
481                                continue;
482                        } else {
483                                $magpie->$var = $this->$var;
484                        }
485                }
486
487                $magpie->items = array();
488
489                if ( is_object($this->feed) && method_exists($this->feed, 'to_magpie') ) {
490                        $feed = $this->feed->to_magpie();
491
492                        if ( is_array($feed) ) {
493                                foreach ( $this->feed->to_magpie() as $var => $val ) {
494                                        if ( $var == 'items' )
495                                                $magpie->items = $val;
496                                        else 
497                                                $magpie->channel["$var"] = $val;
498                                }
499                        }
500                }
501
502                return $magpie;
503        }
504}
505
506
507class element {
508        function element() {
509        }
510
511        function start_element($p, $el, $attr, &$mag) {
512                $this->name = $el;
513                $this->attributes = $attr;
514
515                array_unshift($mag->stack, $this);
516        }
517        function cdata($p, $data, &$mag) {
518                if ( empty($this->children) )
519                        $this->appendText($data);
520        }
521
522        function end_element($p, $el, &$mag) {
523                array_shift($mag->stack);
524
525                $this->bathe();
526
527                if ( is_object($mag->stack[0]) )
528                        $mag->stack[0]->appendChild($this);
529        }
530
531        function wrap_cdata() {
532                if ( strpos($this->innerText, '<') ) {
533                        $spacer = (substr($this->innerText, -1) == ']') ? ' ' : '';
534                        $this->innerText = str_replace(']]>', ']]&gt;', $this->innerText);
535                        $this->innerText = '<![CDATA[' . $this->innerText . $spacer . ']]>';
536                }
537        }
538
539        function appendText($text) {
540                $this->innerText .= $text;
541        }
542
543        function appendChild($object) {
544                $this->children[] = & $object;
545        }
546
547        // Add self to array
548        function to_array(&$array) {
549                $self = get_object_vars($this);
550
551                unset($self['children']);
552
553                foreach ( $this->children as $child )
554                        $child->to_array($self);
555
556                $array[$this->name] = $self;
557        }
558
559        // Return self as XML
560        function to_xml($indent = "\t", $generation = 0) {
561                $self = "<$this->name";
562                $self .= $this->attributes ? ' '.string_attributes($this->attributes) : '';
563                if ( empty($this->innerText) && empty($this->children) )
564                        $self .= " />\n";
565                else {
566                        $self .= ">";
567                        if ( $this->children ) {
568                                $self .= "\n";
569                                foreach ( $this->children as $child )
570                                        $self .= $child->to_xml($indent, $generation + 1);
571                                $self .= $indent ? $this->indent('', $indent, $generation) : '';
572                        } else {
573                                $self .= $this->innerText;
574                        }
575                        $self .= "</$this->name>\n";
576                }
577                return $indent ? $this->indent($self, $indent, $generation) : $self;
578        }
579
580        function indent($string, $indent, $generation) {
581                for ( $i = ''; strlen($i) < $generation; $i .= $indent ) ;
582                return $i . $string;
583        }
584
585        /**
586         * Gets the innerText of the first matching element in the vars
587         * @param string Provide any number of strings to try
588         * @return mixed
589         */
590        function getChildText() {
591                foreach ( func_get_args() as $name )
592                        foreach ( $this->children as $element )
593                                if ( $element->name == $name )
594                                        return $element->innerText;
595                return false;
596        }
597
598        /**
599         * Gets a ref to the first matching element
600         * @param string Provide any number of strings to try
601         * @return object
602         */
603        function &getChildElement() {
604                foreach ( func_get_args() as $name )
605                        foreach ( $this->children as $key => $element )
606                                if ( $element->name == $name )
607                                        return $this->children[$key];
608                return false;
609        }
610
611        function getAttribute($name) {
612                if ( is_array($this->attributes) )
613                        foreach ( $this->attributes as $attr => $value )
614                                if ( $attr == $name )
615                                        return $value;
616                return null;
617        }
618
619        function bathe() {
620                foreach ( get_object_vars($this) as $var => $value )
621                        if ( empty($this->$var) )
622                                unset($this->$var);
623        }
624
625        function to_magpie() {
626                if ( strlen(trim($this->innerText)) > 0 )
627                        return $this->innerText;
628
629                if ( is_array($this->attributes) )
630                        $e = $this->attributes;
631
632                if ( is_array($this->children) && count($this->children) > 0 )
633                        foreach ( $this->children as $k => $c )
634                                $e[$k] = $this->children[$k]->to_magpie();
635
636                return $e;
637        }
638}
639
640// Base Atom class
641class feed extends element {
642        function end_element($p, $el, &$mag) {
643                $mag->feed = $this;
644                $mag->is_feed = true;
645                $mag->last_modified = $this->last_modified();
646                $mag->stack = array();
647        }
648        function last_modified() {
649                $time = parse_w3cdtf($this->getChildText('modified'));
650                return gmdate('D, d M Y H:i:s T', $time);
651        }
652        function to_magpie() {
653                foreach ( $this->children as $k => $c ) {
654                        if ( $this->children[$k]->name == 'entry' )
655                                $magpie['items'][] = $this->children[$k]->to_magpie();
656                        else
657                                $magpie[$this->children[$k]->name] = $this->children[$k]->innerText;
658                }
659
660                return $magpie;
661        }
662}
663
664// RSS base class
665class rss extends feed {
666        function last_modified() {
667                $channel =& $this->getChildElement('channel');
668                $date = $channel->getChildText('pubDate', 'lastBuildDate');
669                return gmdate('D, d M Y H:i:s T', strtotime($date));
670        }
671        function to_magpie() {
672                return $this->children[0]->to_magpie();
673        }
674}
675class rdf_rdf extends feed {
676        function to_magpie() {
677                $magpie = array();
678                foreach ( $this->children as $k => $child ) {
679                        if ( $this->children[$k]->name == 'item' )
680                                $magpie['items'][] = $this->children[$k]->to_magpie();
681                        elseif ( method_exists($this->children[$k], 'to_magpie') )
682                                $magpie[$this->children[$k]->name] = $this->children[$k]->to_magpie();
683                        else
684                                $magpie[$this->children[$k]->name] = $this->children[$k]->innerText;
685                }
686                if ( is_array($magpie['channel']) )
687                        $magpie = array_merge($magpie['channel'], $magpie);
688
689                return $magpie;
690        }
691}
692class channel extends element {
693        function to_magpie() {
694                foreach ( $this->children as $k => $c ) {
695                        if ( $this->children[$k]->name == 'item' )
696                                $magpie['items'][] = $this->children[$k]->to_magpie();
697                        else
698                                $magpie[$this->children[$k]->name] = $this->children[$k]->innerText;
699                }
700
701                return $magpie;
702        }
703}
704
705// Atom article class
706class entry extends element {
707        function to_magpie() {
708                foreach ( $this->children as $k => $v ) {
709                        if ( is_object($this->children[$k]) )
710                                $value = $this->children[$k]->to_magpie();
711                        else
712                                $value = $this->children[$k]->innerText;
713
714                        $name = $this->children[$k]->name;
715
716                        $norms = array(
717                                        'dc:subject'    => 'categories',
718                                        'summary'       => 'description',
719                                        );
720                        $name = str_replace(array_keys($norms), array_values($norms), $name);
721
722                        switch ( $name ) {
723                                // The ones that needs to be dereferenced
724                                case 'author':
725                                        $magpie[$name] = $value[0];
726                                        break;
727
728                                // The ones that can be multiple
729                                case 'categories':
730                                         $magpie[$name][] = $value;
731                                         break;
732
733                                default:
734                                        $magpie[$name] = $value;
735                        }
736                }
737                return $magpie;
738        }
739
740        function to_array(&$array) {
741                $self = get_object_vars($this);
742
743                unset($self['children']);
744
745                foreach ( $this->children as $child )
746                        $child->to_array($self);
747
748                $array['items'][] = $self;
749        }
750}
751
752// RSS article class
753class item extends entry {
754        function to_magpie() {
755                foreach ( $this->children as $k => $v ) {
756                        if ( is_object($this->children[$k]) )
757                                $value = $this->children[$k]->to_magpie();
758                        else
759                                $value = $this->children[$k]->innerText;
760
761                        $name = $this->children[$k]->name;
762
763                        $norms = array(
764                                        'category'      => 'categories',
765                                        'content:encoded'       => 'content',
766                                        'dc:creator'    => 'author',
767                                        'wfw:commentRss'        => 'comments',
768                                        'wfw:commentRSS'        => 'comments',
769                                        'pubDate'       => 'pubdate',
770                                        );
771                        $name = str_replace(array_keys($norms), array_values($norms), $name);
772
773                        switch ( $name ) {
774                                // The ones that needs to be dereferenced
775                                case 'taxo:topics':
776                                        $magpie[$name] = $value[0];
777                                        break;
778
779                                // The ones that can be multiple
780                                case 'categories':
781                                         $magpie[$name][] = $value;
782                                         break;
783
784                                default:
785                                        $magpie[$name] = $value;
786                        }
787                }
788                return $magpie;
789        }
790}
791
792class category extends element {
793        function to_array(&$array) {
794                $self = get_object_vars($this);
795
796                unset($self['children']);
797
798                foreach ( $this->children as $child )
799                        $child->to_array($self);
800
801                $array['categories'][] = $self;
802        }
803}
804
805class link extends element {
806        function end_element($p, $el, &$mag) {
807                if ( $href = $this->getAttribute('href') )
808                        $this->innerText = $href;
809
810                parent::end_element($p, $el, $mag);
811        }
812}
813
814
815/* Version 0.9, 6th April 2003 - Simon Willison ( http://simon.incutio.com/ )
816   Manual: http://scripts.incutio.com/httpclient/
817*/
818
819if ( !class_exists('HttpClient') ) :
820class HttpClient {
821    // Request vars
822    var $host;
823    var $port;
824    var $path;
825    var $method;
826    var $postdata = '';
827    var $cookies = array();
828    var $referer;
829    var $accept = 'text/xml,application/xml,application/xhtml+xml,text/html,text/plain,image/png,image/jpeg,image/gif,*/*';
830    var $accept_encoding = 'gzip';
831    var $accept_language = 'en-us';
832    var $user_agent = 'Incutio HttpClient v0.9';
833    // Options
834    var $timeout = 20;
835    var $use_gzip = true;
836    var $no_cache = false;
837    var $persist_cookies = true;  // If true, received cookies are placed in the $this->cookies array ready for the next request
838                                  // Note: This currently ignores the cookie path (and time) completely. Time is not important,
839                                  //       but path could possibly lead to security problems.
840    var $persist_referers = true; // For each request, sends path of last request as referer
841    var $debug = false;
842    var $handle_redirects = true; // Auaomtically redirect if Location or URI header is found
843    var $max_redirects = 5;
844    var $headers_only = false;    // If true, stops receiving once headers have been read.
845    // Basic authorization variables
846    var $username;
847    var $password;
848    // Response vars
849    var $status;
850    var $headers = array();
851    var $content = '';
852    var $errormsg;
853    // Tracker variables
854    var $redirect_count = 0;
855    var $cookie_host = '';
856    function HttpClient($host, $port=80) {
857        $this->host = $host;
858        $this->port = $port;
859    }
860    function get($path, $data = false) {
861        $this->path = $path;
862        $this->method = 'GET';
863        if ($data) {
864            $this->path .= '?'.$this->buildQueryString($data);
865        }
866        return $this->doRequest();
867    }
868    function post($path, $data) {
869        $this->path = $path;
870        $this->method = 'POST';
871        $this->postdata = $this->buildQueryString($data);
872        return $this->doRequest();
873    }
874    function buildQueryString($data) {
875        $querystring = '';
876        if (is_array($data)) {
877            // Change data in to postable data
878                foreach ($data as $key => $val) {
879                        if (is_array($val)) {
880                                foreach ($val as $val2) {
881                                        $querystring .= urlencode($key).'='.urlencode($val2).'&';
882                                }
883                        } else {
884                                $querystring .= urlencode($key).'='.urlencode($val).'&';
885                        }
886                }
887                $querystring = substr($querystring, 0, -1); // Eliminate unnecessary &
888        } else {
889            $querystring = $data;
890        }
891        return $querystring;
892    }
893    function doRequest() {
894        // Performs the actual HTTP request, returning true or false depending on outcome
895                if (!$fp = @fsockopen($this->host, $this->port, $errno, $errstr, $this->timeout)) {
896                    // Set error message
897            switch($errno) {
898                                case -3:
899                                        $this->errormsg = 'Socket creation failed (-3)';
900                                case -4:
901                                        $this->errormsg = 'DNS lookup failure (-4)';
902                                case -5:
903                                        $this->errormsg = 'Connection refused or timed out (-5)';
904                                default:
905                                        $this->errormsg = 'Connection failed ('.$errno.')';
906                            $this->errormsg .= ' '.$errstr;
907                            $this->debug($this->errormsg);
908                        }
909                        return false;
910        }
911        socket_set_timeout($fp, $this->timeout);
912        $request = $this->buildRequest();
913        $this->debug('Request', $request);
914        fwrite($fp, $request);
915        // Reset all the variables that should not persist between requests
916        $this->headers = array();
917        $this->content = '';
918        $this->errormsg = '';
919        // Set a couple of flags
920        $inHeaders = true;
921        // Now start reading back the response
922            $line = fgets($fp, 4096);
923        // Deal with first line of returned data
924        if (!preg_match('/HTTP\/(\\d\\.\\d)\\s*(\\d+)\\s*(.*)/', $line, $m)) {
925            $this->errormsg = "Status code line invalid: ".htmlentities($line);
926            $this->debug($this->errormsg);
927            return false;
928        }
929        $http_version = $m[1]; // not used
930        $this->status = $m[2];
931        $status_string = $m[3]; // not used
932        $this->debug(trim($line));
933        while (!feof($fp)) {
934            $line = fgets($fp, 4096);
935            if ($inHeaders) {
936                if (trim($line) == '') {
937                    $inHeaders = false;
938                    $this->debug('Received Headers', $this->headers);
939                    if ($this->headers_only) {
940                        break; // Skip the rest of the input
941                    }
942                    continue;
943                }
944                if (!preg_match('/([^:]+):\\s*(.*)/', $line, $m)) {
945                    // Skip to the next header
946                    continue;
947                }
948                $key = strtolower(trim($m[1]));
949                $val = trim($m[2]);
950                // Deal with the possibility of multiple headers of same name
951                if (isset($this->headers[$key])) {
952                    if (is_array($this->headers[$key])) {
953                        $this->headers[$key][] = $val;
954                    } else {
955                        $this->headers[$key] = array($this->headers[$key], $val);
956                    }
957                } else {
958                    $this->headers[$key] = $val;
959                }
960                continue;
961            }
962            // We're not in the headers, so append the line to the contents
963            $this->content .= $line;
964        }
965        fclose($fp);
966        // If data is compressed, uncompress it
967        if (isset($this->headers['content-encoding']) && $this->headers['content-encoding'] == 'gzip') {
968            $this->debug('Content is gzip encoded, unzipping it');
969            $this->content = substr($this->content, 10); // See http://www.php.net/manual/en/function.gzencode.php
970            $this->content = gzinflate($this->content);
971        }
972        // If $persist_cookies, deal with any cookies
973        if ($this->persist_cookies && isset($this->headers['set-cookie']) && $this->host == $this->cookie_host) {
974            $cookies = $this->headers['set-cookie'];
975            if (!is_array($cookies)) {
976                $cookies = array($cookies);
977            }
978            foreach ($cookies as $cookie) {
979                if (preg_match('/([^=]+)=([^;]+);/', $cookie, $m)) {
980                    $this->cookies[$m[1]] = $m[2];
981                }
982            }
983            // Record domain of cookies for security reasons
984            $this->cookie_host = $this->host;
985        }
986        // If $persist_referers, set the referer ready for the next request
987        if ($this->persist_referers) {
988            $this->debug('Persisting referer: '.$this->getRequestURL());
989            $this->referer = $this->getRequestURL();
990        }
991        // Finally, if handle_redirects and a redirect is sent, do that
992        if ($this->handle_redirects) {
993            if (++$this->redirect_count >= $this->max_redirects) {
994                $this->errormsg = 'Number of redirects exceeded maximum ('.$this->max_redirects.')';
995                $this->debug($this->errormsg);
996                $this->redirect_count = 0;
997                return false;
998            }
999            $location = isset($this->headers['location']) ? $this->headers['location'] : '';
1000            $uri = isset($this->headers['uri']) ? $this->headers['uri'] : '';
1001            if ($location || $uri) {
1002                $url = parse_url($location.$uri);
1003                // This will FAIL if redirect is to a different site
1004                return $this->get($url['path']);
1005            }
1006        }
1007        return true;
1008    }
1009    function buildRequest() {
1010        $headers = array();
1011        $headers[] = "{$this->method} {$this->path} HTTP/1.0"; // Using 1.1 leads to all manner of problems, such as "chunked" encoding
1012        $headers[] = "Host: {$this->host}";
1013        $headers[] = "User-Agent: {$this->user_agent}";
1014        $headers[] = "Accept: {$this->accept}";
1015        if ($this->use_gzip) {
1016            $headers[] = "Accept-encoding: {$this->accept_encoding}";
1017        }
1018        $headers[] = "Accept-language: {$this->accept_language}";
1019        if ($this->referer) {
1020            $headers[] = "Referer: {$this->referer}";
1021        }
1022        if ($this->no_cache) {
1023                $headers[] = "Pragma: no-cache";
1024                $headers[] = "Cache-control: no-cache";
1025        }
1026        // Cookies
1027        if ($this->cookies) {
1028            $cookie = 'Cookie: ';
1029            foreach ($this->cookies as $key => $value) {
1030                $cookie .= "$key=$value; ";
1031            }
1032            $headers[] = $cookie;
1033        }
1034        // Basic authentication
1035        if ($this->username && $this->password) {
1036            $headers[] = 'Authorization: BASIC '.base64_encode($this->username.':'.$this->password);
1037        }
1038        // If this is a POST, set the content type and length
1039        if ($this->postdata) {
1040            $headers[] = 'Content-Type: application/x-www-form-urlencoded';
1041            $headers[] = 'Content-Length: '.strlen($this->postdata);
1042        }
1043        $request = implode("\r\n", $headers)."\r\n\r\n".$this->postdata;
1044        return $request;
1045    }
1046    function getStatus() {
1047        return $this->status;
1048    }
1049    function getContent() {
1050        return $this->content;
1051    }
1052    function getHeaders() {
1053        return $this->headers;
1054    }
1055    function getHeader($header) {
1056        $header = strtolower($header);
1057        if (isset($this->headers[$header])) {
1058            return $this->headers[$header];
1059        } else {
1060            return false;
1061        }
1062    }
1063    function getError() {
1064        return $this->errormsg;
1065    }
1066    function getCookies() {
1067        return $this->cookies;
1068    }
1069    function getRequestURL() {
1070        $url = 'http://'.$this->host;
1071        if ($this->port != 80) {
1072            $url .= ':'.$this->port;
1073        }           
1074        $url .= $this->path;
1075        return $url;
1076    }
1077    // Setter methods
1078    function setUserAgent($string) {
1079        $this->user_agent = $string;
1080    }
1081    function setAuthorization($username, $password) {
1082        $this->username = $username;
1083        $this->password = $password;
1084    }
1085    function setCookies($array) {
1086        $this->cookies = $array;
1087    }
1088    // Option setting methods
1089    function useGzip($boolean) {
1090        $this->use_gzip = $boolean;
1091    }
1092    function setPersistCookies($boolean) {
1093        $this->persist_cookies = $boolean;
1094    }
1095    function setPersistReferers($boolean) {
1096        $this->persist_referers = $boolean;
1097    }
1098    function setHandleRedirects($boolean) {
1099        $this->handle_redirects = $boolean;
1100    }
1101    function setMaxRedirects($num) {
1102        $this->max_redirects = $num;
1103    }
1104    function setHeadersOnly($boolean) {
1105        $this->headers_only = $boolean;
1106    }
1107    function setDebug($boolean) {
1108        $this->debug = $boolean;
1109    }
1110    // "Quick" static methods
1111    function quickGet($url) {
1112        $bits = parse_url($url);
1113        $host = $bits['host'];
1114        $port = isset($bits['port']) ? $bits['port'] : 80;
1115        $path = isset($bits['path']) ? $bits['path'] : '/';
1116        if (isset($bits['query'])) {
1117            $path .= '?'.$bits['query'];
1118        }
1119        $client = new HttpClient($host, $port);
1120        if (!$client->get($path)) {
1121            return false;
1122        } else {
1123            return $client->getContent();
1124        }
1125    }
1126    function quickPost($url, $data) {
1127        $bits = parse_url($url);
1128        $host = $bits['host'];
1129        $port = isset($bits['port']) ? $bits['port'] : 80;
1130        $path = isset($bits['path']) ? $bits['path'] : '/';
1131        $client = new HttpClient($host, $port);
1132        if (!$client->post($path, $data)) {
1133            return false;
1134        } else {
1135            return $client->getContent();
1136        }
1137    }
1138    function debug($msg, $object = false) {
1139        if ($this->debug) {
1140            print '<div style="border: 1px solid red; padding: 0.5em; margin: 0.5em;"><strong>HttpClient Debug:</strong> '.$msg;
1141            if ($object) {
1142                ob_start();
1143                    print_r($object);
1144                    $content = htmlentities(ob_get_contents());
1145                    ob_end_clean();
1146                    print '<pre>'.$content.'</pre>';
1147                }
1148                print '</div>';
1149        }
1150    }
1151}
1152endif;
1153
1154function string_attributes($attrs) {
1155        return join(' ', array_map(create_function('$k,$v', 'return "$k=\"".htmlspecialchars($v)."\"";'), array_keys($attrs), array_values($attrs) ) );
1156}
1157
1158if ( !function_exists('parse_w3cdtf') ) :
1159function parse_w3cdtf ( $date_str ) {
1160        # regex to match wc3dtf
1161        $pat = "/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(:(\d{2}))?(?:([-+])(\d{2}):?(\d{2})|(Z))?/";
1162
1163        if ( preg_match( $pat, $date_str, $match ) ) {
1164                list( $year, $month, $day, $hours, $minutes, $seconds) =
1165                                array( $match[1], $match[2], $match[3], $match[4], $match[5], $match[7]);
1166
1167                # calc epoch for current date assuming GMT
1168                $epoch = gmmktime( $hours, $minutes, $seconds, $month, $day, $year);
1169
1170                $offset = 0;
1171                if ( $match[10] == 'Z' ) {
1172                        # zulu time, aka GMT
1173                }
1174                else {
1175                        list( $tz_mod, $tz_hour, $tz_min ) =
1176                        array( $match[8], $match[9], $match[10]);
1177
1178                        # zero out the variables
1179                        if ( ! $tz_hour ) { $tz_hour = 0; }
1180                        if ( ! $tz_min ) { $tz_min = 0; }
1181
1182                        $offset_secs = (($tz_hour*60)+$tz_min)*60;
1183
1184                        # is timezone ahead of GMT?  then subtract offset
1185                        #
1186                        if ( $tz_mod == '+' ) {
1187                                $offset_secs = $offset_secs * -1;
1188                        }
1189
1190                        $offset = $offset_secs;
1191                }
1192                $epoch = $epoch + $offset;
1193                return $epoch;
1194        }
1195        else {
1196                return -1;
1197        }
1198}
1199endif;
1200
1201?>