PageRenderTime 52ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/system/expressionengine/plugins/pi.magpie.php

https://bitbucket.org/studiobreakfast/sync
PHP | 3119 lines | 2297 code | 349 blank | 473 comment | 229 complexity | 0681b94a7b39661234b074fd0e74efd9 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /*
  3. =====================================================
  4. ExpressionEngine - by EllisLab
  5. -----------------------------------------------------
  6. http://expressionengine.com/
  7. -----------------------------------------------------
  8. Copyright (c) 2004 - 2012 EllisLab, Inc.
  9. =====================================================
  10. THIS IS COPYRIGHTED SOFTWARE
  11. PLEASE READ THE LICENSE AGREEMENT
  12. http://expressionengine.com/user_guide/license.html
  13. =====================================================
  14. File: pi.magpie.php
  15. -----------------------------------------------------
  16. Purpose: Magpie RSS plugin
  17. =====================================================
  18. */
  19. $plugin_info = array(
  20. 'pi_name' => 'Magpie RSS Parser',
  21. 'pi_version' => '1.3.5',
  22. 'pi_author' => 'Paul Burdick',
  23. 'pi_author_url' => 'http://expressionengine.com/',
  24. 'pi_description' => 'Retrieves and Parses RSS/Atom Feeds',
  25. 'pi_usage' => Magpie::usage()
  26. );
  27. Class Magpie {
  28. var $cache_name = 'magpie_cache'; // Name of cache directory
  29. var $cache_refresh = 360; // Period between cache refreshes (in minutes)
  30. var $cache_data = ''; // Data from cache file
  31. var $cache_path = ''; // Path to cache file.
  32. var $cache_tpath = ''; // Path to cache file's time file.
  33. var $page_url = ''; // URL being requested
  34. var $items = array(); // Information about items returned
  35. var $dates = array('lastupdate','linkcreated'); // Date elements
  36. var $return_data = ''; // Data sent back to Template parser
  37. /** -------------------------------------
  38. /** Constructor
  39. /** -------------------------------------*/
  40. function Magpie()
  41. {
  42. // Make a local reference of the ExpressionEngine super object
  43. $this->EE =& get_instance();
  44. /** -------------------------------
  45. /** Set Parameters
  46. /** -------------------------------*/
  47. $this->cache_refresh = ( ! $this->EE->TMPL->fetch_param('refresh')) ? $this->cache_refresh : $this->EE->TMPL->fetch_param('refresh');
  48. $this->page_url = ( ! $this->EE->TMPL->fetch_param('url')) ? '' : trim($this->EE->TMPL->fetch_param('url'));
  49. $limit = ( ! $this->EE->TMPL->fetch_param('limit')) ? 20 : $this->EE->TMPL->fetch_param('limit');
  50. $offset = ( ! $this->EE->TMPL->fetch_param('offset')) ? 0 : $this->EE->TMPL->fetch_param('offset');
  51. $template = $this->EE->TMPL->tagdata;
  52. if ($this->page_url == '')
  53. {
  54. return $this->return_data;
  55. }
  56. if ($this->EE->config->item('debug') == 2 OR ($this->EE->config->item('debug') == 1 && $this->EE->session->userdata['group_id'] == 1))
  57. {
  58. if ( ! defined('MAGPIE_DEBUG'))
  59. {
  60. define('MAGPIE_DEBUG', 1);
  61. }
  62. }
  63. else
  64. {
  65. if ( ! defined('MAGPIE_DEBUG'))
  66. {
  67. define('MAGPIE_DEBUG', 0);
  68. }
  69. }
  70. /** -------------------------------
  71. /** Check and Retrive Cache
  72. /** -------------------------------*/
  73. if ( ! defined('MAGPIE_CACHE_DIR'))
  74. {
  75. define('MAGPIE_CACHE_DIR', APPPATH.'cache/'.$this->cache_name.'/');
  76. }
  77. if ( ! defined('MAGPIE_CACHE_AGE'))
  78. {
  79. define('MAGPIE_CACHE_AGE', $this->cache_refresh * 60);
  80. }
  81. $this->RSS = fetch_rss($this->page_url);
  82. if (count($this->RSS->items) == 0)
  83. {
  84. return $this->return_data;
  85. }
  86. /** -----------------------------------
  87. /** Parse Template - ITEMS
  88. /** -----------------------------------*/
  89. if (preg_match("/(".LD."items".RD."(.*?)".LD.'\/'.'items'.RD."|".LD."magpie:items".RD."(.*?)".LD.'\/'.'magpie:items'.RD.")/s", $template, $matches))
  90. {
  91. $items_data = '';
  92. $i = 0;
  93. if (count($this->RSS->items) > 0)
  94. {
  95. foreach($this->RSS->items as $item)
  96. {
  97. $i++;
  98. if ($i <= $offset) continue;
  99. $temp_data = $matches['1'];
  100. /** ----------------------------------------
  101. /** Quick and Dirty Conditionals
  102. /** ----------------------------------------*/
  103. if (stristr($temp_data, LD.'if'))
  104. {
  105. $tagdata = $this->EE->functions->prep_conditionals($temp_data, $item, '', 'magpie:');
  106. }
  107. /** ----------------------------------------
  108. /** Single Variables
  109. /** ----------------------------------------*/
  110. foreach($item as $key => $value)
  111. {
  112. if ( ! is_array($value))
  113. {
  114. $temp_data = str_replace(LD.$key.RD, $value, $temp_data);
  115. $temp_data = str_replace(LD.'magpie:'.$key.RD, $value, $temp_data);
  116. if ($key == 'atom_content')
  117. {
  118. $temp_data = str_replace(LD.'content'.RD, $value, $temp_data);
  119. $temp_data = str_replace(LD.'magpie:content'.RD, $value, $temp_data);
  120. }
  121. }
  122. else
  123. {
  124. foreach ($value as $vk => $vv)
  125. {
  126. $temp_data = str_replace(LD.$key.'_'.$vk.RD, $vv, $temp_data);
  127. $temp_data = str_replace(LD.'magpie:'.$key.'_'.$vk.RD, $vv, $temp_data);
  128. if ($key == 'dc')
  129. {
  130. $temp_data = str_replace(LD.$vk.RD, $vv, $temp_data);
  131. $temp_data = str_replace(LD.'magpie:'.$vk.RD, $vv, $temp_data);
  132. }
  133. }
  134. }
  135. }
  136. $items_data .= $temp_data;
  137. if ($i >= ($limit + $offset))
  138. {
  139. break;
  140. }
  141. }
  142. }
  143. /** ----------------------------------------
  144. /** Clean up left over variables
  145. /** ----------------------------------------*/
  146. $items_data = str_replace(LD.'exp:', 'TgB903He0mnv3dd098', $items_data);
  147. $items_data = str_replace(LD.'/exp:', 'Mu87ddk2QPoid990iod', $items_data);
  148. $items_data = preg_replace("/".LD."if.*?".RD.".+?".LD.'\/'."if".RD."/s", '', $items_data);
  149. $items_data = preg_replace("/".LD.".+?".RD."/", '', $items_data);
  150. $items_data = str_replace('TgB903He0mnv3dd098', LD.'exp:', $items_data);
  151. $items_data = str_replace('Mu87ddk2QPoid990iod', LD.'/exp:', $items_data);
  152. $template = str_replace($matches['0'], $items_data, $template);
  153. }
  154. /** -----------------------------------
  155. /** Parse Template
  156. /** -----------------------------------*/
  157. $channel_variables = array('title', 'link', 'modified', 'generator',
  158. 'copyright', 'description', 'language',
  159. 'pubdate', 'lastbuilddate', 'generator',
  160. 'tagline', 'creator', 'date', 'rights');
  161. $image_variables = array('title','url', 'link','description', 'width', 'height');
  162. foreach ($this->EE->TMPL->var_single as $key => $val)
  163. {
  164. /** ----------------------------------------
  165. /** {feed_version} - Version of RSS/Atom Feed
  166. /** ----------------------------------------*/
  167. if ($key == "feed_version" OR $key == "magpie:feed_version")
  168. {
  169. if ( ! isset($this->RSS->feed_version)) $this->RSS->feed_version = '';
  170. $template = $this->EE->TMPL->swap_var_single($val, $this->RSS->feed_version, $template);
  171. }
  172. /** ----------------------------------------
  173. /** {feed_type}
  174. /** ----------------------------------------*/
  175. if (($key == "feed_type" OR $key == "magpie:feed_type") && isset($this->RSS->feed_type))
  176. {
  177. if ( ! isset($this->RSS->feed_type)) $this->RSS->feed_type = '';
  178. $template = $this->EE->TMPL->swap_var_single($val, $this->RSS->feed_type, $template);
  179. }
  180. /** ----------------------------------------
  181. /** Image related variables
  182. /** ----------------------------------------*/
  183. foreach ($image_variables as $variable)
  184. {
  185. if ($key == 'image_'.$variable OR $key == 'magpie:image_'.$variable)
  186. {
  187. if ( ! isset($this->RSS->image[$variable])) $this->RSS->image[$variable] = '';
  188. $template = $this->EE->TMPL->swap_var_single($val, $this->RSS->image[$variable], $template);
  189. }
  190. }
  191. /** ----------------------------------------
  192. /** Channel related variables
  193. /** ----------------------------------------*/
  194. foreach ($channel_variables as $variable)
  195. {
  196. if ($key == 'channel_'.$variable OR $key == 'magpie:channel_'.$variable)
  197. {
  198. if ( ! isset($this->RSS->channel[$variable]))
  199. {
  200. $this->RSS->channel[$variable] = ( ! isset($this->RSS->channel['dc'][$variable])) ? '' : $this->RSS->channel['dc'][$variable];
  201. }
  202. $template = $this->EE->TMPL->swap_var_single($val, $this->RSS->channel[$variable], $template);
  203. }
  204. }
  205. /** ----------------------------------------
  206. /** {page_url}
  207. /** ----------------------------------------*/
  208. if ($key == 'page_url' OR $key == 'magpie:page_url')
  209. {
  210. $template = $this->EE->TMPL->swap_var_single($val, $this->page_url, $template);
  211. }
  212. }
  213. $this->return_data = &$template;
  214. }
  215. /** ----------------------------------------
  216. /** Plugin Usage
  217. /** ----------------------------------------*/
  218. function usage()
  219. {
  220. ob_start();
  221. ?>
  222. STEP ONE:
  223. Insert plugin tag into your template. Set parameters and variables.
  224. PARAMETERS:
  225. The tag has three parameters:
  226. 1. url - The URL of the RSS or Atom feed.
  227. 2. limit - Number of items to display from feed.
  228. 3. offset - Skip a certain number of items in the display of the feed.
  229. 4. refresh - How often to refresh the cache file in minutes. The plugin default is to refresh the cached file every three hours.
  230. Example opening tag: {exp:magpie url="http://expressionengine.com/feeds/rss/full/" limit="8" refresh="720"}
  231. SINGLE VARIABLES:
  232. feed_version - What version of RSS or Atom is this feed
  233. feed_type - What type of feed is this, Atom or RSS
  234. page_url - Page URL of the feed.
  235. image_title - [RSS] The contents of the &lt;title&gt; element contained within the sub-element &lt;channel&gt;
  236. image_url - [RSS] The contents of the &lt;url&gt; element contained within the sub-element &lt;channel&gt;
  237. image_link - [RSS] The contents of the &lt;link&gt; element contained within the sub-element &lt;channel&gt;
  238. image_description - [RSS] The contents of the optional &lt;description&gt; element contained within the sub-element &lt;channel&gt;
  239. image_width - [RSS] The contents of the optional &lt;width&gt; element contained within the sub-element &lt;channel&gt;
  240. image_height - [RSS] The contents of the optional &lt;height&gt; element contained within the sub-element &lt;channel&gt;
  241. channel_title - [ATOM/RSS-0.91/RSS-1.0/RSS-2.0]
  242. channel_link - [ATOM/RSS-0.91/RSS-1.0/RSS-2.0]
  243. channel_modified - [ATOM]
  244. channel_generator - [ATOM]
  245. channel_copyright - [ATOM]
  246. channel_description - [RSS-0.91/ATOM]
  247. channel_language - [RSS-0.91/RSS-1.0/RSS-2.0]
  248. channel_pubdate - [RSS-0.91]
  249. channel_lastbuilddate - [RSS-0.91]
  250. channel_tagline - [RSS-0.91/RSS-1.0/RSS-2.0]
  251. channel_creator - [RSS-1.0/RSS-2.0]
  252. channel_date - [RSS-1.0/RSS-2.0]
  253. channel_rights - [RSS-2.0]
  254. PAIR VARIABLES:
  255. Only one pair variable, {items}, is available, and it is for the entries/items in the RSS/Atom Feeds. This pair
  256. variable allows many different other single variables to be contained within it depending on the type of feed.
  257. title - [ATOM/RSS-0.91/RSS-1.0/RSS-2.0]
  258. link - [ATOM/RSS-0.91/RSS-1.0/RSS-2.0]
  259. description - [RSS-0.91/RSS-1.0/RSS-2.0]
  260. about - [RSS-1.0]
  261. atom_content - [ATOM]
  262. author_name - [ATOM]
  263. author_email - [ATOM]
  264. content - [ATOM/RSS-2.0]
  265. created - [ATOM]
  266. creator - [RSS-1.0]
  267. pubdate/date - (varies by feed design)
  268. description - [ATOM]
  269. id - [ATOM]
  270. issued - [ATOM]
  271. modified - [ATOM]
  272. subject - [ATOM/RSS-1.0]
  273. summary - [ATOM/RSS-1.0/RSS-2.0]
  274. EXAMPLE:
  275. {exp:magpie url="http://expressionengine.com/feeds/rss/full/" limit="10" refresh="720"}
  276. <ul>
  277. {items}
  278. <li><a href="{link}">{title}</a></li>
  279. {/items}
  280. </ul>
  281. {/exp:magpie}
  282. ***************************
  283. Version 1.2
  284. ***************************
  285. Complete Rewrite That Improved the Caching System Dramatically
  286. ***************************
  287. Version 1.2.1 + 1.2.2
  288. ***************************
  289. Bug Fixes
  290. ***************************
  291. Version 1.2.3
  292. ***************************
  293. Modified the code so that one can put 'magpie:' as a prefix on all plugin variables,
  294. which allows the embedding of this plugin in a {exp:channel:entries} tag and using
  295. that tag's variables in this plugin's parameter (url="" parameter, specifically).
  296. {exp:magpie url="http://expressionengine.com/feeds/rss/full/" limit="10" refresh="720"}
  297. <ul>
  298. {magpie:items}
  299. <li><a href="{magpie:link}">{magpie:title}</a></li>
  300. {/magpie:items}
  301. </ul>
  302. {/exp:magpie}
  303. ***************************
  304. Version 1.2.4
  305. ***************************
  306. Added the ability for the encoding to be parsed out of the XML feed and used to
  307. convert the feed's data into the encoding specified in the preferences. Requires
  308. that the Multibyte String (mbstring: http://us4.php.net/manual/en/ref.mbstring.php)
  309. library be compiled into PHP.
  310. ***************************
  311. Version 1.2.5
  312. ***************************
  313. Fixed a bug where the Magpie library was adding slashes to the cache directory
  314. without doing any sort of double slash checking.
  315. ***************************
  316. Version 1.3
  317. ***************************
  318. Fixed a bug where the channel and image variables were not showing up because of a bug
  319. introuced in 1.2.
  320. ***************************
  321. Version 1.3.1
  322. ***************************
  323. New parameter convert_entities="y" which will have any entities in the RSS feed converted
  324. before being parsed by the PHP XML parser. This is helpful because sometimes the XML
  325. Parser converts entities incorrectly. You have to empty your Magpie cache after enabling this setting.
  326. New parameter encoding="ISO-8859-1". Allows you to specify the encoding of the RSS
  327. feed, which is sometimes helpful when using the convert_encoding="y" parameter.
  328. ***************************
  329. Version 1.3.2
  330. ***************************
  331. Eliminated all of the darn encoding parameters previously being used and used the
  332. encoding abilities recently added to the Magpie library that attempts to do all of the
  333. converting early on.
  334. ***************************
  335. Version 1.3.3
  336. ***************************
  337. The Snoopy library that is included with the Magpie plugin by default was causing
  338. problems with the Snoopy library included in the Third Party Linklist module, so
  339. the name was changed to eliminate the conflict.
  340. ***************************
  341. Version 1.3.4
  342. ***************************
  343. The offset="" parameter was undocumented and had a bug. Fixed.
  344. ***************************
  345. Version 1.3.5
  346. ***************************
  347. Added ability to override caching options when using fetch_rss() directly.
  348. <?php
  349. $buffer = ob_get_contents();
  350. ob_end_clean();
  351. return $buffer;
  352. }
  353. } // END Magpie class
  354. /*
  355. // -------------------------------------------
  356. // BEGIN MagpieRSS Class
  357. // -------------------------------------------
  358. The MagpieRSS class is used here under a BSD license with the author's permission.
  359. Copyright (c) 2002, Kellan Elliott-McCrea All rights reserved.
  360. Redistribution and use in source and binary forms, with or without
  361. modification, are permitted provided that the following conditions are met:
  362. - Redistributions of source code must retain the above copyright notice, this
  363. list of conditions and the following disclaimer.
  364. - Redistributions in binary form must reproduce the above copyright notice,
  365. this list of conditions and the following disclaimer in the documentation
  366. and/or other materials provided with the distribution.
  367. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  368. CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  369. LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
  370. PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
  371. CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  372. EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  373. PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
  374. BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
  375. IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  376. ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  377. POSSIBILITY OF SUCH DAMAGE.
  378. * Project: MagpieRSS: a simple RSS integration tool
  379. * File: rss_parse.inc - parse an RSS or Atom feed
  380. * return as a simple object.
  381. *
  382. * Handles RSS 0.9x, RSS 2.0, RSS 1.0, and Atom 0.3
  383. *
  384. * The lastest version of MagpieRSS can be obtained from:
  385. * http://magpierss.sourceforge.net
  386. *
  387. * For questions, help, comments, discussion, etc., please join the
  388. * Magpie mailing list:
  389. * magpierss-general@lists.sourceforge.net
  390. *
  391. * Author: Kellan Elliott-McCrea <kellan@protest.net>
  392. * Version: 0.6a
  393. * License: GPL
  394. *
  395. *
  396. * ABOUT MAGPIE's APPROACH TO PARSING:
  397. * - Magpie is based on expat, an XML parser, and therefore will only parse
  398. * valid XML files. This includes all properly constructed RSS or Atom.
  399. *
  400. * - Magpie is an inclusive parser. It will include any elements that
  401. * it can turn into a key value pair in the parsed feed object it returns.
  402. *
  403. * - Magpie supports namespaces, and will return any elements found in a
  404. * namespace in a sub-array, with the key point to that array being the
  405. * namespace prefix.
  406. * (e.g. if an item contains a <dc:date> element, then that date can
  407. * be accessed at $item['dc']['date']
  408. *
  409. * - Magpie supports nested elements by combining the names. If an item
  410. * includes XML like:
  411. * <author>
  412. * <name>Kellan</name>
  413. * </author>
  414. *
  415. * The name field is accessible at $item['author_name']
  416. *
  417. * - Magpie makes no attempt validate a feed beyond insuring that it
  418. * is valid XML.
  419. * RSS validators are readily available on the web at:
  420. * http://feeds.archive.org/validator/
  421. * http://www.ldodds.com/rss_validator/1.0/validator.html
  422. *
  423. *
  424. * EXAMPLE PARSED RSS ITEM:
  425. *
  426. * Magpie tries to parse RSS into easy to use PHP datastructures.
  427. *
  428. * For example, Magpie on encountering (a rather complex) RSS 1.0 item entry:
  429. *
  430. * <item rdf:about="http://protest.net/NorthEast/calendrome.cgi?span=event&#38;ID=210257">
  431. * <title>Weekly Peace Vigil</title>
  432. * <link>http://protest.net/NorthEast/calendrome.cgi?span=event&#38;ID=210257</link>
  433. * <description>Wear a white ribbon</description>
  434. * <dc:subject>Peace</dc:subject>
  435. * <ev:startdate>2002-06-01T11:00:00</ev:startdate>
  436. * <ev:location>Northampton, MA</ev:location>
  437. * <ev:type>Protest</ev:type>
  438. * </item>
  439. *
  440. * Would transform it into the following associative array, and push it
  441. * onto the array $rss-items
  442. *
  443. * array(
  444. * title => 'Weekly Peace Vigil',
  445. * link => 'http://protest.net/NorthEast/calendrome.cgi?span=event&#38;ID=210257',
  446. * description => 'Wear a white ribbon',
  447. * dc => array (
  448. * subject => 'Peace'
  449. * ),
  450. * ev => array (
  451. * startdate => '2002-06-01T11:00:00',
  452. * enddate => '2002-06-01T12:00:00',
  453. * type => 'Protest',
  454. * location => 'Northampton, MA'
  455. * )
  456. * )
  457. *
  458. *
  459. *
  460. * A FEW NOTES ON PARSING Atom FEEDS
  461. *
  462. * Atom support is considered alpha. Atom elements will be often be available
  463. * as their RSS equivalent, summary is available as description for example.
  464. *
  465. * Elements of mode=xml, as flattened into a single string, just as if they
  466. * had been wrapped in a CDATA container.
  467. *
  468. * See: http://laughingmeme.org/archives/001676.html
  469. *
  470. */
  471. define('RSS', 'RSS');
  472. define('ATOM', 'Atom');
  473. class MagpieRSS {
  474. /*
  475. * Hybrid parser, and object. (probably a bad idea! :)
  476. *
  477. * Useage Example:
  478. *
  479. * $some_rss = "<?xml version="1.0"......
  480. *
  481. * $rss = new MagpieRSS( $some_rss );
  482. *
  483. * // print rss chanel title
  484. * echo $rss->channel['title'];
  485. *
  486. * // print the title of each item
  487. * foreach ($rss->items as $item )
  488. * {
  489. * echo $item[title];
  490. * }
  491. *
  492. * see: rss_fetch.inc for a simpler interface
  493. */
  494. var $parser;
  495. var $current_item = array(); // item currently being parsed
  496. var $items = array(); // collection of parsed items
  497. var $channel = array(); // hash of channel fields
  498. var $textinput = array();
  499. var $image = array();
  500. var $feed_type;
  501. var $feed_version;
  502. var $encoding;
  503. // parser variables
  504. var $stack = array(); // parser stack
  505. var $inchannel = false;
  506. var $initem = false;
  507. var $incontent = false; // if in Atom <content mode="xml"> field
  508. var $intextinput = false;
  509. var $inimage = false;
  510. var $current_field = '';
  511. var $current_namespace = false;
  512. var $etag = false;
  513. var $ERROR = "";
  514. var $_CONTENT_CONSTRUCTS = array('content', 'summary', 'info', 'title', 'tagline', 'copyright');
  515. var $_KNOWN_ENCODINGS = array('UTF-8', 'US-ASCII', 'ISO-8859-1');
  516. /*======================================================================*\
  517. Function: MagpieRSS
  518. Purpose: Constructor, sets up XML parser,parses source,
  519. and populates object..
  520. Input: String containing the RSS to be parsed
  521. \*======================================================================*/
  522. function MagpieRSS ($source, $output_encoding='ISO-8859-1',
  523. $input_encoding=null, $detect_encoding=true)
  524. {
  525. // Make a local reference of the ExpressionEngine super object
  526. $this->EE =& get_instance();
  527. # if PHP xml isn't compiled in, die
  528. #
  529. if ( ! function_exists('xml_parser_create'))
  530. {
  531. $this->error( "Failed to load PHP's XML Extension. " .
  532. "http://www.php.net/manual/en/ref.xml.php",
  533. E_USER_ERROR );
  534. }
  535. list($parser, $source) = $this->create_parser($source,
  536. $output_encoding, $input_encoding, $detect_encoding);
  537. if ( ! is_resource($parser))
  538. {
  539. $this->error( "Failed to create an instance of PHP's XML parser. " .
  540. "http://www.php.net/manual/en/ref.xml.php",
  541. E_USER_ERROR );
  542. }
  543. # pass in parser, and a reference to this object
  544. # setup handlers
  545. #
  546. xml_set_object( $parser, $this );
  547. xml_set_element_handler($parser,
  548. 'feed_start_element', 'feed_end_element' );
  549. xml_set_character_data_handler( $parser, 'feed_cdata' );
  550. $status = @xml_parse($parser, $source);
  551. if ( ! $status )
  552. {
  553. $errorcode = xml_get_error_code( $parser );
  554. if ( $errorcode != XML_ERROR_NONE )
  555. {
  556. $xml_error = xml_error_string( $errorcode );
  557. $error_line = xml_get_current_line_number($parser);
  558. $error_col = xml_get_current_column_number($parser);
  559. $errormsg = "$xml_error at line $error_line, column $error_col";
  560. $this->error( $errormsg );
  561. return FALSE;
  562. }
  563. }
  564. xml_parser_free( $parser );
  565. $this->normalize();
  566. }
  567. function change_key_case($array)
  568. {
  569. $new_array = array();
  570. foreach($array as $key => $value)
  571. {
  572. $new_array[strtolower($key)] = $value;
  573. }
  574. return $new_array;
  575. }
  576. function feed_start_element($p, $element, &$attrs)
  577. {
  578. $el = $element = strtolower($element);
  579. if ( ! function_exists('array_change_key_case'))
  580. {
  581. $attrs = $this->change_key_case($attrs);
  582. }
  583. else
  584. {
  585. $attrs = array_change_key_case($attrs, CASE_LOWER);
  586. }
  587. // check for a namespace, and split if found
  588. $ns = false;
  589. if ( strpos( $element, ':' ) )
  590. {
  591. list($ns, $el) = explode( ':', $element, 2);
  592. }
  593. if ( $ns and $ns != 'rdf' )
  594. {
  595. $this->current_namespace = $ns;
  596. }
  597. # if feed type isn't set, then this is first element of feed
  598. # identify feed from root element
  599. #
  600. if ( ! isset($this->feed_type) )
  601. {
  602. if ( $el == 'rdf' )
  603. {
  604. $this->feed_type = RSS;
  605. $this->feed_version = '1.0';
  606. }
  607. elseif ( $el == 'rss' )
  608. {
  609. $this->feed_type = RSS;
  610. $this->feed_version = $attrs['version'];
  611. }
  612. elseif ( $el == 'feed' )
  613. {
  614. $this->feed_type = ATOM;
  615. $this->feed_version = $attrs['version'];
  616. $this->inchannel = true;
  617. }
  618. return;
  619. }
  620. if ( $el == 'channel' )
  621. {
  622. $this->inchannel = true;
  623. }
  624. elseif ($el == 'item' or $el == 'entry' )
  625. {
  626. $this->initem = true;
  627. if ( isset($attrs['rdf:about']) )
  628. {
  629. $this->current_item['about'] = $attrs['rdf:about'];
  630. }
  631. }
  632. // if we're in the default namespace of an RSS feed,
  633. // record textinput or image fields
  634. elseif (
  635. $this->feed_type == RSS and
  636. $this->current_namespace == '' and
  637. $el == 'textinput' )
  638. {
  639. $this->intextinput = true;
  640. }
  641. elseif (
  642. $this->feed_type == RSS and
  643. $this->current_namespace == '' and
  644. $el == 'image' )
  645. {
  646. $this->inimage = true;
  647. }
  648. # handle atom content constructs
  649. elseif ( $this->feed_type == ATOM and in_array($el, $this->_CONTENT_CONSTRUCTS) )
  650. {
  651. // avoid clashing w/ RSS mod_content
  652. if ($el == 'content' )
  653. {
  654. $el = 'atom_content';
  655. }
  656. $this->incontent = $el;
  657. }
  658. // if inside an Atom content construct (e.g. content or summary) field treat tags as text
  659. elseif ($this->feed_type == ATOM and $this->incontent )
  660. {
  661. // if tags are inlined, then flatten
  662. $attrs_str = join(' ',
  663. array_map('map_attrs',
  664. array_keys($attrs),
  665. array_values($attrs) ) );
  666. $this->append_content( "<$element $attrs_str>" );
  667. array_unshift( $this->stack, $el );
  668. }
  669. // Atom support many links per containging element.
  670. // Magpie treats link elements of type rel='alternate'
  671. // as being equivalent to RSS's simple link element.
  672. //
  673. elseif ($this->feed_type == ATOM and $el == 'link' )
  674. {
  675. if ( isset($attrs['rel']) and $attrs['rel'] == 'alternate' )
  676. {
  677. $link_el = 'link';
  678. }
  679. else
  680. {
  681. $link_el = 'link_' . $attrs['rel'];
  682. }
  683. $this->append($link_el, $attrs['href']);
  684. }
  685. // set stack[0] to current element
  686. else
  687. {
  688. array_unshift($this->stack, $el);
  689. }
  690. }
  691. function feed_cdata ($p, $text)
  692. {
  693. if ($this->feed_type == ATOM and $this->incontent)
  694. {
  695. $this->append_content( $text );
  696. }
  697. else
  698. {
  699. $current_el = join('_', array_reverse($this->stack));
  700. $this->append($current_el, $text);
  701. }
  702. }
  703. function feed_end_element ($p, $el)
  704. {
  705. $el = strtolower($el);
  706. if ( $el == 'item' or $el == 'entry' )
  707. {
  708. $this->items[] = $this->current_item;
  709. $this->current_item = array();
  710. $this->initem = false;
  711. }
  712. elseif ($this->feed_type == RSS and $this->current_namespace == '' and $el == 'textinput' )
  713. {
  714. $this->intextinput = false;
  715. }
  716. elseif ($this->feed_type == RSS and $this->current_namespace == '' and $el == 'image' )
  717. {
  718. $this->inimage = false;
  719. }
  720. elseif ($this->feed_type == ATOM and in_array($el, $this->_CONTENT_CONSTRUCTS) )
  721. {
  722. $this->incontent = false;
  723. }
  724. elseif ($el == 'channel' or $el == 'feed' )
  725. {
  726. $this->inchannel = false;
  727. }
  728. elseif ($this->feed_type == ATOM and $this->incontent )
  729. {
  730. // balance tags properly
  731. // note: i don't think this is actually neccessary
  732. if ( $this->stack[0] == $el )
  733. {
  734. $this->append_content("</$el>");
  735. }
  736. else
  737. {
  738. $this->append_content("<$el />");
  739. }
  740. array_shift( $this->stack );
  741. }
  742. else
  743. {
  744. array_shift( $this->stack );
  745. }
  746. $this->current_namespace = false;
  747. }
  748. function concat (&$str1, $str2="")
  749. {
  750. if ( ! isset($str1) )
  751. {
  752. $str1="";
  753. }
  754. $str1 .= $str2;
  755. }
  756. function append_content($text)
  757. {
  758. if ( $this->initem )
  759. {
  760. $this->concat( $this->current_item[ $this->incontent ], $text );
  761. }
  762. elseif ( $this->inchannel )
  763. {
  764. $this->concat( $this->channel[ $this->incontent ], $text );
  765. }
  766. }
  767. // smart append - field and namespace aware
  768. function append($el, $text)
  769. {
  770. if ( ! $el)
  771. {
  772. return;
  773. }
  774. if ( $this->current_namespace )
  775. {
  776. if ( $this->initem )
  777. {
  778. $this->concat($this->current_item[ $this->current_namespace ][ $el ], $text);
  779. }
  780. elseif ($this->inchannel)
  781. {
  782. $this->concat($this->channel[ $this->current_namespace][ $el ], $text );
  783. }
  784. elseif ($this->intextinput)
  785. {
  786. $this->concat($this->textinput[ $this->current_namespace][ $el ], $text );
  787. }
  788. elseif ($this->inimage)
  789. {
  790. $this->concat($this->image[ $this->current_namespace ][ $el ], $text );
  791. }
  792. }
  793. else
  794. {
  795. if ( $this->initem )
  796. {
  797. $this->concat($this->current_item[ $el ], $text);
  798. }
  799. elseif ($this->intextinput)
  800. {
  801. $this->concat($this->textinput[ $el ], $text );
  802. }
  803. elseif ($this->inimage)
  804. {
  805. $this->concat($this->image[ $el ], $text );
  806. }
  807. elseif ($this->inchannel)
  808. {
  809. $this->concat($this->channel[ $el ], $text );
  810. }
  811. }
  812. }
  813. function normalize ()
  814. {
  815. // if atom populate rss fields
  816. if ( $this->is_atom() )
  817. {
  818. $this->channel['descripton'] = ( ! isset($this->channel['tagline'])) ? '' : $this->channel['tagline'];
  819. for ( $i = 0; $i < count($this->items); $i++)
  820. {
  821. $item = $this->items[$i];
  822. if ( isset($item['summary']) )
  823. {
  824. $item['description'] = $item['summary'];
  825. }
  826. if ( isset($item['atom_content']))
  827. {
  828. $item['content']['encoded'] = $item['atom_content'];
  829. }
  830. $this->items[$i] = $item;
  831. }
  832. }
  833. elseif ( $this->is_rss() )
  834. {
  835. $this->channel['tagline'] = ( ! isset($this->channel['description'])) ? '' : $this->channel['description'];
  836. for ( $i = 0; $i < count($this->items); $i++)
  837. {
  838. $item = $this->items[$i];
  839. if ( isset($item['description']))
  840. {
  841. $item['summary'] = $item['description'];
  842. }
  843. if ( isset($item['content']['encoded'] ) )
  844. {
  845. $item['atom_content'] = $item['content']['encoded'];
  846. }
  847. $this->items[$i] = $item;
  848. }
  849. }
  850. }
  851. function error ($errormsg, $lvl=E_USER_WARNING)
  852. {
  853. // append PHP's error message if track_errors enabled
  854. if ( isset($php_errormsg) )
  855. {
  856. $errormsg .= " ($php_errormsg)";
  857. }
  858. $this->ERROR = $errormsg;
  859. if (MAGPIE_DEBUG)
  860. {
  861. trigger_error($errormsg, $lvl);
  862. }
  863. else
  864. {
  865. error_log($errormsg, 0);
  866. }
  867. }
  868. function is_rss ()
  869. {
  870. if ( $this->feed_type == RSS )
  871. {
  872. return $this->feed_version;
  873. }
  874. else
  875. {
  876. return false;
  877. }
  878. }
  879. function is_atom()
  880. {
  881. if ( $this->feed_type == ATOM )
  882. {
  883. return $this->feed_version;
  884. }
  885. else
  886. {
  887. return false;
  888. }
  889. }
  890. /**
  891. * return XML parser, and possibly re-encoded source
  892. *
  893. */
  894. function create_parser($source, $out_enc, $in_enc, $detect)
  895. {
  896. if ( substr(phpversion(),0,1) == 5)
  897. {
  898. $parser = $this->php5_create_parser($in_enc, $detect);
  899. }
  900. else
  901. {
  902. list($parser, $source) = $this->php4_create_parser($source, $in_enc, $detect);
  903. }
  904. $this->encoding = $this->EE->config->item('charset');
  905. if (in_array(strtolower($this->encoding), array('iso-8859-1', 'us-ascii', 'utf-8')))
  906. {
  907. xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $this->encoding);
  908. }
  909. return array($parser, $source);
  910. }
  911. /**
  912. * Instantiate an XML parser under PHP5
  913. *
  914. * PHP5 will do a fine job of detecting input encoding
  915. * if passed an empty string as the encoding.
  916. *
  917. * All hail libxml2!
  918. *
  919. */
  920. function php5_create_parser($in_enc, $detect)
  921. {
  922. // by default php5 does a fine job of detecting input encodings
  923. if ( ! $detect && $in_enc)
  924. {
  925. return xml_parser_create($in_enc);
  926. }
  927. else
  928. {
  929. return xml_parser_create('');
  930. }
  931. }
  932. /**
  933. * Instaniate an XML parser under PHP4
  934. *
  935. * Unfortunately PHP4's support for character encodings
  936. * and especially XML and character encodings sucks. As
  937. * long as the documents you parse only contain characters
  938. * from the ISO-8859-1 character set (a superset of ASCII,
  939. * and a subset of UTF-8) you're fine. However once you
  940. * step out of that comfy little world things get mad, bad,
  941. * and dangerous to know.
  942. *
  943. * The following code is based on SJM's work with FoF
  944. * @see http://minutillo.com/steve/channel/2004/6/17/php-xml-and-character-encodings-a-tale-of-sadness-rage-and-data-loss
  945. *
  946. */
  947. function php4_create_parser($source, $in_enc, $detect)
  948. {
  949. if ( ! $detect )
  950. {
  951. return array(xml_parser_create($in_enc), $source);
  952. }
  953. if ( ! $in_enc)
  954. {
  955. if (preg_match('/<?xml.*encoding=[\'"](.*?)[\'"].*?>/m', $source, $m))
  956. {
  957. $in_enc = strtoupper($m[1]);
  958. $this->source_encoding = $in_enc;
  959. }
  960. else
  961. {
  962. $in_enc = 'UTF-8';
  963. }
  964. }
  965. if ($this->known_encoding($in_enc))
  966. {
  967. return array(xml_parser_create($in_enc), $source);
  968. }
  969. // the dectected encoding is not one of the simple encodings PHP knows
  970. // attempt to use the iconv extension to
  971. // cast the XML to a known encoding
  972. // @see http://php.net/iconv
  973. if (function_exists('iconv'))
  974. {
  975. $encoded_source = iconv($in_enc,'UTF-8', $source);
  976. if ($encoded_source)
  977. {
  978. return array(xml_parser_create('UTF-8'), $encoded_source);
  979. }
  980. }
  981. // iconv didn't work, try mb_convert_encoding
  982. // @see http://php.net/mbstring
  983. if (function_exists('mb_convert_encoding'))
  984. {
  985. $encoded_source = mb_convert_encoding($source, 'UTF-8', $in_enc );
  986. if ($encoded_source)
  987. {
  988. return array(xml_parser_create('UTF-8'), $encoded_source);
  989. }
  990. }
  991. // else
  992. $this->error("Feed is in an unsupported character encoding. ($in_enc) " .
  993. "You may see strange artifacts, and mangled characters.",
  994. E_USER_NOTICE
  995. );
  996. return array(xml_parser_create(), $source);
  997. }
  998. function known_encoding($enc)
  999. {
  1000. $enc = strtoupper($enc);
  1001. if ( in_array($enc, $this->_KNOWN_ENCODINGS) )
  1002. {
  1003. return $enc;
  1004. }
  1005. else
  1006. {
  1007. return false;
  1008. }
  1009. }
  1010. /*======================================================================*\
  1011. EVERYTHING BELOW HERE IS FOR DEBUGGING PURPOSES
  1012. \*======================================================================*/
  1013. function show_list ()
  1014. {
  1015. echo "<ol>\n";
  1016. foreach ($this->items as $item)
  1017. {
  1018. echo "<li>", $this->show_item( $item );
  1019. }
  1020. echo "</ol>";
  1021. }
  1022. function show_channel ()
  1023. {
  1024. echo "channel:<br>";
  1025. echo "<ul>";
  1026. while ( list($key, $value) = each( $this->channel ) )
  1027. {
  1028. echo "<li> $key: $value";
  1029. }
  1030. echo "</ul>";
  1031. }
  1032. function show_item ($item)
  1033. {
  1034. echo "item: $item[title]";
  1035. echo "<ul>";
  1036. while ( list($key, $value) = each($item) )
  1037. {
  1038. if ( is_array($value) )
  1039. {
  1040. echo "<br><b>$key</b>";
  1041. echo "<ul>";
  1042. while ( list( $ns_key, $ns_value) = each( $value ) )
  1043. {
  1044. echo "<li>$ns_key: $ns_value";
  1045. }
  1046. echo "</ul>";
  1047. }
  1048. else
  1049. {
  1050. echo "<li> $key: $value";
  1051. }
  1052. }
  1053. echo "</ul>";
  1054. }
  1055. /*======================================================================*\
  1056. END DEBUGGING FUNCTIONS
  1057. \*======================================================================*/
  1058. } # end class RSS
  1059. function map_attrs($k, $v)
  1060. {
  1061. return "$k=\"$v\"";
  1062. }
  1063. /*
  1064. * Project: MagpieRSS: a simple RSS integration tool
  1065. * File: rss_fetch.inc, a simple functional interface
  1066. to fetching and parsing RSS files, via the
  1067. function fetch_rss()
  1068. * Author: Kellan Elliott-McCrea <kellan@protest.net>
  1069. * License: GPL
  1070. *
  1071. * The lastest version of MagpieRSS can be obtained from:
  1072. * http://magpierss.sourceforge.net
  1073. *
  1074. * For questions, help, comments, discussion, etc., please join the
  1075. * Magpie mailing list:
  1076. * magpierss-general@lists.sourceforge.net
  1077. *
  1078. */
  1079. // Setup MAGPIE_DIR for use on hosts that don't include
  1080. // the current path in include_path.
  1081. // with thanks to rajiv and smarty
  1082. if ( ! defined('DIR_SEP'))
  1083. {
  1084. define('DIR_SEP', DIRECTORY_SEPARATOR);
  1085. }
  1086. if ( ! defined('MAGPIE_DIR'))
  1087. {
  1088. define('MAGPIE_DIR', dirname(__FILE__) . DIR_SEP);
  1089. }
  1090. /*
  1091. * CONSTANTS - redefine these in your script to change the
  1092. * behaviour of fetch_rss() currently, most options effect the cache
  1093. *
  1094. * MAGPIE_CACHE_ON - Should Magpie cache parsed RSS objects?
  1095. * For me a built in cache was essential to creating a "PHP-like"
  1096. * feel to Magpie, see rss_cache.inc for rationale
  1097. *
  1098. *
  1099. * MAGPIE_CACHE_DIR - Where should Magpie cache parsed RSS objects?
  1100. * This should be a location that the webserver can write to. If this
  1101. * directory does not already exist Mapie will try to be smart and create
  1102. * it. This will often fail for permissions reasons.
  1103. *
  1104. *
  1105. * MAGPIE_CACHE_AGE - How long to store cached RSS objects? In seconds.
  1106. *
  1107. *
  1108. * MAGPIE_CACHE_FRESH_ONLY - If remote fetch fails, throw error
  1109. * instead of returning stale object?
  1110. *
  1111. * MAGPIE_DEBUG - Display debugging notices?
  1112. *
  1113. */
  1114. /*=======================================================================*\
  1115. Function: fetch_rss:
  1116. Purpose: return RSS object for the give url
  1117. maintain the cache
  1118. Input: url of RSS file
  1119. Output: parsed RSS object (see rss_parse.inc)
  1120. NOTES ON CACHEING:
  1121. If caching is on (MAGPIE_CACHE_ON) fetch_rss will first check the cache.
  1122. NOTES ON RETRIEVING REMOTE FILES:
  1123. If conditional gets are on (MAGPIE_CONDITIONAL_GET_ON) fetch_rss will
  1124. return a cached object, and touch the cache object upon recieving a
  1125. 304.
  1126. NOTES ON FAILED REQUESTS:
  1127. If there is an HTTP error while fetching an RSS object, the cached
  1128. version will be return, if it exists (and if MAGPIE_CACHE_FRESH_ONLY is off)
  1129. \*=======================================================================*/
  1130. define('MAGPIE_VERSION', '0.61');
  1131. $MAGPIE_ERROR = "";
  1132. function fetch_rss ($url, $cache_age = '')
  1133. {
  1134. // initialize constants
  1135. init();
  1136. if ( ! isset($url) )
  1137. {
  1138. error("fetch_rss called without a url");
  1139. return false;
  1140. }
  1141. // if cache is disabled
  1142. if ( !MAGPIE_CACHE_ON )
  1143. {
  1144. // fetch file, and parse it
  1145. $resp = _fetch_remote_file( $url );
  1146. if ( is_success( $resp->status ) )
  1147. {
  1148. return _response_to_rss( $resp );
  1149. }
  1150. else
  1151. {
  1152. error("Failed to fetch $url and cache is off");
  1153. return false;
  1154. }
  1155. }
  1156. // else cache is ON
  1157. else
  1158. {
  1159. // Flow
  1160. // 1. check cache
  1161. // 2. if there is a hit, make sure its fresh
  1162. // 3. if cached obj fails freshness check, fetch remote
  1163. // 4. if remote fails, return stale object, or error
  1164. $cache = new RSSCache( MAGPIE_CACHE_DIR, ($cache_age != '') ? $cache_age : MAGPIE_CACHE_AGE );
  1165. if (MAGPIE_DEBUG and $cache->ERROR)
  1166. {
  1167. debug($cache->ERROR, E_USER_WARNING);
  1168. }
  1169. $cache_status = 0; // response of check_cache
  1170. $request_headers = array(); // HTTP headers to send with fetch
  1171. $rss = 0; // parsed RSS object
  1172. $errormsg = 0; // errors, if any
  1173. if ( ! $cache->ERROR)
  1174. {
  1175. // return cache HIT, MISS, or STALE
  1176. $cache_status = $cache->check_cache( $url );
  1177. }
  1178. // if object cached, and cache is fresh, return cached obj
  1179. if ( $cache_status == 'HIT' )
  1180. {
  1181. $rss = $cache->get( $url );
  1182. if ( isset($rss) and $rss )
  1183. {
  1184. $rss->from_cache = 1;
  1185. if ( MAGPIE_DEBUG > 1)
  1186. {
  1187. debug("MagpieRSS: Cache HIT", E_USER_NOTICE);
  1188. }
  1189. return $rss;
  1190. }
  1191. }
  1192. // else attempt a conditional get
  1193. // setup headers
  1194. if ( $cache_status == 'STALE' )
  1195. {
  1196. $rss = $cache->get( $url );
  1197. if (isset($rss->etag) && isset($rss->last_modified))
  1198. {
  1199. $request_headers['If-None-Match'] = $rss->etag;
  1200. $request_headers['If-Last-Modified'] = $rss->last_modified;
  1201. }
  1202. }
  1203. $resp = _fetch_remote_file( $url, $request_headers );
  1204. if (isset($resp) and $resp)
  1205. {
  1206. if ($resp->status == '304' )
  1207. {
  1208. // we have the most current copy
  1209. if ( MAGPIE_DEBUG > 1)
  1210. {
  1211. debug("Got 304 for $url");
  1212. }
  1213. // reset cache on 304 (at minutillo insistent prodding)
  1214. $cache->set($url, $rss);
  1215. return $rss;
  1216. }
  1217. elseif ( is_success( $resp->status ) )
  1218. {
  1219. $rss = _response_to_rss( $resp );
  1220. if ( $rss )
  1221. {
  1222. if (MAGPIE_DEBUG > 1)
  1223. {
  1224. debug("Fetch successful");
  1225. }
  1226. // add object to cache
  1227. $cache->set( $url, $rss );
  1228. return $rss;
  1229. }
  1230. }
  1231. else
  1232. {
  1233. $errormsg = "Failed to fetch $url. ";
  1234. if ( $resp->error )
  1235. {
  1236. # compensate for Snoopy's annoying habbit to tacking
  1237. # on '\n'
  1238. $http_error = substr($resp->error, 0, -2);
  1239. $errormsg .= "(HTTP Error: $http_error)";
  1240. }
  1241. else
  1242. {
  1243. $errormsg .= "(HTTP Response: " . $resp->response_code .')';
  1244. }
  1245. }
  1246. }
  1247. else
  1248. {
  1249. $errormsg = "Unable to retrieve RSS file for unknown reasons.";
  1250. }
  1251. // else fetch failed
  1252. // attempt to return cached object
  1253. if ($rss)
  1254. {
  1255. if ( MAGPIE_DEBUG )
  1256. {
  1257. //debug("Returning STALE object for $url");
  1258. }
  1259. return $rss;
  1260. }
  1261. // else we totally failed
  1262. error( $errormsg );
  1263. return false;
  1264. } // end if ( !MAGPIE_CACHE_ON )
  1265. } // end fetch_rss()
  1266. /*=======================================================================*\
  1267. Function: error
  1268. Purpose: set MAGPIE_ERROR, and trigger error
  1269. \*=======================================================================*/
  1270. function error ($errormsg, $lvl=E_USER_WARNING)
  1271. {
  1272. global $MAGPIE_ERROR;
  1273. // append PHP's error message if track_errors enabled
  1274. if ( isset($php_errormsg) )
  1275. {
  1276. $errormsg .= " ($php_errormsg)";
  1277. }
  1278. if ( $errormsg )
  1279. {
  1280. $errormsg = "MagpieRSS: $errormsg";
  1281. $MAGPIE_ERROR = $errormsg;
  1282. if (MAGPIE_DEBUG)
  1283. {
  1284. trigger_error($errormsg, $lvl);
  1285. }
  1286. else
  1287. {
  1288. error_log($errormsg, 0);
  1289. }
  1290. }
  1291. }
  1292. function debug ($debugmsg, $lvl=E_USER_NOTICE)
  1293. {
  1294. trigger_error("MagpieRSS [debug] $debugmsg", $lvl);
  1295. }
  1296. /*=======================================================================*\
  1297. Function: magpie_error
  1298. Purpose: accessor for the magpie error variable
  1299. \*=======================================================================*/
  1300. function magpie_error ($errormsg="")
  1301. {
  1302. global $MAGPIE_ERROR;
  1303. if ( isset($errormsg) and $errormsg )
  1304. {
  1305. $MAGPIE_ERROR = $errormsg;
  1306. }
  1307. return $MAGPIE_ERROR;
  1308. }
  1309. /*=======================================================================*\
  1310. Function: _fetch_remote_file
  1311. Purpose: retrieve an arbitrary remote file
  1312. Input: url of the remote file
  1313. headers to send along with the request (optional)
  1314. Output: an HTTP response object (see Snoopy.class.inc)
  1315. \*=======================================================================*/
  1316. function _fetch_remote_file ($url, $headers = "" )
  1317. {
  1318. // Snoopy is an HTTP client in PHP
  1319. $client = new M_Snoopy();
  1320. $client->agent = MAGPIE_USER_AGENT;
  1321. $client->read_timeout = MAGPIE_FETCH_TIME_OUT;
  1322. $client->use_gzip = MAGPIE_USE_GZIP;
  1323. if (is_array($headers) )
  1324. {
  1325. $client->rawheaders = $headers;
  1326. }
  1327. @$client->fetch($url);
  1328. return $client;
  1329. }
  1330. /*=======================================================================*\
  1331. Function: _response_to_rss
  1332. Purpose: parse an HTTP response object into an RSS object
  1333. Input: an HTTP response object (see Snoopy)
  1334. Output: parsed RSS object (see rss_parse)
  1335. \*=======================================================================*/
  1336. function _response_to_rss ($resp)
  1337. {
  1338. $rss = new MagpieRSS( $resp->results );
  1339. // if RSS parsed successfully
  1340. if ( $rss and ! $rss->ERROR)
  1341. {
  1342. // find Etag, and Last-Modified
  1343. foreach($resp->headers as $h)
  1344. {
  1345. // 2003-03-02 - Nicola Asuni (www.tecnick.com) - fixed bug "Undefined offset: 1"
  1346. if (strpos($h, ": "))
  1347. {
  1348. list($field, $val) = explode(": ", $h, 2);
  1349. }
  1350. else
  1351. {
  1352. $field = $h;
  1353. $val = "";
  1354. }
  1355. if ( $field == 'ETag' )
  1356. {
  1357. $rss->etag = $val;
  1358. }
  1359. if ( $field == 'Last-Modified' )
  1360. {
  1361. $rss->last_modified = $val;
  1362. }
  1363. }
  1364. return $rss;
  1365. } // else construct error message
  1366. else
  1367. {
  1368. $errormsg = "Failed to parse RSS file.";
  1369. if ($rss)
  1370. {
  1371. $errormsg .= " (" . $rss->ERROR . ")";
  1372. }
  1373. error($errormsg);
  1374. return false;
  1375. } // end if ($rss and ! $rss->error)
  1376. }
  1377. /*=======================================================================*\
  1378. Function: init
  1379. Purpose: setup constants with default values
  1380. check for user overrides
  1381. \*=======================================================================*/
  1382. function init ()
  1383. {
  1384. if ( defined('MAGPIE_INITALIZED') )
  1385. {
  1386. return;
  1387. }
  1388. else
  1389. {
  1390. define('MAGPIE_INITALIZED', 1);
  1391. }
  1392. if ( ! defined('MAGPIE_CACHE_ON') )
  1393. {
  1394. define('MAGPIE_CACHE_ON', 1);
  1395. }
  1396. if ( ! defined('MAGPIE_CACHE_DIR') )
  1397. {
  1398. define('MAGPIE_CACHE_DIR', './cache');
  1399. }
  1400. if ( ! defined('MAGPIE_CACHE_AGE') )
  1401. {
  1402. define('MAGPIE_CACHE_AGE', 60*60); // one hour
  1403. }
  1404. if ( ! defined('MAGPIE_CACHE_FRESH_ONLY') )
  1405. {
  1406. define('MAGPIE_CACHE_FRESH_ONLY', 0);
  1407. }
  1408. if ( ! defined('MAGPIE_DEBUG') )
  1409. {
  1410. define('MAGPIE_DEBUG', 0);
  1411. }
  1412. if ( ! defined('MAGPIE_USER_AGENT') )
  1413. {
  1414. $ua = 'MagpieRSS/'. MAGPIE_VERSION . ' (+http://magpierss.sf.net';
  1415. if ( MAGPIE_CACHE_ON )
  1416. {
  1417. $ua = $ua . ')';
  1418. }
  1419. else
  1420. {
  1421. $ua = $ua . '; No cache)';
  1422. }
  1423. define('MAGPIE_USER_AGENT', $ua);
  1424. }
  1425. if ( ! defined('MAGPIE_FETCH_TIME_OUT') )
  1426. {
  1427. define('MAGPIE_FETCH_TIME_OUT', 5); // 5 second timeout
  1428. }
  1429. // use gzip encoding to fetch rss files if supported?
  1430. if ( ! defined('MAGPIE_USE_GZIP') )
  1431. {
  1432. define('MAGPIE_USE_GZIP', true);
  1433. }
  1434. }
  1435. // NOTE: the following code should really be in Snoopy, or at least
  1436. // somewhere other then rss_fetch!
  1437. /*=======================================================================*\
  1438. HTTP STATUS CODE PREDICATES
  1439. These functions attempt to classify an HTTP status code
  1440. based on RFC 2616 and RFC 2518.
  1441. All of them take an HTTP status code as input, and return true or false
  1442. All this code is adapted from LWP's HTTP::Status.
  1443. \*=======================================================================*/
  1444. /*=======================================================================*\
  1445. Function: is_info
  1446. Purpose: return true if Informational status code
  1447. \*=======================================================================*/
  1448. function is_info ($sc)
  1449. {
  1450. return $sc >= 100 && $sc < 200;
  1451. }
  1452. /*=======================================================================*\
  1453. Function: is_success
  1454. Purpose: return true if Successful status code
  1455. \*=======================================================================*/
  1456. function is_success ($sc)
  1457. {
  1458. return $sc >= 200 && $sc < 300;
  1459. }
  1460. /*=======================================================================*\
  1461. Function: is_redirect
  1462. Purpose: return true if Redirection status code
  1463. \*=======================================================================*/
  1464. function is_redirect ($sc)
  1465. {
  1466. return $sc >= 300 && $sc < 400;
  1467. }
  1468. /*=======================================================================*\
  1469. Function: is_error
  1470. Purpose: return true if Error status code
  1471. \*=======================================================================*/
  1472. function is_error ($sc)
  1473. {
  1474. return $sc >= 400 && $sc < 600;
  1475. }
  1476. /*=======================================================================*\
  1477. Function: is_client_error
  1478. Purpose: return true if Error status code, and its a client error
  1479. \*=======================================================================*/
  1480. function is_client_error ($sc)
  1481. {
  1482. return $sc >= 400 && $sc < 500;
  1483. }
  1484. /*=======================================================================*\
  1485. Function: is_client_error
  1486. Purpose: return true if Error status code, and its a server error
  1487. \*=======================================================================*/
  1488. function is_server_error ($sc)
  1489. {
  1490. return $sc >= 500 && $sc < 600;
  1491. }
  1492. /*
  1493. * Project: MagpieRSS: a simple RSS integration tool
  1494. * File: rss_cache.inc, a simple, rolling(no GC), cache
  1495. * for RSS objects, keyed on URL.
  1496. * Author: Kellan Elliott-McCrea <kellan@protest.net>
  1497. * Version: 0.51
  1498. * License: GPL
  1499. *
  1500. * The lastest version of MagpieRSS can be obtained from:
  1501. * http://magpierss.sourceforge.net
  1502. *
  1503. * For questions, help, comments, discussion, etc., please join the
  1504. * Magpie mailing list:
  1505. * http://lists.sourceforge.net/lists/listinfo/magpierss-general
  1506. *
  1507. */
  1508. class RSSCache {
  1509. var $BASE_CACHE = './cache'; // where the cache files are stored
  1510. var $MAX_AGE = 3600; // when are files stale, default one hour
  1511. var $ERROR = ""; // accumulate error messages
  1512. function RSSCache ($base='', $age='')
  1513. {
  1514. // Make a local reference of the ExpressionEngine super object
  1515. $this->EE =& get_instance();
  1516. if ( $base )
  1517. {
  1518. $this->BASE_CACHE = $base;
  1519. }
  1520. if ( $age )
  1521. {
  1522. $this->MAX_AGE = $age;
  1523. }
  1524. // attempt to make the cache directory
  1525. if ( ! file_exists( $this->BASE_CACHE ) )
  1526. {
  1527. $status = @mkdir( $this->BASE_CACHE, DIR_READ_MODE );
  1528. @chmod($this->BASE_CACHE, DIR_WRITE_MODE);
  1529. // if make failed
  1530. if ( ! $status )
  1531. {
  1532. $this->error(
  1533. "Cache couldn't make dir '" . $this->BASE_CACHE . "'."
  1534. );
  1535. }
  1536. }
  1537. else
  1538. {
  1539. // EE - Make sure cache is 777
  1540. @chmod($this->BASE_CACHE, DIR_WRITE_MODE);
  1541. }
  1542. }
  1543. /*=======================================================================*\
  1544. Function: set
  1545. Purpose: add an item to the cache, keyed on url
  1546. Input: url from wich the rss file was fetched
  1547. Output: true on sucess
  1548. \*=======================================================================*/
  1549. function set ($url, $rss)
  1550. {
  1551. $this->ERROR = "";
  1552. $cache_file = $this->file_name( $url );
  1553. $fp = @fopen( $cache_file, 'w' );
  1554. if ( ! $fp )
  1555. {
  1556. $this->error(
  1557. "Cache unable to open file for writing: $cache_file"
  1558. );
  1559. return 0;
  1560. }
  1561. $data = serialize( $rss );
  1562. fwrite( $fp, $data );
  1563. fclose( $fp );
  1564. @chmod($cache_file, FILE_WRITE_MODE);
  1565. return $cache_file;
  1566. }
  1567. /*=======================================================================*\
  1568. Function: get
  1569. Purpose: fetch an item from the cache
  1570. Input: url from wich the rss file was fetched
  1571. Output: cached object on HIT, false on MISS
  1572. \*=======================================================================*/
  1573. function get ($url)
  1574. {
  1575. $this->ERROR = "";
  1576. $cache_file = $this->file_name( $url );
  1577. if ( ! file_exists( $cache_file ) )
  1578. {
  1579. $this->debug(
  1580. "Cache doesn't contain: $url (cache file: $cache_file)"
  1581. );
  1582. return 0;
  1583. }
  1584. $fp = @fopen($cache_file, 'r');
  1585. if ( ! $fp )
  1586. {
  1587. $this->error(
  1588. "Failed to open cache file for reading: $cache_file"
  1589. );
  1590. return 0;
  1591. }
  1592. if (($file_size = filesize($cache_file)) == 0)
  1593. {
  1594. return 0;
  1595. }
  1596. $data = fread( $fp, $file_size );
  1597. $rss = unserialize( $data );
  1598. @chmod($cache_file, FILE_WRITE_MODE);
  1599. return $rss;
  1600. }
  1601. /*=======================================================================*\
  1602. Function: check_cache
  1603. Purpose: check a url for membership in the cache
  1604. and whether the object is older then MAX_AGE (ie. STALE)
  1605. Input: url from wich the rss file was fetched
  1606. Output: cached object on HIT, false on MISS
  1607. \*=======================================================================*/
  1608. function check_cache ( $url )
  1609. {
  1610. $this->ERROR = "";
  1611. $filename = $this->file_name( $url );
  1612. if ( file_exists( $filename ) )
  1613. {
  1614. // find how long ago the file was added to the cache
  1615. // and whether that is longer then MAX_AGE
  1616. $mtime = filemtime( $filename );
  1617. $age = time() - $mtime;
  1618. if ( $this->

Large files files are truncated, but you can click here to view the full file