| @@ -1,4 +1,1207 @@ | ||
| 1 | 1 | <?php |
| 2 | -// Deprecated. Use rss.php instead. | |
| 3 | -require_once (ABSPATH . WPINC . '/rss.php'); | |
| 4 | -?> | |
| 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.7wp | |
| 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 a modification of 0.7. In addition to improved handling of character | |
| 15 | + * encoding and other updates, this branch of MagpieRSS 0.7 also supports | |
| 16 | + * multiple categorization of posts (using <dc:subject> or <category>). The | |
| 17 | + * file is, therefore, derived from four sources: (1) Kellan's MagpieRSS 0.51, | |
| 18 | + * (2) the WordPress development team's modifications to MagpieRSS 0.51, | |
| 19 | + * (3) Kellan's MagpieRSS 0.7, and (4) Charles Johnson's modifications to | |
| 20 | + * MagpieRSS 0.7. All possible because of the GPL. Yay for free software! | |
| 21 | + * | |
| 22 | + * Differences from the main branch of MagpieRSS: | |
| 23 | + * | |
| 24 | + * 1. Everything in rss_parse.inc, rss_fetch.inc, rss_cache.inc, and | |
| 25 | + * rss_utils.inc is included in one file. | |
| 26 | + * | |
| 27 | + * 2. MagpieRSS returns the WordPress version as the user agent, rather than | |
| 28 | + * Magpie | |
| 29 | + * | |
| 30 | + * 3. class RSSCache is a modified version by WordPress developers, which | |
| 31 | + * caches feeds in the WordPress database (in the options table), rather | |
| 32 | + * than writing external files directly. | |
| 33 | + * | |
| 34 | + * 4. There are two WordPress-specific functions, get_rss() and wp_rss() | |
| 35 | + * | |
| 36 | + * 5. New cases added to MagpieRSS::feed_start_element(), | |
| 37 | + * MagpieRSS::feed_end_element(), and MagpieRSS::normalize() to handle | |
| 38 | + * multiple categories correctly. | |
| 39 | + */ | |
| 40 | + | |
| 41 | +define('RSS', 'RSS'); | |
| 42 | +define('ATOM', 'Atom'); | |
| 43 | +define('MAGPIE_USER_AGENT', 'WordPress/' . $wp_version); | |
| 44 | + | |
| 45 | +# UPDATED: rss_parse.inc: class MagpieRSS, function map_attrs | |
| 46 | +# --- cut here --- | |
| 47 | +/** | |
| 48 | +* Hybrid parser, and object, takes RSS as a string and returns a simple object. | |
| 49 | +* | |
| 50 | +* see: rss_fetch.inc for a simpler interface with integrated caching support | |
| 51 | +* | |
| 52 | +*/ | |
| 53 | +class MagpieRSS { | |
| 54 | + var $parser; | |
| 55 | + | |
| 56 | + var $current_item = array(); // item currently being parsed | |
| 57 | + var $items = array(); // collection of parsed items | |
| 58 | + var $channel = array(); // hash of channel fields | |
| 59 | + var $textinput = array(); | |
| 60 | + var $image = array(); | |
| 61 | + var $feed_type; | |
| 62 | + var $feed_version; | |
| 63 | + var $encoding = ''; // output encoding of parsed rss | |
| 64 | + | |
| 65 | + var $_source_encoding = ''; // only set if we have to parse xml prolog | |
| 66 | + | |
| 67 | + var $ERROR = ""; | |
| 68 | + var $WARNING = ""; | |
| 69 | + | |
| 70 | + // define some constants | |
| 71 | + | |
| 72 | + var $_CONTENT_CONSTRUCTS = array('content', 'summary', 'info', 'title', 'tagline', 'copyright'); | |
| 73 | + var $_KNOWN_ENCODINGS = array('UTF-8', 'US-ASCII', 'ISO-8859-1'); | |
| 74 | + | |
| 75 | + // parser variables, useless if you're not a parser, treat as private | |
| 76 | + var $stack = array(); // parser stack | |
| 77 | + var $inchannel = false; | |
| 78 | + var $initem = false; | |
| 79 | + var $incontent = false; // if in Atom <content mode="xml"> field | |
| 80 | + var $intextinput = false; | |
| 81 | + var $inimage = false; | |
| 82 | + var $current_namespace = false; | |
| 83 | + | |
| 84 | + var $incategory = false; | |
| 85 | + var $current_category = 0; | |
| 86 | + | |
| 87 | + /** | |
| 88 | + * Set up XML parser, parse source, and return populated RSS object.. | |
| 89 | + * | |
| 90 | + * @param string $source string containing the RSS to be parsed | |
| 91 | + * | |
| 92 | + * NOTE: Probably a good idea to leave the encoding options alone unless | |
| 93 | + * you know what you're doing as PHP's character set support is | |
| 94 | + * a little weird. | |
| 95 | + * | |
| 96 | + * NOTE: A lot of this is unnecessary but harmless with PHP5 | |
| 97 | + * | |
| 98 | + * | |
| 99 | + * @param string $output_encoding output the parsed RSS in this character | |
| 100 | + * set defaults to ISO-8859-1 as this is PHP's | |
| 101 | + * default. | |
| 102 | + * | |
| 103 | + * NOTE: might be changed to UTF-8 in future | |
| 104 | + * versions. | |
| 105 | + * | |
| 106 | + * @param string $input_encoding the character set of the incoming RSS source. | |
| 107 | + * Leave blank and Magpie will try to figure it | |
| 108 | + * out. | |
| 109 | + * | |
| 110 | + * | |
| 111 | + * @param bool $detect_encoding if false Magpie won't attempt to detect | |
| 112 | + * source encoding. (caveat emptor) | |
| 113 | + * | |
| 114 | + */ | |
| 115 | + function MagpieRSS ($source, $output_encoding='ISO-8859-1', | |
| 116 | + $input_encoding=null, $detect_encoding=true) | |
| 117 | + { | |
| 118 | + # if PHP xml isn't compiled in, die | |
| 119 | + # | |
| 120 | + if (!function_exists('xml_parser_create')) { | |
| 121 | + $this->error( "Failed to load PHP's XML Extension. " . | |
| 122 | + "http://www.php.net/manual/en/ref.xml.php", | |
| 123 | + E_USER_ERROR ); | |
| 124 | + } | |
| 125 | + | |
| 126 | + list($parser, $source) = $this->create_parser($source, | |
| 127 | + $output_encoding, $input_encoding, $detect_encoding); | |
| 128 | + | |
| 129 | + | |
| 130 | + if (!is_resource($parser)) { | |
| 131 | + $this->error( "Failed to create an instance of PHP's XML parser. " . | |
| 132 | + "http://www.php.net/manual/en/ref.xml.php", | |
| 133 | + E_USER_ERROR ); | |
| 134 | + } | |
| 135 | + | |
| 136 | + | |
| 137 | + $this->parser = $parser; | |
| 138 | + | |
| 139 | + # pass in parser, and a reference to this object | |
| 140 | + # setup handlers | |
| 141 | + # | |
| 142 | + xml_set_object( $this->parser, $this ); | |
| 143 | + xml_set_element_handler($this->parser, | |
| 144 | + 'feed_start_element', 'feed_end_element' ); | |
| 145 | + | |
| 146 | + xml_set_character_data_handler( $this->parser, 'feed_cdata' ); | |
| 147 | + | |
| 148 | + $status = xml_parse( $this->parser, $source ); | |
| 149 | + | |
| 150 | + if (! $status ) { | |
| 151 | + $errorcode = xml_get_error_code( $this->parser ); | |
| 152 | + if ( $errorcode != XML_ERROR_NONE ) { | |
| 153 | + $xml_error = xml_error_string( $errorcode ); | |
| 154 | + $error_line = xml_get_current_line_number($this->parser); | |
| 155 | + $error_col = xml_get_current_column_number($this->parser); | |
| 156 | + $errormsg = "$xml_error at line $error_line, column $error_col"; | |
| 157 | + | |
| 158 | + $this->error( $errormsg ); | |
| 159 | + } | |
| 160 | + } | |
| 161 | + | |
| 162 | + xml_parser_free( $this->parser ); | |
| 163 | + | |
| 164 | + $this->normalize(); | |
| 165 | + } | |
| 166 | + | |
| 167 | + function feed_start_element($p, $element, &$attrs) { | |
| 168 | + $el = $element = strtolower($element); | |
| 169 | + $attrs = array_change_key_case($attrs, CASE_LOWER); | |
| 170 | + | |
| 171 | + // check for a namespace, and split if found | |
| 172 | + $ns = false; | |
| 173 | + if ( strpos( $element, ':' ) ) { | |
| 174 | + list($ns, $el) = split( ':', $element, 2); | |
| 175 | + } | |
| 176 | + if ( $ns and $ns != 'rdf' ) { | |
| 177 | + $this->current_namespace = $ns; | |
| 178 | + } | |
| 179 | + | |
| 180 | + # if feed type isn't set, then this is first element of feed | |
| 181 | + # identify feed from root element | |
| 182 | + # | |
| 183 | + if (!isset($this->feed_type) ) { | |
| 184 | + if ( $el == 'rdf' ) { | |
| 185 | + $this->feed_type = RSS; | |
| 186 | + $this->feed_version = '1.0'; | |
| 187 | + } | |
| 188 | + elseif ( $el == 'rss' ) { | |
| 189 | + $this->feed_type = RSS; | |
| 190 | + $this->feed_version = $attrs['version']; | |
| 191 | + } | |
| 192 | + elseif ( $el == 'feed' ) { | |
| 193 | + $this->feed_type = ATOM; | |
| 194 | + $this->feed_version = $attrs['version']; | |
| 195 | + $this->inchannel = true; | |
| 196 | + } | |
| 197 | + return; | |
| 198 | + } | |
| 199 | + | |
| 200 | + if ( $el == 'channel' ) | |
| 201 | + { | |
| 202 | + $this->inchannel = true; | |
| 203 | + } | |
| 204 | + elseif ($el == 'item' or $el == 'entry' ) | |
| 205 | + { | |
| 206 | + $this->initem = true; | |
| 207 | + if ( isset($attrs['rdf:about']) ) { | |
| 208 | + $this->current_item['about'] = $attrs['rdf:about']; | |
| 209 | + } | |
| 210 | + } | |
| 211 | + | |
| 212 | + elseif ($this->initem and ($el == 'category' or ($this->current_namespace == 'dc' and $el == 'subject'))) { | |
| 213 | + $this->incategory = true; | |
| 214 | + array_unshift( $this->stack, $el ); | |
| 215 | + } | |
| 216 | + | |
| 217 | + // if we're in the default namespace of an RSS feed, | |
| 218 | + // record textinput or image fields | |
| 219 | + elseif ( | |
| 220 | + $this->feed_type == RSS and | |
| 221 | + $this->current_namespace == '' and | |
| 222 | + $el == 'textinput' ) | |
| 223 | + { | |
| 224 | + $this->intextinput = true; | |
| 225 | + } | |
| 226 | + | |
| 227 | + elseif ( | |
| 228 | + $this->feed_type == RSS and | |
| 229 | + $this->current_namespace == '' and | |
| 230 | + $el == 'image' ) | |
| 231 | + { | |
| 232 | + $this->inimage = true; | |
| 233 | + } | |
| 234 | + | |
| 235 | + # handle atom content constructs | |
| 236 | + elseif ( $this->feed_type == ATOM and in_array($el, $this->_CONTENT_CONSTRUCTS) ) | |
| 237 | + { | |
| 238 | + // avoid clashing w/ RSS mod_content | |
| 239 | + if ($el == 'content' ) { | |
| 240 | + $el = 'atom_content'; | |
| 241 | + } | |
| 242 | + | |
| 243 | + $this->incontent = $el; | |
| 244 | + | |
| 245 | + | |
| 246 | + } | |
| 247 | + | |
| 248 | + // if inside an Atom content construct (e.g. content or summary) field treat tags as text | |
| 249 | + elseif ($this->feed_type == ATOM and $this->incontent ) | |
| 250 | + { | |
| 251 | + // if tags are inlined, then flatten | |
| 252 | + $attrs_str = join(' ', | |
| 253 | + array_map('map_attrs', | |
| 254 | + array_keys($attrs), | |
| 255 | + array_values($attrs) ) ); | |
| 256 | + | |
| 257 | + $this->append_content( "<$element $attrs_str>" ); | |
| 258 | + | |
| 259 | + array_unshift( $this->stack, $el ); | |
| 260 | + } | |
| 261 | + | |
| 262 | + // Atom support many links per containging element. | |
| 263 | + // Magpie treats link elements of type rel='alternate' | |
| 264 | + // as being equivalent to RSS's simple link element. | |
| 265 | + // | |
| 266 | + elseif ($this->feed_type == ATOM and $el == 'link' ) | |
| 267 | + { | |
| 268 | + if ( isset($attrs['rel']) and $attrs['rel'] == 'alternate' ) | |
| 269 | + { | |
| 270 | + $link_el = 'link'; | |
| 271 | + } | |
| 272 | + else { | |
| 273 | + $link_el = 'link_' . $attrs['rel']; | |
| 274 | + } | |
| 275 | + | |
| 276 | + $this->append($link_el, $attrs['href']); | |
| 277 | + } | |
| 278 | + | |
| 279 | + // set stack[0] to current element | |
| 280 | + else { | |
| 281 | + array_unshift($this->stack, $el); | |
| 282 | + } | |
| 283 | + } | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + function feed_cdata ($p, $text) { | |
| 288 | + if ($this->feed_type == ATOM and $this->incontent) | |
| 289 | + { | |
| 290 | + $this->append_content( $text ); | |
| 291 | + } | |
| 292 | + else { | |
| 293 | + $current_el = join('_', array_reverse($this->stack)); | |
| 294 | + $this->append($current_el, $text); | |
| 295 | + } | |
| 296 | + } | |
| 297 | + | |
| 298 | + function feed_end_element ($p, $el) { | |
| 299 | + $el = strtolower($el); | |
| 300 | + | |
| 301 | + if ( $el == 'item' or $el == 'entry' ) | |
| 302 | + { | |
| 303 | + $this->items[] = $this->current_item; | |
| 304 | + $this->current_item = array(); | |
| 305 | + $this->initem = false; | |
| 306 | + | |
| 307 | + $this->current_category = 0; | |
| 308 | + } | |
| 309 | + elseif ($this->initem and ($el == 'category' or $el == 'dc:subject')) { | |
| 310 | + $this->incategory = false; | |
| 311 | + $this->current_category = $this->current_category + 1; | |
| 312 | + array_shift( $this->stack ); | |
| 313 | + } | |
| 314 | + elseif ($this->feed_type == RSS and $this->current_namespace == '' and $el == 'textinput' ) | |
| 315 | + { | |
| 316 | + $this->intextinput = false; | |
| 317 | + } | |
| 318 | + elseif ($this->feed_type == RSS and $this->current_namespace == '' and $el == 'image' ) | |
| 319 | + { | |
| 320 | + $this->inimage = false; | |
| 321 | + } | |
| 322 | + elseif ($this->feed_type == ATOM and in_array($el, $this->_CONTENT_CONSTRUCTS) ) | |
| 323 | + { | |
| 324 | + $this->incontent = false; | |
| 325 | + } | |
| 326 | + elseif ($el == 'channel' or $el == 'feed' ) | |
| 327 | + { | |
| 328 | + $this->inchannel = false; | |
| 329 | + } | |
| 330 | + elseif ($this->feed_type == ATOM and $this->incontent ) { | |
| 331 | + // balance tags properly | |
| 332 | + // note: i don't think this is actually neccessary | |
| 333 | + if ( $this->stack[0] == $el ) | |
| 334 | + { | |
| 335 | + $this->append_content("</$el>"); | |
| 336 | + } | |
| 337 | + else { | |
| 338 | + $this->append_content("<$el />"); | |
| 339 | + } | |
| 340 | + | |
| 341 | + array_shift( $this->stack ); | |
| 342 | + } | |
| 343 | + else { | |
| 344 | + array_shift( $this->stack ); | |
| 345 | + } | |
| 346 | + | |
| 347 | + $this->current_namespace = false; | |
| 348 | + } | |
| 349 | + | |
| 350 | + function concat (&$str1, $str2="") { | |
| 351 | + if (!isset($str1) ) { | |
| 352 | + $str1=""; | |
| 353 | + } | |
| 354 | + $str1 .= $str2; | |
| 355 | + } | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + function append_content($text) { | |
| 360 | + if ( $this->initem ) { | |
| 361 | + $this->concat( $this->current_item[ $this->incontent ], $text ); | |
| 362 | + } | |
| 363 | + elseif ( $this->inchannel ) { | |
| 364 | + $this->concat( $this->channel[ $this->incontent ], $text ); | |
| 365 | + } | |
| 366 | + } | |
| 367 | + | |
| 368 | + // smart append - field and namespace aware | |
| 369 | + function append($el, $text) { | |
| 370 | + if (!$el) { | |
| 371 | + return; | |
| 372 | + } | |
| 373 | + if ( $this->current_namespace ) | |
| 374 | + { | |
| 375 | + if ( $this->incategory ) { | |
| 376 | + $this->concat( $this->current_item['categories'][$this->current_category], $text ); | |
| 377 | + } | |
| 378 | + elseif ( $this->initem ) { | |
| 379 | + $this->concat( | |
| 380 | + $this->current_item[ $this->current_namespace ][ $el ], $text ); | |
| 381 | + } | |
| 382 | + elseif ($this->inchannel) { | |
| 383 | + $this->concat( | |
| 384 | + $this->channel[ $this->current_namespace][ $el ], $text ); | |
| 385 | + } | |
| 386 | + elseif ($this->intextinput) { | |
| 387 | + $this->concat( | |
| 388 | + $this->textinput[ $this->current_namespace][ $el ], $text ); | |
| 389 | + } | |
| 390 | + elseif ($this->inimage) { | |
| 391 | + $this->concat( | |
| 392 | + $this->image[ $this->current_namespace ][ $el ], $text ); | |
| 393 | + } | |
| 394 | + } | |
| 395 | + else { | |
| 396 | + if ( $this->incategory ) { | |
| 397 | + $this->concat( $this->current_item['categories'][$this->current_category], $text ); | |
| 398 | + } | |
| 399 | + elseif ( $this->initem ) { | |
| 400 | + $this->concat( | |
| 401 | + $this->current_item[ $el ], $text); | |
| 402 | + } | |
| 403 | + elseif ($this->intextinput) { | |
| 404 | + $this->concat( | |
| 405 | + $this->textinput[ $el ], $text ); | |
| 406 | + } | |
| 407 | + elseif ($this->inimage) { | |
| 408 | + $this->concat( | |
| 409 | + $this->image[ $el ], $text ); | |
| 410 | + } | |
| 411 | + elseif ($this->inchannel) { | |
| 412 | + $this->concat( | |
| 413 | + $this->channel[ $el ], $text ); | |
| 414 | + } | |
| 415 | + | |
| 416 | + } | |
| 417 | + } | |
| 418 | + | |
| 419 | + function normalize () { | |
| 420 | + // if atom populate rss fields | |
| 421 | + if ( $this->is_atom() ) { | |
| 422 | + $this->channel['description'] = $this->channel['tagline']; | |
| 423 | + for ( $i = 0; $i < count($this->items); $i++) { | |
| 424 | + $item = $this->items[$i]; | |
| 425 | + if ( isset($item['summary']) ) | |
| 426 | + $item['description'] = $item['summary']; | |
| 427 | + if ( isset($item['atom_content'])) | |
| 428 | + $item['content']['encoded'] = $item['atom_content']; | |
| 429 | + | |
| 430 | + $atom_date = (isset($item['issued']) ) ? $item['issued'] : $item['modified']; | |
| 431 | + if ( $atom_date ) { | |
| 432 | + $epoch = @parse_w3cdtf($item['modified']); | |
| 433 | + if ($epoch and $epoch > 0) { | |
| 434 | + $item['date_timestamp'] = $epoch; | |
| 435 | + } | |
| 436 | + } | |
| 437 | + | |
| 438 | + if ( is_array($item['categories']) ) { | |
| 439 | + $item['category'] = $item['categories'][0]; | |
| 440 | + $item['dc']['subjects'] = $item['categories']; | |
| 441 | + $item['dc']['subject'] = $item['category']; | |
| 442 | + } | |
| 443 | + | |
| 444 | + $this->items[$i] = $item; | |
| 445 | + } | |
| 446 | + } | |
| 447 | + elseif ( $this->is_rss() ) { | |
| 448 | + $this->channel['tagline'] = $this->channel['description']; | |
| 449 | + for ( $i = 0; $i < count($this->items); $i++) { | |
| 450 | + $item = $this->items[$i]; | |
| 451 | + if ( isset($item['description'])) | |
| 452 | + $item['summary'] = $item['description']; | |
| 453 | + if ( isset($item['content']['encoded'] ) ) | |
| 454 | + $item['atom_content'] = $item['content']['encoded']; | |
| 455 | + | |
| 456 | + if ( $this->is_rss() == '1.0' and isset($item['dc']['date']) ) { | |
| 457 | + $epoch = @parse_w3cdtf($item['dc']['date']); | |
| 458 | + if ($epoch and $epoch > 0) { | |
| 459 | + $item['date_timestamp'] = $epoch; | |
| 460 | + } | |
| 461 | + } | |
| 462 | + elseif ( isset($item['pubdate']) ) { | |
| 463 | + $epoch = @strtotime($item['pubdate']); | |
| 464 | + if ($epoch > 0) { | |
| 465 | + $item['date_timestamp'] = $epoch; | |
| 466 | + } | |
| 467 | + } | |
| 468 | + | |
| 469 | + if ( is_array($item['categories']) ) { | |
| 470 | + $item['category'] = $item['categories'][0]; | |
| 471 | + $item['dc']['subjects'] = $item['categories']; | |
| 472 | + $item['dc']['subject'] = $item['category']; | |
| 473 | + } | |
| 474 | + | |
| 475 | + $this->items[$i] = $item; | |
| 476 | + } | |
| 477 | + } | |
| 478 | + } | |
| 479 | + | |
| 480 | + | |
| 481 | + function is_rss () { | |
| 482 | + if ( $this->feed_type == RSS ) { | |
| 483 | + return $this->feed_version; | |
| 484 | + } | |
| 485 | + else { | |
| 486 | + return false; | |
| 487 | + } | |
| 488 | + } | |
| 489 | + | |
| 490 | + function is_atom() { | |
| 491 | + if ( $this->feed_type == ATOM ) { | |
| 492 | + return $this->feed_version; | |
| 493 | + } | |
| 494 | + else { | |
| 495 | + return false; | |
| 496 | + } | |
| 497 | + } | |
| 498 | + | |
| 499 | + /** | |
| 500 | + * return XML parser, and possibly re-encoded source | |
| 501 | + * | |
| 502 | + */ | |
| 503 | + function create_parser($source, $out_enc, $in_enc, $detect) { | |
| 504 | + if ( substr(phpversion(),0,1) == 5) { | |
| 505 | + $parser = $this->php5_create_parser($in_enc, $detect); | |
| 506 | + } | |
| 507 | + else { | |
| 508 | + list($parser, $source) = $this->php4_create_parser($source, $in_enc, $detect); | |
| 509 | + } | |
| 510 | + if ($out_enc) { | |
| 511 | + $this->encoding = $out_enc; | |
| 512 | + xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $out_enc); | |
| 513 | + } | |
| 514 | + | |
| 515 | + return array($parser, $source); | |
| 516 | + } | |
| 517 | + | |
| 518 | + /** | |
| 519 | + * Instantiate an XML parser under PHP5 | |
| 520 | + * | |
| 521 | + * PHP5 will do a fine job of detecting input encoding | |
| 522 | + * if passed an empty string as the encoding. | |
| 523 | + * | |
| 524 | + * All hail libxml2! | |
| 525 | + * | |
| 526 | + */ | |
| 527 | + function php5_create_parser($in_enc, $detect) { | |
| 528 | + // by default php5 does a fine job of detecting input encodings | |
| 529 | + if(!$detect && $in_enc) { | |
| 530 | + return xml_parser_create($in_enc); | |
| 531 | + } | |
| 532 | + else { | |
| 533 | + return xml_parser_create(''); | |
| 534 | + } | |
| 535 | + } | |
| 536 | + | |
| 537 | + /** | |
| 538 | + * Instaniate an XML parser under PHP4 | |
| 539 | + * | |
| 540 | + * Unfortunately PHP4's support for character encodings | |
| 541 | + * and especially XML and character encodings sucks. As | |
| 542 | + * long as the documents you parse only contain characters | |
| 543 | + * from the ISO-8859-1 character set (a superset of ASCII, | |
| 544 | + * and a subset of UTF-8) you're fine. However once you | |
| 545 | + * step out of that comfy little world things get mad, bad, | |
| 546 | + * and dangerous to know. | |
| 547 | + * | |
| 548 | + * The following code is based on SJM's work with FoF | |
| 549 | + * @see http://minutillo.com/steve/weblog/2004/6/17/php-xml-and-character-encodings-a-tale-of-sadness-rage-and-data-loss | |
| 550 | + * | |
| 551 | + */ | |
| 552 | + function php4_create_parser($source, $in_enc, $detect) { | |
| 553 | + if ( !$detect ) { | |
| 554 | + return array(xml_parser_create($in_enc), $source); | |
| 555 | + } | |
| 556 | + | |
| 557 | + if (!$in_enc) { | |
| 558 | + if (preg_match('/<?xml.*encoding=[\'"](.*?)[\'"].*?>/m', $source, $m)) { | |
| 559 | + $in_enc = strtoupper($m[1]); | |
| 560 | + $this->source_encoding = $in_enc; | |
| 561 | + } | |
| 562 | + else { | |
| 563 | + $in_enc = 'UTF-8'; | |
| 564 | + } | |
| 565 | + } | |
| 566 | + | |
| 567 | + if ($this->known_encoding($in_enc)) { | |
| 568 | + return array(xml_parser_create($in_enc), $source); | |
| 569 | + } | |
| 570 | + | |
| 571 | + // the dectected encoding is not one of the simple encodings PHP knows | |
| 572 | + | |
| 573 | + // attempt to use the iconv extension to | |
| 574 | + // cast the XML to a known encoding | |
| 575 | + // @see http://php.net/iconv | |
| 576 | + | |
| 577 | + if (function_exists('iconv')) { | |
| 578 | + $encoded_source = iconv($in_enc,'UTF-8', $source); | |
| 579 | + if ($encoded_source) { | |
| 580 | + return array(xml_parser_create('UTF-8'), $encoded_source); | |
| 581 | + } | |
| 582 | + } | |
| 583 | + | |
| 584 | + // iconv didn't work, try mb_convert_encoding | |
| 585 | + // @see http://php.net/mbstring | |
| 586 | + if(function_exists('mb_convert_encoding')) { | |
| 587 | + $encoded_source = mb_convert_encoding($source, 'UTF-8', $in_enc ); | |
| 588 | + if ($encoded_source) { | |
| 589 | + return array(xml_parser_create('UTF-8'), $encoded_source); | |
| 590 | + } | |
| 591 | + } | |
| 592 | + | |
| 593 | + // else | |
| 594 | + $this->error("Feed is in an unsupported character encoding. ($in_enc) " . | |
| 595 | + "You may see strange artifacts, and mangled characters.", | |
| 596 | + E_USER_NOTICE); | |
| 597 | + | |
| 598 | + return array(xml_parser_create(), $source); | |
| 599 | + } | |
| 600 | + | |
| 601 | + function known_encoding($enc) { | |
| 602 | + $enc = strtoupper($enc); | |
| 603 | + if ( in_array($enc, $this->_KNOWN_ENCODINGS) ) { | |
| 604 | + return $enc; | |
| 605 | + } | |
| 606 | + else { | |
| 607 | + return false; | |
| 608 | + } | |
| 609 | + } | |
| 610 | + | |
| 611 | + function error ($errormsg, $lvl=E_USER_WARNING) { | |
| 612 | + // append PHP's error message if track_errors enabled | |
| 613 | + if ( $php_errormsg ) { | |
| 614 | + $errormsg .= " ($php_errormsg)"; | |
| 615 | + } | |
| 616 | + if ( MAGPIE_DEBUG ) { | |
| 617 | + trigger_error( $errormsg, $lvl); | |
| 618 | + } | |
| 619 | + else { | |
| 620 | + error_log( $errormsg, 0); | |
| 621 | + } | |
| 622 | + | |
| 623 | + $notices = E_USER_NOTICE|E_NOTICE; | |
| 624 | + if ( $lvl&$notices ) { | |
| 625 | + $this->WARNING = $errormsg; | |
| 626 | + } else { | |
| 627 | + $this->ERROR = $errormsg; | |
| 628 | + } | |
| 629 | + } | |
| 630 | +} // end class RSS | |
| 631 | + | |
| 632 | +function map_attrs($k, $v) { | |
| 633 | + return "$k=\"$v\""; | |
| 634 | +} | |
| 635 | +# ---- cut here ---- | |
| 636 | + | |
| 637 | +require_once( dirname(__FILE__) . '/class-snoopy.php'); | |
| 638 | + | |
| 639 | +# -- UPDATED from rss_fetch.inc: fetch_rss, error, debug, magpie_error | |
| 640 | +# --- cut here --- | |
| 641 | +function fetch_rss ($url) { | |
| 642 | + // initialize constants | |
| 643 | + init(); | |
| 644 | + | |
| 645 | + if ( !isset($url) ) { | |
| 646 | + error("fetch_rss called without a url"); | |
| 647 | + return false; | |
| 648 | + } | |
| 649 | + | |
| 650 | + // if cache is disabled | |
| 651 | + if ( !MAGPIE_CACHE_ON ) { | |
| 652 | + // fetch file, and parse it | |
| 653 | + $resp = _fetch_remote_file( $url ); | |
| 654 | + if ( is_success( $resp->status ) ) { | |
| 655 | + return _response_to_rss( $resp ); | |
| 656 | + } | |
| 657 | + else { | |
| 658 | + error("Failed to fetch $url and cache is off"); | |
| 659 | + return false; | |
| 660 | + } | |
| 661 | + } | |
| 662 | + // else cache is ON | |
| 663 | + else { | |
| 664 | + // Flow | |
| 665 | + // 1. check cache | |
| 666 | + // 2. if there is a hit, make sure its fresh | |
| 667 | + // 3. if cached obj fails freshness check, fetch remote | |
| 668 | + // 4. if remote fails, return stale object, or error | |
| 669 | + | |
| 670 | + $cache = new RSSCache( MAGPIE_CACHE_DIR, MAGPIE_CACHE_AGE ); | |
| 671 | + | |
| 672 | + if (MAGPIE_DEBUG and $cache->ERROR) { | |
| 673 | + debug($cache->ERROR, E_USER_WARNING); | |
| 674 | + } | |
| 675 | + | |
| 676 | + | |
| 677 | + $cache_status = 0; // response of check_cache | |
| 678 | + $request_headers = array(); // HTTP headers to send with fetch | |
| 679 | + $rss = 0; // parsed RSS object | |
| 680 | + $errormsg = 0; // errors, if any | |
| 681 | + | |
| 682 | + // store parsed XML by desired output encoding | |
| 683 | + // as character munging happens at parse time | |
| 684 | + $cache_key = $url . MAGPIE_OUTPUT_ENCODING; | |
| 685 | + | |
| 686 | + if (!$cache->ERROR) { | |
| 687 | + // return cache HIT, MISS, or STALE | |
| 688 | + $cache_status = $cache->check_cache( $cache_key); | |
| 689 | + } | |
| 690 | + | |
| 691 | + // if object cached, and cache is fresh, return cached obj | |
| 692 | + if ( $cache_status == 'HIT' ) { | |
| 693 | + $rss = $cache->get( $cache_key ); | |
| 694 | + if ( isset($rss) and $rss ) { | |
| 695 | + // should be cache age | |
| 696 | + $rss->from_cache = 1; | |
| 697 | + if ( MAGPIE_DEBUG > 1) { | |
| 698 | + debug("MagpieRSS: Cache HIT", E_USER_NOTICE); | |
| 699 | + } | |
| 700 | + return $rss; | |
| 701 | + } | |
| 702 | + } | |
| 703 | + | |
| 704 | + // else attempt a conditional get | |
| 705 | + | |
| 706 | + // setup headers | |
| 707 | + if ( $cache_status == 'STALE' ) { | |
| 708 | + $rss = $cache->get( $cache_key ); | |
| 709 | + if ( $rss and $rss->etag and $rss->last_modified ) { | |
| 710 | + $request_headers['If-None-Match'] = $rss->etag; | |
| 711 | + $request_headers['If-Last-Modified'] = $rss->last_modified; | |
| 712 | + } | |
| 713 | + } | |
| 714 | + | |
| 715 | + $resp = _fetch_remote_file( $url, $request_headers ); | |
| 716 | + | |
| 717 | + if (isset($resp) and $resp) { | |
| 718 | + if ($resp->status == '304' ) { | |
| 719 | + // we have the most current copy | |
| 720 | + if ( MAGPIE_DEBUG > 1) { | |
| 721 | + debug("Got 304 for $url"); | |
| 722 | + } | |
| 723 | + // reset cache on 304 (at minutillo insistent prodding) | |
| 724 | + $cache->set($cache_key, $rss); | |
| 725 | + return $rss; | |
| 726 | + } | |
| 727 | + elseif ( is_success( $resp->status ) ) { | |
| 728 | + $rss = _response_to_rss( $resp ); | |
| 729 | + if ( $rss ) { | |
| 730 | + if (MAGPIE_DEBUG > 1) { | |
| 731 | + debug("Fetch successful"); | |
| 732 | + } | |
| 733 | + // add object to cache | |
| 734 | + $cache->set( $cache_key, $rss ); | |
| 735 | + return $rss; | |
| 736 | + } | |
| 737 | + } | |
| 738 | + else { | |
| 739 | + $errormsg = "Failed to fetch $url "; | |
| 740 | + if ( $resp->status == '-100' ) { | |
| 741 | + $errormsg .= "(Request timed out after " . MAGPIE_FETCH_TIME_OUT . " seconds)"; | |
| 742 | + } | |
| 743 | + elseif ( $resp->error ) { | |
| 744 | + # compensate for Snoopy's annoying habbit to tacking | |
| 745 | + # on '\n' | |
| 746 | + $http_error = substr($resp->error, 0, -2); | |
| 747 | + $errormsg .= "(HTTP Error: $http_error)"; | |
| 748 | + } | |
| 749 | + else { | |
| 750 | + $errormsg .= "(HTTP Response: " . $resp->response_code .')'; | |
| 751 | + } | |
| 752 | + } | |
| 753 | + } | |
| 754 | + else { | |
| 755 | + $errormsg = "Unable to retrieve RSS file for unknown reasons."; | |
| 756 | + } | |
| 757 | + | |
| 758 | + // else fetch failed | |
| 759 | + | |
| 760 | + // attempt to return cached object | |
| 761 | + if ($rss) { | |
| 762 | + if ( MAGPIE_DEBUG ) { | |
| 763 | + debug("Returning STALE object for $url"); | |
| 764 | + } | |
| 765 | + return $rss; | |
| 766 | + } | |
| 767 | + | |
| 768 | + // else we totally failed | |
| 769 | + error( $errormsg ); | |
| 770 | + | |
| 771 | + return false; | |
| 772 | + | |
| 773 | + } // end if ( !MAGPIE_CACHE_ON ) { | |
| 774 | +} // end fetch_rss() | |
| 775 | + | |
| 776 | +/*=======================================================================*\ | |
| 777 | + Function: error | |
| 778 | + Purpose: set MAGPIE_ERROR, and trigger error | |
| 779 | +\*=======================================================================*/ | |
| 780 | + | |
| 781 | +function error ($errormsg, $lvl=E_USER_WARNING) { | |
| 782 | + global $MAGPIE_ERROR; | |
| 783 | + | |
| 784 | + // append PHP's error message if track_errors enabled | |
| 785 | + if ( isset($php_errormsg) ) { | |
| 786 | + $errormsg .= " ($php_errormsg)"; | |
| 787 | + } | |
| 788 | + if ( $errormsg ) { | |
| 789 | + $errormsg = "MagpieRSS: $errormsg"; | |
| 790 | + $MAGPIE_ERROR = $errormsg; | |
| 791 | + trigger_error( $errormsg, $lvl); | |
| 792 | + } | |
| 793 | +} | |
| 794 | + | |
| 795 | +function debug ($debugmsg, $lvl=E_USER_NOTICE) { | |
| 796 | + trigger_error("MagpieRSS [debug] $debugmsg", $lvl); | |
| 797 | +} | |
| 798 | + | |
| 799 | +/*=======================================================================*\ | |
| 800 | + Function: magpie_error | |
| 801 | + Purpose: accessor for the magpie error variable | |
| 802 | +\*=======================================================================*/ | |
| 803 | +function magpie_error ($errormsg="") { | |
| 804 | + global $MAGPIE_ERROR; | |
| 805 | + | |
| 806 | + if ( isset($errormsg) and $errormsg ) { | |
| 807 | + $MAGPIE_ERROR = $errormsg; | |
| 808 | + } | |
| 809 | + | |
| 810 | + return $MAGPIE_ERROR; | |
| 811 | +} | |
| 812 | +# --- cut here --- | |
| 813 | + | |
| 814 | +# UPDATED FROM: rss_fetch.inc: _fetch_remote_file, _response_to_rss, init | |
| 815 | +# --- cut here --- | |
| 816 | +/*=======================================================================*\ | |
| 817 | + Function: _fetch_remote_file | |
| 818 | + Purpose: retrieve an arbitrary remote file | |
| 819 | + Input: url of the remote file | |
| 820 | + headers to send along with the request (optional) | |
| 821 | + Output: an HTTP response object (see Snoopy.class.inc) | |
| 822 | +\*=======================================================================*/ | |
| 823 | +function _fetch_remote_file ($url, $headers = "" ) { | |
| 824 | + // Snoopy is an HTTP client in PHP | |
| 825 | + $client = new Snoopy(); | |
| 826 | + $client->agent = MAGPIE_USER_AGENT; | |
| 827 | + $client->read_timeout = MAGPIE_FETCH_TIME_OUT; | |
| 828 | + $client->use_gzip = MAGPIE_USE_GZIP; | |
| 829 | + if (is_array($headers) ) { | |
| 830 | + $client->rawheaders = $headers; | |
| 831 | + } | |
| 832 | + | |
| 833 | + @$client->fetch($url); | |
| 834 | + return $client; | |
| 835 | + | |
| 836 | +} | |
| 837 | + | |
| 838 | +/*=======================================================================*\ | |
| 839 | + Function: _response_to_rss | |
| 840 | + Purpose: parse an HTTP response object into an RSS object | |
| 841 | + Input: an HTTP response object (see Snoopy) | |
| 842 | + Output: parsed RSS object (see rss_parse) | |
| 843 | +\*=======================================================================*/ | |
| 844 | +function _response_to_rss ($resp) { | |
| 845 | + $rss = new MagpieRSS( $resp->results, MAGPIE_OUTPUT_ENCODING, MAGPIE_INPUT_ENCODING, MAGPIE_DETECT_ENCODING ); | |
| 846 | + | |
| 847 | + // if RSS parsed successfully | |
| 848 | + if ( $rss and !$rss->ERROR) { | |
| 849 | + | |
| 850 | + // find Etag, and Last-Modified | |
| 851 | + foreach($resp->headers as $h) { | |
| 852 | + // 2003-03-02 - Nicola Asuni (www.tecnick.com) - fixed bug "Undefined offset: 1" | |
| 853 | + if (strpos($h, ": ")) { | |
| 854 | + list($field, $val) = explode(": ", $h, 2); | |
| 855 | + } | |
| 856 | + else { | |
| 857 | + $field = $h; | |
| 858 | + $val = ""; | |
| 859 | + } | |
| 860 | + | |
| 861 | + if ( $field == 'ETag' ) { | |
| 862 | + $rss->etag = $val; | |
| 863 | + } | |
| 864 | + | |
| 865 | + if ( $field == 'Last-Modified' ) { | |
| 866 | + $rss->last_modified = $val; | |
| 867 | + } | |
| 868 | + } | |
| 869 | + | |
| 870 | + return $rss; | |
| 871 | + } // else construct error message | |
| 872 | + else { | |
| 873 | + $errormsg = "Failed to parse RSS file."; | |
| 874 | + | |
| 875 | + if ($rss) { | |
| 876 | + $errormsg .= " (" . $rss->ERROR . ")"; | |
| 877 | + } | |
| 878 | + error($errormsg); | |
| 879 | + | |
| 880 | + return false; | |
| 881 | + } // end if ($rss and !$rss->error) | |
| 882 | +} | |
| 883 | + | |
| 884 | +/*=======================================================================*\ | |
| 885 | + Function: init | |
| 886 | + Purpose: setup constants with default values | |
| 887 | + check for user overrides | |
| 888 | +\*=======================================================================*/ | |
| 889 | +function init () { | |
| 890 | + if ( defined('MAGPIE_INITALIZED') ) { | |
| 891 | + return; | |
| 892 | + } | |
| 893 | + else { | |
| 894 | + define('MAGPIE_INITALIZED', true); | |
| 895 | + } | |
| 896 | + | |
| 897 | + if ( !defined('MAGPIE_CACHE_ON') ) { | |
| 898 | + define('MAGPIE_CACHE_ON', true); | |
| 899 | + } | |
| 900 | + | |
| 901 | + if ( !defined('MAGPIE_CACHE_DIR') ) { | |
| 902 | + define('MAGPIE_CACHE_DIR', './cache'); | |
| 903 | + } | |
| 904 | + | |
| 905 | + if ( !defined('MAGPIE_CACHE_AGE') ) { | |
| 906 | + define('MAGPIE_CACHE_AGE', 60*60); // one hour | |
| 907 | + } | |
| 908 | + | |
| 909 | + if ( !defined('MAGPIE_CACHE_FRESH_ONLY') ) { | |
| 910 | + define('MAGPIE_CACHE_FRESH_ONLY', false); | |
| 911 | + } | |
| 912 | + | |
| 913 | + if ( !defined('MAGPIE_OUTPUT_ENCODING') ) { | |
| 914 | + # WORDPRESS MODIFICATION: use whatever charset the blog is using | |
| 915 | + # --- cut here --- | |
| 916 | + $wp_encoding = get_settings('blog_charset'); | |
| 917 | + define('MAGPIE_OUTPUT_ENCODING', ($wp_encoding?$wp_encoding:'ISO-8859-1')); | |
| 918 | + # --- cut here --- | |
| 919 | + } | |
| 920 | + | |
| 921 | + if ( !defined('MAGPIE_INPUT_ENCODING') ) { | |
| 922 | + define('MAGPIE_INPUT_ENCODING', null); | |
| 923 | + } | |
| 924 | + | |
| 925 | + if ( !defined('MAGPIE_DETECT_ENCODING') ) { | |
| 926 | + define('MAGPIE_DETECT_ENCODING', true); | |
| 927 | + } | |
| 928 | + | |
| 929 | + if ( !defined('MAGPIE_DEBUG') ) { | |
| 930 | + define('MAGPIE_DEBUG', 0); | |
| 931 | + } | |
| 932 | + | |
| 933 | + if ( !defined('MAGPIE_USER_AGENT') ) { | |
| 934 | + # WORDPRESS MODIFICATION: send WordPress as user-agent | |
| 935 | + # --- cut here --- | |
| 936 | + $ua = 'WordPress/'. $wp_version . ' (+http://www.wordpress.org'; | |
| 937 | + # --- cut here --- | |
| 938 | + | |
| 939 | + if ( MAGPIE_CACHE_ON ) { | |
| 940 | + $ua = $ua . ')'; | |
| 941 | + } | |
| 942 | + else { | |
| 943 | + $ua = $ua . '; No cache)'; | |
| 944 | + } | |
| 945 | + | |
| 946 | + define('MAGPIE_USER_AGENT', $ua); | |
| 947 | + } | |
| 948 | + | |
| 949 | + if ( !defined('MAGPIE_FETCH_TIME_OUT') ) { | |
| 950 | + define('MAGPIE_FETCH_TIME_OUT', 5); // 5 second timeout | |
| 951 | + } | |
| 952 | + | |
| 953 | + // use gzip encoding to fetch rss files if supported? | |
| 954 | + if ( !defined('MAGPIE_USE_GZIP') ) { | |
| 955 | + define('MAGPIE_USE_GZIP', true); | |
| 956 | + } | |
| 957 | +} | |
| 958 | +# --- cut here --- | |
| 959 | + | |
| 960 | +function is_info ($sc) { | |
| 961 | + return $sc >= 100 && $sc < 200; | |
| 962 | +} | |
| 963 | + | |
| 964 | +function is_success ($sc) { | |
| 965 | + return $sc >= 200 && $sc < 300; | |
| 966 | +} | |
| 967 | + | |
| 968 | +function is_redirect ($sc) { | |
| 969 | + return $sc >= 300 && $sc < 400; | |
| 970 | +} | |
| 971 | + | |
| 972 | +function is_error ($sc) { | |
| 973 | + return $sc >= 400 && $sc < 600; | |
| 974 | +} | |
| 975 | + | |
| 976 | +function is_client_error ($sc) { | |
| 977 | + return $sc >= 400 && $sc < 500; | |
| 978 | +} | |
| 979 | + | |
| 980 | +function is_server_error ($sc) { | |
| 981 | + return $sc >= 500 && $sc < 600; | |
| 982 | +} | |
| 983 | + | |
| 984 | +# WORDPRESS-SPECIFIC: class RSSCache (modified to use WP database) | |
| 985 | +# --- cut here --- | |
| 986 | +class RSSCache { | |
| 987 | + var $BASE_CACHE = 'wp-content/cache'; // where the cache files are stored | |
| 988 | + var $MAX_AGE = 43200; // when are files stale, default twelve hours | |
| 989 | + var $ERROR = ''; // accumulate error messages | |
| 990 | + | |
| 991 | + function RSSCache ($base='', $age='') { | |
| 992 | + if ( $base ) { | |
| 993 | + $this->BASE_CACHE = $base; | |
| 994 | + } | |
| 995 | + if ( $age ) { | |
| 996 | + $this->MAX_AGE = $age; | |
| 997 | + } | |
| 998 | + | |
| 999 | + } | |
| 1000 | + | |
| 1001 | +/*=======================================================================*\ | |
| 1002 | + Function: set | |
| 1003 | + Purpose: add an item to the cache, keyed on url | |
| 1004 | + Input: url from wich the rss file was fetched | |
| 1005 | + Output: true on sucess | |
| 1006 | +\*=======================================================================*/ | |
| 1007 | + function set ($url, $rss) { | |
| 1008 | + global $wpdb; | |
| 1009 | + $cache_option = 'rss_' . $this->file_name( $url ); | |
| 1010 | + $cache_timestamp = 'rss_' . $this->file_name( $url ) . '_ts'; | |
| 1011 | + | |
| 1012 | + if ( !$wpdb->get_var("SELECT option_name FROM $wpdb->options WHERE option_name = '$cache_option'") ) | |
| 1013 | + add_option($cache_option, '', '', 'no'); | |
| 1014 | + if ( !$wpdb->get_var("SELECT option_name FROM $wpdb->options WHERE option_name = '$cache_timestamp'") ) | |
| 1015 | + add_option($cache_timestamp, '', '', 'no'); | |
| 1016 | + | |
| 1017 | + update_option($cache_option, $rss); | |
| 1018 | + update_option($cache_timestamp, time() ); | |
| 1019 | + | |
| 1020 | + return $cache_option; | |
| 1021 | + } | |
| 1022 | + | |
| 1023 | +/*=======================================================================*\ | |
| 1024 | + Function: get | |
| 1025 | + Purpose: fetch an item from the cache | |
| 1026 | + Input: url from wich the rss file was fetched | |
| 1027 | + Output: cached object on HIT, false on MISS | |
| 1028 | +\*=======================================================================*/ | |
| 1029 | + function get ($url) { | |
| 1030 | + $this->ERROR = ""; | |
| 1031 | + $cache_option = 'rss_' . $this->file_name( $url ); | |
| 1032 | + | |
| 1033 | + if ( ! get_option( $cache_option ) ) { | |
| 1034 | + $this->debug( | |
| 1035 | + "Cache doesn't contain: $url (cache option: $cache_option)" | |
| 1036 | + ); | |
| 1037 | + return 0; | |
| 1038 | + } | |
| 1039 | + | |
| 1040 | + $rss = get_option( $cache_option ); | |
| 1041 | + | |
| 1042 | + return $rss; | |
| 1043 | + } | |
| 1044 | + | |
| 1045 | +/*=======================================================================*\ | |
| 1046 | + Function: check_cache | |
| 1047 | + Purpose: check a url for membership in the cache | |
| 1048 | + and whether the object is older then MAX_AGE (ie. STALE) | |
| 1049 | + Input: url from wich the rss file was fetched | |
| 1050 | + Output: cached object on HIT, false on MISS | |
| 1051 | +\*=======================================================================*/ | |
| 1052 | + function check_cache ( $url ) { | |
| 1053 | + $this->ERROR = ""; | |
| 1054 | + $cache_option = $this->file_name( $url ); | |
| 1055 | + $cache_timestamp = 'rss_' . $this->file_name( $url ) . '_ts'; | |
| 1056 | + | |
| 1057 | + if ( $mtime = get_option($cache_timestamp) ) { | |
| 1058 | + // find how long ago the file was added to the cache | |
| 1059 | + // and whether that is longer then MAX_AGE | |
| 1060 | + $age = time() - $mtime; | |
| 1061 | + if ( $this->MAX_AGE > $age ) { | |
| 1062 | + // object exists and is current | |
| 1063 | + return 'HIT'; | |
| 1064 | + } | |
| 1065 | + else { | |
| 1066 | + // object exists but is old | |
| 1067 | + return 'STALE'; | |
| 1068 | + } | |
| 1069 | + } | |
| 1070 | + else { | |
| 1071 | + // object does not exist | |
| 1072 | + return 'MISS'; | |
| 1073 | + } | |
| 1074 | + } | |
| 1075 | + | |
| 1076 | +/*=======================================================================*\ | |
| 1077 | + Function: serialize | |
| 1078 | +\*=======================================================================*/ | |
| 1079 | + function serialize ( $rss ) { | |
| 1080 | + return serialize( $rss ); | |
| 1081 | + } | |
| 1082 | + | |
| 1083 | +/*=======================================================================*\ | |
| 1084 | + Function: unserialize | |
| 1085 | +\*=======================================================================*/ | |
| 1086 | + function unserialize ( $data ) { | |
| 1087 | + return unserialize( $data ); | |
| 1088 | + } | |
| 1089 | + | |
| 1090 | +/*=======================================================================*\ | |
| 1091 | + Function: file_name | |
| 1092 | + Purpose: map url to location in cache | |
| 1093 | + Input: url from wich the rss file was fetched | |
| 1094 | + Output: a file name | |
| 1095 | +\*=======================================================================*/ | |
| 1096 | + function file_name ($url) { | |
| 1097 | + return md5( $url ); | |
| 1098 | + } | |
| 1099 | + | |
| 1100 | +/*=======================================================================*\ | |
| 1101 | + Function: error | |
| 1102 | + Purpose: register error | |
| 1103 | +\*=======================================================================*/ | |
| 1104 | + function error ($errormsg, $lvl=E_USER_WARNING) { | |
| 1105 | + // append PHP's error message if track_errors enabled | |
| 1106 | + if ( isset($php_errormsg) ) { | |
| 1107 | + $errormsg .= " ($php_errormsg)"; | |
| 1108 | + } | |
| 1109 | + $this->ERROR = $errormsg; | |
| 1110 | + if ( MAGPIE_DEBUG ) { | |
| 1111 | + trigger_error( $errormsg, $lvl); | |
| 1112 | + } | |
| 1113 | + else { | |
| 1114 | + error_log( $errormsg, 0); | |
| 1115 | + } | |
| 1116 | + } | |
| 1117 | + function debug ($debugmsg, $lvl=E_USER_NOTICE) { | |
| 1118 | + if ( MAGPIE_DEBUG ) { | |
| 1119 | + $this->error("MagpieRSS [debug] $debugmsg", $lvl); | |
| 1120 | + } | |
| 1121 | + } | |
| 1122 | +} | |
| 1123 | +# --- cut here --- | |
| 1124 | + | |
| 1125 | +function parse_w3cdtf ( $date_str ) { | |
| 1126 | + | |
| 1127 | + # regex to match wc3dtf | |
| 1128 | + $pat = "/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(:(\d{2}))?(?:([-+])(\d{2}):?(\d{2})|(Z))?/"; | |
| 1129 | + | |
| 1130 | + if ( preg_match( $pat, $date_str, $match ) ) { | |
| 1131 | + list( $year, $month, $day, $hours, $minutes, $seconds) = | |
| 1132 | + array( $match[1], $match[2], $match[3], $match[4], $match[5], $match[6]); | |
| 1133 | + | |
| 1134 | + # calc epoch for current date assuming GMT | |
| 1135 | + $epoch = gmmktime( $hours, $minutes, $seconds, $month, $day, $year); | |
| 1136 | + | |
| 1137 | + $offset = 0; | |
| 1138 | + if ( $match[10] == 'Z' ) { | |
| 1139 | + # zulu time, aka GMT | |
| 1140 | + } | |
| 1141 | + else { | |
| 1142 | + list( $tz_mod, $tz_hour, $tz_min ) = | |
| 1143 | + array( $match[8], $match[9], $match[10]); | |
| 1144 | + | |
| 1145 | + # zero out the variables | |
| 1146 | + if ( ! $tz_hour ) { $tz_hour = 0; } | |
| 1147 | + if ( ! $tz_min ) { $tz_min = 0; } | |
| 1148 | + | |
| 1149 | + $offset_secs = (($tz_hour*60)+$tz_min)*60; | |
| 1150 | + | |
| 1151 | + # is timezone ahead of GMT? then subtract offset | |
| 1152 | + # | |
| 1153 | + if ( $tz_mod == '+' ) { | |
| 1154 | + $offset_secs = $offset_secs * -1; | |
| 1155 | + } | |
| 1156 | + | |
| 1157 | + $offset = $offset_secs; | |
| 1158 | + } | |
| 1159 | + $epoch = $epoch + $offset; | |
| 1160 | + return $epoch; | |
| 1161 | + } | |
| 1162 | + else { | |
| 1163 | + return -1; | |
| 1164 | + } | |
| 1165 | +} | |
| 1166 | + | |
| 1167 | +# WORDPRESS-SPECIFIC: wp_rss (), get_rss () | |
| 1168 | +# --- cut here --- | |
| 1169 | +function wp_rss ($url, $num) { | |
| 1170 | + //ini_set("display_errors", false); uncomment to suppress php errors thrown if the feed is not returned. | |
| 1171 | + $num_items = $num; | |
| 1172 | + $rss = fetch_rss($url); | |
| 1173 | + if ( $rss ) { | |
| 1174 | + echo "<ul>"; | |
| 1175 | + $rss->items = array_slice($rss->items, 0, $num_items); | |
| 1176 | + foreach ($rss->items as $item ) { | |
| 1177 | + echo "<li>\n"; | |
| 1178 | + echo "<a href='$item[link]' title='$item[description]'>"; | |
| 1179 | + echo htmlentities($item['title']); | |
| 1180 | + echo "</a><br />\n"; | |
| 1181 | + echo "</li>\n"; | |
| 1182 | + } | |
| 1183 | + echo "</ul>"; | |
| 1184 | + } | |
| 1185 | + else { | |
| 1186 | + echo "an error has occured the feed is probably down, try again later."; | |
| 1187 | + } | |
| 1188 | +} | |
| 1189 | + | |
| 1190 | +function get_rss ($uri, $num = 5) { // Like get posts, but for RSS | |
| 1191 | + $rss = fetch_rss($url); | |
| 1192 | + if ( $rss ) { | |
| 1193 | + $rss->items = array_slice($rss->items, 0, $num_items); | |
| 1194 | + foreach ($rss->items as $item ) { | |
| 1195 | + echo "<li>\n"; | |
| 1196 | + echo "<a href='$item[link]' title='$item[description]'>"; | |
| 1197 | + echo htmlentities($item['title']); | |
| 1198 | + echo "</a><br />\n"; | |
| 1199 | + echo "</li>\n"; | |
| 1200 | + } | |
| 1201 | + return $posts; | |
| 1202 | + } else { | |
| 1203 | + return false; | |
| 1204 | + } | |
| 1205 | +} | |
| 1206 | +# --- cut here --- | |
| 1207 | +?> | |