PHP Cross Reference of WordPress Subversion HEAD

[ Index ]     [ Classes ]     [ Functions ]     [ Variables ]     [ Constants ]

title

Body

[close]

/wp-includes/ -> rss.php (source)

   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