Make WordPress Core

Ticket #2864: rss.php

File rss.php, 32.9 KB (added by skeltoac, 20 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?>