PluginProbe
FeedWordPress / 0.99
FeedWordPress v0.99
trunk 0.8 0.9 0.91 0.95 0.96 0.97 0.98 0.981 0.99 0.991 0.992 0.993 2008.1030 2008.1101 2008.1105 2008.1214 2009.0612 2009.0613 2009.0618 2009.0707 2009.1111 2009.1112 2010.0127 2010.0528 All 65 releases
feedwordpress / OPTIONAL / wp-includes / rss.php

rss.php in FeedWordPress 0.99, at OPTIONAL/wp-includes/rss.php

2,006 lines 70.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /* Project: MagpieRSS: a simple RSS integration tool
3 * File: A compiled file for RSS syndication
4 * Author: Kellan Elliot-McCrea <kellan@protest.net>
5 * WordPress development team <http://www.wordpress.org/>
6 * Charles Johnson <technophilia@radgeek.com>
7 * Version: 0.85wp (2007.09.24)
8 * License: GPL
9 *
10 * Provenance:
11 *
12 * This is a drop-in replacement for the `rss-functions.php` provided with the
13 * WordPress 1.5 distribution, which upgrades the version of MagpieRSS from 0.51
14 * to 0.8a. The update improves handling of character encoding, supports
15 * multiple categories for posts (using <dc:subject> or <category>), supports
16 * Atom 1.0, and implements many other useful features. The file is derived from
17 * a combination of (1) the WordPress development team's modifications to
18 * MagpieRSS 0.51 and (2) the latest bleeding-edge updates to the "official"
19 * MagpieRSS software, including Kellan's original work and some substantial
20 * updates by Charles Johnson. All possible through the magic of the GPL. Yay
21 * for free software!
22 *
23 * Differences from the main branch of MagpieRSS:
24 *
25 * 1. Everything in rss_parse.inc, rss_fetch.inc, rss_cache.inc, and
26 * rss_utils.inc is included in one file.
27 *
28 * 2. MagpieRSS returns the WordPress version as the user agent, rather than
29 * Magpie
30 *
31 * 3. class RSSCache is a modified version by WordPress developers, which
32 * caches feeds in the WordPress database (in the options table), rather
33 * than writing external files directly.
34 *
35 * 4. There are two WordPress-specific functions, get_rss() and wp_rss()
36 *
37 * Differences from the version of MagpieRSS packaged with WordPress:
38 *
39 * 1. Support for translation between multiple character encodings. Under
40 * PHP 5 this is very nicely handled by the XML parsing library. Under PHP
41 * 4 we need to do a little bit of work ourselves, using either iconv or
42 * mb_convert_encoding if it is not one of the (extremely limited) number
43 * of character sets that PHP 4's XML module can handle natively.
44 *
45 * 2. Numerous bug fixes.
46 *
47 * 3. The parser class MagpieRSS has been substantially revised to better
48 * support popular features such as enclosures and multiple categories,
49 * and to support the new Atom 1.0 IETF standard. (Atom feeds are
50 * normalized so as to make the data available using terminology from
51 * either Atom 0.3 or Atom 1.0. Atom 0.3 backward-compatibility is provided
52 * to allow existing software to easily begin accepting Atom 1.0 data; new
53 * software SHOULD NOT depend on the 0.3 terminology, but rather use the
54 * normalization as a convenient way to keep supporting 0.3 feeds while
55 * they linger in the world.)
56 *
57 * The upgraded MagpieRSS can also now handle some content constructs that
58 * had not been handled well by previous versions of Magpie (such as the
59 * use of namespaced XHTML in <xhtml:body> or <xhtml:div> elements to
60 * provide the full content of posts in RSS 2.0 feeds).
61 *
62 * Unlike previous versions of MagpieRSS, this version can parse multiple
63 * instances of the same child element in item/entry and channel/feed
64 * containers. This is done using simple counters next to the element
65 * names: the first <category> element on an RSS item, for example, can be
66 * found in $item['category'] (thus preserving backward compatibility); the
67 * second in $item['category#2'], the third in $item['category#3'], and so
68 * on. The number of categories applied to the item can be found in
69 * $item['category#']
70 *
71 * Also unlike previous versions of MagpieRSS, this version allows you to
72 * access the values of elements' attributes as well as the content they
73 * contain. This can be done using a simple syntax inspired by XPath: to
74 * access the type attribute of an RSS 2.0 enclosure, for example, you
75 * need only access `$item['enclosure@type']`. A comma-separated list of
76 * attributes for the enclosure element is stored in `$item['enclosure@']`.
77 * (This syntax interacts easily with the syntax for multiple categories;
78 * for example, the value of the `scheme` attribute for the fourth category
79 * element on a particular item is stored in `$item['category#4@scheme']`.)
80 *
81 * Note also that this implementation IS NOT backward-compatible with the
82 * kludges that were used to hack in support for multiple categories and
83 * for enclosures in upgraded versions of MagpieRSS distributed with
84 * previous versions of FeedWordPress. If your hacks or filter plugins
85 * depended on the old way of doing things... well, I warned you that they
86 * might not be permanent. Sorry!
87 */
88
89 define('RSS', 'RSS');
90 define('ATOM', 'Atom');
91
92 ################################################################################
93 ## WordPress: make some settings WordPress-appropriate #########################
94 ################################################################################
95
96 define('MAGPIE_USER_AGENT', 'WordPress/' . $wp_version . '(+http://www.wordpress.org)');
97
98 $wp_encoding = get_settings('blog_charset');
99 define('MAGPIE_OUTPUT_ENCODING', ($wp_encoding?$wp_encoding:'ISO-8859-1'));
100
101 ################################################################################
102 ## rss_parse.inc: from MagpieRSS 0.85 ##########################################
103 ################################################################################
104
105 /**
106 * Hybrid parser, and object, takes RSS as a string and returns a simple object.
107 *
108 * see: rss_fetch.inc for a simpler interface with integrated caching support
109 *
110 */
111 class MagpieRSS {
112 var $parser;
113
114 var $current_item = array(); // item currently being parsed
115 var $items = array(); // collection of parsed items
116 var $channel = array(); // hash of channel fields
117 var $textinput = array();
118 var $image = array();
119 var $feed_type;
120 var $feed_version;
121 var $encoding = ''; // output encoding of parsed rss
122
123 var $_source_encoding = ''; // only set if we have to parse xml prolog
124
125 var $ERROR = "";
126 var $WARNING = "";
127
128 // define some constants
129 var $_XMLNS_FAMILIAR = array (
130 'http://www.w3.org/2005/Atom' => 'atom' /* 1.0 */,
131 'http://purl.org/atom/ns#' => 'atom' /* pre-1.0 */,
132 'http://purl.org/rss/1.0/' => 'rss' /* 1.0 */,
133 'http://backend.userland.com/RSS2' => 'rss' /* 2.0 */,
134 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' => 'rdf',
135 'http://www.w3.org/1999/xhtml' => 'xhtml',
136 'http://purl.org/dc/elements/1.1/' => 'dc',
137 'http://purl.org/dc/terms/' => 'dcterms',
138 'http://purl.org/rss/1.0/modules/content/' => 'content',
139 'http://purl.org/rss/1.0/modules/syndication/' => 'sy',
140 'http://purl.org/rss/1.0/modules/taxonomy/' => 'taxo',
141 'http://purl.org/rss/1.0/modules/dc/' => 'dc',
142 'http://wellformedweb.org/CommentAPI/' => 'wfw',
143 'http://webns.net/mvcb/' => 'admin',
144 'http://purl.org/rss/1.0/modules/annotate/' => 'annotate',
145 'http://xmlns.com/foaf/0.1/' => 'foaf',
146 'http://madskills.com/public/xml/rss/module/trackback/' => 'trackback',
147 'http://web.resource.org/cc/' => 'cc'
148 );
149
150 var $_XMLBASE_RESOLVE = array (
151 // Atom 0.3 and 1.0 xml:base support
152 'atom' => array (
153 'link' => array ('href' => true),
154 'content' => array ('src' => true, '*xml' => true, '*html' => true),
155 'summary' => array ('*xml' => true, '*html' => true),
156 'title' => array ('*xml' => true, '*html' => true),
157 'rights' => array ('*xml' => true, '*html' => true),
158 'subtitle' => array ('*xml' => true, '*html' => true),
159 'info' => array('*xml' => true, '*html' => true),
160 'tagline' => array('*xml' => true, '*html' => true),
161 'copyright' => array ('*xml' => true, '*html' => true),
162 'generator' => array ('uri' => true, 'url' => true),
163 'uri' => array ('*content' => true),
164 'url' => array ('*content' => true),
165 'icon' => array ('*content' => true),
166 'logo' => array ('*content' => true),
167 ),
168
169 // for inline namespaced XHTML
170 'xhtml' => array (
171 'a' => array ('href' => true),
172 'applet' => array('codebase' => true),
173 'area' => array('href' => true),
174 'blockquote' => array('cite' => true),
175 'body' => array('background' => true),
176 'del' => array('cite' => true),
177 'form' => array('action' => true),
178 'frame' => array('longdesc' => true, 'src' => true),
179 'iframe' => array('longdesc' => true, 'iframe' => true, 'src' => true),
180 'head' => array('profile' => true),
181 'img' => array('longdesc' => true, 'src' => true, 'usemap' => true),
182 'input' => array('src' => true, 'usemap' => true),
183 'ins' => array('cite' => true),
184 'link' => array('href' => true),
185 'object' => array('classid' => true, 'codebase' => true, 'data' => true, 'usemap' => true),
186 'q' => array('cite' => true),
187 'script' => array('src' => true),
188 ),
189 );
190
191 var $_ATOM_CONTENT_CONSTRUCTS = array(
192 'content', 'summary', 'title', /* common */
193 'info', 'tagline', 'copyright', /* Atom 0.3 */
194 'rights', 'subtitle', /* Atom 1.0 */
195 );
196 var $_XHTML_CONTENT_CONSTRUCTS = array('body', 'div');
197 var $_KNOWN_ENCODINGS = array('UTF-8', 'US-ASCII', 'ISO-8859-1');
198
199 // parser variables, useless if you're not a parser, treat as private
200 var $stack = array('element' => array (), 'xmlns' => array (), 'xml:base' => array ()); // stack of XML data
201
202 var $inchannel = false;
203 var $initem = false;
204
205 var $incontent = array(); // non-empty if in namespaced XML content field
206 var $xml_escape = false; // true when accepting namespaced XML
207 var $exclude_top = false; // true when Atom 1.0 type="xhtml"
208
209 var $intextinput = false;
210 var $inimage = false;
211 var $root_namespaces = array();
212 var $current_namespace = false;
213 var $working_namespace_table = array();
214
215 /**
216 * Set up XML parser, parse source, and return populated RSS object..
217 *
218 * @param string $source string containing the RSS to be parsed
219 *
220 * NOTE: Probably a good idea to leave the encoding options alone unless
221 * you know what you're doing as PHP's character set support is
222 * a little weird.
223 *
224 * NOTE: A lot of this is unnecessary but harmless with PHP5
225 *
226 *
227 * @param string $output_encoding output the parsed RSS in this character
228 * set defaults to ISO-8859-1 as this is PHP's
229 * default.
230 *
231 * NOTE: might be changed to UTF-8 in future
232 * versions.
233 *
234 * @param string $input_encoding the character set of the incoming RSS source.
235 * Leave blank and Magpie will try to figure it
236 * out.
237 *
238 *
239 * @param bool $detect_encoding if false Magpie won't attempt to detect
240 * source encoding. (caveat emptor)
241 *
242 */
243 function MagpieRSS ($source, $output_encoding='ISO-8859-1',
244 $input_encoding=null, $detect_encoding=true, $base_uri=null)
245 {
246 # if PHP xml isn't compiled in, die
247 #
248 if (!function_exists('xml_parser_create')) {
249 $this->error( "Failed to load PHP's XML Extension. " .
250 "http://www.php.net/manual/en/ref.xml.php",
251 E_USER_ERROR );
252 }
253
254 list($parser, $source) = $this->create_parser($source,
255 $output_encoding, $input_encoding, $detect_encoding);
256
257
258 if (!is_resource($parser)) {
259 $this->error( "Failed to create an instance of PHP's XML parser. " .
260 "http://www.php.net/manual/en/ref.xml.php",
261 E_USER_ERROR );
262 }
263
264
265 $this->parser = $parser;
266
267 # pass in parser, and a reference to this object
268 # setup handlers
269 #
270 xml_set_object( $this->parser, $this );
271 xml_set_element_handler($this->parser,
272 'feed_start_element', 'feed_end_element' );
273
274 xml_set_character_data_handler( $this->parser, 'feed_cdata' );
275
276 $this->stack['xml:base'] = array($base_uri);
277
278 $status = xml_parse( $this->parser, $source );
279
280 if (! $status ) {
281 $errorcode = xml_get_error_code( $this->parser );
282 if ( $errorcode != XML_ERROR_NONE ) {
283 $xml_error = xml_error_string( $errorcode );
284 $error_line = xml_get_current_line_number($this->parser);
285 $error_col = xml_get_current_column_number($this->parser);
286 $errormsg = "$xml_error at line $error_line, column $error_col";
287
288 $this->error( $errormsg );
289 }
290 }
291
292 xml_parser_free( $this->parser );
293
294 $this->normalize();
295 }
296
297 function feed_start_element($p, $element, &$attributes) {
298 $el = strtolower($element);
299
300 $namespaces = end($this->stack['xmlns']);
301 $baseuri = end($this->stack['xml:base']);
302
303 if (isset($attributes['xml:base'])) {
304 $baseuri = Relative_URI::resolve($attributes['xml:base'], $baseuri);
305 }
306 array_push($this->stack['xml:base'], $baseuri);
307
308 // scan for xml namespace declarations. ugly ugly ugly.
309 // theoretically we could use xml_set_start_namespace_decl_handler and
310 // xml_set_end_namespace_decl_handler to handle this more elegantly, but
311 // support for these is buggy
312 foreach ($attributes as $attr => $value) {
313 if ( preg_match('/^xmlns(\:([A-Z_a-z].*))?$/', $attr, $match) ) {
314 $ns = (isset($match[2]) ? $match[2] : '');
315 $namespaces[$ns] = $value;
316 }
317 }
318
319 array_push($this->stack['xmlns'], $namespaces);
320
321 // check for a namespace, and split if found
322 // Don't munge content tags
323 $ns = $this->namespace($element);
324 if ( empty($this->incontent) ) {
325 $el = strtolower($ns['element']);
326 $this->current_namespace = $ns['effective'];
327 }
328
329 $nsc = $ns['canonical']; $nse = $ns['element'];
330 if ( isset($this->_XMLBASE_RESOLVE[$nsc][$nse]) ) {
331 if (isset($this->_XMLBASE_RESOLVE[$nsc][$nse]['*xml'])) {
332 $attributes['xml:base'] = $baseuri;
333 }
334 foreach ($attributes as $key => $value) {
335 if (isset($this->_XMLBASE_RESOLVE[$nsc][$nse][strtolower($key)])) {
336 $attributes[$key] = Relative_URI::resolve($attributes[$key], $baseuri);
337 }
338 }
339 }
340
341 $attrs = array_change_key_case($attributes, CASE_LOWER);
342
343 # if feed type isn't set, then this is first element of feed
344 # identify feed from root element
345 #
346 if (!isset($this->feed_type) ) {
347 if ( $el == 'rdf' ) {
348 $this->feed_type = RSS;
349 $this->root_namespaces = array('rss', 'rdf');
350 $this->feed_version = '1.0';
351 }
352 elseif ( $el == 'rss' ) {
353 $this->feed_type = RSS;
354 $this->root_namespaces = array('rss');
355 $this->feed_version = $attrs['version'];
356 }
357 elseif ( $el == 'feed' ) {
358 $this->feed_type = ATOM;
359 $this->root_namespaces = array('atom');
360 if ($ns['uri'] == 'http://www.w3.org/2005/Atom') { // Atom 1.0
361 $this->root_namespaces = array('atom');
362 $this->feed_version = '1.0';
363 }
364 else { // Atom 0.3, probably.
365 $this->feed_version = $attrs['version'];
366 }
367 $this->inchannel = true;
368 }
369 return;
370 }
371
372 // if we're inside a namespaced content construct, treat tags as text
373 if ( !empty($this->incontent) )
374 {
375 if ((count($this->incontent) > 1) or !$this->exclude_top) {
376 if ($ns['effective']=='xhtml') {
377 $tag = $ns['element'];
378 }
379 else {
380 $tag = $element;
381 $xmlns = 'xmlns';
382 if (strlen($ns['prefix'])>0) {
383 $xmlns = $xmlns . ':' . $ns['prefix'];
384 }
385 $attributes[$xmlns] = $ns['uri']; // make sure it's visible
386 }
387
388 // if tags are inlined, then flatten
389 $attrs_str = join(' ',
390 array_map(array($this, 'map_attrs'),
391 array_keys($attributes),
392 array_values($attributes) )
393 );
394
395 if (strlen($attrs_str) > 0) { $attrs_str = ' '.$attrs_str; }
396 $this->append_content( "<{$tag}{$attrs_str}>" );
397 }
398 array_push($this->incontent, $ns); // stack for parsing content XML
399 }
400
401 elseif ( $el == 'channel' ) {
402 $this->inchannel = true;
403 }
404
405 elseif ($el == 'item' or $el == 'entry' )
406 {
407 $this->initem = true;
408 if ( isset($attrs['rdf:about']) ) {
409 $this->current_item['about'] = $attrs['rdf:about'];
410 }
411 }
412
413 // if we're in the default namespace of an RSS feed,
414 // record textinput or image fields
415 elseif (
416 $this->feed_type == RSS and
417 $this->current_namespace == '' and
418 $el == 'textinput' )
419 {
420 $this->intextinput = true;
421 }
422
423 elseif (
424 $this->feed_type == RSS and
425 $this->current_namespace == '' and
426 $el == 'image' )
427 {
428 $this->inimage = true;
429 }
430
431 // set stack[0] to current element
432 else {
433 // Atom support many links per containing element.
434 // Magpie treats link elements of type rel='alternate'
435 // as being equivalent to RSS's simple link element.
436
437 $atom_link = false;
438 if ($this->feed_type == ATOM and $el == 'link') {
439 $atom_link = true;
440 if (isset($attrs['rel']) and $attrs['rel'] != 'alternate') {
441 $el = $el . "_" . $attrs['rel']; // pseudo-element names for Atom link elements
442 }
443 }
444 # handle atom content constructs
445 elseif ( $this->feed_type == ATOM and in_array($el, $this->_ATOM_CONTENT_CONSTRUCTS) )
446 {
447 // avoid clashing w/ RSS mod_content
448 if ($el == 'content' ) {
449 $el = 'atom_content';
450 }
451
452 // assume that everything accepts namespaced XML
453 // (that will pass through some non-validating feeds;
454 // but so what? this isn't a validating parser)
455 $this->incontent = array();
456 array_push($this->incontent, $ns); // start a stack
457
458 $this->xml_escape = $this->accepts_namespaced_xml($attrs);
459
460 if ( isset($attrs['type']) and trim(strtolower($attrs['type']))=='xhtml') {
461 $this->exclude_top = true;
462 } else {
463 $this->exclude_top = false;
464 }
465 }
466 # Handle inline XHTML body elements --CWJ
467 elseif ($ns['effective']=='xhtml' and in_array($el, $this->_XHTML_CONTENT_CONSTRUCTS)) {
468 $this->current_namespace = 'xhtml';
469 $this->incontent = array();
470 array_push($this->incontent, $ns); // start a stack
471
472 $this->xml_escape = true;
473 $this->exclude_top = false;
474 }
475
476 array_unshift($this->stack['element'], $el);
477 $elpath = join('_', array_reverse($this->stack['element']));
478
479 $n = $this->element_count($elpath);
480 $this->element_count($elpath, $n+1);
481
482 if ($n > 0) {
483 array_shift($this->stack['element']);
484 array_unshift($this->stack['element'], $el.'#'.($n+1));
485 $elpath = join('_', array_reverse($this->stack['element']));
486 }
487
488 // this makes the baby Jesus cry, but we can't do it in normalize()
489 // because we've made the element name for Atom links unpredictable
490 // by tacking on the relation to the end. -CWJ
491 if ($atom_link and isset($attrs['href'])) {
492 $this->append($elpath, $attrs['href']);
493 }
494
495 // add attributes
496 if (count($attrs) > 0) {
497 $this->append($elpath.'@', join(',', array_keys($attrs)));
498 foreach ($attrs as $attr => $value) {
499 $this->append($elpath.'@'.$attr, $value);
500 }
501 }
502 }
503 }
504
505 function feed_cdata ($p, $text) {
506 if ($this->incontent) {
507 if ($this->xml_escape) { $text = htmlspecialchars($text, ENT_COMPAT, $this->encoding); }
508 $this->append_content( $text );
509 } else {
510 $current_el = join('_', array_reverse($this->stack['element']));
511 $this->append($current_el, $text);
512 }
513 }
514
515 function feed_end_element ($p, $el) {
516 $closer = $this->namespace($el);
517
518 if ( $this->incontent ) {
519 $opener = array_pop($this->incontent);
520
521 // balance tags properly
522 // note: i don't think this is actually neccessary
523 if ($opener != $closer) {
524 array_push($this->incontent, $opener);
525 $this->append_content("<$el />");
526 } elseif ($this->incontent) { // are we in the content construct still?
527 if ((count($this->incontent) > 1) or !$this->exclude_top) {
528 if ($closer['effective']=='xhtml') {
529 $tag = $closer['element'];
530 }
531 else {
532 $tag = $el;
533 }
534 $this->append_content("</$tag>");
535 }
536 } else { // if we're done with the content construct, shift the opening of the content construct off the normal stack
537 array_shift( $this->stack['element'] );
538 }
539 }
540 elseif ($closer['effective'] == '') {
541 $el = strtolower($closer['element']);
542 if ( $el == 'item' or $el == 'entry' ) {
543 $this->items[] = $this->current_item;
544 $this->current_item = array();
545 $this->initem = false;
546 $this->current_category = 0;
547 }
548 elseif ($this->feed_type == RSS and $el == 'textinput' ) {
549 $this->intextinput = false;
550 }
551 elseif ($this->feed_type == RSS and $el == 'image' ) {
552 $this->inimage = false;
553 }
554 elseif ($el == 'channel' or $el == 'feed' ) {
555 $this->inchannel = false;
556 } else {
557 $nsc = $closer['canonical']; $nse = $closer['element'];
558 if (isset($this->_XMLBASE_RESOLVE[$nsc][$nse]['*content'])) {
559 // Resolve relative URI in content of tag
560 $this->dereference_current_element();
561 }
562 array_shift( $this->stack['element'] );
563 }
564 } else {
565 $nsc = $closer['canonical']; $nse = strtolower($closer['element']);
566 if (isset($this->_XMLBASE_RESOLVE[$nsc][$nse]['*content'])) {
567 // Resolve relative URI in content of tag
568 $this->dereference_current_element();
569 }
570 array_shift( $this->stack['element'] );
571 }
572
573 if ( !$this->incontent ) { // Don't munge the namespace after finishing with elements in namespaced content constructs -CWJ
574 $this->current_namespace = false;
575 }
576 array_pop($this->stack['xmlns']);
577 array_pop($this->stack['xml:base']);
578 }
579
580 // Namespace handling functions
581 function namespace ($element) {
582 $namespaces = end($this->stack['xmlns']);
583 $ns = '';
584 if ( strpos( $element, ':' ) ) {
585 list($ns, $element) = split( ':', $element, 2);
586 }
587
588 $uri = (isset($namespaces[$ns]) ? $namespaces[$ns] : null);
589
590 if (!is_null($uri)) {
591 $canonical = (
592 isset($this->_XMLNS_FAMILIAR[$uri])
593 ? $this->_XMLNS_FAMILIAR[$uri]
594 : $uri
595 );
596 } else {
597 $canonical = $ns;
598 }
599
600 if (in_array($canonical, $this->root_namespaces)) {
601 $effective = '';
602 } else {
603 $effective = $canonical;
604 }
605
606 return array('effective' => $effective, 'canonical' => $canonical, 'prefix' => $ns, 'uri' => $uri, 'element' => $element);
607 }
608
609 // Utility functions for accessing data structure
610
611 // for smart, namespace-aware methods...
612 function magpie_data ($el, $method, $text = NULL) {
613 $ret = NULL;
614 if ($el) {
615 if (is_array($method)) {
616 $el = $this->{$method['key']}($el);
617 $method = $method['value'];
618 }
619
620 if ( $this->current_namespace ) {
621 if ( $this->initem ) {
622 $ret = $this->{$method} (
623 $this->current_item[ $this->current_namespace ][ $el ],
624 $text
625 );
626 }
627 elseif ($this->inchannel) {
628 $ret = $this->{$method} (
629 $this->channel[ $this->current_namespace][ $el ],
630 $text
631 );
632 }
633 elseif ($this->intextinput) {
634 $ret = $this->{$method} (
635 $this->textinput[ $this->current_namespace][ $el ],
636 $text
637 );
638 }
639 elseif ($this->inimage) {
640 $ret = $this->{$method} (
641 $this->image[ $this->current_namespace ][ $el ], $text );
642 }
643 }
644 else {
645 if ( $this->initem ) {
646 $ret = $this->{$method} (
647 $this->current_item[ $el ], $text);
648 }
649 elseif ($this->intextinput) {
650 $ret = $this->{$method} (
651 $this->textinput[ $el ], $text );
652 }
653 elseif ($this->inimage) {
654 $ret = $this->{$method} (
655 $this->image[ $el ], $text );
656 }
657 elseif ($this->inchannel) {
658 $ret = $this->{$method} (
659 $this->channel[ $el ], $text );
660 }
661 }
662 }
663 return $ret;
664 }
665
666 function concat (&$str1, $str2="") {
667 if (!isset($str1) ) {
668 $str1="";
669 }
670 $str1 .= $str2;
671 }
672
673 function retrieve_value (&$el, $text /*ignore*/) {
674 return $el;
675 }
676 function replace_value (&$el, $text) {
677 $el = $text;
678 }
679 function counter_key ($el) {
680 return $el.'#';
681 }
682
683
684 function append_content($text) {
685 $construct = reset($this->incontent);
686 $ns = $construct['effective'];
687
688 // Keeping data about parent elements is necessary to
689 // properly handle atom:source and its children elements
690 $tag = join('_', array_reverse($this->stack['element']));
691
692 if ( $this->initem ) {
693 if ($ns) {
694 $this->concat( $this->current_item[$ns][$tag], $text );
695 } else {
696 $this->concat( $this->current_item[$tag], $text );
697 }
698 }
699 elseif ( $this->inchannel ) {
700 if ($this->current_namespace) {
701 $this->concat( $this->channel[$ns][$tag], $text );
702 } else {
703 $this->concat( $this->channel[$tag], $text );
704 }
705 }
706 }
707
708 // smart append - field and namespace aware
709 function append($el, $text) {
710 $this->magpie_data($el, 'concat', $text);
711 }
712
713 function dereference_current_element () {
714 $el = join('_', array_reverse($this->stack['element']));
715 $base = end($this->stack['xml:base']);
716 $uri = $this->magpie_data($el, 'retrieve_value');
717 $this->magpie_data($el, 'replace_value', Relative_URI::resolve($uri, $base));
718 }
719
720 // smart count - field and namespace aware
721 function element_count ($el, $set = NULL) {
722 if (!is_null($set)) {
723 $ret = $this->magpie_data($el, array('key' => 'counter_key', 'value' => 'replace_value'), $set);
724 }
725 $ret = $this->magpie_data($el, array('key' => 'counter_key', 'value' => 'retrieve_value'));
726 return ($ret ? $ret : 0);
727 }
728
729 function normalize_enclosure (&$source, $from, &$dest, $to, $i) {
730 $id_from = $this->element_id($from, $i);
731 $id_to = $this->element_id($to, $i);
732 if (isset($source["{$id_from}@"])) {
733 foreach (explode(',', $source["{$id_from}@"]) as $attr) {
734 if ($from=='link_enclosure' and $attr=='href') { // from Atom
735 $dest["{$id_to}@url"] = $source["{$id_from}@{$attr}"];
736 $dest["{$id_to}"] = $source["{$id_from}@{$attr}"];
737 }
738 elseif ($from=='enclosure' and $attr=='url') { // from RSS
739 $dest["{$id_to}@href"] = $source["{$id_from}@{$attr}"];
740 $dest["{$id_to}"] = $source["{$id_from}@{$attr}"];
741 }
742 else {
743 $dest["{$id_to}@{$attr}"] = $source["{$id_from}@{$attr}"];
744 }
745 }
746 }
747 }
748
749 function normalize_atom_person (&$source, $person, &$dest, $to, $i) {
750 $id = $this->element_id($person, $i);
751 $id_to = $this->element_id($to, $i);
752
753 // Atom 0.3 <=> Atom 1.0
754 if ($this->feed_version >= 1.0) { $used = 'uri'; $norm = 'url'; }
755 else { $used = 'url'; $norm = 'uri'; }
756
757 if (isset($source["{$id}_{$used}"])) {
758 $dest["{$id_to}_{$norm}"] = $source["{$id}_{$used}"];
759 }
760
761 // Atom to RSS 2.0 and Dublin Core
762 // RSS 2.0 person strings should be valid e-mail addresses if possible.
763 if (isset($source["{$id}_email"])) {
764 $rss_author = $source["{$id}_email"];
765 }
766 if (isset($source["{$id}_name"])) {
767 $rss_author = $source["{$id}_name"]
768 . (isset($rss_author) ? " <$rss_author>" : '');
769 }
770 if (isset($rss_author)) {
771 $source[$id] = $rss_author; // goes to top-level author or contributor
772 $dest[$id_to] = $rss_author; // goes to dc:creator or dc:contributor
773 }
774 }
775
776 // Normalize Atom 1.0 and RSS 2.0 categories to Dublin Core...
777 function normalize_category (&$source, $from, &$dest, $to, $i) {
778 $cat_id = $this->element_id($from, $i);
779 $dc_id = $this->element_id($to, $i);
780
781 // first normalize category elements: Atom 1.0 <=> RSS 2.0
782 if ( isset($source["{$cat_id}@term"]) ) { // category identifier
783 $source[$cat_id] = $source["{$cat_id}@term"];
784 } elseif ( $this->feed_type == RSS ) {
785 $source["{$cat_id}@term"] = $source[$cat_id];
786 }
787
788 if ( isset($source["{$cat_id}@scheme"]) ) { // URI to taxonomy
789 $source["{$cat_id}@domain"] = $source["{$cat_id}@scheme"];
790 } elseif ( isset($source["{$cat_id}@domain"]) ) {
791 $source["{$cat_id}@scheme"] = $source["{$cat_id}@domain"];
792 }
793
794 // Now put the identifier into dc:subject
795 $dest[$dc_id] = $source[$cat_id];
796 }
797
798 // ... or vice versa
799 function normalize_dc_subject (&$source, $from, &$dest, $to, $i) {
800 $dc_id = $this->element_id($from, $i);
801 $cat_id = $this->element_id($to, $i);
802
803 $dest[$cat_id] = $source[$dc_id]; // RSS 2.0
804 $dest["{$cat_id}@term"] = $source[$dc_id]; // Atom 1.0
805 }
806
807 // simplify the logic for normalize(). Makes sure that count of elements and
808 // each of multiple elements is normalized properly. If you need to mess
809 // with things like attributes or change formats or the like, pass it a
810 // callback to handle each element.
811 function normalize_element (&$source, $from, &$dest, $to, $via = NULL) {
812 if (isset($source[$from]) or isset($source["{$from}#"])) {
813 if (isset($source["{$from}#"])) {
814 $n = $source["{$from}#"];
815 $dest["{$to}#"] = $source["{$from}#"];
816 }
817 else { $n = 1; }
818
819 for ($i = 1; $i <= $n; $i++) {
820 if (isset($via)) { // custom callback for ninja attacks
821 $this->{$via}($source, $from, $dest, $to, $i);
822 }
823 else { // just make it the same
824 $from_id = $this->element_id($from, $i);
825 $to_id = $this->element_id($to, $i);
826 $dest[$to_id] = $source[$from_id];
827 }
828 }
829 }
830 }
831
832 function normalize () {
833 // if atom populate rss fields and normalize 0.3 and 1.0 feeds
834 if ( $this->is_atom() ) {
835 // Atom 1.0 elements <=> Atom 0.3 elements (Thanks, o brilliant wordsmiths of the Atom 1.0 standard!)
836 if ($this->feed_version < 1.0) {
837 $this->normalize_element($this->channel, 'tagline', $this->channel, 'subtitle');
838 $this->normalize_element($this->channel, 'copyright', $this->channel, 'rights');
839 $this->normalize_element($this->channel, 'modified', $this->channel, 'updated');
840 } else {
841 $this->normalize_element($this->channel, 'subtitle', $this->channel, 'tagline');
842 $this->normalize_element($this->channel, 'rights', $this->channel, 'copyright');
843 $this->normalize_element($this->channel, 'updated', $this->channel, 'modified');
844 }
845 $this->normalize_element($this->channel, 'author', $this->channel['dc'], 'creator', 'normalize_atom_person');
846 $this->normalize_element($this->channel, 'contributor', $this->channel['dc'], 'contributor', 'normalize_atom_person');
847
848 // Atom elements to RSS elements
849 $this->normalize_element($this->channel, 'subtitle', $this->channel, 'description');
850
851 if ( isset($this->channel['logo']) ) {
852 $this->normalize_element($this->channel, 'logo', $this->image, 'url');
853 $this->normalize_element($this->channel, 'link', $this->image, 'link');
854 $this->normalize_element($this->channel, 'title', $this->image, 'title');
855 }
856
857 for ( $i = 0; $i < count($this->items); $i++) {
858 $item = $this->items[$i];
859
860 // Atom 1.0 elements <=> Atom 0.3 elements
861 if ($this->feed_version < 1.0) {
862 $this->normalize_element($item, 'modified', $item, 'updated');
863 $this->normalize_element($item, 'issued', $item, 'published');
864 } else {
865 $this->normalize_element($item, 'updated', $item, 'modified');
866 $this->normalize_element($item, 'published', $item, 'issued');
867 }
868
869 // "If an atom:entry element does not contain
870 // atom:author elements, then the atom:author elements
871 // of the contained atom:source element are considered
872 // to apply. In an Atom Feed Document, the atom:author
873 // elements of the containing atom:feed element are
874 // considered to apply to the entry if there are no
875 // atom:author elements in the locations described
876 // above." <http://atompub.org/2005/08/17/draft-ietf-atompub-format-11.html#rfc.section.4.2.1>
877 if (!isset($item["author#"])) {
878 if (isset($item["source_author#"])) { // from aggregation source
879 $source = $item;
880 $author = "source_author";
881 } elseif (isset($this->channel["author#"])) { // from containing feed
882 $source = $this->channel;
883 $author = "author";
884 } else {
885 $author = null;
886 }
887
888 if (!is_null($author)) {
889 $item["author#"] = $source["{$author}#"];
890 for ($au = 1; $au <= $item["author#"]; $au++) {
891 $id_to = $this->element_id('author', $au);
892 $id_from = $this->element_id($author, $au);
893
894 $item[$id_to] = $source[$id_from];
895 foreach (array('name', 'email', 'uri', 'url') as $what) {
896 if (isset($source["{$id_from}_{$what}"])) {
897 $item["{$id_to}_{$what}"] = $source["{$id_from}_{$what}"];
898 }
899 }
900 }
901 }
902 }
903
904 // Atom elements to RSS elements
905 $this->normalize_element($item, 'author', $item['dc'], 'creator', 'normalize_atom_person');
906 $this->normalize_element($item, 'contributor', $item['dc'], 'contributor', 'normalize_atom_person');
907 $this->normalize_element($item, 'summary', $item, 'description');
908 $this->normalize_element($item, 'atom_content', $item['content'], 'encoded');
909 $this->normalize_element($item, 'link_enclosure', $item, 'enclosure', 'normalize_enclosure');
910
911 // Categories
912 if ( isset($item['category#']) ) { // Atom 1.0 categories to dc:subject and RSS 2.0 categories
913 $this->normalize_element($item, 'category', $item['dc'], 'subject', 'normalize_category');
914 }
915 elseif ( isset($item['dc']['subject#']) ) { // dc:subject to Atom 1.0 and RSS 2.0 categories
916 $this->normalize_element($item['dc'], 'subject', $item, 'category', 'normalize_dc_subject');
917 }
918
919 // Normalized item timestamp
920 $atom_date = (isset($item['published']) ) ? $item['published'] : $item['updated'];
921 if ( $atom_date ) {
922 $epoch = @parse_w3cdtf($atom_date);
923 if ($epoch and $epoch > 0) {
924 $item['date_timestamp'] = $epoch;
925 }
926 }
927
928 $this->items[$i] = $item;
929 }
930 }
931 elseif ( $this->is_rss() ) {
932 // RSS elements to Atom elements
933 $this->normalize_element($this->channel, 'description', $this->channel, 'tagline'); // Atom 0.3
934 $this->normalize_element($this->channel, 'description', $this->channel, 'subtitle'); // Atom 1.0 (yay wordsmithing!)
935 $this->normalize_element($this->image, 'url', $this->channel, 'logo');
936
937 for ( $i = 0; $i < count($this->items); $i++) {
938 $item = $this->items[$i];
939
940 // RSS elements to Atom elements
941 $this->normalize_element($item, 'description', $item, 'summary');
942 $this->normalize_element($item, 'enclosure', $item, 'link_enclosure', 'normalize_enclosure');
943
944 // Categories
945 if ( isset($item['category#']) ) { // RSS 2.0 categories to dc:subject and Atom 1.0 categories
946 $this->normalize_element($item, 'category', $item['dc'], 'subject', 'normalize_category');
947 }
948 elseif ( isset($item['dc']['subject#']) ) { // dc:subject to Atom 1.0 and RSS 2.0 categories
949 $this->normalize_element($item['dc'], 'subject', $item, 'category', 'normalize_dc_subject');
950 }
951
952 // Normalized item timestamp
953 if ( $this->is_rss() == '1.0' and isset($item['dc']['date']) ) {
954 $epoch = @parse_w3cdtf($item['dc']['date']);
955 if ($epoch and $epoch > 0) {
956 $item['date_timestamp'] = $epoch;
957 }
958 }
959 elseif ( isset($item['pubdate']) ) {
960 $epoch = @strtotime($item['pubdate']);
961 if ($epoch > 0) {
962 $item['date_timestamp'] = $epoch;
963 }
964 }
965
966 $this->items[$i] = $item;
967 }
968 }
969 }
970
971
972 function is_rss () {
973 if ( $this->feed_type == RSS ) {
974 return $this->feed_version;
975 }
976 else {
977 return false;
978 }
979 }
980
981 function is_atom() {
982 if ( $this->feed_type == ATOM ) {
983 return $this->feed_version;
984 }
985 else {
986 return false;
987 }
988 }
989
990 /**
991 * return XML parser, and possibly re-encoded source
992 *
993 */
994 function create_parser($source, $out_enc, $in_enc, $detect) {
995 if ( substr(phpversion(),0,1) == 5) {
996 $parser = $this->php5_create_parser($in_enc, $detect);
997 }
998 else {
999 list($parser, $source) = $this->php4_create_parser($source, $in_enc, $detect);
1000 }
1001 if ($out_enc) {
1002 $this->encoding = $out_enc;
1003 xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $out_enc);
1004 }
1005 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
1006 return array($parser, $source);
1007 }
1008
1009 /**
1010 * Instantiate an XML parser under PHP5
1011 *
1012 * PHP5 will do a fine job of detecting input encoding
1013 * if passed an empty string as the encoding.
1014 *
1015 * All hail libxml2!
1016 *
1017 */
1018 function php5_create_parser($in_enc, $detect) {
1019 // by default php5 does a fine job of detecting input encodings
1020 if(!$detect && $in_enc) {
1021 return xml_parser_create($in_enc);
1022 }
1023 else {
1024 return xml_parser_create('');
1025 }
1026 }
1027
1028 /**
1029 * Instaniate an XML parser under PHP4
1030 *
1031 * Unfortunately PHP4's support for character encodings
1032 * and especially XML and character encodings sucks. As
1033 * long as the documents you parse only contain characters
1034 * from the ISO-8859-1 character set (a superset of ASCII,
1035 * and a subset of UTF-8) you're fine. However once you
1036 * step out of that comfy little world things get mad, bad,
1037 * and dangerous to know.
1038 *
1039 * The following code is based on SJM's work with FoF
1040 * @see http://minutillo.com/steve/weblog/2004/6/17/php-xml-and-character-encodings-a-tale-of-sadness-rage-and-data-loss
1041 *
1042 */
1043 function php4_create_parser($source, $in_enc, $detect) {
1044 if ( !$detect ) {
1045 return array(xml_parser_create($in_enc), $source);
1046 }
1047
1048 if (!$in_enc) {
1049 if (preg_match('/<?xml.*encoding=[\'"](.*?)[\'"].*?>/m', $source, $m)) {
1050 $in_enc = strtoupper($m[1]);
1051 $this->source_encoding = $in_enc;
1052 }
1053 else {
1054 $in_enc = 'UTF-8';
1055 }
1056 }
1057
1058 if ($this->known_encoding($in_enc)) {
1059 return array(xml_parser_create($in_enc), $source);
1060 }
1061
1062 // the dectected encoding is not one of the simple encodings PHP knows
1063
1064 // attempt to use the iconv extension to
1065 // cast the XML to a known encoding
1066 // @see http://php.net/iconv
1067
1068 if (function_exists('iconv')) {
1069 $encoded_source = iconv($in_enc,'UTF-8', $source);
1070 if ($encoded_source) {
1071 return array(xml_parser_create('UTF-8'), $encoded_source);
1072 }
1073 }
1074
1075 // iconv didn't work, try mb_convert_encoding
1076 // @see http://php.net/mbstring
1077 if(function_exists('mb_convert_encoding')) {
1078 $encoded_source = mb_convert_encoding($source, 'UTF-8', $in_enc );
1079 if ($encoded_source) {
1080 return array(xml_parser_create('UTF-8'), $encoded_source);
1081 }
1082 }
1083
1084 // else
1085 $this->error("Feed is in an unsupported character encoding. ($in_enc) " .
1086 "You may see strange artifacts, and mangled characters.",
1087 E_USER_NOTICE);
1088
1089 return array(xml_parser_create(), $source);
1090 }
1091
1092 function known_encoding($enc) {
1093 $enc = strtoupper($enc);
1094 if ( in_array($enc, $this->_KNOWN_ENCODINGS) ) {
1095 return $enc;
1096 }
1097 else {
1098 return false;
1099 }
1100 }
1101
1102 function error ($errormsg, $lvl=E_USER_WARNING) {
1103 // append PHP's error message if track_errors enabled
1104 if ( isset($php_errormsg) ) {
1105 $errormsg .= " ($php_errormsg)";
1106 }
1107 if ( MAGPIE_DEBUG ) {
1108 trigger_error( $errormsg, $lvl);
1109 }
1110 else {
1111 error_log( $errormsg, 0);
1112 }
1113
1114 $notices = E_USER_NOTICE|E_NOTICE;
1115 if ( $lvl&$notices ) {
1116 $this->WARNING = $errormsg;
1117 } else {
1118 $this->ERROR = $errormsg;
1119 }
1120 }
1121
1122 // magic ID function for multiple elemenets.
1123 // can be called as static MagpieRSS::element_id()
1124 function element_id ($el, $counter) {
1125 return $el . (($counter > 1) ? '#'.$counter : '');
1126 }
1127
1128 function map_attrs($k, $v) {
1129 return $k.'="'.htmlspecialchars($v, ENT_COMPAT, $this->encoding).'"';
1130 }
1131
1132 function accepts_namespaced_xml ($attrs) {
1133 $mode = (isset($attrs['mode']) ? trim(strtolower($attrs['mode'])) : 'xml');
1134 $type = (isset($attrs['type']) ? trim(strtolower($attrs['type'])) : null);
1135 if ($this->feed_type == ATOM and $this->feed_version < 1.0) {
1136 if ($mode=='xml' and preg_match(':[/+](html|xml)$:i', $type)) {
1137 $ret = true;
1138 } else {
1139 $ret = false;
1140 }
1141 } elseif ($this->feed_type == ATOM and $this->feed_version >= 1.0) {
1142 if ($type=='xhtml' or preg_match(':[/+]xml$:i', $type)) {
1143 $ret = true;
1144 } else {
1145 $ret = false;
1146 }
1147 } else {
1148 $ret = false; // Don't munge unless you're sure
1149 }
1150 return $ret;
1151 }
1152 } // end class RSS
1153
1154
1155 // patch to support medieval versions of PHP4.1.x,
1156 // courtesy, Ryan Currie, ryan@digibliss.com
1157
1158 if (!function_exists('array_change_key_case')) {
1159 define("CASE_UPPER",1);
1160 define("CASE_LOWER",0);
1161
1162
1163 function array_change_key_case($array,$case=CASE_LOWER) {
1164 if ($case==CASE_LOWER) $cmd='strtolower';
1165 elseif ($case==CASE_UPPER) $cmd='strtoupper';
1166 foreach($array as $key=>$value) {
1167 $output[$cmd($key)]=$value;
1168 }
1169 return $output;
1170 }
1171
1172 }
1173
1174 ################################################################################
1175 ## WordPress: Load in Snoopy from wp-includes ##################################
1176 ################################################################################
1177
1178 require_once( dirname(__FILE__) . '/class-snoopy.php');
1179
1180 ################################################################################
1181 ## rss_fetch.inc: from MagpieRSS 0.8a ##########################################
1182 ################################################################################
1183
1184 /*=======================================================================*\
1185 Function: fetch_rss:
1186 Purpose: return RSS object for the give url
1187 maintain the cache
1188 Input: url of RSS file
1189 Output: parsed RSS object (see rss_parse.inc)
1190
1191 NOTES ON CACHEING:
1192 If caching is on (MAGPIE_CACHE_ON) fetch_rss will first check the cache.
1193
1194 NOTES ON RETRIEVING REMOTE FILES:
1195 If conditional gets are on (MAGPIE_CONDITIONAL_GET_ON) fetch_rss will
1196 return a cached object, and touch the cache object upon recieving a
1197 304.
1198
1199 NOTES ON FAILED REQUESTS:
1200 If there is an HTTP error while fetching an RSS object, the cached
1201 version will be return, if it exists (and if MAGPIE_CACHE_FRESH_ONLY is off)
1202 \*=======================================================================*/
1203
1204 define('MAGPIE_VERSION', '0.85');
1205
1206 $MAGPIE_ERROR = "";
1207
1208 function fetch_rss ($url) {
1209 // initialize constants
1210 init();
1211
1212 if ( !isset($url) ) {
1213 error("fetch_rss called without a url");
1214 return false;
1215 }
1216
1217 // if cache is disabled
1218 if ( !MAGPIE_CACHE_ON ) {
1219 // fetch file, and parse it
1220 $resp = _fetch_remote_file( $url );
1221 if ( is_success( $resp->status ) ) {
1222 return _response_to_rss( $resp, $url );
1223 }
1224 else {
1225 error("Failed to fetch $url and cache is off");
1226 return false;
1227 }
1228 }
1229 // else cache is ON
1230 else {
1231 // Flow
1232 // 1. check cache
1233 // 2. if there is a hit, make sure its fresh
1234 // 3. if cached obj fails freshness check, fetch remote
1235 // 4. if remote fails, return stale object, or error
1236
1237 $cache = new RSSCache( MAGPIE_CACHE_DIR, MAGPIE_CACHE_AGE );
1238
1239 if (MAGPIE_DEBUG and $cache->ERROR) {
1240 debug($cache->ERROR, E_USER_WARNING);
1241 }
1242
1243
1244 $cache_status = 0; // response of check_cache
1245 $request_headers = array(); // HTTP headers to send with fetch
1246 $rss = 0; // parsed RSS object
1247 $errormsg = 0; // errors, if any
1248
1249 // store parsed XML by desired output encoding
1250 // as character munging happens at parse time
1251 $cache_key = $url . MAGPIE_OUTPUT_ENCODING;
1252
1253 if (!$cache->ERROR) {
1254 // return cache HIT, MISS, or STALE
1255 $cache_status = $cache->check_cache( $cache_key);
1256 }
1257
1258 // if object cached, and cache is fresh, return cached obj
1259 if ( $cache_status == 'HIT' ) {
1260 $rss = $cache->get( $cache_key );
1261 if ( isset($rss) and $rss ) {
1262 // should be cache age
1263 $rss->from_cache = 1;
1264 if ( MAGPIE_DEBUG > 1) {
1265 debug("MagpieRSS: Cache HIT", E_USER_NOTICE);
1266 }
1267 return $rss;
1268 }
1269 }
1270
1271 // else attempt a conditional get
1272
1273 // setup headers
1274 if ( $cache_status == 'STALE' ) {
1275 $rss = $cache->get( $cache_key );
1276 if ( $rss and $rss->etag and $rss->last_modified ) {
1277 $request_headers['If-None-Match'] = $rss->etag;
1278 $request_headers['If-Last-Modified'] = $rss->last_modified;
1279 }
1280 }
1281
1282 $resp = _fetch_remote_file( $url, $request_headers );
1283
1284 if (isset($resp) and $resp) {
1285 if ($resp->status == '304' ) {
1286 // we have the most current copy
1287 if ( MAGPIE_DEBUG > 1) {
1288 debug("Got 304 for $url");
1289 }
1290 // reset cache on 304 (at minutillo insistent prodding)
1291 $cache->set($cache_key, $rss);
1292 return $rss;
1293 }
1294 elseif ( is_success( $resp->status ) ) {
1295 $rss = _response_to_rss( $resp, $url );
1296 if ( $rss ) {
1297 if (MAGPIE_DEBUG > 1) {
1298 debug("Fetch successful");
1299 }
1300 // add object to cache
1301 $cache->set( $cache_key, $rss );
1302 return $rss;
1303 }
1304 }
1305 else {
1306 $errormsg = "Failed to fetch $url ";
1307 if ( $resp->status == '-100' ) {
1308 $errormsg .= "(Request timed out after " . MAGPIE_FETCH_TIME_OUT . " seconds)";
1309 }
1310 elseif ( $resp->error ) {
1311 # compensate for Snoopy's annoying habbit to tacking
1312 # on '\n'
1313 $http_error = substr($resp->error, 0, -2);
1314 $errormsg .= "(HTTP Error: $http_error)";
1315 }
1316 else {
1317 $errormsg .= "(HTTP Response: " . $resp->response_code .')';
1318 }
1319 }
1320 }
1321 else {
1322 $errormsg = "Unable to retrieve RSS file for unknown reasons.";
1323 }
1324
1325 // else fetch failed
1326
1327 // attempt to return cached object
1328 if ($rss) {
1329 if ( MAGPIE_DEBUG ) {
1330 debug("Returning STALE object for $url");
1331 }
1332 return $rss;
1333 }
1334
1335 // else we totally failed
1336 error( $errormsg );
1337
1338 return false;
1339
1340 } // end if ( !MAGPIE_CACHE_ON ) {
1341 } // end fetch_rss()
1342
1343 /*=======================================================================*\
1344 Function: error
1345 Purpose: set MAGPIE_ERROR, and trigger error
1346 \*=======================================================================*/
1347
1348 function error ($errormsg, $lvl=E_USER_WARNING) {
1349 global $MAGPIE_ERROR;
1350
1351 // append PHP's error message if track_errors enabled
1352 if ( isset($php_errormsg) ) {
1353 $errormsg .= " ($php_errormsg)";
1354 }
1355 if ( $errormsg ) {
1356 $errormsg = "MagpieRSS: $errormsg";
1357 $MAGPIE_ERROR = $errormsg;
1358 trigger_error( $errormsg, $lvl);
1359 }
1360 }
1361
1362 function debug ($debugmsg, $lvl=E_USER_NOTICE) {
1363 trigger_error("MagpieRSS [debug] $debugmsg", $lvl);
1364 }
1365
1366 /*=======================================================================*\
1367 Function: magpie_error
1368 Purpose: accessor for the magpie error variable
1369 \*=======================================================================*/
1370 function magpie_error ($errormsg="") {
1371 global $MAGPIE_ERROR;
1372
1373 if ( isset($errormsg) and $errormsg ) {
1374 $MAGPIE_ERROR = $errormsg;
1375 }
1376
1377 return $MAGPIE_ERROR;
1378 }
1379
1380 /*=======================================================================*\
1381 Function: _fetch_remote_file
1382 Purpose: retrieve an arbitrary remote file
1383 Input: url of the remote file
1384 headers to send along with the request (optional)
1385 Output: an HTTP response object (see Snoopy.class.inc)
1386 \*=======================================================================*/
1387 function _fetch_remote_file ($url, $headers = "" ) {
1388 // Snoopy is an HTTP client in PHP
1389 $client = new Snoopy();
1390 $client->agent = MAGPIE_USER_AGENT;
1391 $client->read_timeout = MAGPIE_FETCH_TIME_OUT;
1392 $client->use_gzip = MAGPIE_USE_GZIP;
1393 if (is_array($headers) ) {
1394 $client->rawheaders = $headers;
1395 }
1396
1397 @$client->fetch($url);
1398 return $client;
1399
1400 }
1401
1402 /*=======================================================================*\
1403 Function: _response_to_rss
1404 Purpose: parse an HTTP response object into an RSS object
1405 Input: an HTTP response object (see Snoopy)
1406 Output: parsed RSS object (see rss_parse)
1407 \*=======================================================================*/
1408 function _response_to_rss ($resp, $url = null) {
1409 $rss = new MagpieRSS( $resp->results, MAGPIE_OUTPUT_ENCODING, MAGPIE_INPUT_ENCODING, MAGPIE_DETECT_ENCODING, $url );
1410
1411 // if RSS parsed successfully
1412 if ( $rss and !$rss->ERROR) {
1413 $rss->http_status = $resp->status;
1414
1415 // find Etag, and Last-Modified
1416 foreach($resp->headers as $h) {
1417 // 2003-03-02 - Nicola Asuni (www.tecnick.com) - fixed bug "Undefined offset: 1"
1418 if (strpos($h, ": ")) {
1419 list($field, $val) = explode(": ", $h, 2);
1420 }
1421 else {
1422 $field = $h;
1423 $val = "";
1424 }
1425
1426 $rss->header[$field] = $val;
1427
1428 if ( $field == 'ETag' ) {
1429 $rss->etag = $val;
1430 }
1431
1432 if ( $field == 'Last-Modified' ) {
1433 $rss->last_modified = $val;
1434 }
1435 }
1436
1437 return $rss;
1438 } // else construct error message
1439 else {
1440 $errormsg = "Failed to parse RSS file.";
1441
1442 if ($rss) {
1443 $errormsg .= " (" . $rss->ERROR . ")";
1444 }
1445 error($errormsg);
1446
1447 return false;
1448 } // end if ($rss and !$rss->error)
1449 }
1450
1451 /*=======================================================================*\
1452 Function: init
1453 Purpose: setup constants with default values
1454 check for user overrides
1455 \*=======================================================================*/
1456 function init () {
1457 if ( defined('MAGPIE_INITALIZED') ) {
1458 return;
1459 }
1460 else {
1461 define('MAGPIE_INITALIZED', true);
1462 }
1463
1464 if ( !defined('MAGPIE_CACHE_ON') ) {
1465 define('MAGPIE_CACHE_ON', true);
1466 }
1467
1468 if ( !defined('MAGPIE_CACHE_DIR') ) {
1469 define('MAGPIE_CACHE_DIR', './cache');
1470 }
1471
1472 if ( !defined('MAGPIE_CACHE_AGE') ) {
1473 define('MAGPIE_CACHE_AGE', 60*60); // one hour
1474 }
1475
1476 if ( !defined('MAGPIE_CACHE_FRESH_ONLY') ) {
1477 define('MAGPIE_CACHE_FRESH_ONLY', false);
1478 }
1479
1480 if ( !defined('MAGPIE_OUTPUT_ENCODING') ) {
1481 define('MAGPIE_OUTPUT_ENCODING', 'ISO-8859-1');
1482 }
1483
1484 if ( !defined('MAGPIE_INPUT_ENCODING') ) {
1485 define('MAGPIE_INPUT_ENCODING', null);
1486 }
1487
1488 if ( !defined('MAGPIE_DETECT_ENCODING') ) {
1489 define('MAGPIE_DETECT_ENCODING', true);
1490 }
1491
1492 if ( !defined('MAGPIE_DEBUG') ) {
1493 define('MAGPIE_DEBUG', 0);
1494 }
1495
1496 if ( !defined('MAGPIE_USER_AGENT') ) {
1497 $ua = 'MagpieRSS/'. MAGPIE_VERSION . ' (+http://magpierss.sf.net';
1498
1499 if ( MAGPIE_CACHE_ON ) {
1500 $ua = $ua . ')';
1501 }
1502 else {
1503 $ua = $ua . '; No cache)';
1504 }
1505
1506 define('MAGPIE_USER_AGENT', $ua);
1507 }
1508
1509 if ( !defined('MAGPIE_FETCH_TIME_OUT') ) {
1510 define('MAGPIE_FETCH_TIME_OUT', 5); // 5 second timeout
1511 }
1512
1513 // use gzip encoding to fetch rss files if supported?
1514 if ( !defined('MAGPIE_USE_GZIP') ) {
1515 define('MAGPIE_USE_GZIP', true);
1516 }
1517 }
1518
1519 // NOTE: the following code should really be in Snoopy, or at least
1520 // somewhere other then rss_fetch!
1521
1522 /*=======================================================================*\
1523 HTTP STATUS CODE PREDICATES
1524 These functions attempt to classify an HTTP status code
1525 based on RFC 2616 and RFC 2518.
1526
1527 All of them take an HTTP status code as input, and return true or false
1528
1529 All this code is adapted from LWP's HTTP::Status.
1530 \*=======================================================================*/
1531
1532
1533 /*=======================================================================*\
1534 Function: is_info
1535 Purpose: return true if Informational status code
1536 \*=======================================================================*/
1537 function is_info ($sc) {
1538 return $sc >= 100 && $sc < 200;
1539 }
1540
1541 /*=======================================================================*\
1542 Function: is_success
1543 Purpose: return true if Successful status code
1544 \*=======================================================================*/
1545 function is_success ($sc) {
1546 return $sc >= 200 && $sc < 300;
1547 }
1548
1549 /*=======================================================================*\
1550 Function: is_redirect
1551 Purpose: return true if Redirection status code
1552 \*=======================================================================*/
1553 function is_redirect ($sc) {
1554 return $sc >= 300 && $sc < 400;
1555 }
1556
1557 /*=======================================================================*\
1558 Function: is_error
1559 Purpose: return true if Error status code
1560 \*=======================================================================*/
1561 function is_error ($sc) {
1562 return $sc >= 400 && $sc < 600;
1563 }
1564
1565 /*=======================================================================*\
1566 Function: is_client_error
1567 Purpose: return true if Error status code, and its a client error
1568 \*=======================================================================*/
1569 function is_client_error ($sc) {
1570 return $sc >= 400 && $sc < 500;
1571 }
1572
1573 /*=======================================================================*\
1574 Function: is_client_error
1575 Purpose: return true if Error status code, and its a server error
1576 \*=======================================================================*/
1577 function is_server_error ($sc) {
1578 return $sc >= 500 && $sc < 600;
1579 }
1580
1581 ################################################################################
1582 ## rss_cache.inc: from WordPress 1.5 ###########################################
1583 ################################################################################
1584
1585 class RSSCache {
1586 var $BASE_CACHE = 'wp-content/cache'; // where the cache files are stored
1587 var $MAX_AGE = 43200; // when are files stale, default twelve hours
1588 var $ERROR = ''; // accumulate error messages
1589
1590 function RSSCache ($base='', $age='') {
1591 if ( $base ) {
1592 $this->BASE_CACHE = $base;
1593 }
1594 if ( $age ) {
1595 $this->MAX_AGE = $age;
1596 }
1597
1598 }
1599
1600 /*=======================================================================*\
1601 Function: set
1602 Purpose: add an item to the cache, keyed on url
1603 Input: url from wich the rss file was fetched
1604 Output: true on sucess
1605 \*=======================================================================*/
1606 function set ($url, $rss) {
1607 global $wpdb;
1608 $cache_option = 'rss_' . $this->file_name( $url );
1609 $cache_timestamp = 'rss_' . $this->file_name( $url ) . '_ts';
1610
1611 if ( !$wpdb->get_var("SELECT option_name FROM $wpdb->options WHERE option_name = '$cache_option'") )
1612 add_option($cache_option, '', '', 'no');
1613 if ( !$wpdb->get_var("SELECT option_name FROM $wpdb->options WHERE option_name = '$cache_timestamp'") )
1614 add_option($cache_timestamp, '', '', 'no');
1615
1616 update_option($cache_option, $rss);
1617 update_option($cache_timestamp, time() );
1618
1619 return $cache_option;
1620 }
1621
1622 /*=======================================================================*\
1623 Function: get
1624 Purpose: fetch an item from the cache
1625 Input: url from wich the rss file was fetched
1626 Output: cached object on HIT, false on MISS
1627 \*=======================================================================*/
1628 function get ($url) {
1629 $this->ERROR = "";
1630 $cache_option = 'rss_' . $this->file_name( $url );
1631
1632 if ( ! get_option( $cache_option ) ) {
1633 $this->debug(
1634 "Cache doesn't contain: $url (cache option: $cache_option)"
1635 );
1636 return 0;
1637 }
1638
1639 $rss = get_option( $cache_option );
1640
1641 return $rss;
1642 }
1643
1644 /*=======================================================================*\
1645 Function: check_cache
1646 Purpose: check a url for membership in the cache
1647 and whether the object is older then MAX_AGE (ie. STALE)
1648 Input: url from wich the rss file was fetched
1649 Output: cached object on HIT, false on MISS
1650 \*=======================================================================*/
1651 function check_cache ( $url ) {
1652 $this->ERROR = "";
1653 $cache_option = $this->file_name( $url );
1654 $cache_timestamp = 'rss_' . $this->file_name( $url ) . '_ts';
1655
1656 if ( $mtime = get_option($cache_timestamp) ) {
1657 // find how long ago the file was added to the cache
1658 // and whether that is longer then MAX_AGE
1659 $age = time() - $mtime;
1660 if ( $this->MAX_AGE > $age ) {
1661 // object exists and is current
1662 return 'HIT';
1663 }
1664 else {
1665 // object exists but is old
1666 return 'STALE';
1667 }
1668 }
1669 else {
1670 // object does not exist
1671 return 'MISS';
1672 }
1673 }
1674
1675 /*=======================================================================*\
1676 Function: serialize
1677 \*=======================================================================*/
1678 function serialize ( $rss ) {
1679 return serialize( $rss );
1680 }
1681
1682 /*=======================================================================*\
1683 Function: unserialize
1684 \*=======================================================================*/
1685 function unserialize ( $data ) {
1686 return unserialize( $data );
1687 }
1688
1689 /*=======================================================================*\
1690 Function: file_name
1691 Purpose: map url to location in cache
1692 Input: url from wich the rss file was fetched
1693 Output: a file name
1694 \*=======================================================================*/
1695 function file_name ($url) {
1696 return md5( $url );
1697 }
1698
1699 /*=======================================================================*\
1700 Function: error
1701 Purpose: register error
1702 \*=======================================================================*/
1703 function error ($errormsg, $lvl=E_USER_WARNING) {
1704 // append PHP's error message if track_errors enabled
1705 if ( isset($php_errormsg) ) {
1706 $errormsg .= " ($php_errormsg)";
1707 }
1708 $this->ERROR = $errormsg;
1709 if ( MAGPIE_DEBUG ) {
1710 trigger_error( $errormsg, $lvl);
1711 }
1712 else {
1713 error_log( $errormsg, 0);
1714 }
1715 }
1716 function debug ($debugmsg, $lvl=E_USER_NOTICE) {
1717 if ( MAGPIE_DEBUG ) {
1718 $this->error("MagpieRSS [debug] $debugmsg", $lvl);
1719 }
1720 }
1721 }
1722
1723 ################################################################################
1724 ## rss_utils.inc: from MagpieRSS 0.8a ##########################################
1725 ################################################################################
1726
1727 /*======================================================================*\
1728 Function: parse_w3cdtf
1729 Purpose: parse a W3CDTF date into unix epoch
1730
1731 NOTE: http://www.w3.org/TR/NOTE-datetime
1732 \*======================================================================*/
1733
1734 function parse_w3cdtf ( $date_str ) {
1735
1736 # regex to match wc3dtf
1737 $pat = "/^\s*(\d{4})(-(\d{2})(-(\d{2})(T(\d{2}):(\d{2})(:(\d{2})(\.\d+)?)?(?:([-+])(\d{2}):?(\d{2})|(Z))?)?)?)?\s*\$/";
1738
1739 if ( preg_match( $pat, $date_str, $match ) ) {
1740 list( $year, $month, $day, $hours, $minutes, $seconds) =
1741 array( $match[1], $match[3], $match[5], $match[7], $match[8], $match[10]);
1742
1743 # W3C dates can omit the time, the day of the month, or even the month.
1744 # Fill in any blanks using information from the present moment. --CWJ
1745 $default['hr'] = (int) gmdate('H');
1746 $default['day'] = (int) gmdate('d');
1747 $default['month'] = (int) gmdate('m');
1748
1749 if (is_null($hours)) : $hours = $default['hr']; $minutes = 0; $seconds = 0; endif;
1750 if (is_null($day)) : $day = $default['day']; endif;
1751 if (is_null($month)) : $month = $default['month']; endif;
1752
1753 # calc epoch for current date assuming GMT
1754 $epoch = gmmktime( $hours, $minutes, $seconds, $month, $day, $year);
1755
1756 $offset = 0;
1757 if ( $match[14] == 'Z' ) {
1758 # zulu time, aka GMT
1759 }
1760 else {
1761 list( $tz_mod, $tz_hour, $tz_min ) =
1762 array( $match[12], $match[13], $match[14]);
1763
1764 # zero out the variables
1765 if ( ! $tz_hour ) { $tz_hour = 0; }
1766 if ( ! $tz_min ) { $tz_min = 0; }
1767
1768 $offset_secs = (($tz_hour*60)+$tz_min)*60;
1769
1770 # is timezone ahead of GMT? then subtract offset
1771 #
1772 if ( $tz_mod == '+' ) {
1773 $offset_secs = $offset_secs * -1;
1774 }
1775
1776 $offset = $offset_secs;
1777 }
1778 $epoch = $epoch + $offset;
1779 return $epoch;
1780 }
1781 else {
1782 return -1;
1783 }
1784 }
1785
1786 # Relative URI static class: PHP class for resolving relative URLs
1787 #
1788 # This class is derived (under the terms of the GPL) from URL Class 0.3 by
1789 # Keyvan Minoukadeh <keyvan@k1m.com>, which is great but more than we need
1790 # for MagpieRSS's purposes. The class has been stripped down to a single
1791 # public method: Relative_URI::resolve($url, $base), which resolves the URI in
1792 # $url relative to the URI in $base
1793 #
1794 # FeedWordPress also uses this class. So if we have it loaded in, don't load it
1795 # again.
1796 #
1797 # -- Charles Johnson <technophilia@radgeek.com>
1798 if (!class_exists('Relative_URI')) {
1799 class Relative_URI
1800 {
1801 // Resolve relative URI in $url against the base URI in $base. If $base
1802 // is not supplied, then we use the REQUEST_URI of this script.
1803 //
1804 // I'm hoping this method reflects RFC 2396 Section 5.2
1805 function resolve ($url, $base = NULL)
1806 {
1807 if (is_null($base)):
1808 $base = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
1809 endif;
1810
1811 $base = Relative_URI::_encode(trim($base));
1812 $uri_parts = Relative_URI::_parse_url($base);
1813
1814 $url = Relative_URI::_encode(trim($url));
1815 $parts = Relative_URI::_parse_url($url);
1816
1817 $uri_parts['fragment'] = (isset($parts['fragment']) ? $parts['fragment'] : null);
1818 $uri_parts['query'] = (isset($parts['query']) ? $parts['query'] : null);
1819
1820 // if path is empty, and scheme, host, and query are undefined,
1821 // the URL is referring the base URL
1822
1823 if (($parts['path'] == '') && !isset($parts['scheme']) && !isset($parts['host']) && !isset($parts['query'])) {
1824 // If the URI is empty or only a fragment, return the base URI
1825 return $base . (isset($parts['fragment']) ? '#'.$parts['fragment'] : '');
1826 } elseif (isset($parts['scheme'])) {
1827 // If the scheme is set, then the URI is absolute.
1828 return $url;
1829 } elseif (isset($parts['host'])) {
1830 $uri_parts['host'] = $parts['host'];
1831 $uri_parts['path'] = $parts['path'];
1832 } else {
1833 // We have a relative path but not a host.
1834
1835 // start ugly fix:
1836 // prepend slash to path if base host is set, base path is not set, and url path is not absolute
1837 if ($uri_parts['host'] && ($uri_parts['path'] == '')
1838 && (strlen($parts['path']) > 0)
1839 && (substr($parts['path'], 0, 1) != '/')) {
1840 $parts['path'] = '/'.$parts['path'];
1841 } // end ugly fix
1842
1843 if (substr($parts['path'], 0, 1) == '/') {
1844 $uri_parts['path'] = $parts['path'];
1845 } else {
1846 // copy base path excluding any characters after the last (right-most) slash character
1847 $buffer = substr($uri_parts['path'], 0, (int)strrpos($uri_parts['path'], '/')+1);
1848 // append relative path
1849 $buffer .= $parts['path'];
1850 // remove "./" where "." is a complete path segment.
1851 $buffer = str_replace('/./', '/', $buffer);
1852 if (substr($buffer, 0, 2) == './') {
1853 $buffer = substr($buffer, 2);
1854 }
1855 // if buffer ends with "." as a complete path segment, remove it
1856 if (substr($buffer, -2) == '/.') {
1857 $buffer = substr($buffer, 0, -1);
1858 }
1859 // remove "<segment>/../" where <segment> is a complete path segment not equal to ".."
1860 $search_finished = false;
1861 $segment = explode('/', $buffer);
1862 while (!$search_finished) {
1863 for ($x=0; $x+1 < count($segment);) {
1864 if (($segment[$x] != '') && ($segment[$x] != '..') && ($segment[$x+1] == '..')) {
1865 if ($x+2 == count($segment)) $segment[] = '';
1866 unset($segment[$x], $segment[$x+1]);
1867 $segment = array_values($segment);
1868 continue 2;
1869 } else {
1870 $x++;
1871 }
1872 }
1873 $search_finished = true;
1874 }
1875 $buffer = (count($segment) == 1) ? '/' : implode('/', $segment);
1876 $uri_parts['path'] = $buffer;
1877
1878 }
1879 }
1880
1881 // If we've gotten to this point, we can try to put the pieces
1882 // back together.
1883 $ret = '';
1884 if (isset($uri_parts['scheme'])) $ret .= $uri_parts['scheme'].':';
1885 if (isset($uri_parts['user'])) {
1886 $ret .= $uri_parts['user'];
1887 if (isset($uri_parts['pass'])) $ret .= ':'.$uri_parts['parts'];
1888 $ret .= '@';
1889 }
1890 if (isset($uri_parts['host'])) {
1891 $ret .= '//'.$uri_parts['host'];
1892 if (isset($uri_parts['port'])) $ret .= ':'.$uri_parts['port'];
1893 }
1894 $ret .= $uri_parts['path'];
1895 if (isset($uri_parts['query'])) $ret .= '?'.$uri_parts['query'];
1896 if (isset($uri_parts['fragment'])) $ret .= '#'.$uri_parts['fragment'];
1897
1898 return $ret;
1899 }
1900
1901 /**
1902 * Parse URL
1903 *
1904 * Regular expression grabbed from RFC 2396 Appendix B.
1905 * This is a replacement for PHPs builtin parse_url().
1906 * @param string $url
1907 * @access private
1908 * @return array
1909 */
1910 function _parse_url($url)
1911 {
1912 // I'm using this pattern instead of parse_url() as there's a few strings where parse_url()
1913 // generates a warning.
1914 if (preg_match('!^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?!', $url, $match)) {
1915 $parts = array();
1916 if ($match[1] != '') $parts['scheme'] = $match[2];
1917 if ($match[3] != '') $parts['auth'] = $match[4];
1918 // parse auth
1919 if (isset($parts['auth'])) {
1920 // store user info
1921 if (($at_pos = strpos($parts['auth'], '@')) !== false) {
1922 $userinfo = explode(':', substr($parts['auth'], 0, $at_pos), 2);
1923 $parts['user'] = $userinfo[0];
1924 if (isset($userinfo[1])) $parts['pass'] = $userinfo[1];
1925 $parts['auth'] = substr($parts['auth'], $at_pos+1);
1926 }
1927 // get port number
1928 if ($port_pos = strrpos($parts['auth'], ':')) {
1929 $parts['host'] = substr($parts['auth'], 0, $port_pos);
1930 $parts['port'] = (int)substr($parts['auth'], $port_pos+1);
1931 if ($parts['port'] < 1) $parts['port'] = null;
1932 } else {
1933 $parts['host'] = $parts['auth'];
1934 }
1935 }
1936 unset($parts['auth']);
1937 $parts['path'] = $match[5];
1938 if (isset($match[6]) && ($match[6] != '')) $parts['query'] = $match[7];
1939 if (isset($match[8]) && ($match[8] != '')) $parts['fragment'] = $match[9];
1940 return $parts;
1941 }
1942 // shouldn't reach here
1943 return array('path'=>'');
1944 }
1945
1946 function _encode($string)
1947 {
1948 static $replace = array();
1949 if (!count($replace)) {
1950 $find = array(32, 34, 60, 62, 123, 124, 125, 91, 92, 93, 94, 96, 127);
1951 $find = array_merge(range(0, 31), $find);
1952 $find = array_map('chr', $find);
1953 foreach ($find as $char) {
1954 $replace[$char] = '%'.bin2hex($char);
1955 }
1956 }
1957 // escape control characters and a few other characters
1958 $encoded = strtr($string, $replace);
1959 // remove any character outside the hex range: 21 - 7E (see www.asciitable.com)
1960 return preg_replace('/[^\x21-\x7e]/', '', $encoded);
1961 }
1962 } // class Relative_URI
1963 }
1964
1965 ################################################################################
1966 ## WordPress: wp_rss(), get_rss() ##############################################
1967 ################################################################################
1968
1969 function wp_rss ($url, $num) {
1970 //ini_set("display_errors", false); uncomment to suppress php errors thrown if the feed is not returned.
1971 $num_items = $num;
1972 $rss = fetch_rss($url);
1973 if ( $rss ) {
1974 echo "<ul>";
1975 $rss->items = array_slice($rss->items, 0, $num_items);
1976 foreach ($rss->items as $item ) {
1977 echo "<li>\n";
1978 echo "<a href='$item[link]' title='$item[description]'>";
1979 echo htmlentities($item['title']);
1980 echo "</a><br />\n";
1981 echo "</li>\n";
1982 }
1983 echo "</ul>";
1984 }
1985 else {
1986 echo "an error has occured the feed is probably down, try again later.";
1987 }
1988 }
1989
1990 function get_rss ($uri, $num = 5) { // Like get posts, but for RSS
1991 $rss = fetch_rss($url);
1992 if ( $rss ) {
1993 $rss->items = array_slice($rss->items, 0, $num_items);
1994 foreach ($rss->items as $item ) {
1995 echo "<li>\n";
1996 echo "<a href='$item[link]' title='$item[description]'>";
1997 echo htmlentities($item['title']);
1998 echo "</a><br />\n";
1999 echo "</li>\n";
2000 }
2001 return $posts;
2002 } else {
2003 return false;
2004 }
2005 }
2006 ?>