PHP Cross Reference of WordPress Subversion HEAD |
| [ Index ] [ Classes ] [ Functions ] [ Variables ] [ Constants ] |
[Summary view] [Print] [Text view]
1 <?php 2 do_action('load_feed_engine'); 3 4 /* 5 * Project: MagpieRSS: a simple RSS integration tool 6 * File: A compiled file for RSS syndication 7 * Author: Kellan Elliott-McCrea <kellan@protest.net> 8 * Version: 0.51 9 * License: GPL 10 */ 11 12 define('RSS', 'RSS'); 13 define('ATOM', 'Atom'); 14 define('MAGPIE_USER_AGENT', 'WordPress/' . $GLOBALS['wp_version']); 15 16 class MagpieRSS { 17 var $parser; 18 var $current_item = array(); // item currently being parsed 19 var $items = array(); // collection of parsed items 20 var $channel = array(); // hash of channel fields 21 var $textinput = array(); 22 var $image = array(); 23 var $feed_type; 24 var $feed_version; 25 26 // parser variables 27 var $stack = array(); // parser stack 28 var $inchannel = false; 29 var $initem = false; 30 var $incontent = false; // if in Atom <content mode="xml"> field 31 var $intextinput = false; 32 var $inimage = false; 33 var $current_field = ''; 34 var $current_namespace = false; 35 36 //var $ERROR = ""; 37 38 var $_CONTENT_CONSTRUCTS = array('content', 'summary', 'info', 'title', 'tagline', 'copyright'); 39 40 function MagpieRSS ($source) { 41 42 # if PHP xml isn't compiled in, die 43 # 44 if ( !function_exists('xml_parser_create') ) 45 trigger_error( "Failed to load PHP's XML Extension. http://www.php.net/manual/en/ref.xml.php" ); 46 47 $parser = @xml_parser_create(); 48 49 if ( !is_resource($parser) ) 50 trigger_error( "Failed to create an instance of PHP's XML parser. http://www.php.net/manual/en/ref.xml.php"); 51 52 53 $this->parser = $parser; 54 55 # pass in parser, and a reference to this object 56 # setup handlers 57 # 58 xml_set_object( $this->parser, $this ); 59 xml_set_element_handler($this->parser, 60 'feed_start_element', 'feed_end_element' ); 61 62 xml_set_character_data_handler( $this->parser, 'feed_cdata' ); 63 64 $status = xml_parse( $this->parser, $source ); 65 66 if (! $status ) { 67 $errorcode = xml_get_error_code( $this->parser ); 68 if ( $errorcode != XML_ERROR_NONE ) { 69 $xml_error = xml_error_string( $errorcode ); 70 $error_line = xml_get_current_line_number($this->parser); 71 $error_col = xml_get_current_column_number($this->parser); 72 $errormsg = "$xml_error at line $error_line, column $error_col"; 73 74 $this->error( $errormsg ); 75 } 76 } 77 78 xml_parser_free( $this->parser ); 79 80 $this->normalize(); 81 } 82 83 function feed_start_element($p, $element, &$attrs) { 84 $el = $element = strtolower($element); 85 $attrs = array_change_key_case($attrs, CASE_LOWER); 86 87 // check for a namespace, and split if found 88 $ns = false; 89 if ( strpos( $element, ':' ) ) { 90 list($ns, $el) = split( ':', $element, 2); 91 } 92 if ( $ns and $ns != 'rdf' ) { 93 $this->current_namespace = $ns; 94 } 95 96 # if feed type isn't set, then this is first element of feed 97 # identify feed from root element 98 # 99 if (!isset($this->feed_type) ) { 100 if ( $el == 'rdf' ) { 101 $this->feed_type = RSS; 102 $this->feed_version = '1.0'; 103 } 104 elseif ( $el == 'rss' ) { 105 $this->feed_type = RSS; 106 $this->feed_version = $attrs['version']; 107 } 108 elseif ( $el == 'feed' ) { 109 $this->feed_type = ATOM; 110 $this->feed_version = $attrs['version']; 111 $this->inchannel = true; 112 } 113 return; 114 } 115 116 if ( $el == 'channel' ) 117 { 118 $this->inchannel = true; 119 } 120 elseif ($el == 'item' or $el == 'entry' ) 121 { 122 $this->initem = true; 123 if ( isset($attrs['rdf:about']) ) { 124 $this->current_item['about'] = $attrs['rdf:about']; 125 } 126 } 127 128 // if we're in the default namespace of an RSS feed, 129 // record textinput or image fields 130 elseif ( 131 $this->feed_type == RSS and 132 $this->current_namespace == '' and 133 $el == 'textinput' ) 134 { 135 $this->intextinput = true; 136 } 137 138 elseif ( 139 $this->feed_type == RSS and 140 $this->current_namespace == '' and 141 $el == 'image' ) 142 { 143 $this->inimage = true; 144 } 145 146 # handle atom content constructs 147 elseif ( $this->feed_type == ATOM and in_array($el, $this->_CONTENT_CONSTRUCTS) ) 148 { 149 // avoid clashing w/ RSS mod_content 150 if ($el == 'content' ) { 151 $el = 'atom_content'; 152 } 153 154 $this->incontent = $el; 155 156 157 } 158 159 // if inside an Atom content construct (e.g. content or summary) field treat tags as text 160 elseif ($this->feed_type == ATOM and $this->incontent ) 161 { 162 // if tags are inlined, then flatten 163 $attrs_str = join(' ', 164 array_map('map_attrs', 165 array_keys($attrs), 166 array_values($attrs) ) ); 167 168 $this->append_content( "<$element $attrs_str>" ); 169 170 array_unshift( $this->stack, $el ); 171 } 172 173 // Atom support many links per containging element. 174 // Magpie treats link elements of type rel='alternate' 175 // as being equivalent to RSS's simple link element. 176 // 177 elseif ($this->feed_type == ATOM and $el == 'link' ) 178 { 179 if ( isset($attrs['rel']) and $attrs['rel'] == 'alternate' ) 180 { 181 $link_el = 'link'; 182 } 183 else { 184 $link_el = 'link_' . $attrs['rel']; 185 } 186 187 $this->append($link_el, $attrs['href']); 188 } 189 // set stack[0] to current element 190 else { 191 array_unshift($this->stack, $el); 192 } 193 } 194 195 196 197 function feed_cdata ($p, $text) { 198 199 if ($this->feed_type == ATOM and $this->incontent) 200 { 201 $this->append_content( $text ); 202 } 203 else { 204 $current_el = join('_', array_reverse($this->stack)); 205 $this->append($current_el, $text); 206 } 207 } 208 209 function feed_end_element ($p, $el) { 210 $el = strtolower($el); 211 212 if ( $el == 'item' or $el == 'entry' ) 213 { 214 $this->items[] = $this->current_item; 215 $this->current_item = array(); 216 $this->initem = false; 217 } 218 elseif ($this->feed_type == RSS and $this->current_namespace == '' and $el == 'textinput' ) 219 { 220 $this->intextinput = false; 221 } 222 elseif ($this->feed_type == RSS and $this->current_namespace == '' and $el == 'image' ) 223 { 224 $this->inimage = false; 225 } 226 elseif ($this->feed_type == ATOM and in_array($el, $this->_CONTENT_CONSTRUCTS) ) 227 { 228 $this->incontent = false; 229 } 230 elseif ($el == 'channel' or $el == 'feed' ) 231 { 232 $this->inchannel = false; 233 } 234 elseif ($this->feed_type == ATOM and $this->incontent ) { 235 // balance tags properly 236 // note: i don't think this is actually neccessary 237 if ( $this->stack[0] == $el ) 238 { 239 $this->append_content("</$el>"); 240 } 241 else { 242 $this->append_content("<$el />"); 243 } 244 245 array_shift( $this->stack ); 246 } 247 else { 248 array_shift( $this->stack ); 249 } 250 251 $this->current_namespace = false; 252 } 253 254 function concat (&$str1, $str2="") { 255 if (!isset($str1) ) { 256 $str1=""; 257 } 258 $str1 .= $str2; 259 } 260 261 function append_content($text) { 262 if ( $this->initem ) { 263 $this->concat( $this->current_item[ $this->incontent ], $text ); 264 } 265 elseif ( $this->inchannel ) { 266 $this->concat( $this->channel[ $this->incontent ], $text ); 267 } 268 } 269 270 // smart append - field and namespace aware 271 function append($el, $text) { 272 if (!$el) { 273 return; 274 } 275 if ( $this->current_namespace ) 276 { 277 if ( $this->initem ) { 278 $this->concat( 279 $this->current_item[ $this->current_namespace ][ $el ], $text); 280 } 281 elseif ($this->inchannel) { 282 $this->concat( 283 $this->channel[ $this->current_namespace][ $el ], $text ); 284 } 285 elseif ($this->intextinput) { 286 $this->concat( 287 $this->textinput[ $this->current_namespace][ $el ], $text ); 288 } 289 elseif ($this->inimage) { 290 $this->concat( 291 $this->image[ $this->current_namespace ][ $el ], $text ); 292 } 293 } 294 else { 295 if ( $this->initem ) { 296 $this->concat( 297 $this->current_item[ $el ], $text); 298 } 299 elseif ($this->intextinput) { 300 $this->concat( 301 $this->textinput[ $el ], $text ); 302 } 303 elseif ($this->inimage) { 304 $this->concat( 305 $this->image[ $el ], $text ); 306 } 307 elseif ($this->inchannel) { 308 $this->concat( 309 $this->channel[ $el ], $text ); 310 } 311 312 } 313 } 314 315 function normalize () { 316 // if atom populate rss fields 317 if ( $this->is_atom() ) { 318 $this->channel['descripton'] = $this->channel['tagline']; 319 for ( $i = 0; $i < count($this->items); $i++) { 320 $item = $this->items[$i]; 321 if ( isset($item['summary']) ) 322 $item['description'] = $item['summary']; 323 if ( isset($item['atom_content'])) 324 $item['content']['encoded'] = $item['atom_content']; 325 326 $this->items[$i] = $item; 327 } 328 } 329 elseif ( $this->is_rss() ) { 330 $this->channel['tagline'] = $this->channel['description']; 331 for ( $i = 0; $i < count($this->items); $i++) { 332 $item = $this->items[$i]; 333 if ( isset($item['description'])) 334 $item['summary'] = $item['description']; 335 if ( isset($item['content']['encoded'] ) ) 336 $item['atom_content'] = $item['content']['encoded']; 337 338 $this->items[$i] = $item; 339 } 340 } 341 } 342 343 function is_rss () { 344 if ( $this->feed_type == RSS ) { 345 return $this->feed_version; 346 } 347 else { 348 return false; 349 } 350 } 351 352 function is_atom() { 353 if ( $this->feed_type == ATOM ) { 354 return $this->feed_version; 355 } 356 else { 357 return false; 358 } 359 } 360 361 function map_attrs($k, $v) { 362 return "$k=\"$v\""; 363 } 364 365 function error( $errormsg, $lvl = E_USER_WARNING ) { 366 // append PHP's error message if track_errors enabled 367 if ( isset($php_errormsg) ) { 368 $errormsg .= " ($php_errormsg)"; 369 } 370 if ( MAGPIE_DEBUG ) { 371 trigger_error( $errormsg, $lvl); 372 } else { 373 error_log( $errormsg, 0); 374 } 375 } 376 377 } 378 require_once( dirname(__FILE__) . '/class-snoopy.php'); 379 380 if ( !function_exists('fetch_rss') ) : 381 function fetch_rss ($url) { 382 // initialize constants 383 init(); 384 385 if ( !isset($url) ) { 386 // error("fetch_rss called without a url"); 387 return false; 388 } 389 390 // if cache is disabled 391 if ( !MAGPIE_CACHE_ON ) { 392 // fetch file, and parse it 393 $resp = _fetch_remote_file( $url ); 394 if ( is_success( $resp->status ) ) { 395 return _response_to_rss( $resp ); 396 } 397 else { 398 // error("Failed to fetch $url and cache is off"); 399 return false; 400 } 401 } 402 // else cache is ON 403 else { 404 // Flow 405 // 1. check cache 406 // 2. if there is a hit, make sure its fresh 407 // 3. if cached obj fails freshness check, fetch remote 408 // 4. if remote fails, return stale object, or error 409 410 $cache = new RSSCache( MAGPIE_CACHE_DIR, MAGPIE_CACHE_AGE ); 411 412 if (MAGPIE_DEBUG and $cache->ERROR) { 413 debug($cache->ERROR, E_USER_WARNING); 414 } 415 416 417 $cache_status = 0; // response of check_cache 418 $request_headers = array(); // HTTP headers to send with fetch 419 $rss = 0; // parsed RSS object 420 $errormsg = 0; // errors, if any 421 422 if (!$cache->ERROR) { 423 // return cache HIT, MISS, or STALE 424 $cache_status = $cache->check_cache( $url ); 425 } 426 427 // if object cached, and cache is fresh, return cached obj 428 if ( $cache_status == 'HIT' ) { 429 $rss = $cache->get( $url ); 430 if ( isset($rss) and $rss ) { 431 $rss->from_cache = 1; 432 if ( MAGPIE_DEBUG > 1) { 433 debug("MagpieRSS: Cache HIT", E_USER_NOTICE); 434 } 435 return $rss; 436 } 437 } 438 439 // else attempt a conditional get 440 441 // setup headers 442 if ( $cache_status == 'STALE' ) { 443 $rss = $cache->get( $url ); 444 if ( $rss->etag and $rss->last_modified ) { 445 $request_headers['If-None-Match'] = $rss->etag; 446 $request_headers['If-Last-Modified'] = $rss->last_modified; 447 } 448 } 449 450 $resp = _fetch_remote_file( $url, $request_headers ); 451 452 if (isset($resp) and $resp) { 453 if ($resp->status == '304' ) { 454 // we have the most current copy 455 if ( MAGPIE_DEBUG > 1) { 456 debug("Got 304 for $url"); 457 } 458 // reset cache on 304 (at minutillo insistent prodding) 459 $cache->set($url, $rss); 460 return $rss; 461 } 462 elseif ( is_success( $resp->status ) ) { 463 $rss = _response_to_rss( $resp ); 464 if ( $rss ) { 465 if (MAGPIE_DEBUG > 1) { 466 debug("Fetch successful"); 467 } 468 // add object to cache 469 $cache->set( $url, $rss ); 470 return $rss; 471 } 472 } 473 else { 474 $errormsg = "Failed to fetch $url. "; 475 if ( $resp->error ) { 476 # compensate for Snoopy's annoying habbit to tacking 477 # on '\n' 478 $http_error = substr($resp->error, 0, -2); 479 $errormsg .= "(HTTP Error: $http_error)"; 480 } 481 else { 482 $errormsg .= "(HTTP Response: " . $resp->response_code .')'; 483 } 484 } 485 } 486 else { 487 $errormsg = "Unable to retrieve RSS file for unknown reasons."; 488 } 489 490 // else fetch failed 491 492 // attempt to return cached object 493 if ($rss) { 494 if ( MAGPIE_DEBUG ) { 495 debug("Returning STALE object for $url"); 496 } 497