| @@ -3,9 +3,9 @@ | ||
| 3 | 3 | * File: A compiled file for RSS syndication |
| 4 | 4 | * Author: Kellan Elliot-McCrea <kellan@protest.net> |
| 5 | 5 | * WordPress development team <http://www.wordpress.org/> |
| 6 | 6 | * Charles Johnson <technophilia@radgeek.com> |
| 7 | - * Version: 0.7wp (2005.05.07) | |
| 7 | + * Version: 0.8wp (2005.10.14) | |
| 8 | 8 | * License: GPL |
| 9 | 9 | * |
| 10 | 10 | * Provenance: |
| 11 | 11 | * |
| @@ -10,15 +10,16 @@ | ||
| 10 | 10 | * Provenance: |
| 11 | 11 | * |
| 12 | 12 | * This is a drop-in replacement for the `rss-functions.php` provided with the |
| 13 | 13 | * WordPress 1.5 distribution, which upgrades the version of MagpieRSS from 0.51 |
| 14 | - * to a modification of 0.7. In addition to improved handling of character | |
| 15 | - * encoding and other updates, this branch of MagpieRSS 0.7 also supports | |
| 16 | - * multiple categorization of posts (using <dc:subject> or <category>). The | |
| 17 | - * file is, therefore, derived from four sources: (1) Kellan's MagpieRSS 0.51, | |
| 18 | - * (2) the WordPress development team's modifications to MagpieRSS 0.51, | |
| 19 | - * (3) Kellan's MagpieRSS 0.7, and (4) Charles Johnson's modifications to | |
| 20 | - * MagpieRSS 0.7. All possible because of the GPL. Yay for free software! | |
| 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! | |
| 21 | 22 | * |
| 22 | 23 | * Differences from the main branch of MagpieRSS: |
| 23 | 24 | * |
| 24 | 25 | * 1. Everything in rss_parse.inc, rss_fetch.inc, rss_cache.inc, and |
| @@ -32,39 +33,76 @@ | ||
| 32 | 33 | * than writing external files directly. |
| 33 | 34 | * |
| 34 | 35 | * 4. There are two WordPress-specific functions, get_rss() and wp_rss() |
| 35 | 36 | * |
| 36 | - * 5. New cases added to MagpieRSS::feed_start_element(), | |
| 37 | - * MagpieRSS::feed_end_element(), and MagpieRSS::normalize() to handle: | |
| 37 | + * Differences from the version of MagpieRSS packaged with WordPress: | |
| 38 | 38 | * |
| 39 | - * (a) Multiple categories | |
| 40 | - * (b) RSS 2.0 and Atom 0.6+ enclosures | |
| 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. | |
| 41 | 44 | * |
| 42 | - * Categories are stored in $item['category'], $item['categories'], | |
| 43 | - * $item['dc']['subject'], and $item['dc']['subjects'] (the singular keys | |
| 44 | - * point to string values for the first category used; the plural keys | |
| 45 | - * point to an array of all the categories the item is in) | |
| 45 | + * 2. Numerous bug fixes. | |
| 46 | 46 | * |
| 47 | - * Enclosures are stored in the array $item['enclosure'] as suggested | |
| 48 | - * at <http://magpie.laughingmeme.org/blog/?p=101>. So the URL of the first | |
| 49 | - * enclosure is $item['enclosure'][0]['url']; the length is | |
| 50 | - * $item['enclosure'][0]['length']; and the type is | |
| 51 | - * $item['enclosure'][0]['type'] | |
| 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.) | |
| 52 | 56 | * |
| 53 | - * Note that these are hacked-in solutions for inherited problems with | |
| 54 | - * MagpieRSS as of version 0.7. They are not guaranteed to be | |
| 55 | - * forward-compatible when/if Kellan solves these problems in the official | |
| 56 | - * Magpie branch in the future. If you have filters (for example) that | |
| 57 | - * depend on either categories or enclosures working as they currently do, | |
| 58 | - * keep an eye on the ChangeLog in future releases. | |
| 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! | |
| 59 | 87 | */ |
| 60 | 88 | |
| 61 | 89 | define('RSS', 'RSS'); |
| 62 | 90 | define('ATOM', 'Atom'); |
| 63 | -define('MAGPIE_USER_AGENT', 'WordPress/' . $wp_version); | |
| 64 | 91 | |
| 65 | -# UPDATED: rss_parse.inc: class MagpieRSS, function map_attrs | |
| 66 | -# --- cut here --- | |
| 92 | +################################################################################ | |
| 93 | +## WordPress: make some settings WordPress-appropriate ######################### | |
| 94 | +################################################################################ | |
| 95 | + | |
| 96 | +define('MAGPIE_USER_AGENT', 'WordPress/' . $wp_version . '(+http://www.wordpress.org)'); | |
| 97 | + | |
| 98 | +$wp_encoding = get_settings('blog_charset'); | |
| 99 | +define('MAGPIE_OUTPUT_ENCODING', ($wp_encoding?$wp_encoding:'ISO-8859-1')); | |
| 100 | + | |
| 101 | +################################################################################ | |
| 102 | +## rss_parse.inc: from MagpieRSS 0.8a ########################################## | |
| 103 | +################################################################################ | |
| 104 | + | |
| 67 | 105 | /** |
| 68 | 106 | * Hybrid parser, and object, takes RSS as a string and returns a simple object. |
| 69 | 107 | * |
| 70 | 108 | * see: rss_fetch.inc for a simpler interface with integrated caching support |
| @@ -88,9 +126,14 @@ | ||
| 88 | 126 | var $WARNING = ""; |
| 89 | 127 | |
| 90 | 128 | // define some constants |
| 91 | 129 | |
| 92 | - var $_CONTENT_CONSTRUCTS = array('content', 'summary', 'info', 'title', 'tagline', 'copyright'); | |
| 130 | + var $_ATOM_CONTENT_CONSTRUCTS = array( | |
| 131 | + 'content', 'summary', 'title', /* common */ | |
| 132 | + 'info', 'tagline', 'copyright', /* Atom 0.3 */ | |
| 133 | + 'rights', 'subtitle', /* Atom 1.0 */ | |
| 134 | + ); | |
| 135 | + var $_XHTML_CONTENT_CONSTRUCTS = array('body', 'div'); | |
| 93 | 136 | var $_KNOWN_ENCODINGS = array('UTF-8', 'US-ASCII', 'ISO-8859-1'); |
| 94 | 137 | |
| 95 | 138 | // parser variables, useless if you're not a parser, treat as private |
| 96 | 139 | var $stack = array(); // parser stack |
| @@ -95,16 +138,16 @@ | ||
| 95 | 138 | // parser variables, useless if you're not a parser, treat as private |
| 96 | 139 | var $stack = array(); // parser stack |
| 97 | 140 | var $inchannel = false; |
| 98 | 141 | var $initem = false; |
| 99 | - var $incontent = false; // if in Atom <content mode="xml"> field | |
| 142 | + | |
| 143 | + var $incontent = array(); // non-empty if in namespaced XML content field | |
| 144 | + var $exclude_top = false; // true when Atom 1.0 type="xhtml" | |
| 145 | + | |
| 100 | 146 | var $intextinput = false; |
| 101 | 147 | var $inimage = false; |
| 102 | 148 | var $current_namespace = false; |
| 103 | 149 | |
| 104 | - var $incategory = false; | |
| 105 | - var $current_category = 0; | |
| 106 | - | |
| 107 | 150 | /** |
| 108 | 151 | * Set up XML parser, parse source, and return populated RSS object.. |
| 109 | 152 | * |
| 110 | 153 | * @param string $source string containing the RSS to be parsed |
| @@ -163,9 +206,9 @@ | ||
| 163 | 206 | xml_set_element_handler($this->parser, |
| 164 | 207 | 'feed_start_element', 'feed_end_element' ); |
| 165 | 208 | |
| 166 | 209 | xml_set_character_data_handler( $this->parser, 'feed_cdata' ); |
| 167 | - | |
| 210 | + | |
| 168 | 211 | $status = xml_parse( $this->parser, $source ); |
| 169 | 212 | |
| 170 | 213 | if (! $status ) { |
| 171 | 214 | $errorcode = xml_get_error_code( $this->parser ); |
| @@ -188,16 +231,18 @@ | ||
| 188 | 231 | $el = $element = strtolower($element); |
| 189 | 232 | $attrs = array_change_key_case($attrs, CASE_LOWER); |
| 190 | 233 | |
| 191 | 234 | // check for a namespace, and split if found |
| 192 | - $ns = false; | |
| 193 | - if ( strpos( $element, ':' ) ) { | |
| 194 | - list($ns, $el) = split( ':', $element, 2); | |
| 195 | - } | |
| 196 | - if ( $ns and $ns != 'rdf' ) { | |
| 197 | - $this->current_namespace = $ns; | |
| 198 | - } | |
| 199 | - | |
| 235 | + if ( empty($this->incontent) ) { // Don't munge content tags | |
| 236 | + $ns = false; | |
| 237 | + if ( strpos( $element, ':' ) ) { | |
| 238 | + list($ns, $el) = split( ':', $element, 2); | |
| 239 | + } | |
| 240 | + if ( $ns and $ns != 'rdf' ) { | |
| 241 | + $this->current_namespace = $ns; | |
| 242 | + } | |
| 243 | + } | |
| 244 | + | |
| 200 | 245 | # if feed type isn't set, then this is first element of feed |
| 201 | 246 | # identify feed from root element |
| 202 | 247 | # |
| 203 | 248 | if (!isset($this->feed_type) ) { |
| @@ -210,18 +255,40 @@ | ||
| 210 | 255 | $this->feed_version = $attrs['version']; |
| 211 | 256 | } |
| 212 | 257 | elseif ( $el == 'feed' ) { |
| 213 | 258 | $this->feed_type = ATOM; |
| 214 | - $this->feed_version = $attrs['version']; | |
| 259 | + if ($attrs['xmlns'] == 'http://www.w3.org/2005/Atom') { // Atom 1.0 | |
| 260 | + $this->feed_version = '1.0'; | |
| 261 | + } | |
| 262 | + else { // Atom 0.3, probably. | |
| 263 | + $this->feed_version = $attrs['version']; | |
| 264 | + } | |
| 215 | 265 | $this->inchannel = true; |
| 216 | 266 | } |
| 217 | 267 | return; |
| 218 | 268 | } |
| 219 | 269 | |
| 220 | - if ( $el == 'channel' ) | |
| 270 | + // if we're inside a namespaced content construct, treat tags as text | |
| 271 | + if ( !empty($this->incontent) ) | |
| 221 | 272 | { |
| 273 | + if ((count($this->incontent) > 1) or !$this->exclude_top) { | |
| 274 | + // if tags are inlined, then flatten | |
| 275 | + $attrs_str = join(' ', | |
| 276 | + array_map('map_attrs', | |
| 277 | + array_keys($attrs), | |
| 278 | + array_values($attrs) ) ); | |
| 279 | + if (strlen($attrs_str) > 0) $attrs_str = ' '.$attrs_str; | |
| 280 | + | |
| 281 | + $this->append_content( "<{$element}{$attrs_str}>" ); | |
| 282 | + } | |
| 283 | + array_push($this->incontent, $el); // stack for parsing content XML | |
| 284 | + } | |
| 285 | + | |
| 286 | + elseif ( $el == 'channel' ) | |
| 287 | + { | |
| 222 | 288 | $this->inchannel = true; |
| 223 | 289 | } |
| 290 | + | |
| 224 | 291 | elseif ($el == 'item' or $el == 'entry' ) |
| 225 | 292 | { |
| 226 | 293 | $this->initem = true; |
| 227 | 294 | if ( isset($attrs['rdf:about']) ) { |
| @@ -227,14 +294,9 @@ | ||
| 227 | 294 | if ( isset($attrs['rdf:about']) ) { |
| 228 | 295 | $this->current_item['about'] = $attrs['rdf:about']; |
| 229 | 296 | } |
| 230 | 297 | } |
| 231 | - | |
| 232 | - elseif ($this->initem and ($el == 'category' or ($this->current_namespace == 'dc' and $el == 'subject'))) { | |
| 233 | - $this->incategory = true; | |
| 234 | - array_unshift( $this->stack, $el ); | |
| 235 | - } | |
| 236 | - | |
| 298 | + | |
| 237 | 299 | // if we're in the default namespace of an RSS feed, |
| 238 | 300 | // record textinput or image fields |
| 239 | 301 | elseif ( |
| 240 | 302 | $this->feed_type == RSS and |
| @@ -251,76 +313,81 @@ | ||
| 251 | 313 | { |
| 252 | 314 | $this->inimage = true; |
| 253 | 315 | } |
| 254 | 316 | |
| 255 | - # -- Handle RSS 2 enclosures. Suggested by <http://magpie.laughingmeme.org/blog/?p=101> | |
| 256 | - elseif ( | |
| 257 | - $this->feed_type == RSS and | |
| 258 | - $el == 'enclosure' ) | |
| 259 | - { | |
| 260 | - $this->current_item[$el][] = $attrs; | |
| 261 | - $this->incontent = $el; | |
| 262 | - } | |
| 317 | + // set stack[0] to current element | |
| 318 | + else { | |
| 319 | + // Atom support many links per containing element. | |
| 320 | + // Magpie treats link elements of type rel='alternate' | |
| 321 | + // as being equivalent to RSS's simple link element. | |
| 263 | 322 | |
| 264 | - # handle atom content constructs | |
| 265 | - elseif ( $this->feed_type == ATOM and in_array($el, $this->_CONTENT_CONSTRUCTS) ) | |
| 266 | - { | |
| 267 | - // avoid clashing w/ RSS mod_content | |
| 268 | - if ($el == 'content' ) { | |
| 269 | - $el = 'atom_content'; | |
| 270 | - } | |
| 271 | - | |
| 272 | - $this->incontent = $el; | |
| 273 | - | |
| 274 | - | |
| 275 | - } | |
| 323 | + $atom_link = false; | |
| 324 | + if ($this->feed_type == ATOM and $el == 'link') { | |
| 325 | + $atom_link = true; | |
| 326 | + if (isset($attrs['rel']) and $attrs['rel'] != 'alternate') { | |
| 327 | + $el = $el . "_" . $attrs['rel']; // pseudo-element names for Atom link elements | |
| 328 | + } | |
| 329 | + } | |
| 330 | + # handle atom content constructs | |
| 331 | + elseif ( $this->feed_type == ATOM and in_array($el, $this->_ATOM_CONTENT_CONSTRUCTS) ) | |
| 332 | + { | |
| 333 | + // avoid clashing w/ RSS mod_content | |
| 334 | + if ($el == 'content' ) { | |
| 335 | + $el = 'atom_content'; | |
| 336 | + } | |
| 276 | 337 | |
| 277 | - // if inside an Atom content construct (e.g. content or summary) field treat tags as text | |
| 278 | - elseif ($this->feed_type == ATOM and $this->incontent ) | |
| 279 | - { | |
| 280 | - // if tags are inlined, then flatten | |
| 281 | - $attrs_str = join(' ', | |
| 282 | - array_map('map_attrs', | |
| 283 | - array_keys($attrs), | |
| 284 | - array_values($attrs) ) ); | |
| 285 | - | |
| 286 | - $this->append_content( "<$element $attrs_str>" ); | |
| 287 | - | |
| 288 | - array_unshift( $this->stack, $el ); | |
| 289 | - } | |
| 290 | - | |
| 291 | - // Atom support many links per containging element. | |
| 292 | - // Magpie treats link elements of type rel='alternate' | |
| 293 | - // as being equivalent to RSS's simple link element. | |
| 294 | - // | |
| 295 | - elseif ($this->feed_type == ATOM and $el == 'link' ) | |
| 296 | - { | |
| 297 | - # -- CWJ: Treat <link> elements without explicit rel as rel="alternate" | |
| 298 | - if ( !isset($attrs['rel']) or isset($attrs['rel']) and $attrs['rel'] == 'alternate' ) | |
| 299 | - { | |
| 300 | - $link_el = 'link'; | |
| 301 | - } | |
| 302 | - # -- CWJ: support Atom 0.6+ enclosures | |
| 303 | - elseif ( isset($attrs['rel']) and $attrs['rel'] == 'enclosure' ) | |
| 304 | - { | |
| 305 | - $link_el = 'link_' . $attrs['rel']; | |
| 306 | - | |
| 307 | - # -- CWJ: Normalize to RSS 2.0 enclosure handling | |
| 308 | - $n = count($this->current_item[$attrs['rel']]); | |
| 309 | - $this->current_item[$attrs['rel']][$n] = $attrs; | |
| 310 | - $this->current_item[$attrs['rel']][$n]['url'] = | |
| 311 | - $this->current_item[$attrs['rel']][$n]['href']; | |
| 312 | - } | |
| 313 | - else { | |
| 314 | - $link_el = 'link_' . $attrs['rel']; | |
| 315 | - } | |
| 316 | - | |
| 317 | - $this->append($link_el, $attrs['href']); | |
| 318 | - } | |
| 338 | + // assume that everything accepts namespaced XML | |
| 339 | + // (that will pass through some non-validating feeds; | |
| 340 | + // but so what? this isn't a validating parser) | |
| 341 | + $this->incontent = array(); | |
| 342 | + array_push($this->incontent, $el); // start a stack | |
| 319 | 343 | |
| 320 | - // set stack[0] to current element | |
| 321 | - else { | |
| 322 | - array_unshift($this->stack, $el); | |
| 344 | + if ( | |
| 345 | + isset($attrs['type']) | |
| 346 | + and trim(strtolower($attrs['type']))=='xhtml' | |
| 347 | + ) { | |
| 348 | + $this->exclude_top = true; | |
| 349 | + } else { | |
| 350 | + $this->exclude_top = false; | |
| 351 | + } | |
| 352 | + } | |
| 353 | + # Handle inline XHTML body elements --CWJ | |
| 354 | + elseif ( | |
| 355 | + ($this->current_namespace=='xhtml' or (isset($attrs['xmlns']) and $attrs['xmlns'] == 'http://www.w3.org/1999/xhtml')) | |
| 356 | + and in_array($el, $this->_XHTML_CONTENT_CONSTRUCTS) ) | |
| 357 | + { | |
| 358 | + $this->current_namespace = 'xhtml'; | |
| 359 | + $this->incontent = array(); | |
| 360 | + array_push($this->incontent, $el); // start a stack | |
| 361 | + $this->exclude_top = false; | |
| 362 | + } | |
| 363 | + | |
| 364 | + array_unshift($this->stack, $el); | |
| 365 | + $elpath = join('_', array_reverse($this->stack)); | |
| 366 | + | |
| 367 | + $n = $this->element_count($elpath); | |
| 368 | + $this->element_count($elpath, $n+1); | |
| 369 | + | |
| 370 | + if ($n > 0) { | |
| 371 | + array_shift($this->stack); | |
| 372 | + array_unshift($this->stack, $el.'#'.($n+1)); | |
| 373 | + $elpath = join('_', array_reverse($this->stack)); | |
| 374 | + } | |
| 375 | + | |
| 376 | + // this makes the baby Jesus cry, but we can't do it in normalize() | |
| 377 | + // because we've made the element name for Atom links unpredictable | |
| 378 | + // by tacking on the relation to the end. -CWJ | |
| 379 | + if ($atom_link and isset($attrs['href'])) { | |
| 380 | + $this->append($elpath, $attrs['href']); | |
| 381 | + } | |
| 382 | + | |
| 383 | + // add attributes | |
| 384 | + if (count($attrs) > 0) { | |
| 385 | + $this->append($elpath.'@', join(',', array_keys($attrs))); | |
| 386 | + foreach ($attrs as $attr => $value) { | |
| 387 | + $this->append($elpath.'@'.$attr, $value); | |
| 388 | + } | |
| 389 | + } | |
| 323 | 390 | } |
| 324 | 391 | } |
| 325 | 392 | |
| 326 | 393 | |
| @@ -325,10 +392,10 @@ | ||
| 325 | 392 | |
| 326 | 393 | |
| 327 | 394 | |
| 328 | 395 | function feed_cdata ($p, $text) { |
| 329 | - if ($this->feed_type == ATOM and $this->incontent) | |
| 330 | - { | |
| 396 | + | |
| 397 | + if ($this->incontent) { | |
| 331 | 398 | $this->append_content( $text ); |
| 332 | 399 | } |
| 333 | 400 | else { |
| 334 | 401 | $current_el = join('_', array_reverse($this->stack)); |
| @@ -338,9 +405,34 @@ | ||
| 338 | 405 | |
| 339 | 406 | function feed_end_element ($p, $el) { |
| 340 | 407 | $el = strtolower($el); |
| 341 | 408 | |
| 342 | - if ( $el == 'item' or $el == 'entry' ) | |
| 409 | + if ( $this->incontent ) { | |
| 410 | + $opener = array_pop($this->incontent); | |
| 411 | + | |
| 412 | + // Don't get bamboozled by namespace voodoo | |
| 413 | + if (strpos($el, ':')) { list($ns, $closer) = split(':', $el); } | |
| 414 | + else { $ns = false; $closer = $el; } | |
| 415 | + | |
| 416 | + // Don't get bamboozled by our munging of <atom:content>, either | |
| 417 | + if ($this->feed_type == ATOM and $closer == 'content') { | |
| 418 | + $closer = 'atom_content'; | |
| 419 | + } | |
| 420 | + | |
| 421 | + // balance tags properly | |
| 422 | + // note: i don't think this is actually neccessary | |
| 423 | + if ($opener != $closer) { | |
| 424 | + array_push($this->incontent, $opener); | |
| 425 | + $this->append_content("<$el />"); | |
| 426 | + } elseif ($this->incontent) { // are we in the content construct still? | |
| 427 | + if ((count($this->incontent) > 1) or !$this->exclude_top) { | |
| 428 | + $this->append_content("</$el>"); | |
| 429 | + } | |
| 430 | + } else { // shift the opening of the content construct off the normal stack | |
| 431 | + array_shift( $this->stack ); | |
| 432 | + } | |
| 433 | + } | |
| 434 | + elseif ( $el == 'item' or $el == 'entry' ) | |
| 343 | 435 | { |
| 344 | 436 | $this->items[] = $this->current_item; |
| 345 | 437 | $this->current_item = array(); |
| 346 | 438 | $this->initem = false; |
| @@ -346,14 +438,9 @@ | ||
| 346 | 438 | $this->initem = false; |
| 347 | 439 | |
| 348 | 440 | $this->current_category = 0; |
| 349 | 441 | } |
| 350 | - elseif ($this->initem and ($el == 'category' or $el == 'dc:subject')) { | |
| 351 | - $this->incategory = false; | |
| 352 | - $this->current_category = $this->current_category + 1; | |
| 353 | - array_shift( $this->stack ); | |
| 354 | - } | |
| 355 | - elseif ($this->feed_type == RSS and $this->current_namespace == '' and $el == 'textinput' ) | |
| 442 | + elseif ($this->feed_type == RSS and $this->current_namespace == '' and $el == 'textinput' ) | |
| 356 | 443 | { |
| 357 | 444 | $this->intextinput = false; |
| 358 | 445 | } |
| 359 | 446 | elseif ($this->feed_type == RSS and $this->current_namespace == '' and $el == 'image' ) |
| @@ -359,34 +446,19 @@ | ||
| 359 | 446 | elseif ($this->feed_type == RSS and $this->current_namespace == '' and $el == 'image' ) |
| 360 | 447 | { |
| 361 | 448 | $this->inimage = false; |
| 362 | 449 | } |
| 363 | - elseif ($this->feed_type == ATOM and in_array($el, $this->_CONTENT_CONSTRUCTS) ) | |
| 364 | - { | |
| 365 | - $this->incontent = false; | |
| 366 | - } | |
| 367 | 450 | elseif ($el == 'channel' or $el == 'feed' ) |
| 368 | 451 | { |
| 369 | 452 | $this->inchannel = false; |
| 370 | 453 | } |
| 371 | - elseif ($this->feed_type == ATOM and $this->incontent ) { | |
| 372 | - // balance tags properly | |
| 373 | - // note: i don't think this is actually neccessary | |
| 374 | - if ( $this->stack[0] == $el ) | |
| 375 | - { | |
| 376 | - $this->append_content("</$el>"); | |
| 377 | - } | |
| 378 | - else { | |
| 379 | - $this->append_content("<$el />"); | |
| 380 | - } | |
| 381 | - | |
| 382 | - array_shift( $this->stack ); | |
| 383 | - } | |
| 384 | 454 | else { |
| 385 | - array_shift( $this->stack ); | |
| 455 | + array_shift( $this->stack ); | |
| 386 | 456 | } |
| 387 | 457 | |
| 388 | - $this->current_namespace = false; | |
| 458 | + if ( !$this->incontent ) { // Don't munge the namespace after finishing with elements in namespaced content constructs -CWJ | |
| 459 | + $this->current_namespace = false; | |
| 460 | + } | |
| 389 | 461 | } |
| 390 | 462 | |
| 391 | 463 | function concat (&$str1, $str2="") { |
| 392 | 464 | if (!isset($str1) ) { |
| @@ -394,17 +466,23 @@ | ||
| 394 | 466 | } |
| 395 | 467 | $str1 .= $str2; |
| 396 | 468 | } |
| 397 | 469 | |
| 398 | - | |
| 399 | - | |
| 400 | 470 | function append_content($text) { |
| 401 | - if ( $this->initem ) { | |
| 402 | - $this->concat( $this->current_item[ $this->incontent ], $text ); | |
| 403 | - } | |
| 404 | - elseif ( $this->inchannel ) { | |
| 405 | - $this->concat( $this->channel[ $this->incontent ], $text ); | |
| 406 | - } | |
| 471 | + if ( $this->initem ) { | |
| 472 | + if ($this->current_namespace) { | |
| 473 | + $this->concat( $this->current_item[$this->current_namespace][ reset($this->incontent) ], $text ); | |
| 474 | + } else { | |
| 475 | + $this->concat( $this->current_item[ reset($this->incontent) ], $text ); | |
| 476 | + } | |
| 477 | + } | |
| 478 | + elseif ( $this->inchannel ) { | |
| 479 | + if ($this->current_namespace) { | |
| 480 | + $this->concat( $this->channel[$this->current_namespace][ reset($this->incontent) ], $text ); | |
| 481 | + } else { | |
| 482 | + $this->concat( $this->channel[ reset($this->incontent) ], $text ); | |
| 483 | + } | |
| 484 | + } | |
| 407 | 485 | } |
| 408 | 486 | |
| 409 | 487 | // smart append - field and namespace aware |
| 410 | 488 | function append($el, $text) { |
| @@ -412,19 +490,16 @@ | ||
| 412 | 490 | return; |
| 413 | 491 | } |
| 414 | 492 | if ( $this->current_namespace ) |
| 415 | 493 | { |
| 416 | - if ( $this->incategory ) { | |
| 417 | - $this->concat( $this->current_item['categories'][$this->current_category], $text ); | |
| 494 | + if ( $this->initem ) { | |
| 495 | + $this->concat( | |
| 496 | + $this->current_item[ $this->current_namespace ][ $el ], $text); | |
| 418 | 497 | } |
| 419 | - elseif ( $this->initem ) { | |
| 420 | - $this->concat( | |
| 421 | - $this->current_item[ $this->current_namespace ][ $el ], $text ); | |
| 422 | - } | |
| 423 | 498 | elseif ($this->inchannel) { |
| 424 | - $this->concat( | |
| 425 | - $this->channel[ $this->current_namespace][ $el ], $text ); | |
| 426 | - } | |
| 499 | + $this->concat( | |
| 500 | + $this->channel[ $this->current_namespace][ $el ], $text ); | |
| 501 | + } | |
| 427 | 502 | elseif ($this->intextinput) { |
| 428 | 503 | $this->concat( |
| 429 | 504 | $this->textinput[ $this->current_namespace][ $el ], $text ); |
| 430 | 505 | } |
| @@ -433,15 +508,12 @@ | ||
| 433 | 508 | $this->image[ $this->current_namespace ][ $el ], $text ); |
| 434 | 509 | } |
| 435 | 510 | } |
| 436 | 511 | else { |
| 437 | - if ( $this->incategory ) { | |
| 438 | - $this->concat( $this->current_item['categories'][$this->current_category], $text ); | |
| 512 | + if ( $this->initem ) { | |
| 513 | + $this->concat( | |
| 514 | + $this->current_item[ $el ], $text); | |
| 439 | 515 | } |
| 440 | - elseif ( $this->initem ) { | |
| 441 | - $this->concat( | |
| 442 | - $this->current_item[ $el ], $text); | |
| 443 | - } | |
| 444 | 516 | elseif ($this->intextinput) { |
| 445 | 517 | $this->concat( |
| 446 | 518 | $this->textinput[ $el ], $text ); |
| 447 | 519 | } |
| @@ -449,52 +521,269 @@ | ||
| 449 | 521 | $this->concat( |
| 450 | 522 | $this->image[ $el ], $text ); |
| 451 | 523 | } |
| 452 | 524 | elseif ($this->inchannel) { |
| 453 | - $this->concat( | |
| 454 | - $this->channel[ $el ], $text ); | |
| 525 | + $this->concat( | |
| 526 | + $this->channel[ $el ], $text ); | |
| 455 | 527 | } |
| 456 | 528 | |
| 457 | 529 | } |
| 458 | 530 | } |
| 531 | + | |
| 532 | + // smart count - field and namespace aware | |
| 533 | + function element_count ($el, $set = NULL) { | |
| 534 | + if (!$el) { | |
| 535 | + return; | |
| 536 | + } | |
| 537 | + if ( $this->current_namespace ) | |
| 538 | + { | |
| 539 | + if ( $this->initem ) { | |
| 540 | + if (!is_null($set)) { $this->current_item[ $this->current_namespace ][ $el.'#' ] = $set; } | |
| 541 | + $ret = (isset($this->current_item[ $this->current_namespace ][ $el.'#' ]) ? | |
| 542 | + $this->current_item[ $this->current_namespace ][ $el.'#' ] : 0); | |
| 543 | + } | |
| 544 | + elseif ($this->inchannel) { | |
| 545 | + if (!is_null($set)) { $this->channel[ $this->current_namespace ][ $el.'#' ] = $set; } | |
| 546 | + $ret = (isset($this->channel[ $this->current_namespace][ $el.'#' ]) ? | |
| 547 | + $this->channel[ $this->current_namespace][ $el.'#' ] : 0); | |
| 548 | + } | |
| 549 | + } | |
| 550 | + else { | |
| 551 | + if ( $this->initem ) { | |
| 552 | + if (!is_null($set)) { $this->current_item[ $el.'#' ] = $set; } | |
| 553 | + $ret = (isset($this->current_item[ $el.'#' ]) ? | |
| 554 | + $this->current_item[ $el.'#' ] : 0); | |
| 555 | + } | |
| 556 | + elseif ($this->inchannel) { | |
| 557 | + if (!is_null($set)) {$this->channel[ $el.'#' ] = $set; } | |
| 558 | + $ret = (isset($this->channel[ $el.'#' ]) ? | |
| 559 | + $this->channel[ $el.'#' ] : 0); | |
| 560 | + } | |
| 561 | + } | |
| 562 | + return $ret; | |
| 563 | + } | |
| 564 | + | |
| 565 | + function normalize_enclosure (&$source, $from, &$dest, $to, $i) { | |
| 566 | + $id_from = $this->element_id($from, $i); | |
| 567 | + $id_to = $this->element_id($to, $i); | |
| 568 | + if (isset($source["{$id_from}@"])) { | |
| 569 | + foreach (explode(',', $source["{$id_from}@"]) as $attr) { | |
| 570 | + if ($from=='link_enclosure' and $attr=='href') { // from Atom | |
| 571 | + $dest["{$id_to}@url"] = $source["{$id_from}@{$attr}"]; | |
| 572 | + $dest["{$id_to}"] = $source["{$id_from}@{$attr}"]; | |
| 573 | + } | |
| 574 | + elseif ($from=='enclosure' and $attr=='url') { // from RSS | |
| 575 | + $dest["{$id_to}@href"] = $source["{$id_from}@{$attr}"]; | |
| 576 | + $dest["{$id_to}"] = $source["{$id_from}@{$attr}"]; | |
| 577 | + } | |
| 578 | + else { | |
| 579 | + $dest["{$id_to}@{$attr}"] = $source["{$id_from}@{$attr}"]; | |
| 580 | + } | |
| 581 | + } | |
| 582 | + } | |
| 583 | + } | |
| 584 | + | |
| 585 | + function normalize_atom_person (&$source, $person, &$dest, $to, $i) { | |
| 586 | + $id = $this->element_id($person, $i); | |
| 587 | + $id_to = $this->element_id($to, $i); | |
| 588 | + | |
| 589 | + // Atom 0.3 <=> Atom 1.0 | |
| 590 | + if ($this->feed_version >= 1.0) { $used = 'uri'; $norm = 'url'; } | |
| 591 | + else { $used = 'url'; $norm = 'uri'; } | |
| 592 | + | |
| 593 | + if (isset($source["{$id}_{$used}"])) { | |
| 594 | + $dest["{$id_to}_{$norm}"] = $source["{$id}_{$used}"]; | |
| 595 | + } | |
| 596 | + | |
| 597 | + // Atom to RSS 2.0 and Dublin Core | |
| 598 | + // RSS 2.0 person strings should be valid e-mail addresses if possible. | |
| 599 | + if (isset($source["{$id}_email"])) { | |
| 600 | + $rss_author = $source["{$id}_email"]; | |
| 601 | + } | |
| 602 | + if (isset($source["{$id}_name"])) { | |
| 603 | + $rss_author = $source["{$id}_name"] | |
| 604 | + . (isset($rss_author) ? " <$rss_author>" : ''); | |
| 605 | + } | |
| 606 | + if (isset($rss_author)) { | |
| 607 | + $source[$id] = $rss_author; // goes to top-level author or contributor | |
| 608 | + $dest[$id_to] = $rss_author; // goes to dc:creator or dc:contributor | |
| 609 | + } | |
| 610 | + } | |
| 611 | + | |
| 612 | + // Normalize Atom 1.0 and RSS 2.0 categories to Dublin Core... | |
| 613 | + function normalize_category (&$source, $from, &$dest, $to, $i) { | |
| 614 | + $cat_id = $this->element_id($from, $i); | |
| 615 | + $dc_id = $this->element_id($to, $i); | |
| 616 | + | |
| 617 | + // first normalize category elements: Atom 1.0 <=> RSS 2.0 | |
| 618 | + if ( isset($source["{$cat_id}@term"]) ) { // category identifier | |
| 619 | + $source[$cat_id] = $source["{$cat_id}@term"]; | |
| 620 | + } elseif ( $this->feed_type == RSS ) { | |
| 621 | + $source["{$cat_id}@term"] = $source[$cat_id]; | |
| 622 | + } | |
| 623 | + | |
| 624 | + if ( isset($source["{$cat_id}@scheme"]) ) { // URI to taxonomy | |
| 625 | + $source["{$cat_id}@domain"] = $source["{$cat_id}@scheme"]; | |
| 626 | + } elseif ( isset($source["{$cat_id}@domain"]) ) { | |
| 627 | + $source["{$cat_id}@scheme"] = $source["{$cat_id}@domain"]; | |
| 628 | + } | |
| 629 | + | |
| 630 | + // Now put the identifier into dc:subject | |
| 631 | + $dest[$dc_id] = $source[$cat_id]; | |
| 632 | + } | |
| 459 | 633 | |
| 634 | + // ... or vice versa | |
| 635 | + function normalize_dc_subject (&$source, $from, &$dest, $to, $i) { | |
| 636 | + $dc_id = $this->element_id($from, $i); | |
| 637 | + $cat_id = $this->element_id($to, $i); | |
| 638 | + | |
| 639 | + $dest[$cat_id] = $source[$dc_id]; // RSS 2.0 | |
| 640 | + $dest["{$cat_id}@term"] = $source[$dc_id]; // Atom 1.0 | |
| 641 | + } | |
| 642 | + | |
| 643 | + // simplify the logic for normalize(). Makes sure that count of elements and | |
| 644 | + // each of multiple elements is normalized properly. If you need to mess | |
| 645 | + // with things like attributes or change formats or the like, pass it a | |
| 646 | + // callback to handle each element. | |
| 647 | + function normalize_element (&$source, $from, &$dest, $to, $via = NULL) { | |
| 648 | + if (isset($source[$from]) or isset($source["{$from}#"])) { | |
| 649 | + if (isset($source["{$from}#"])) { | |
| 650 | + $n = $source["{$from}#"]; | |
| 651 | + $dest["{$to}#"] = $source["{$from}#"]; | |
| 652 | + } | |
| 653 | + else { $n = 1; } | |
| 654 | + | |
| 655 | + for ($i = 1; $i <= $n; $i++) { | |
| 656 | + if (isset($via)) { // custom callback for ninja attacks | |
| 657 | + $this->{$via}($source, $from, $dest, $to, $i); | |
| 658 | + } | |
| 659 | + else { // just make it the same | |
| 660 | + $from_id = $this->element_id($from, $i); | |
| 661 | + $to_id = $this->element_id($to, $i); | |
| 662 | + $dest[$to_id] = $source[$from_id]; | |
| 663 | + } | |
| 664 | + } | |
| 665 | + } | |
| 666 | + } | |
| 667 | + | |
| 460 | 668 | function normalize () { |
| 461 | - // if atom populate rss fields | |
| 669 | + // if atom populate rss fields and normalize 0.3 and 1.0 feeds | |
| 462 | 670 | if ( $this->is_atom() ) { |
| 463 | - $this->channel['description'] = $this->channel['tagline']; | |
| 464 | - for ( $i = 0; $i < count($this->items); $i++) { | |
| 465 | - $item = $this->items[$i]; | |
| 466 | - if ( isset($item['summary']) ) | |
| 467 | - $item['description'] = $item['summary']; | |
| 468 | - if ( isset($item['atom_content'])) | |
| 469 | - $item['content']['encoded'] = $item['atom_content']; | |
| 470 | - | |
| 471 | - $atom_date = (isset($item['issued']) ) ? $item['issued'] : $item['modified']; | |
| 472 | - if ( $atom_date ) { | |
| 473 | - $epoch = @parse_w3cdtf($item['modified']); | |
| 474 | - if ($epoch and $epoch > 0) { | |
| 475 | - $item['date_timestamp'] = $epoch; | |
| 476 | - } | |
| 477 | - } | |
| 671 | + // Atom 1.0 elements <=> Atom 0.3 elements (Thanks, o brilliant wordsmiths of the Atom 1.0 standard!) | |
| 672 | + if ($this->feed_version < 1.0) { | |
| 673 | + $this->normalize_element($this->channel, 'tagline', $this->channel, 'subtitle'); | |
| 674 | + $this->normalize_element($this->channel, 'copyright', $this->channel, 'rights'); | |
| 675 | + $this->normalize_element($this->channel, 'modified', $this->channel, 'updated'); | |
| 676 | + } else { | |
| 677 | + $this->normalize_element($this->channel, 'subtitle', $this->channel, 'tagline'); | |
| 678 | + $this->normalize_element($this->channel, 'rights', $this->channel, 'copyright'); | |
| 679 | + $this->normalize_element($this->channel, 'updated', $this->channel, 'modified'); | |
| 680 | + } | |
| 681 | + $this->normalize_element($this->channel, 'author', $this->channel['dc'], 'creator', 'normalize_atom_person'); | |
| 682 | + $this->normalize_element($this->channel, 'contributor', $this->channel['dc'], 'contributor', 'normalize_atom_person'); | |
| 478 | 683 | |
| 479 | - if ( is_array($item['categories']) ) { | |
| 480 | - $item['category'] = $item['categories'][0]; | |
| 481 | - $item['dc']['subjects'] = $item['categories']; | |
| 482 | - $item['dc']['subject'] = $item['category']; | |
| 483 | - } | |
| 484 | - | |
| 485 | - $this->items[$i] = $item; | |
| 486 | - } | |
| 684 | + // Atom elements to RSS elements | |
| 685 | + $this->normalize_element($this->channel, 'subtitle', $this->channel, 'description'); | |
| 686 | + | |
| 687 | + if ( isset($this->channel['logo']) ) { | |
| 688 | + $this->normalize_element($this->channel, 'logo', $this->image, 'url'); | |
| 689 | + $this->normalize_element($this->channel, 'link', $this->image, 'link'); | |
| 690 | + $this->normalize_element($this->channel, 'title', $this->image, 'title'); | |
| 691 | + } | |
| 692 | + | |
| 693 | + for ( $i = 0; $i < count($this->items); $i++) { | |
| 694 | + $item = $this->items[$i]; | |
| 695 | + | |
| 696 | + // Atom 1.0 elements <=> Atom 0.3 elements | |
| 697 | + if ($this->feed_version < 1.0) { | |
| 698 | + $this->normalize_element($item, 'modified', $item, 'updated'); | |
| 699 | + $this->normalize_element($item, 'issued', $item, 'published'); | |
| 700 | + } else { | |
| 701 | + $this->normalize_element($item, 'updated', $item, 'modified'); | |
| 702 | + $this->normalize_element($item, 'published', $item, 'issued'); | |
| 703 | + } | |
| 704 | + | |
| 705 | + // "If an atom:entry element does not contain | |
| 706 | + // atom:author elements, then the atom:author elements | |
| 707 | + // of the contained atom:source element are considered | |
| 708 | + // to apply. In an Atom Feed Document, the atom:author | |
| 709 | + // elements of the containing atom:feed element are | |
| 710 | + // considered to apply to the entry if there are no | |
| 711 | + // atom:author elements in the locations described | |
| 712 | + // above." <http://atompub.org/2005/08/17/draft-ietf-atompub-format-11.html#rfc.section.4.2.1> | |
| 713 | + if (!isset($item["author#"])) { | |
| 714 | + if (isset($item["source_author#"])) { // from aggregation source | |
| 715 | + $source = $item; | |
| 716 | + $author = "source_author"; | |
| 717 | + } elseif (isset($this->channel["author#"])) { // from containing feed | |
| 718 | + $source = $this->channel; | |
| 719 | + $author = "author"; | |
| 720 | + } | |
| 721 | + | |
| 722 | + $item["author#"] = $source["{$author}#"]; | |
| 723 | + for ($au = 1; $au <= $item["author#"]; $au++) { | |
| 724 | + $id_to = $this->element_id('author', $au); | |
| 725 | + $id_from = $this->element_id($author, $au); | |
| 726 | + | |
| 727 | + $item[$id_to] = $source[$id_from]; | |
| 728 | + foreach (array('name', 'email', 'uri', 'url') as $what) { | |
| 729 | + if (isset($source["{$id_from}_{$what}"])) { | |
| 730 | + $item["{$id_to}_{$what}"] = $source["{$id_from}_{$what}"]; | |
| 731 | + } | |
| 732 | + } | |
| 733 | + } | |
| 734 | + } | |
| 735 | + | |
| 736 | + // Atom elements to RSS elements | |
| 737 | + $this->normalize_element($item, 'author', $item['dc'], 'creator', 'normalize_atom_person'); | |
| 738 | + $this->normalize_element($item, 'contributor', $item['dc'], 'contributor', 'normalize_atom_person'); | |
| 739 | + $this->normalize_element($item, 'summary', $item, 'description'); | |
| 740 | + $this->normalize_element($item, 'atom_content', $item['content'], 'encoded'); | |
| 741 | + $this->normalize_element($item, 'link_enclosure', $item, 'enclosure', 'normalize_enclosure'); | |
| 742 | + | |
| 743 | + // Categories | |
| 744 | + if ( isset($item['category#']) ) { // Atom 1.0 categories to dc:subject and RSS 2.0 categories | |
| 745 | + $this->normalize_element($item, 'category', $item['dc'], 'subject', 'normalize_category'); | |
| 746 | + } | |
| 747 | + elseif ( isset($item['dc']['subject#']) ) { // dc:subject to Atom 1.0 and RSS 2.0 categories | |
| 748 | + $this->normalize_element($item['dc'], 'subject', $item, 'category', 'normalize_dc_subject'); | |
| 749 | + } | |
| 750 | + | |
| 751 | + // Normalized item timestamp | |
| 752 | + $atom_date = (isset($item['published']) ) ? $item['published'] : $item['updated']; | |
| 753 | + if ( $atom_date ) { | |
| 754 | + $epoch = @parse_w3cdtf($atom_date); | |
| 755 | + if ($epoch and $epoch > 0) { | |
| 756 | + $item['date_timestamp'] = $epoch; | |
| 757 | + } | |
| 758 | + } | |
| 759 | + | |
| 760 | + $this->items[$i] = $item; | |
| 761 | + } | |
| 487 | 762 | } |
| 488 | 763 | elseif ( $this->is_rss() ) { |
| 489 | - $this->channel['tagline'] = $this->channel['description']; | |
| 764 | + // RSS elements to Atom elements | |
| 765 | + $this->normalize_element($this->channel, 'description', $this->channel, 'tagline'); // Atom 0.3 | |
| 766 | + $this->normalize_element($this->channel, 'description', $this->channel, 'subtitle'); // Atom 1.0 (yay wordsmithing!) | |
| 767 | + $this->normalize_element($this->image, 'url', $this->channel, 'logo'); | |
| 768 | + | |
| 490 | 769 | for ( $i = 0; $i < count($this->items); $i++) { |
| 491 | 770 | $item = $this->items[$i]; |
| 492 | - if ( isset($item['description'])) | |
| 493 | - $item['summary'] = $item['description']; | |
| 494 | - if ( isset($item['content']['encoded'] ) ) | |
| 495 | - $item['atom_content'] = $item['content']['encoded']; | |
| 496 | - | |
| 771 | + | |
| 772 | + // RSS elements to Atom elements | |
| 773 | + $this->normalize_element($item, 'description', $item, 'summary'); | |
| 774 | + $this->normalize_element($item['content'], 'encoded', $item, 'atom_content'); | |
| 775 | + $this->normalize_element($item, 'enclosure', $item, 'link_enclosure', 'normalize_enclosure'); | |
| 776 | + | |
| 777 | + // Categories | |
| 778 | + if ( isset($item['category#']) ) { // RSS 2.0 categories to dc:subject and Atom 1.0 categories | |
| 779 | + $this->normalize_element($item, 'category', $item['dc'], 'subject', 'normalize_category'); | |
| 780 | + } | |
| 781 | + elseif ( isset($item['dc']['subject#']) ) { // dc:subject to Atom 1.0 and RSS 2.0 categories | |
| 782 | + $this->normalize_element($item['dc'], 'subject', $item, 'category', 'normalize_dc_subject'); | |
| 783 | + } | |
| 784 | + | |
| 785 | + // Normalized item timestamp | |
| 497 | 786 | if ( $this->is_rss() == '1.0' and isset($item['dc']['date']) ) { |
| 498 | 787 | $epoch = @parse_w3cdtf($item['dc']['date']); |
| 499 | 788 | if ($epoch and $epoch > 0) { |
| 500 | 789 | $item['date_timestamp'] = $epoch; |
| @@ -505,14 +794,8 @@ | ||
| 505 | 794 | if ($epoch > 0) { |
| 506 | 795 | $item['date_timestamp'] = $epoch; |
| 507 | 796 | } |
| 508 | 797 | } |
| 509 | - | |
| 510 | - if ( is_array($item['categories']) ) { | |
| 511 | - $item['category'] = $item['categories'][0]; | |
| 512 | - $item['dc']['subjects'] = $item['categories']; | |
| 513 | - $item['dc']['subject'] = $item['category']; | |
| 514 | - } | |
| 515 | 798 | |
| 516 | 799 | $this->items[$i] = $item; |
| 517 | 800 | } |
| 518 | 801 | } |
| @@ -650,9 +933,9 @@ | ||
| 650 | 933 | } |
| 651 | 934 | |
| 652 | 935 | function error ($errormsg, $lvl=E_USER_WARNING) { |
| 653 | 936 | // append PHP's error message if track_errors enabled |
| 654 | - if ( $php_errormsg ) { | |
| 937 | + if ( isset($php_errormsg) ) { | |
| 655 | 938 | $errormsg .= " ($php_errormsg)"; |
| 656 | 939 | } |
| 657 | 940 | if ( MAGPIE_DEBUG ) { |
| 658 | 941 | trigger_error( $errormsg, $lvl); |
| @@ -667,19 +950,73 @@ | ||
| 667 | 950 | } else { |
| 668 | 951 | $this->ERROR = $errormsg; |
| 669 | 952 | } |
| 670 | 953 | } |
| 954 | + | |
| 955 | + // magic ID function for multiple elemenets. | |
| 956 | + // can be called as static MagpieRSS::element_id() | |
| 957 | + function element_id ($el, $counter) { | |
| 958 | + return $el . (($counter > 1) ? '#'.$counter : ''); | |
| 959 | + } | |
| 671 | 960 | } // end class RSS |
| 672 | 961 | |
| 673 | 962 | function map_attrs($k, $v) { |
| 674 | 963 | return "$k=\"$v\""; |
| 675 | 964 | } |
| 676 | -# ---- cut here ---- | |
| 677 | 965 | |
| 966 | +// patch to support medieval versions of PHP4.1.x, | |
| 967 | +// courtesy, Ryan Currie, ryan@digibliss.com | |
| 968 | + | |
| 969 | +if (!function_exists('array_change_key_case')) { | |
| 970 | + define("CASE_UPPER",1); | |
| 971 | + define("CASE_LOWER",0); | |
| 972 | + | |
| 973 | + | |
| 974 | + function array_change_key_case($array,$case=CASE_LOWER) { | |
| 975 | + if ($case==CASE_LOWER) $cmd='strtolower'; | |
| 976 | + elseif ($case==CASE_UPPER) $cmd='strtoupper'; | |
| 977 | + foreach($array as $key=>$value) { | |
| 978 | + $output[$cmd($key)]=$value; | |
| 979 | + } | |
| 980 | + return $output; | |
| 981 | + } | |
| 982 | + | |
| 983 | +} | |
| 984 | + | |
| 985 | +################################################################################ | |
| 986 | +## WordPress: Load in Snoopy from wp-includes ################################## | |
| 987 | +################################################################################ | |
| 988 | + | |
| 678 | 989 | require_once( dirname(__FILE__) . '/class-snoopy.php'); |
| 679 | 990 | |
| 680 | -# -- UPDATED from rss_fetch.inc: fetch_rss, error, debug, magpie_error | |
| 681 | -# --- cut here --- | |
| 991 | +################################################################################ | |
| 992 | +## rss_fetch.inc: from MagpieRSS 0.8a ########################################## | |
| 993 | +################################################################################ | |
| 994 | + | |
| 995 | +/*=======================================================================*\ | |
| 996 | + Function: fetch_rss: | |
| 997 | + Purpose: return RSS object for the give url | |
| 998 | + maintain the cache | |
| 999 | + Input: url of RSS file | |
| 1000 | + Output: parsed RSS object (see rss_parse.inc) | |
| 1001 | + | |
| 1002 | + NOTES ON CACHEING: | |
| 1003 | + If caching is on (MAGPIE_CACHE_ON) fetch_rss will first check the cache. | |
| 1004 | + | |
| 1005 | + NOTES ON RETRIEVING REMOTE FILES: | |
| 1006 | + If conditional gets are on (MAGPIE_CONDITIONAL_GET_ON) fetch_rss will | |
| 1007 | + return a cached object, and touch the cache object upon recieving a | |
| 1008 | + 304. | |
| 1009 | + | |
| 1010 | + NOTES ON FAILED REQUESTS: | |
| 1011 | + If there is an HTTP error while fetching an RSS object, the cached | |
| 1012 | + version will be return, if it exists (and if MAGPIE_CACHE_FRESH_ONLY is off) | |
| 1013 | +\*=======================================================================*/ | |
| 1014 | + | |
| 1015 | +define('MAGPIE_VERSION', '0.7'); | |
| 1016 | + | |
| 1017 | +$MAGPIE_ERROR = ""; | |
| 1018 | + | |
| 682 | 1019 | function fetch_rss ($url) { |
| 683 | 1020 | // initialize constants |
| 684 | 1021 | init(); |
| 685 | 1022 | |
| @@ -732,9 +1069,9 @@ | ||
| 732 | 1069 | // if object cached, and cache is fresh, return cached obj |
| 733 | 1070 | if ( $cache_status == 'HIT' ) { |
| 734 | 1071 | $rss = $cache->get( $cache_key ); |
| 735 | 1072 | if ( isset($rss) and $rss ) { |
| 736 | - // should be cache age | |
| 1073 | + // should be cache age | |
| 737 | 1074 | $rss->from_cache = 1; |
| 738 | 1075 | if ( MAGPIE_DEBUG > 1) { |
| 739 | 1076 | debug("MagpieRSS: Cache HIT", E_USER_NOTICE); |
| 740 | 1077 | } |
| @@ -849,12 +1186,9 @@ | ||
| 849 | 1186 | } |
| 850 | 1187 | |
| 851 | 1188 | return $MAGPIE_ERROR; |
| 852 | 1189 | } |
| 853 | -# --- cut here --- | |
| 854 | 1190 | |
| 855 | -# UPDATED FROM: rss_fetch.inc: _fetch_remote_file, _response_to_rss, init | |
| 856 | -# --- cut here --- | |
| 857 | 1191 | /*=======================================================================*\ |
| 858 | 1192 | Function: _fetch_remote_file |
| 859 | 1193 | Purpose: retrieve an arbitrary remote file |
| 860 | 1194 | Input: url of the remote file |
| @@ -951,13 +1285,9 @@ | ||
| 951 | 1285 | define('MAGPIE_CACHE_FRESH_ONLY', false); |
| 952 | 1286 | } |
| 953 | 1287 | |
| 954 | 1288 | if ( !defined('MAGPIE_OUTPUT_ENCODING') ) { |
| 955 | - # WORDPRESS MODIFICATION: use whatever charset the blog is using | |
| 956 | - # --- cut here --- | |
| 957 | - $wp_encoding = get_settings('blog_charset'); | |
| 958 | - define('MAGPIE_OUTPUT_ENCODING', ($wp_encoding?$wp_encoding:'ISO-8859-1')); | |
| 959 | - # --- cut here --- | |
| 1289 | + define('MAGPIE_OUTPUT_ENCODING', 'ISO-8859-1'); | |
| 960 | 1290 | } |
| 961 | 1291 | |
| 962 | 1292 | if ( !defined('MAGPIE_INPUT_ENCODING') ) { |
| 963 | 1293 | define('MAGPIE_INPUT_ENCODING', null); |
| @@ -971,13 +1301,10 @@ | ||
| 971 | 1301 | define('MAGPIE_DEBUG', 0); |
| 972 | 1302 | } |
| 973 | 1303 | |
| 974 | 1304 | if ( !defined('MAGPIE_USER_AGENT') ) { |
| 975 | - # WORDPRESS MODIFICATION: send WordPress as user-agent | |
| 976 | - # --- cut here --- | |
| 977 | - $ua = 'WordPress/'. $wp_version . ' (+http://www.wordpress.org'; | |
| 978 | - # --- cut here --- | |
| 979 | - | |
| 1305 | + $ua = 'MagpieRSS/'. MAGPIE_VERSION . ' (+http://magpierss.sf.net'; | |
| 1306 | + | |
| 980 | 1307 | if ( MAGPIE_CACHE_ON ) { |
| 981 | 1308 | $ua = $ua . ')'; |
| 982 | 1309 | } |
| 983 | 1310 | else { |
| @@ -995,36 +1322,75 @@ | ||
| 995 | 1322 | if ( !defined('MAGPIE_USE_GZIP') ) { |
| 996 | 1323 | define('MAGPIE_USE_GZIP', true); |
| 997 | 1324 | } |
| 998 | 1325 | } |
| 999 | -# --- cut here --- | |
| 1000 | 1326 | |
| 1327 | +// NOTE: the following code should really be in Snoopy, or at least | |
| 1328 | +// somewhere other then rss_fetch! | |
| 1329 | + | |
| 1330 | +/*=======================================================================*\ | |
| 1331 | + HTTP STATUS CODE PREDICATES | |
| 1332 | + These functions attempt to classify an HTTP status code | |
| 1333 | + based on RFC 2616 and RFC 2518. | |
| 1334 | + | |
| 1335 | + All of them take an HTTP status code as input, and return true or false | |
| 1336 | + | |
| 1337 | + All this code is adapted from LWP's HTTP::Status. | |
| 1338 | +\*=======================================================================*/ | |
| 1339 | + | |
| 1340 | + | |
| 1341 | +/*=======================================================================*\ | |
| 1342 | + Function: is_info | |
| 1343 | + Purpose: return true if Informational status code | |
| 1344 | +\*=======================================================================*/ | |
| 1001 | 1345 | function is_info ($sc) { |
| 1002 | - return $sc >= 100 && $sc < 200; | |
| 1346 | + return $sc >= 100 && $sc < 200; | |
| 1003 | 1347 | } |
| 1004 | 1348 | |
| 1349 | +/*=======================================================================*\ | |
| 1350 | + Function: is_success | |
| 1351 | + Purpose: return true if Successful status code | |
| 1352 | +\*=======================================================================*/ | |
| 1005 | 1353 | function is_success ($sc) { |
| 1006 | - return $sc >= 200 && $sc < 300; | |
| 1354 | + return $sc >= 200 && $sc < 300; | |
| 1007 | 1355 | } |
| 1008 | 1356 | |
| 1357 | +/*=======================================================================*\ | |
| 1358 | + Function: is_redirect | |
| 1359 | + Purpose: return true if Redirection status code | |
| 1360 | +\*=======================================================================*/ | |
| 1009 | 1361 | function is_redirect ($sc) { |
| 1010 | - return $sc >= 300 && $sc < 400; | |
| 1362 | + return $sc >= 300 && $sc < 400; | |
| 1011 | 1363 | } |
| 1012 | 1364 | |
| 1365 | +/*=======================================================================*\ | |
| 1366 | + Function: is_error | |
| 1367 | + Purpose: return true if Error status code | |
| 1368 | +\*=======================================================================*/ | |
| 1013 | 1369 | function is_error ($sc) { |
| 1014 | - return $sc >= 400 && $sc < 600; | |
| 1370 | + return $sc >= 400 && $sc < 600; | |
| 1015 | 1371 | } |
| 1016 | 1372 | |
| 1373 | +/*=======================================================================*\ | |
| 1374 | + Function: is_client_error | |
| 1375 | + Purpose: return true if Error status code, and its a client error | |
| 1376 | +\*=======================================================================*/ | |
| 1017 | 1377 | function is_client_error ($sc) { |
| 1018 | - return $sc >= 400 && $sc < 500; | |
| 1378 | + return $sc >= 400 && $sc < 500; | |
| 1019 | 1379 | } |
| 1020 | 1380 | |
| 1381 | +/*=======================================================================*\ | |
| 1382 | + Function: is_client_error | |
| 1383 | + Purpose: return true if Error status code, and its a server error | |
| 1384 | +\*=======================================================================*/ | |
| 1021 | 1385 | function is_server_error ($sc) { |
| 1022 | - return $sc >= 500 && $sc < 600; | |
| 1386 | + return $sc >= 500 && $sc < 600; | |
| 1023 | 1387 | } |
| 1024 | 1388 | |
| 1025 | -# WORDPRESS-SPECIFIC: class RSSCache (modified to use WP database) | |
| 1026 | -# --- cut here --- | |
| 1389 | +################################################################################ | |
| 1390 | +## rss_cache.inc: from WordPress 1.5 ########################################### | |
| 1391 | +################################################################################ | |
| 1392 | + | |
| 1027 | 1393 | class RSSCache { |
| 1028 | 1394 | var $BASE_CACHE = 'wp-content/cache'; // where the cache files are stored |
| 1029 | 1395 | var $MAX_AGE = 43200; // when are files stale, default twelve hours |
| 1030 | 1396 | var $ERROR = ''; // accumulate error messages |
| @@ -1160,54 +1526,76 @@ | ||
| 1160 | 1526 | $this->error("MagpieRSS [debug] $debugmsg", $lvl); |
| 1161 | 1527 | } |
| 1162 | 1528 | } |
| 1163 | 1529 | } |
| 1164 | -# --- cut here --- | |
| 1165 | 1530 | |
| 1531 | +################################################################################ | |
| 1532 | +## rss_utils.inc: from MagpieRSS 0.8a ########################################## | |
| 1533 | +################################################################################ | |
| 1534 | + | |
| 1535 | +/*======================================================================*\ | |
| 1536 | + Function: parse_w3cdtf | |
| 1537 | + Purpose: parse a W3CDTF date into unix epoch | |
| 1538 | + | |
| 1539 | + NOTE: http://www.w3.org/TR/NOTE-datetime | |
| 1540 | +\*======================================================================*/ | |
| 1541 | + | |
| 1166 | 1542 | function parse_w3cdtf ( $date_str ) { |
| 1167 | - | |
| 1168 | - # regex to match wc3dtf | |
| 1169 | - $pat = "/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(:(\d{2}))?(?:([-+])(\d{2}):?(\d{2})|(Z))?/"; | |
| 1170 | - | |
| 1171 | - if ( preg_match( $pat, $date_str, $match ) ) { | |
| 1172 | - list( $year, $month, $day, $hours, $minutes, $seconds) = | |
| 1173 | - array( $match[1], $match[2], $match[3], $match[4], $match[5], $match[6]); | |
| 1174 | - | |
| 1175 | - # calc epoch for current date assuming GMT | |
| 1176 | - $epoch = gmmktime( $hours, $minutes, $seconds, $month, $day, $year); | |
| 1177 | - | |
| 1178 | - $offset = 0; | |
| 1179 | - if ( $match[10] == 'Z' ) { | |
| 1180 | - # zulu time, aka GMT | |
| 1181 | - } | |
| 1182 | - else { | |
| 1183 | - list( $tz_mod, $tz_hour, $tz_min ) = | |
| 1184 | - array( $match[8], $match[9], $match[10]); | |
| 1185 | - | |
| 1186 | - # zero out the variables | |
| 1187 | - if ( ! $tz_hour ) { $tz_hour = 0; } | |
| 1188 | - if ( ! $tz_min ) { $tz_min = 0; } | |
| 1189 | - | |
| 1190 | - $offset_secs = (($tz_hour*60)+$tz_min)*60; | |
| 1191 | - | |
| 1192 | - # is timezone ahead of GMT? then subtract offset | |
| 1193 | - # | |
| 1194 | - if ( $tz_mod == '+' ) { | |
| 1195 | - $offset_secs = $offset_secs * -1; | |
| 1196 | - } | |
| 1197 | - | |
| 1198 | - $offset = $offset_secs; | |
| 1199 | - } | |
| 1200 | - $epoch = $epoch + $offset; | |
| 1201 | - return $epoch; | |
| 1202 | - } | |
| 1203 | - else { | |
| 1204 | - return -1; | |
| 1205 | - } | |
| 1543 | + | |
| 1544 | + # regex to match wc3dtf | |
| 1545 | + $pat = "/^\s*(\d{4})(-(\d{2})(-(\d{2})(T(\d{2}):(\d{2})(:(\d{2})(\.\d+)?)?(?:([-+])(\d{2}):?(\d{2})|(Z))?)?)?)?\s*\$/"; | |
| 1546 | + | |
| 1547 | + if ( preg_match( $pat, $date_str, $match ) ) { | |
| 1548 | + list( $year, $month, $day, $hours, $minutes, $seconds) = | |
| 1549 | + array( $match[1], $match[3], $match[5], $match[7], $match[8], $match[9]); | |
| 1550 | + | |
| 1551 | + # W3C dates can omit the time, the day of the month, or even the month. | |
| 1552 | + # Fill in any blanks using information from the present moment. --CWJ | |
| 1553 | + $default['hr'] = (int) gmdate('H'); | |
| 1554 | + $default['day'] = (int) gmdate('d'); | |
| 1555 | + $default['month'] = (int) gmdate('m'); | |
| 1556 | + | |
| 1557 | + if (is_null($hours)) : $hours = $default['hr']; $minutes = 0; $seconds = 0; endif; | |
| 1558 | + if (is_null($day)) : $day = $default['day']; endif; | |
| 1559 | + if (is_null($month)) : $month = $default['month']; endif; | |
| 1560 | + | |
| 1561 | + # calc epoch for current date assuming GMT | |
| 1562 | + $epoch = gmmktime( $hours, $minutes, $seconds, $month, $day, $year); | |
| 1563 | + | |
| 1564 | + $offset = 0; | |
| 1565 | + if ( $match[14] == 'Z' ) { | |
| 1566 | + # zulu time, aka GMT | |
| 1567 | + } | |
| 1568 | + else { | |
| 1569 | + list( $tz_mod, $tz_hour, $tz_min ) = | |
| 1570 | + array( $match[12], $match[13], $match[14]); | |
| 1571 | + | |
| 1572 | + # zero out the variables | |
| 1573 | + if ( ! $tz_hour ) { $tz_hour = 0; } | |
| 1574 | + if ( ! $tz_min ) { $tz_min = 0; } | |
| 1575 | + | |
| 1576 | + $offset_secs = (($tz_hour*60)+$tz_min)*60; | |
| 1577 | + | |
| 1578 | + # is timezone ahead of GMT? then subtract offset | |
| 1579 | + # | |
| 1580 | + if ( $tz_mod == '+' ) { | |
| 1581 | + $offset_secs = $offset_secs * -1; | |
| 1582 | + } | |
| 1583 | + | |
| 1584 | + $offset = $offset_secs; | |
| 1585 | + } | |
| 1586 | + $epoch = $epoch + $offset; | |
| 1587 | + return $epoch; | |
| 1588 | + } | |
| 1589 | + else { | |
| 1590 | + return -1; | |
| 1591 | + } | |
| 1206 | 1592 | } |
| 1207 | 1593 | |
| 1208 | -# WORDPRESS-SPECIFIC: wp_rss (), get_rss () | |
| 1209 | -# --- cut here --- | |
| 1594 | +################################################################################ | |
| 1595 | +## WordPress: wp_rss(), get_rss() ############################################## | |
| 1596 | +################################################################################ | |
| 1597 | + | |
| 1210 | 1598 | function wp_rss ($url, $num) { |
| 1211 | 1599 | //ini_set("display_errors", false); uncomment to suppress php errors thrown if the feed is not returned. |
| 1212 | 1600 | $num_items = $num; |
| 1213 | 1601 | $rss = fetch_rss($url); |
| @@ -1243,6 +1631,5 @@ | ||
| 1243 | 1631 | } else { |
| 1244 | 1632 | return false; |
| 1245 | 1633 | } |
| 1246 | 1634 | } |
| 1247 | -# --- cut here --- | |
| 1248 | 1635 | ?> |