WordPress.org

Make WordPress Core

Ticket #5378: 5378.r6603.diff

File 5378.r6603.diff, 89.5 KB (added by hansengel, 6 years ago)

Patch version of rss.php, so changes are visible

  • wp-includes/rss.php

     
    11<?php 
    2 /** 
    3  * MagpieRSS: a simple RSS integration tool 
     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 
    49 * 
    5  * A compiled file for RSS syndication 
     10 * Provenance: 
    611 * 
    7  * @author Kellan Elliott-McCrea <kellan@protest.net> 
    8  * @version 0.51 
    9  * @license GPL 
     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! 
    1022 * 
    11  * @package External 
    12  * @subpackage MagpieRSS 
     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! 
    1387 */ 
    1488 
    15 /* 
    16  * Hook to use another RSS object instead of MagpieRSS 
    17  */ 
    18 do_action('load_feed_engine'); 
    19  
    20  
    2189define('RSS', 'RSS'); 
    2290define('ATOM', 'Atom'); 
    23 define('MAGPIE_USER_AGENT', 'WordPress/' . $GLOBALS['wp_version']); 
    2491 
    25 class MagpieRSS { 
    26         var $parser; 
    27         var $current_item       = array();      // item currently being parsed 
    28         var $items                      = array();      // collection of parsed items 
    29         var $channel            = array();      // hash of channel fields 
    30         var $textinput          = array(); 
    31         var $image                      = array(); 
    32         var $feed_type; 
    33         var $feed_version; 
     92################################################################################ 
     93## WordPress: make some settings WordPress-appropriate ######################### 
     94################################################################################ 
    3495 
    35         // parser variables 
    36         var $stack                              = array(); // parser stack 
    37         var $inchannel                  = false; 
    38         var $initem                     = false; 
    39         var $incontent                  = false; // if in Atom <content mode="xml"> field 
    40         var $intextinput                = false; 
    41         var $inimage                    = false; 
    42         var $current_field              = ''; 
    43         var $current_namespace  = false; 
     96define('MAGPIE_USER_AGENT', 'WordPress/' . $wp_version . '(+http://www.wordpress.org)'); 
    4497 
    45         //var $ERROR = ""; 
     98$wp_encoding = get_settings('blog_charset'); 
     99define('MAGPIE_OUTPUT_ENCODING', ($wp_encoding?$wp_encoding:'ISO-8859-1')); 
    46100 
    47         var $_CONTENT_CONSTRUCTS = array('content', 'summary', 'info', 'title', 'tagline', 'copyright'); 
     101################################################################################ 
     102## rss_parse.inc: from MagpieRSS 0.85 ########################################## 
     103################################################################################ 
    48104 
    49         function MagpieRSS ($source) { 
     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*/ 
     111class 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    ); 
    50149 
    51                 # if PHP xml isn't compiled in, die 
    52                 # 
    53                 if ( !function_exists('xml_parser_create') ) 
    54                         trigger_error( "Failed to load PHP's XML Extension. http://www.php.net/manual/en/ref.xml.php" ); 
     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    ); 
    55190 
    56                 $parser = @xml_parser_create(); 
     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'); 
    57198 
    58                 if ( !is_resource($parser) ) 
    59                         trigger_error( "Failed to create an instance of PHP's XML parser. http://www.php.net/manual/en/ref.xml.php"); 
     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 
    60201 
     202    var $inchannel          = false; 
     203    var $initem             = false; 
    61204 
    62                 $this->parser = $parser; 
     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" 
    63208 
    64                 # pass in parser, and a reference to this object 
    65                 # setup handlers 
    66                 # 
    67                 xml_set_object( $this->parser, $this ); 
    68                 xml_set_element_handler($this->parser, 
    69                                 'feed_start_element', 'feed_end_element' ); 
     209    var $intextinput        = false; 
     210    var $inimage            = false; 
     211    var $root_namespaces    = array(); 
     212    var $current_namespace  = false; 
     213    var $working_namespace_table = array(); 
    70214 
    71                 xml_set_character_data_handler( $this->parser, 'feed_cdata' ); 
     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        } 
    72263 
    73                 $status = xml_parse( $this->parser, $source ); 
     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' );  
    74275 
    75                 if (! $status ) { 
    76                         $errorcode = xml_get_error_code( $this->parser ); 
    77                         if ( $errorcode != XML_ERROR_NONE ) { 
    78                                 $xml_error = xml_error_string( $errorcode ); 
    79                                 $error_line = xml_get_current_line_number($this->parser); 
    80                                 $error_col = xml_get_current_column_number($this->parser); 
    81                                 $errormsg = "$xml_error at line $error_line, column $error_col"; 
     276        $this->stack['xml:base'] = array($base_uri); 
    82277 
    83                                 $this->error( $errormsg ); 
    84                         } 
    85                 } 
     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"; 
    86287 
    87                 xml_parser_free( $this->parser ); 
     288                $this->error( $errormsg ); 
     289            } 
     290        } 
     291         
     292        xml_parser_free( $this->parser ); 
    88293 
    89                 $this->normalize(); 
    90         } 
     294        $this->normalize(); 
     295    } 
     296     
     297    function feed_start_element($p, $element, &$attributes) { 
     298        $el = strtolower($element); 
    91299 
    92         function feed_start_element($p, $element, &$attrs) { 
    93                 $el = $element = strtolower($element); 
    94                 $attrs = array_change_key_case($attrs, CASE_LOWER); 
     300        $namespaces = end($this->stack['xmlns']); 
     301        $baseuri = end($this->stack['xml:base']); 
    95302 
    96                 // check for a namespace, and split if found 
    97                 $ns     = false; 
    98                 if ( strpos( $element, ':' ) ) { 
    99                         list($ns, $el) = split( ':', $element, 2); 
    100                 } 
    101                 if ( $ns and $ns != 'rdf' ) { 
    102                         $this->current_namespace = $ns; 
    103                 } 
     303        if (isset($attributes['xml:base'])) { 
     304                $baseuri = Relative_URI::resolve($attributes['xml:base'], $baseuri); 
     305        } 
     306        array_push($this->stack['xml:base'], $baseuri); 
    104307 
    105                 # if feed type isn't set, then this is first element of feed 
    106                 # identify feed from root element 
    107                 # 
    108                 if (!isset($this->feed_type) ) { 
    109                         if ( $el == 'rdf' ) { 
    110                                 $this->feed_type = RSS; 
    111                                 $this->feed_version = '1.0'; 
    112                         } 
    113                         elseif ( $el == 'rss' ) { 
    114                                 $this->feed_type = RSS; 
    115                                 $this->feed_version = $attrs['version']; 
    116                         } 
    117                         elseif ( $el == 'feed' ) { 
    118                                 $this->feed_type = ATOM; 
    119                                 $this->feed_version = $attrs['version']; 
    120                                 $this->inchannel = true; 
    121                         } 
    122                         return; 
     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; 
    123316                } 
     317        } 
    124318 
    125                 if ( $el == 'channel' ) 
    126                 { 
    127                         $this->inchannel = true; 
    128                 } 
    129                 elseif ($el == 'item' or $el == 'entry' ) 
    130                 { 
    131                         $this->initem = true; 
    132                         if ( isset($attrs['rdf:about']) ) { 
    133                                 $this->current_item['about'] = $attrs['rdf:about']; 
    134                         } 
    135                 } 
     319        array_push($this->stack['xmlns'], $namespaces); 
    136320 
    137                 // if we're in the default namespace of an RSS feed, 
    138                 //  record textinput or image fields 
    139                 elseif ( 
    140                         $this->feed_type == RSS and 
    141                         $this->current_namespace == '' and 
    142                         $el == 'textinput' ) 
    143                 { 
    144                         $this->intextinput = true; 
    145                 } 
     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        } 
    146328 
    147                 elseif ( 
    148                         $this->feed_type == RSS and 
    149                         $this->current_namespace == '' and 
    150                         $el == 'image' ) 
    151                 { 
    152                         $this->inimage = true; 
     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; 
    153333                } 
    154  
    155                 # handle atom content constructs 
    156                 elseif ( $this->feed_type == ATOM and in_array($el, $this->_CONTENT_CONSTRUCTS) ) 
    157                 { 
    158                         // avoid clashing w/ RSS mod_content 
    159                         if ($el == 'content' ) { 
    160                                 $el = 'atom_content'; 
     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); 
    161337                        } 
     338                } 
     339        } 
    162340 
    163                         $this->incontent = $el; 
     341        $attrs = array_change_key_case($attributes, CASE_LOWER); 
    164342 
     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        } 
    165371 
    166                 } 
     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                  } 
    167387 
    168                 // if inside an Atom content construct (e.g. content or summary) field treat tags as text 
    169                 elseif ($this->feed_type == ATOM and $this->incontent ) 
    170                 { 
    171                         // if tags are inlined, then flatten 
    172                         $attrs_str = join(' ', 
    173                                         array_map('map_attrs', 
    174                                         array_keys($attrs), 
    175                                         array_values($attrs) ) ); 
     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        } 
    176400 
    177                         $this->append_content( "<$element $attrs_str>"  ); 
     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        } 
    178412 
    179                         array_unshift( $this->stack, $el ); 
    180                 } 
     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. 
    181436 
    182                 // Atom support many links per containging element. 
    183                 // Magpie treats link elements of type rel='alternate' 
    184                 // as being equivalent to RSS's simple link element. 
    185                 // 
    186                 elseif ($this->feed_type == ATOM and $el == 'link' ) 
    187                 { 
    188                         if ( isset($attrs['rel']) and $attrs['rel'] == 'alternate' ) 
    189                         { 
    190                                 $link_el = 'link'; 
    191                         } 
    192                         else { 
    193                                 $link_el = 'link_' . $attrs['rel']; 
    194                         } 
     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                } 
    195451 
    196                         $this->append($link_el, $attrs['href']); 
    197                 } 
    198                 // set stack[0] to current element 
    199                 else { 
    200                         array_unshift($this->stack, $el); 
    201                 } 
    202         } 
     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); 
    203459 
     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 
    204471 
     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    } 
    205504 
    206         function feed_cdata ($p, $text) { 
     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); 
    207517 
    208                 if ($this->feed_type == ATOM and $this->incontent) 
    209                 { 
    210                         $this->append_content( $text ); 
    211                 } 
    212                 else { 
    213                         $current_el = join('_', array_reverse($this->stack)); 
    214                         $this->append($current_el, $text); 
    215                 } 
    216         } 
     518            if ( $this->incontent ) { 
     519                    $opener = array_pop($this->incontent); 
    217520 
    218         function feed_end_element ($p, $el) { 
    219                 $el = strtolower($el); 
    220  
    221                 if ( $el == 'item' or $el == 'entry' ) 
    222                 { 
    223                         $this->items[] = $this->current_item; 
    224                         $this->current_item = array(); 
    225                         $this->initem = false; 
     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); 
    226586                } 
    227                 elseif ($this->feed_type == RSS and $this->current_namespace == '' and $el == 'textinput' ) 
    228                 { 
    229                         $this->intextinput = false; 
    230                 } 
    231                 elseif ($this->feed_type == RSS and $this->current_namespace == '' and $el == 'image' ) 
    232                 { 
    233                         $this->inimage = false; 
    234                 } 
    235                 elseif ($this->feed_type == ATOM and in_array($el, $this->_CONTENT_CONSTRUCTS) ) 
    236                 { 
    237                         $this->incontent = false; 
    238                 } 
    239                 elseif ($el == 'channel' or $el == 'feed' ) 
    240                 { 
    241                         $this->inchannel = false; 
    242                 } 
    243                 elseif ($this->feed_type == ATOM and $this->incontent  ) { 
    244                         // balance tags properly 
    245                         // note:  i don't think this is actually neccessary 
    246                         if ( $this->stack[0] == $el ) 
    247                         { 
    248                                 $this->append_content("</$el>"); 
    249                         } 
    250                         else { 
    251                                 $this->append_content("<$el />"); 
    252                         } 
     587                 
     588                $uri = (isset($namespaces[$ns]) ? $namespaces[$ns] : null); 
    253589 
    254                         array_shift( $this->stack ); 
     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; 
    255598                } 
    256                 else { 
    257                         array_shift( $this->stack ); 
    258                 } 
    259599 
    260                 $this->current_namespace = false; 
    261         } 
    262  
    263         function concat (&$str1, $str2="") { 
    264                 if (!isset($str1) ) { 
    265                         $str1=""; 
     600                if (in_array($canonical, $this->root_namespaces)) { 
     601                        $effective = ''; 
     602                } else { 
     603                        $effective = $canonical; 
    266604                } 
    267                 $str1 .= $str2; 
    268         } 
    269605 
    270         function append_content($text) { 
    271                 if ( $this->initem ) { 
    272                         $this->concat( $this->current_item[ $this->incontent ], $text ); 
    273                 } 
    274                 elseif ( $this->inchannel ) { 
    275                         $this->concat( $this->channel[ $this->incontent ], $text ); 
    276                 } 
     606                return array('effective' => $effective, 'canonical' => $canonical, 'prefix' => $ns, 'uri' => $uri, 'element' => $element); 
    277607        } 
    278608 
    279         // smart append - field and namespace aware 
    280         function append($el, $text) { 
    281                 if (!$el) { 
    282                         return; 
    283                 } 
    284                 if ( $this->current_namespace ) 
    285                 { 
    286                         if ( $this->initem ) { 
    287                                 $this->concat( 
    288                                         $this->current_item[ $this->current_namespace ][ $el ], $text); 
     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']; 
    289618                        } 
    290                         elseif ($this->inchannel) { 
    291                                 $this->concat( 
    292                                         $this->channel[ $this->current_namespace][ $el ], $text ); 
    293                         } 
    294                         elseif ($this->intextinput) { 
    295                                 $this->concat( 
    296                                         $this->textinput[ $this->current_namespace][ $el ], $text ); 
    297                         } 
    298                         elseif ($this->inimage) { 
    299                                 $this->concat( 
     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} ( 
    300641                                        $this->image[ $this->current_namespace ][ $el ], $text ); 
     642                                } 
    301643                        } 
    302                 } 
    303                 else { 
    304                         if ( $this->initem ) { 
    305                                 $this->concat( 
     644                        else { 
     645                                if ( $this->initem ) { 
     646                                        $ret = $this->{$method} ( 
    306647                                        $this->current_item[ $el ], $text); 
    307                         } 
    308                         elseif ($this->intextinput) { 
    309                                 $this->concat( 
     648                                } 
     649                                elseif ($this->intextinput) { 
     650                                        $ret = $this->{$method} ( 
    310651                                        $this->textinput[ $el ], $text ); 
    311                         } 
    312                         elseif ($this->inimage) { 
    313                                 $this->concat( 
     652                                } 
     653                                elseif ($this->inimage) { 
     654                                        $ret = $this->{$method} ( 
    314655                                        $this->image[ $el ], $text ); 
    315                         } 
    316                         elseif ($this->inchannel) { 
    317                                 $this->concat( 
     656                                } 
     657                                elseif ($this->inchannel) { 
     658                                        $ret = $this->{$method} ( 
    318659                                        $this->channel[ $el ], $text ); 
     660                                } 
    319661                        } 
    320  
    321662                } 
     663                return $ret; 
    322664        } 
     665         
     666    function concat (&$str1, $str2="") { 
     667        if (!isset($str1) ) { 
     668            $str1=""; 
     669        } 
     670        $str1 .= $str2; 
     671    } 
    323672 
    324         function normalize () { 
    325                 // if atom populate rss fields 
    326                 if ( $this->is_atom() ) { 
    327                         $this->channel['descripton'] = $this->channel['tagline']; 
    328                         for ( $i = 0; $i < count($this->items); $i++) { 
    329                                 $item = $this->items[$i]; 
    330                                 if ( isset($item['summary']) ) 
    331                                         $item['description'] = $item['summary']; 
    332                                 if ( isset($item['atom_content'])) 
    333                                         $item['content']['encoded'] = $item['atom_content']; 
     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        } 
    334682 
    335                                 $this->items[$i] = $item; 
    336                         } 
    337                 } 
    338                 elseif ( $this->is_rss() ) { 
    339                         $this->channel['tagline'] = $this->channel['description']; 
    340                         for ( $i = 0; $i < count($this->items); $i++) { 
    341                                 $item = $this->items[$i]; 
    342                                 if ( isset($item['description'])) 
    343                                         $item['summary'] = $item['description']; 
    344                                 if ( isset($item['content']['encoded'] ) ) 
    345                                         $item['atom_content'] = $item['content']['encoded']; 
    346683 
    347                                 $this->items[$i] = $item; 
    348                         } 
    349                 } 
    350         } 
     684    function append_content($text) { 
     685            $construct = reset($this->incontent); 
     686            $ns = $construct['effective']; 
    351687 
    352         function is_rss () { 
    353                 if ( $this->feed_type == RSS ) { 
    354                         return $this->feed_version; 
    355                 } 
    356                 else { 
    357                         return false; 
    358                 } 
    359         } 
     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'])); 
    360691 
    361         function is_atom() { 
    362                 if ( $this->feed_type == ATOM ) { 
    363                         return $this->feed_version; 
     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 ); 
    364697                } 
    365                 else { 
    366                         return false; 
     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 ); 
    367704                } 
    368         } 
     705            } 
     706    } 
     707     
     708    // smart append - field and namespace aware 
     709    function append($el, $text) { 
     710        $this->magpie_data($el, 'concat', $text); 
     711    } 
    369712 
    370         function map_attrs($k, $v) { 
    371                 return "$k=\"$v\""; 
     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)); 
    372718        } 
    373719 
    374         function error( $errormsg, $lvl = E_USER_WARNING ) { 
    375                 // append PHP's error message if track_errors enabled 
    376                 if ( isset($php_errormsg) ) { 
    377                         $errormsg .= " ($php_errormsg)"; 
    378                 } 
    379                 if ( MAGPIE_DEBUG ) { 
    380                         trigger_error( $errormsg, $lvl); 
    381                 } else { 
    382                         error_log( $errormsg, 0); 
    383                 } 
     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); 
    384724        } 
     725        $ret = $this->magpie_data($el, array('key' => 'counter_key', 'value' => 'retrieve_value')); 
     726        return ($ret ? $ret : 0); 
     727    } 
    385728 
    386 } 
    387 require_once( dirname(__FILE__) . '/class-snoopy.php'); 
     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    } 
    388748 
    389 if ( !function_exists('fetch_rss') ) : 
    390 function fetch_rss ($url) { 
    391         // initialize constants 
    392         init(); 
     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); 
    393752 
    394         if ( !isset($url) ) { 
    395                 // error("fetch_rss called without a url"); 
    396                 return false; 
    397         } 
     753            // Atom 0.3 <=> Atom 1.0 
     754        if ($this->feed_version >= 1.0) { $used = 'uri'; $norm = 'url'; } 
     755        else { $used = 'url'; $norm = 'uri'; } 
    398756 
    399         // if cache is disabled 
    400         if ( !MAGPIE_CACHE_ON ) { 
    401                 // fetch file, and parse it 
    402                 $resp = _fetch_remote_file( $url ); 
    403                 if ( is_success( $resp->status ) ) { 
    404                         return _response_to_rss( $resp ); 
    405                 } 
    406                 else { 
    407                         // error("Failed to fetch $url and cache is off"); 
    408                         return false; 
    409                 } 
    410         } 
    411         // else cache is ON 
    412         else { 
    413                 // Flow 
    414                 // 1. check cache 
    415                 // 2. if there is a hit, make sure its fresh 
    416                 // 3. if cached obj fails freshness check, fetch remote 
    417                 // 4. if remote fails, return stale object, or error 
     757        if (isset($source["{$id}_{$used}"])) { 
     758            $dest["{$id_to}_{$norm}"] = $source["{$id}_{$used}"]; 
     759        } 
    418760 
    419                 $cache = new RSSCache( MAGPIE_CACHE_DIR, MAGPIE_CACHE_AGE ); 
     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    } 
    420775 
    421                 if (MAGPIE_DEBUG and $cache->ERROR) { 
    422                         debug($cache->ERROR, E_USER_WARNING); 
    423                 } 
     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); 
    424780 
     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        } 
    425793 
    426                 $cache_status    = 0;           // response of check_cache 
    427                 $request_headers = array(); // HTTP headers to send with fetch 
    428                 $rss                     = 0;           // parsed RSS object 
    429                 $errormsg                = 0;           // errors, if any 
     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); 
    430802 
    431                 if (!$cache->ERROR) { 
    432                         // return cache HIT, MISS, or STALE 
    433                         $cache_status = $cache->check_cache( $url ); 
    434                 } 
     803        $dest[$cat_id] = $source[$dc_id];       // RSS 2.0 
     804        $dest["{$cat_id}@term"] = $source[$dc_id];  // Atom 1.0 
     805    } 
    435806 
    436                 // if object cached, and cache is fresh, return cached obj 
    437                 if ( $cache_status == 'HIT' ) { 
    438                         $rss = $cache->get( $url ); 
    439                         if ( isset($rss) and $rss ) { 
    440                                 $rss->from_cache = 1; 
    441                                 if ( MAGPIE_DEBUG > 1) { 
    442                                 debug("MagpieRSS: Cache HIT", E_USER_NOTICE); 
    443                         } 
    444                                 return $rss; 
    445                         } 
    446                 } 
     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; } 
    447818 
    448                 // else attempt a conditional get 
     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    } 
    449831 
    450                 // setup headers 
    451                 if ( $cache_status == 'STALE' ) { 
    452                         $rss = $cache->get( $url ); 
    453                         if ( $rss->etag and $rss->last_modified ) { 
    454                                 $request_headers['If-None-Match'] = $rss->etag; 
    455                                 $request_headers['If-Last-Modified'] = $rss->last_modified; 
     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                            } 
    456900                        } 
    457901                } 
     902            } 
    458903 
    459                 $resp = _fetch_remote_file( $url, $request_headers ); 
     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'); 
    460910 
    461                 if (isset($resp) and $resp) { 
    462                         if ($resp->status == '304' ) { 
    463                                 // we have the most current copy 
    464                                 if ( MAGPIE_DEBUG > 1) { 
    465                                         debug("Got 304 for $url"); 
    466                                 } 
    467                                 // reset cache on 304 (at minutillo insistent prodding) 
    468                                 $cache->set($url, $rss); 
    469                                 return $rss; 
     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'); 
    470947                        } 
    471                         elseif ( is_success( $resp->status ) ) { 
    472                                 $rss = _response_to_rss( $resp ); 
    473                                 if ( $rss ) { 
    474                                         if (MAGPIE_DEBUG > 1) { 
    475                                                 debug("Fetch successful"); 
    476                                         } 
    477                                         // add object to cache 
    478                                         $cache->set( $url, $rss ); 
    479                                         return $rss; 
    480                                 } 
     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'); 
    481950                        } 
    482                         else { 
    483                                 $errormsg = "Failed to fetch $url. "; 
    484                                 if ( $resp->error ) { 
    485                                         # compensate for Snoopy's annoying habbit to tacking 
    486                                         # on '\n' 
    487                                         $http_error = substr($resp->error, 0, -2); 
    488                                         $errormsg .= "(HTTP Error: $http_error)"; 
    489                                 } 
    490                                 else { 
    491                                         $errormsg .=  "(HTTP Response: " . $resp->response_code .')'; 
    492                                 } 
     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                            } 
    493958                        } 
     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; 
    494967                } 
    495                 else { 
    496                         $errormsg = "Unable to retrieve RSS file for unknown reasons."; 
    497                 } 
     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    } 
    498989 
    499                 // else fetch failed 
     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    } 
    5001101 
    501                 // attempt to return cached object 
    502                 if ($rss) { 
    503                         if ( MAGPIE_DEBUG ) { 
    504                                 debug("Returning STALE object for $url"); 
     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; 
    5051140                        } 
    506                         return $rss; 
     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 
    5071149                } 
     1150                return $ret; 
     1151        } 
     1152} // end class RSS 
    5081153 
    509                 // else we totally failed 
    510                 // error( $errormsg ); 
    5111154 
    512                 return false; 
     1155// patch to support medieval versions of PHP4.1.x,  
     1156// courtesy, Ryan Currie, ryan@digibliss.com 
    5131157 
    514         } // end if ( !MAGPIE_CACHE_ON ) { 
    515 } // end fetch_rss() 
    516 endif; 
     1158if (!function_exists('array_change_key_case')) { 
     1159    define("CASE_UPPER",1); 
     1160    define("CASE_LOWER",0); 
    5171161 
    518 function _fetch_remote_file ($url, $headers = "" ) { 
    519         // Snoopy is an HTTP client in PHP 
    520         $client = new Snoopy(); 
    521         $client->agent = MAGPIE_USER_AGENT; 
    522         $client->read_timeout = MAGPIE_FETCH_TIME_OUT; 
    523         $client->use_gzip = MAGPIE_USE_GZIP; 
    524         if (is_array($headers) ) { 
    525                 $client->rawheaders = $headers; 
    526         } 
    5271162 
    528         @$client->fetch($url); 
    529         return $client; 
     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    } 
    5301171 
    5311172} 
    5321173 
    533 function _response_to_rss ($resp) { 
    534         $rss = new MagpieRSS( $resp->results ); 
     1174################################################################################ 
     1175## WordPress: Load in Snoopy from wp-includes ################################## 
     1176################################################################################ 
    5351177 
    536         // if RSS parsed successfully 
    537         if ( $rss and !$rss->ERROR) { 
     1178require_once( ABSPATH . WPINC . '/class-snoopy.php'); 
    5381179 
    539                 // find Etag, and Last-Modified 
    540                 foreach($resp->headers as $h) { 
    541                         // 2003-03-02 - Nicola Asuni (www.tecnick.com) - fixed bug "Undefined offset: 1" 
    542                         if (strpos($h, ": ")) { 
    543                                 list($field, $val) = explode(": ", $h, 2); 
    544                         } 
    545                         else { 
    546                                 $field = $h; 
    547                                 $val = ""; 
    548                         } 
     1180################################################################################ 
     1181## rss_fetch.inc: from MagpieRSS 0.8a ########################################## 
     1182################################################################################ 
    5491183 
    550                         if ( $field == 'ETag' ) { 
    551                                 $rss->etag = $val; 
    552                         } 
     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) 
    5531190 
    554                         if ( $field == 'Last-Modified' ) { 
    555                                 $rss->last_modified = $val; 
    556                         } 
    557                 } 
     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\*=======================================================================*/ 
    5581203 
    559                 return $rss; 
    560         } // else construct error message 
    561         else { 
    562                 $errormsg = "Failed to parse RSS file."; 
     1204define('MAGPIE_VERSION', '0.85'); 
    5631205 
    564                 if ($rss) { 
    565                         $errormsg .= " (" . $rss->ERROR . ")"; 
    566                 } 
    567                 // error($errormsg); 
     1206$MAGPIE_ERROR = ""; 
    5681207 
    569                 return false; 
    570         } // end if ($rss and !$rss->error) 
     1208function 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 
     1348function 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        } 
    5711360} 
    5721361 
     1362function debug ($debugmsg, $lvl=E_USER_NOTICE) { 
     1363    trigger_error("MagpieRSS [debug] $debugmsg", $lvl); 
     1364} 
     1365             
    5731366/*=======================================================================*\ 
    574         Function:       init 
    575         Purpose:        setup constants with default values 
    576                                 check for user overrides 
     1367    Function:   magpie_error 
     1368    Purpose:    accessor for the magpie error variable 
    5771369\*=======================================================================*/ 
    578 function init () { 
    579         if ( defined('MAGPIE_INITALIZED') ) { 
    580                 return; 
    581         } 
    582         else { 
    583                 define('MAGPIE_INITALIZED', 1); 
    584         } 
     1370function 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} 
    5851379 
    586         if ( !defined('MAGPIE_CACHE_ON') ) { 
    587                 define('MAGPIE_CACHE_ON', 1); 
    588         } 
     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\*=======================================================================*/ 
     1387function _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; 
    5891399 
    590         if ( !defined('MAGPIE_CACHE_DIR') ) { 
    591                 define('MAGPIE_CACHE_DIR', './cache'); 
    592         } 
     1400} 
    5931401 
    594         if ( !defined('MAGPIE_CACHE_AGE') ) { 
    595                 define('MAGPIE_CACHE_AGE', 60*60); // one hour 
    596         } 
     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\*=======================================================================*/ 
     1408function _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; 
    5971414 
    598         if ( !defined('MAGPIE_CACHE_FRESH_ONLY') ) { 
    599                 define('MAGPIE_CACHE_FRESH_ONLY', 0); 
    600         } 
     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; 
    6011427 
    602                 if ( !defined('MAGPIE_DEBUG') ) { 
    603                 define('MAGPIE_DEBUG', 0); 
    604         } 
     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} 
    6051450 
    606         if ( !defined('MAGPIE_USER_AGENT') ) { 
    607                 $ua = 'WordPress/' . $GLOBALS['wp_version']; 
     1451/*=======================================================================*\ 
     1452    Function:   init 
     1453    Purpose:    setup constants with default values 
     1454                check for user overrides 
     1455\*=======================================================================*/ 
     1456function 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    } 
    6081467 
    609                 if ( MAGPIE_CACHE_ON ) { 
    610                         $ua = $ua . ')'; 
    611                 } 
    612                 else { 
    613                         $ua = $ua . '; No cache)'; 
    614                 } 
     1468    if ( !defined('MAGPIE_CACHE_DIR') ) { 
     1469        define('MAGPIE_CACHE_DIR', './cache'); 
     1470    } 
    6151471 
    616                 define('MAGPIE_USER_AGENT', $ua); 
    617         } 
     1472    if ( !defined('MAGPIE_CACHE_AGE') ) { 
     1473        define('MAGPIE_CACHE_AGE', 60*60); // one hour 
     1474    } 
    6181475 
    619         if ( !defined('MAGPIE_FETCH_TIME_OUT') ) { 
    620                 define('MAGPIE_FETCH_TIME_OUT', 2);     // 2 second timeout 
    621         } 
     1476    if ( !defined('MAGPIE_CACHE_FRESH_ONLY') ) { 
     1477        define('MAGPIE_CACHE_FRESH_ONLY', false); 
     1478    } 
    6221479 
    623         // use gzip encoding to fetch rss files if supported? 
    624         if ( !defined('MAGPIE_USE_GZIP') ) { 
    625                 define('MAGPIE_USE_GZIP', true); 
    626         } 
     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    } 
    6271517} 
    6281518 
    629 function is_info ($sc) { 
    630         return $sc >= 100 && $sc < 200; 
     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\*=======================================================================*/ 
     1537function is_info ($sc) {  
     1538    return $sc >= 100 && $sc < 200;  
    6311539} 
    6321540 
    633 function is_success ($sc) { 
    634         return $sc >= 200 && $sc < 300; 
     1541/*=======================================================================*\ 
     1542    Function:   is_success 
     1543    Purpose:    return true if Successful status code 
     1544\*=======================================================================*/ 
     1545function is_success ($sc) {  
     1546    return $sc >= 200 && $sc < 300;  
    6351547} 
    6361548 
    637 function is_redirect ($sc) { 
    638         return $sc >= 300 && $sc < 400; 
     1549/*=======================================================================*\ 
     1550    Function:   is_redirect 
     1551    Purpose:    return true if Redirection status code 
     1552\*=======================================================================*/ 
     1553function is_redirect ($sc) {  
     1554    return $sc >= 300 && $sc < 400;  
    6391555} 
    6401556 
    641 function is_error ($sc) { 
    642         return $sc >= 400 && $sc < 600; 
     1557/*=======================================================================*\ 
     1558    Function:   is_error 
     1559    Purpose:    return true if Error status code 
     1560\*=======================================================================*/ 
     1561function is_error ($sc) {  
     1562    return $sc >= 400 && $sc < 600;  
    6431563} 
    6441564 
    645 function is_client_error ($sc) { 
    646         return $sc >= 400 && $sc < 500; 
     1565/*=======================================================================*\ 
     1566    Function:   is_client_error 
     1567    Purpose:    return true if Error status code, and its a client error 
     1568\*=======================================================================*/ 
     1569function is_client_error ($sc) {  
     1570    return $sc >= 400 && $sc < 500;  
    6471571} 
    6481572 
    649 function is_server_error ($sc) { 
    650         return $sc >= 500 && $sc < 600; 
     1573/*=======================================================================*\ 
     1574    Function:   is_client_error 
     1575    Purpose:    return true if Error status code, and its a server error 
     1576\*=======================================================================*/ 
     1577function is_server_error ($sc) {  
     1578    return $sc >= 500 && $sc < 600;  
    6511579} 
    6521580 
     1581################################################################################ 
     1582## rss_cache.inc: from WordPress 1.5 ########################################### 
     1583################################################################################ 
     1584 
    6531585class RSSCache { 
    6541586        var $BASE_CACHE = 'wp-content/cache';   // where the cache files are stored 
    6551587        var $MAX_AGE    = 43200;                // when are files stale, default twelve hours 
    6561588        var $ERROR              = '';                   // accumulate error messages 
    657  
     1589         
    6581590        function RSSCache ($base='', $age='') { 
    6591591                if ( $base ) { 
    6601592                        $this->BASE_CACHE = $base; 
     
    6621594                if ( $age ) { 
    6631595                        $this->MAX_AGE = $age; 
    6641596                } 
    665  
     1597         
    6661598        } 
    667  
     1599         
    6681600/*=======================================================================*\ 
    6691601        Function:       set 
    6701602        Purpose:        add an item to the cache, keyed on url 
    6711603        Input:          url from wich the rss file was fetched 
    672         Output:         true on sucess 
     1604        Output:         true on sucess   
    6731605\*=======================================================================*/ 
    6741606        function set ($url, $rss) { 
    6751607                global $wpdb; 
    6761608                $cache_option = 'rss_' . $this->file_name( $url ); 
    6771609                $cache_timestamp = 'rss_' . $this->file_name( $url ) . '_ts'; 
    678  
    679                 // shouldn't these be using get_option() ? 
    680                 if ( !$wpdb->get_var( $wpdb->prepare( "SELECT option_name FROM $wpdb->options WHERE option_name = %s", $cache_option ) ) ) 
     1610                 
     1611                if ( !$wpdb->get_var("SELECT option_name FROM $wpdb->options WHERE option_name = '$cache_option'") ) 
    6811612                        add_option($cache_option, '', '', 'no'); 
    682                 if ( !$wpdb->get_var( $wpdb->prepare( "SELECT option_name FROM $wpdb->options WHERE option_name = %s", $cache_timestamp ) ) ) 
     1613                if ( !$wpdb->get_var("SELECT option_name FROM $wpdb->options WHERE option_name = '$cache_timestamp'") ) 
    6831614                        add_option($cache_timestamp, '', '', 'no'); 
    684  
     1615                 
    6851616                update_option($cache_option, $rss); 
    6861617                update_option($cache_timestamp, time() ); 
    687  
     1618                 
    6881619                return $cache_option; 
    6891620        } 
    690  
     1621         
    6911622/*=======================================================================*\ 
    6921623        Function:       get 
    6931624        Purpose:        fetch an item from the cache 
    6941625        Input:          url from wich the rss file was fetched 
    695         Output:         cached object on HIT, false on MISS 
    696 \*=======================================================================*/ 
     1626        Output:         cached object on HIT, false on MISS      
     1627\*=======================================================================*/      
    6971628        function get ($url) { 
    6981629                $this->ERROR = ""; 
    6991630                $cache_option = 'rss_' . $this->file_name( $url ); 
    700  
     1631                 
    7011632                if ( ! get_option( $cache_option ) ) { 
    702                         $this->debug( 
     1633                        $this->debug(  
    7031634                                "Cache doesn't contain: $url (cache option: $cache_option)" 
    7041635                        ); 
    7051636                        return 0; 
    7061637                } 
    707  
     1638                 
    7081639                $rss = get_option( $cache_option ); 
    709  
     1640                 
    7101641                return $rss; 
    7111642        } 
    7121643 
     
    7151646        Purpose:        check a url for membership in the cache 
    7161647                                and whether the object is older then MAX_AGE (ie. STALE) 
    7171648        Input:          url from wich the rss file was fetched 
    718         Output:         cached object on HIT, false on MISS 
    719 \*=======================================================================*/ 
     1649        Output:         cached object on HIT, false on MISS      
     1650\*=======================================================================*/              
    7201651        function check_cache ( $url ) { 
    7211652                $this->ERROR = ""; 
    7221653                $cache_option = $this->file_name( $url ); 
     
    7431674 
    7441675/*=======================================================================*\ 
    7451676        Function:       serialize 
    746 \*=======================================================================*/ 
     1677\*=======================================================================*/              
    7471678        function serialize ( $rss ) { 
    7481679                return serialize( $rss ); 
    7491680        } 
    7501681 
    7511682/*=======================================================================*\ 
    7521683        Function:       unserialize 
    753 \*=======================================================================*/ 
     1684\*=======================================================================*/              
    7541685        function unserialize ( $data ) { 
    7551686                return unserialize( $data ); 
    7561687        } 
    757  
     1688         
    7581689/*=======================================================================*\ 
    7591690        Function:       file_name 
    7601691        Purpose:        map url to location in cache 
    7611692        Input:          url from wich the rss file was fetched 
    7621693        Output:         a file name 
    763 \*=======================================================================*/ 
     1694\*=======================================================================*/              
    7641695        function file_name ($url) { 
    7651696                return md5( $url ); 
    7661697        } 
    767  
     1698         
    7681699/*=======================================================================*\ 
    7691700        Function:       error 
    7701701        Purpose:        register error 
    771 \*=======================================================================*/ 
     1702\*=======================================================================*/                      
    7721703        function error ($errormsg, $lvl=E_USER_WARNING) { 
    7731704                // append PHP's error message if track_errors enabled 
    774                 if ( isset($php_errormsg) ) { 
     1705                if ( isset($php_errormsg) ) {  
    7751706                        $errormsg .= " ($php_errormsg)"; 
    7761707                } 
    7771708                $this->ERROR = $errormsg; 
     
    7891720        } 
    7901721} 
    7911722 
    792 if ( !function_exists('parse_w3cdtf') ) : 
    793 function parse_w3cdtf ( $date_str ) { 
     1723################################################################################ 
     1724## rss_utils.inc: from MagpieRSS 0.8a ########################################## 
     1725################################################################################ 
    7941726 
    795         # regex to match wc3dtf 
    796         $pat = "/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(:(\d{2}))?(?:([-+])(\d{2}):?(\d{2})|(Z))?/"; 
     1727/*======================================================================*\ 
     1728    Function: parse_w3cdtf 
     1729    Purpose:  parse a W3CDTF date into unix epoch 
    7971730 
    798         if ( preg_match( $pat, $date_str, $match ) ) { 
    799                 list( $year, $month, $day, $hours, $minutes, $seconds) = 
    800                         array( $match[1], $match[2], $match[3], $match[4], $match[5], $match[7]); 
     1731    NOTE: http://www.w3.org/TR/NOTE-datetime 
     1732\*======================================================================*/ 
    8011733 
    802                 # calc epoch for current date assuming GMT 
    803                 $epoch = gmmktime( $hours, $minutes, $seconds, $month, $day, $year); 
     1734function 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]); 
    8041742 
    805                 $offset = 0; 
    806                 if ( $match[11] == 'Z' ) { 
    807                         # zulu time, aka GMT 
    808                 } 
    809                 else { 
    810                         list( $tz_mod, $tz_hour, $tz_min ) = 
    811                                 array( $match[8], $match[9], $match[10]); 
     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'); 
    8121748 
    813                         # zero out the variables 
    814                         if ( ! $tz_hour ) { $tz_hour = 0; } 
    815                         if ( ! $tz_min ) { $tz_min = 0; } 
     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; 
    8161752 
    817                         $offset_secs = (($tz_hour*60)+$tz_min)*60; 
     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} 
    8181785 
    819                         # is timezone ahead of GMT?  then subtract offset 
    820                         # 
    821                         if ( $tz_mod == '+' ) { 
    822                                 $offset_secs = $offset_secs * -1; 
     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> 
     1798if (!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                                } 
    8231879                        } 
    824  
    825                         $offset = $offset_secs; 
     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; 
    8261941                } 
    827                 $epoch = $epoch + $offset; 
    828                 return $epoch; 
    829         } 
    830         else { 
    831                 return -1; 
    832         } 
     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 
    8331963} 
    834 endif; 
    8351964 
    836 if ( !function_exists('wp_rss') ) : 
    837 function wp_rss( $url, $num_items = -1 ) { 
    838         if ( $rss = fetch_rss( $url ) ) { 
    839                 echo '<ul>'; 
     1965################################################################################ 
     1966## WordPress: wp_rss(), get_rss() ############################################## 
     1967################################################################################ 
    8401968 
    841                 if ( $num_items !== -1 ) { 
    842                         $rss->items = array_slice( $rss->items, 0, $num_items ); 
    843                 } 
    844  
    845                 foreach ( $rss->items as $item ) { 
    846                         printf( 
    847                                 '<li><a href="%1$s" title="%2$s">%3$s</a></li>', 
    848                                 clean_url( $item['link'] ), 
    849                                 attribute_escape( strip_tags( $item['description'] ) ), 
    850                                 htmlentities( $item['title'] ) 
    851                         ); 
    852                 } 
    853  
    854                 echo '</ul>'; 
    855         } else { 
    856                 _e( 'An error has occurred, which probably means the feed is down. Try again later.' ); 
     1969function 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>"; 
    8571984        } 
     1985                else { 
     1986                        echo "an error has occured the feed is probably down, try again later."; 
     1987        } 
    8581988} 
    859 endif; 
    8601989 
    861 if ( !function_exists('get_rss') ) : 
    862 function get_rss ($url, $num_items = 5) { // Like get posts, but for RSS 
     1990function get_rss ($uri, $num = 5) { // Like get posts, but for RSS 
    8631991        $rss = fetch_rss($url); 
    8641992        if ( $rss ) { 
    8651993                $rss->items = array_slice($rss->items, 0, $num_items); 
     
    8701998                        echo "</a><br />\n"; 
    8711999                        echo "</li>\n"; 
    8722000                } 
     2001                return $posts; 
    8732002        } else { 
    8742003                return false; 
    8752004        } 
    8762005} 
    877 endif; 
    878  
    8792006?> 
    880  No newline at end of file