PageRenderTime 46ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/inc/core/class-items.php

http://lilina.googlecode.com/
PHP | 511 lines | 231 code | 53 blank | 227 comment | 30 complexity | 2b16a17279a11bc194bc170f2626860a MD5 | raw file
Possible License(s): AGPL-1.0
  1. <?php
  2. /**
  3. * The Lilina items class
  4. * @package Lilina
  5. * @subpackage Classes
  6. */
  7. class Items {
  8. /**
  9. * @var array|string
  10. */
  11. protected $feeds;
  12. /**
  13. * @var stdObject
  14. */
  15. protected $current_item;
  16. /**
  17. * @var string
  18. */
  19. protected $current_feed;
  20. /**
  21. * Stores item data in an stdClass object
  22. * @var array
  23. */
  24. public $item = array();
  25. /**
  26. * Stores previous item data in an stdClass object
  27. * @var array
  28. */
  29. public $previous_item = array();
  30. /**
  31. * Authoritative item database
  32. *
  33. * Any changes to this should be saved back to a file
  34. * @var array
  35. */
  36. protected $cached_items = array();
  37. /**
  38. * Local copy of item database to work with
  39. * @var array
  40. */
  41. public $items = array();
  42. /**
  43. * Conditions used when filtering items
  44. * @var array
  45. */
  46. protected $conditions = array();
  47. /**
  48. * Singleton instance of self
  49. *
  50. * Holds a singleton instance of self, for use by get_instance()
  51. *
  52. * @access protected
  53. */
  54. protected static $instance;
  55. /**
  56. * DataHandler instance
  57. *
  58. * Saves time, money and memory if we keep the same DataHandler
  59. *
  60. * @access protected
  61. */
  62. protected $data;
  63. /**
  64. * Sorting of items
  65. *
  66. * Decides which item sorting to use. Defaults to 'time' (sorting reverse
  67. * chronologically)
  68. * @var string
  69. */
  70. protected $sort = 'time';
  71. /**
  72. * Grouping of items
  73. *
  74. * Decides what to group items by. For example, 'feed' will group by feed
  75. * ID.
  76. *
  77. * Note: Regardless of grouping, Items::$items is always a single-level
  78. * associative array of items. Grouping simply orders the items by the
  79. * group first, then sorts within each group as per Items::$sort.
  80. * @var string
  81. */
  82. protected $group = '';
  83. /**
  84. * Object constructor
  85. *
  86. * Sets our used properties with user input
  87. */
  88. protected function __construct() {
  89. $this->data = new DataHandler();
  90. $current = $this->data->load('items.data');
  91. if($current !== null) {
  92. // Workaround for old, serialized PHP database
  93. if(($this->items = json_decode($current)) === $current) {
  94. $this->items = unserialize($current);
  95. }
  96. $this->items = (array) $this->items;
  97. $this->cached_items = $this->items;
  98. }
  99. }
  100. public static function &get_instance() {
  101. if ( ! isset( self::$instance ) ) {
  102. self::$instance = new Items();
  103. }
  104. return self::$instance;
  105. }
  106. /**
  107. * Stop object cloning
  108. *
  109. * As this is a singleton, we don't want to be able to clone this
  110. * @access private
  111. */
  112. protected function __clone() {}
  113. /**
  114. * Set the conditions to use when filtering
  115. *
  116. * @param array $conditions
  117. */
  118. public function set_conditions($conditions) {
  119. $this->conditions = array_merge($this->conditions, $conditions);
  120. }
  121. /**
  122. * Load items and resort
  123. */
  124. public function init() {
  125. $this->items = $this->cached_items;
  126. $this->sort_all();
  127. }
  128. public function reset() {
  129. $this->items = $this->cached_items;
  130. }
  131. /**
  132. * usort callback for items
  133. *
  134. * @param stdObject $a First item
  135. * @param stdObject $b Second item
  136. * @param bool
  137. */
  138. public function sort_items($a, $b) {
  139. return $b->timestamp - $a->timestamp;
  140. }
  141. /**
  142. * Sort all items
  143. *
  144. * This bypasses SimplePie's sorting (and lack thereof for items without
  145. * timestamps).
  146. */
  147. public function sort_all() {
  148. if(is_array($this->cached_items))
  149. uasort($this->cached_items, array('Items', 'sort_items'));
  150. if(is_array($this->items)) {
  151. switch($this->sort) {
  152. case 'time':
  153. default:
  154. uasort($this->items, array('Items', 'sort_items'));
  155. break;
  156. }
  157. switch($this->group) {
  158. case 'feed':
  159. $this->group_by_feed();
  160. break;
  161. default:
  162. // No grouping by default
  163. break;
  164. }
  165. }
  166. }
  167. protected function group_by_feed(){
  168. // Group by feed_id
  169. foreach($this->items as $key => $value) {
  170. $grouped[$value->feed_id][$key] = $value;
  171. }
  172. // Flattern
  173. foreach($grouped as $group_items) {
  174. foreach($group_items as $key => $value) {
  175. $items[$key] = $value;
  176. }
  177. }
  178. $this->items = $items;
  179. }
  180. /**
  181. * Retreive the items without fetching new ones
  182. *
  183. * Depending on whether {@link init()} is called or not, this may include
  184. * new items.
  185. * @return array List of items
  186. */
  187. public function retrieve() {
  188. return $this->items;
  189. }
  190. /**
  191. * Normalise a SimplePie_Item into a stdClass
  192. *
  193. * Converts a SimplePie_Item into a new-style stdClass
  194. */
  195. public function normalise($item, $feed_id = '') {
  196. if($enclosure = $item->get_enclosure()) {
  197. $enclosure = $enclosure->get_link();
  198. }
  199. else {
  200. // SimplePie_Item::get_enclosure() returns null, so we need to change this to false
  201. $enclosure = false;
  202. }
  203. if($author = $item->get_author()) {
  204. $author = array(
  205. 'name' => $item->get_author()->get_name(),
  206. 'url' => $item->get_author()->get_link()
  207. );
  208. }
  209. else {
  210. $author = array(
  211. 'name' => false,
  212. 'url' => false
  213. );
  214. }
  215. $new_item = (object) array(
  216. 'hash' => $item->get_id(true),
  217. 'timestamp' => $item->get_date('U'),
  218. 'title' => $item->get_title(),
  219. 'content' => $item->get_content(),
  220. 'summary' => $item->get_description(),
  221. 'permalink' => $item->get_permalink(),
  222. 'metadata' => (object) array(
  223. 'enclosure' => $enclosure
  224. ),
  225. 'author' => (object) $author,
  226. 'feed' => $item->get_feed()->get_link()
  227. );
  228. if(!empty($feed_id))
  229. $new_item->feed_id = $feed_id;
  230. return apply_filters('item_data', $new_item);
  231. }
  232. /**
  233. * Return all items
  234. *
  235. * @since 1.0
  236. *
  237. * @return array All items from the feed
  238. */
  239. public function get_items() {
  240. return $this->items;
  241. }
  242. /**
  243. * Return a specific item
  244. *
  245. * Retrieves a specific item and returns it, if it exists. If it does not
  246. * exist, returns false.
  247. *
  248. * @since 1.0
  249. *
  250. * @param int $hash Item index to retrieve
  251. * @return bool|stdClass False if item doesn't exist, otherwise returns the specified item
  252. */
  253. public function get_item($hash) {
  254. if( !isset($this->items[ $hash ]) )
  255. return false;
  256. $item = $this->items[$hash];
  257. return $item;
  258. }
  259. /**
  260. * Returns the current item
  261. *
  262. * @since 1.0
  263. *
  264. * @return bool|stdClass False if item doesn't exist, otherwise returns the specified item
  265. */
  266. public function current_item() {
  267. $this->previous_item = $this->current_item;
  268. $this->current_item = '';
  269. $item = each($this->items);
  270. $item = $item['value'];
  271. if(!$item)
  272. return false;
  273. $this->current_item = $item;
  274. $this->current_feed = $item->feed;
  275. return $item;
  276. }
  277. /**
  278. * Return the previous item
  279. *
  280. * @since 1.0
  281. *
  282. * @return bool|stdClass False if item doesn't exist, otherwise returns the specified item
  283. */
  284. public function previous_item() {
  285. if(empty($this->previous_item))
  286. return false;
  287. return $this->previous_item;
  288. }
  289. /**
  290. * Reset the item index iterator
  291. *
  292. * Resets LilinaItems::$offset to 0
  293. *
  294. * @since 1.0
  295. */
  296. public function reset_iterator() {
  297. reset($this->items);
  298. }
  299. /**
  300. * reset_iterator() - {@internal Short Description Missing}}
  301. *
  302. * {@internal Long Description Missing}}
  303. */
  304. public function has_items() {
  305. return !!current($this->items);
  306. }
  307. /**
  308. * Check whether the current item has an enclosure or not
  309. *
  310. * Checks to make sure an item has an enclosure and that that enclosure
  311. * has a link to use.
  312. *
  313. * @since 1.0
  314. *
  315. * @return bool
  316. */
  317. public function has_enclosure() {
  318. return !!$this->current_item->metadata->enclosure;
  319. }
  320. /**
  321. * Return the enclosure for the current item
  322. *
  323. * @since 1.0
  324. *
  325. * @return string Absolute URL to the enclosure
  326. */
  327. public function get_enclosure() {
  328. return $this->current_item->metadata->enclosure;
  329. }
  330. /**
  331. * Return the ID for the current item
  332. *
  333. * @since 1.0
  334. *
  335. * @return string MD5 hash
  336. */
  337. public function get_id() {
  338. return $this->current_item->hash;
  339. }
  340. /**
  341. *
  342. */
  343. public function filter() {
  344. if (isset($this->conditions['after'])) {
  345. $ids = array_keys($this->items);
  346. $pos = array_search($this->conditions['after'], $keys);
  347. if ($pos !== false)
  348. $this->items = array_slice($array, $pos + 1);
  349. }
  350. if (isset($this->conditions['until'])) {
  351. $ids = array_keys($this->items);
  352. $pos = array_search($this->conditions['until'], $keys);
  353. if ($pos !== false)
  354. $this->items = array_slice($array, 0, $pos);
  355. }
  356. $this->items = array_filter($this->items, array($this, 'filter_callback'));
  357. if (isset($this->conditions['start']) || isset($this->conditions['limit'])) {
  358. if (!empty($this->conditions['start'])) {
  359. $start = $this->conditions['start'];
  360. }
  361. else {
  362. $start = 0;
  363. }
  364. if (!empty($this->conditions['limit'])) {
  365. $limit = $this->conditions['limit'];
  366. }
  367. else {
  368. $limit = null;
  369. }
  370. $this->items = array_slice($this->items, $start, $limit);
  371. }
  372. }
  373. protected function filter_callback($item) {
  374. foreach($this->conditions as $key => $condition) {
  375. switch($key) {
  376. case 'time':
  377. if($item->timestamp < $condition) {
  378. return false;
  379. }
  380. break;
  381. case 'feed':
  382. if(empty($item->feed_id) || $item->feed_id != $condition) {
  383. return false;
  384. }
  385. break;
  386. }
  387. }
  388. return true;
  389. }
  390. /**
  391. * Check the current item against the cached items
  392. *
  393. * Checks the item against the cached database. If the item does not
  394. * exist, calls insert_item(). If the item is out-of-date, calls
  395. * update_item().
  396. *
  397. * @since 1.0
  398. *
  399. * @param stdClass $item Item to check
  400. */
  401. public function check_item($item) {
  402. if(!isset( $this->cached_items[ $item->hash ] )) {
  403. $this->update_item($item);
  404. do_action('insert_item', $item);
  405. return true;
  406. }
  407. $cached_item = $this->cached_items[ $item->hash ];
  408. if($cached_item->timestamp !== $item->timestamp) {
  409. $this->update_item($item);
  410. do_action('update_item', $item, $cached_item);
  411. return true;
  412. }
  413. return false;
  414. }
  415. /**
  416. * Insert the current item into the cache database
  417. *
  418. * Inserts the item into the database with the information from the
  419. * current item.
  420. *
  421. * @since 1.0
  422. * @deprecated Use {@see update_item()} instead.
  423. *
  424. * @param stdClass $item Item to insert into database
  425. */
  426. protected function insert_item($item) {
  427. $this->update_item($item);
  428. }
  429. /**
  430. * Update the cached version of the current item
  431. *
  432. * Updates the item into the database with the information from the
  433. * current item.
  434. *
  435. * @since 1.0
  436. *
  437. * @param stdClass $item Item to update
  438. */
  439. protected function update_item($item) {
  440. if(isset($this->cached_items[ $item->hash ]))
  441. do_action('itemcache-update', $item);
  442. else
  443. do_action('itemcache-insert', $item);
  444. $this->items[ $item->hash ] = $item;
  445. $this->cached_items[ $item->hash ] = $item;
  446. }
  447. /**
  448. * Cache items
  449. *
  450. * Stores current items back into cache.
  451. *
  452. * @since 1.0
  453. */
  454. public function save_cache() {
  455. $this->cached_items = apply_filters('save_items', $this->cached_items, $this);
  456. $this->data->save('items.data', json_encode($this->cached_items));
  457. }
  458. }