MySQL.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. <?php
  2. /**
  3. * SimplePie
  4. *
  5. * A PHP-Based RSS and Atom Feed Framework.
  6. * Takes the hard work out of managing a complete RSS/Atom solution.
  7. *
  8. * Copyright (c) 2004-2016, Ryan Parman, Sam Sneddon, Ryan McCue, and contributors
  9. * All rights reserved.
  10. *
  11. * Redistribution and use in source and binary forms, with or without modification, are
  12. * permitted provided that the following conditions are met:
  13. *
  14. * * Redistributions of source code must retain the above copyright notice, this list of
  15. * conditions and the following disclaimer.
  16. *
  17. * * Redistributions in binary form must reproduce the above copyright notice, this list
  18. * of conditions and the following disclaimer in the documentation and/or other materials
  19. * provided with the distribution.
  20. *
  21. * * Neither the name of the SimplePie Team nor the names of its contributors may be used
  22. * to endorse or promote products derived from this software without specific prior
  23. * written permission.
  24. *
  25. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
  26. * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
  27. * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
  28. * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  29. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  30. * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  31. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
  32. * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  33. * POSSIBILITY OF SUCH DAMAGE.
  34. *
  35. * @package SimplePie
  36. * @copyright 2004-2016 Ryan Parman, Sam Sneddon, Ryan McCue
  37. * @author Ryan Parman
  38. * @author Sam Sneddon
  39. * @author Ryan McCue
  40. * @link http://simplepie.org/ SimplePie
  41. * @license http://www.opensource.org/licenses/bsd-license.php BSD License
  42. */
  43. /**
  44. * Caches data to a MySQL database
  45. *
  46. * Registered for URLs with the "mysql" protocol
  47. *
  48. * For example, `mysql://root:password@localhost:3306/mydb?prefix=sp_` will
  49. * connect to the `mydb` database on `localhost` on port 3306, with the user
  50. * `root` and the password `password`. All tables will be prefixed with `sp_`
  51. *
  52. * @package SimplePie
  53. * @subpackage Caching
  54. */
  55. class SimplePie_Cache_MySQL extends SimplePie_Cache_DB
  56. {
  57. /**
  58. * PDO instance
  59. *
  60. * @var PDO
  61. */
  62. protected $mysql;
  63. /**
  64. * Options
  65. *
  66. * @var array
  67. */
  68. protected $options;
  69. /**
  70. * Cache ID
  71. *
  72. * @var string
  73. */
  74. protected $id;
  75. /**
  76. * Create a new cache object
  77. *
  78. * @param string $location Location string (from SimplePie::$cache_location)
  79. * @param string $name Unique ID for the cache
  80. * @param string $type Either TYPE_FEED for SimplePie data, or TYPE_IMAGE for image data
  81. */
  82. public function __construct($location, $name, $type)
  83. {
  84. $this->options = array(
  85. 'user' => null,
  86. 'pass' => null,
  87. 'host' => '127.0.0.1',
  88. 'port' => '3306',
  89. 'path' => '',
  90. 'extras' => array(
  91. 'prefix' => '',
  92. 'cache_purge_time' => 2592000
  93. ),
  94. );
  95. $this->options = SimplePie_Misc::array_merge_recursive($this->options, SimplePie_Cache::parse_URL($location));
  96. // Path is prefixed with a "/"
  97. $this->options['dbname'] = substr($this->options['path'], 1);
  98. try
  99. {
  100. $this->mysql = new PDO("mysql:dbname={$this->options['dbname']};host={$this->options['host']};port={$this->options['port']}", $this->options['user'], $this->options['pass'], array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'));
  101. }
  102. catch (PDOException $e)
  103. {
  104. $this->mysql = null;
  105. return;
  106. }
  107. $this->id = $name . $type;
  108. if (!$query = $this->mysql->query('SHOW TABLES'))
  109. {
  110. $this->mysql = null;
  111. return;
  112. }
  113. $db = array();
  114. while ($row = $query->fetchColumn())
  115. {
  116. $db[] = $row;
  117. }
  118. if (!in_array($this->options['extras']['prefix'] . 'cache_data', $db))
  119. {
  120. $query = $this->mysql->exec('CREATE TABLE `' . $this->options['extras']['prefix'] . 'cache_data` (`id` TEXT CHARACTER SET utf8 NOT NULL, `items` SMALLINT NOT NULL DEFAULT 0, `data` BLOB NOT NULL, `mtime` INT UNSIGNED NOT NULL, UNIQUE (`id`(125)))');
  121. if ($query === false)
  122. {
  123. trigger_error("Can't create " . $this->options['extras']['prefix'] . "cache_data table, check permissions", E_USER_WARNING);
  124. $this->mysql = null;
  125. return;
  126. }
  127. }
  128. if (!in_array($this->options['extras']['prefix'] . 'items', $db))
  129. {
  130. $query = $this->mysql->exec('CREATE TABLE `' . $this->options['extras']['prefix'] . 'items` (`feed_id` TEXT CHARACTER SET utf8 NOT NULL, `id` TEXT CHARACTER SET utf8 NOT NULL, `data` MEDIUMBLOB NOT NULL, `posted` INT UNSIGNED NOT NULL, INDEX `feed_id` (`feed_id`(125)))');
  131. if ($query === false)
  132. {
  133. trigger_error("Can't create " . $this->options['extras']['prefix'] . "items table, check permissions", E_USER_WARNING);
  134. $this->mysql = null;
  135. return;
  136. }
  137. }
  138. }
  139. /**
  140. * Save data to the cache
  141. *
  142. * @param array|SimplePie $data Data to store in the cache. If passed a SimplePie object, only cache the $data property
  143. * @return bool Successfulness
  144. */
  145. public function save($data)
  146. {
  147. if ($this->mysql === null)
  148. {
  149. return false;
  150. }
  151. $query = $this->mysql->prepare('DELETE i, cd FROM `' . $this->options['extras']['prefix'] . 'cache_data` cd, ' .
  152. '`' . $this->options['extras']['prefix'] . 'items` i ' .
  153. 'WHERE cd.id = i.feed_id ' .
  154. 'AND cd.mtime < (unix_timestamp() - :purge_time)');
  155. $query->bindValue(':purge_time', $this->options['extras']['cache_purge_time']);
  156. if (!$query->execute())
  157. {
  158. return false;
  159. }
  160. if ($data instanceof SimplePie)
  161. {
  162. $data = clone $data;
  163. $prepared = self::prepare_simplepie_object_for_cache($data);
  164. $query = $this->mysql->prepare('SELECT COUNT(*) FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :feed');
  165. $query->bindValue(':feed', $this->id);
  166. if ($query->execute())
  167. {
  168. if ($query->fetchColumn() > 0)
  169. {
  170. $items = count($prepared[1]);
  171. if ($items)
  172. {
  173. $sql = 'UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `items` = :items, `data` = :data, `mtime` = :time WHERE `id` = :feed';
  174. $query = $this->mysql->prepare($sql);
  175. $query->bindValue(':items', $items);
  176. }
  177. else
  178. {
  179. $sql = 'UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `data` = :data, `mtime` = :time WHERE `id` = :feed';
  180. $query = $this->mysql->prepare($sql);
  181. }
  182. $query->bindValue(':data', $prepared[0]);
  183. $query->bindValue(':time', time());
  184. $query->bindValue(':feed', $this->id);
  185. if (!$query->execute())
  186. {
  187. return false;
  188. }
  189. }
  190. else
  191. {
  192. $query = $this->mysql->prepare('INSERT INTO `' . $this->options['extras']['prefix'] . 'cache_data` (`id`, `items`, `data`, `mtime`) VALUES(:feed, :count, :data, :time)');
  193. $query->bindValue(':feed', $this->id);
  194. $query->bindValue(':count', count($prepared[1]));
  195. $query->bindValue(':data', $prepared[0]);
  196. $query->bindValue(':time', time());
  197. if (!$query->execute())
  198. {
  199. return false;
  200. }
  201. }
  202. $ids = array_keys($prepared[1]);
  203. if (!empty($ids))
  204. {
  205. foreach ($ids as $id)
  206. {
  207. $database_ids[] = $this->mysql->quote($id);
  208. }
  209. $query = $this->mysql->prepare('SELECT `id` FROM `' . $this->options['extras']['prefix'] . 'items` WHERE `id` = ' . implode(' OR `id` = ', $database_ids) . ' AND `feed_id` = :feed');
  210. $query->bindValue(':feed', $this->id);
  211. if ($query->execute())
  212. {
  213. $existing_ids = array();
  214. while ($row = $query->fetchColumn())
  215. {
  216. $existing_ids[] = $row;
  217. }
  218. $new_ids = array_diff($ids, $existing_ids);
  219. foreach ($new_ids as $new_id)
  220. {
  221. if (!($date = $prepared[1][$new_id]->get_date('U')))
  222. {
  223. $date = time();
  224. }
  225. $query = $this->mysql->prepare('INSERT INTO `' . $this->options['extras']['prefix'] . 'items` (`feed_id`, `id`, `data`, `posted`) VALUES(:feed, :id, :data, :date)');
  226. $query->bindValue(':feed', $this->id);
  227. $query->bindValue(':id', $new_id);
  228. $query->bindValue(':data', serialize($prepared[1][$new_id]->data));
  229. $query->bindValue(':date', $date);
  230. if (!$query->execute())
  231. {
  232. return false;
  233. }
  234. }
  235. return true;
  236. }
  237. }
  238. else
  239. {
  240. return true;
  241. }
  242. }
  243. }
  244. else
  245. {
  246. $query = $this->mysql->prepare('SELECT `id` FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :feed');
  247. $query->bindValue(':feed', $this->id);
  248. if ($query->execute())
  249. {
  250. if ($query->rowCount() > 0)
  251. {
  252. $query = $this->mysql->prepare('UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `items` = 0, `data` = :data, `mtime` = :time WHERE `id` = :feed');
  253. $query->bindValue(':data', serialize($data));
  254. $query->bindValue(':time', time());
  255. $query->bindValue(':feed', $this->id);
  256. if ($this->execute())
  257. {
  258. return true;
  259. }
  260. }
  261. else
  262. {
  263. $query = $this->mysql->prepare('INSERT INTO `' . $this->options['extras']['prefix'] . 'cache_data` (`id`, `items`, `data`, `mtime`) VALUES(:id, 0, :data, :time)');
  264. $query->bindValue(':id', $this->id);
  265. $query->bindValue(':data', serialize($data));
  266. $query->bindValue(':time', time());
  267. if ($query->execute())
  268. {
  269. return true;
  270. }
  271. }
  272. }
  273. }
  274. return false;
  275. }
  276. /**
  277. * Retrieve the data saved to the cache
  278. *
  279. * @return array Data for SimplePie::$data
  280. */
  281. public function load()
  282. {
  283. if ($this->mysql === null)
  284. {
  285. return false;
  286. }
  287. $query = $this->mysql->prepare('SELECT `items`, `data` FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :id');
  288. $query->bindValue(':id', $this->id);
  289. if ($query->execute() && ($row = $query->fetch()))
  290. {
  291. $data = unserialize($row[1]);
  292. if (isset($this->options['items'][0]))
  293. {
  294. $items = (int) $this->options['items'][0];
  295. }
  296. else
  297. {
  298. $items = (int) $row[0];
  299. }
  300. if ($items !== 0)
  301. {
  302. if (isset($data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]))
  303. {
  304. $feed =& $data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0];
  305. }
  306. elseif (isset($data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]))
  307. {
  308. $feed =& $data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0];
  309. }
  310. elseif (isset($data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]))
  311. {
  312. $feed =& $data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0];
  313. }
  314. elseif (isset($data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]))
  315. {
  316. $feed =& $data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0];
  317. }
  318. else
  319. {
  320. $feed = null;
  321. }
  322. if ($feed !== null)
  323. {
  324. $sql = 'SELECT `data` FROM `' . $this->options['extras']['prefix'] . 'items` WHERE `feed_id` = :feed ORDER BY `posted` DESC';
  325. if ($items > 0)
  326. {
  327. $sql .= ' LIMIT ' . $items;
  328. }
  329. $query = $this->mysql->prepare($sql);
  330. $query->bindValue(':feed', $this->id);
  331. if ($query->execute())
  332. {
  333. while ($row = $query->fetchColumn())
  334. {
  335. $feed['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['entry'][] = unserialize($row);
  336. }
  337. }
  338. else
  339. {
  340. return false;
  341. }
  342. }
  343. }
  344. return $data;
  345. }
  346. return false;
  347. }
  348. /**
  349. * Retrieve the last modified time for the cache
  350. *
  351. * @return int Timestamp
  352. */
  353. public function mtime()
  354. {
  355. if ($this->mysql === null)
  356. {
  357. return false;
  358. }
  359. $query = $this->mysql->prepare('SELECT `mtime` FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :id');
  360. $query->bindValue(':id', $this->id);
  361. if ($query->execute() && ($time = $query->fetchColumn()))
  362. {
  363. return $time;
  364. }
  365. return false;
  366. }
  367. /**
  368. * Set the last modified time to the current time
  369. *
  370. * @return bool Success status
  371. */
  372. public function touch()
  373. {
  374. if ($this->mysql === null)
  375. {
  376. return false;
  377. }
  378. $query = $this->mysql->prepare('UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `mtime` = :time WHERE `id` = :id');
  379. $query->bindValue(':time', time());
  380. $query->bindValue(':id', $this->id);
  381. return $query->execute() && $query->rowCount() > 0;
  382. }
  383. /**
  384. * Remove the cache
  385. *
  386. * @return bool Success status
  387. */
  388. public function unlink()
  389. {
  390. if ($this->mysql === null)
  391. {
  392. return false;
  393. }
  394. $query = $this->mysql->prepare('DELETE FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :id');
  395. $query->bindValue(':id', $this->id);
  396. $query2 = $this->mysql->prepare('DELETE FROM `' . $this->options['extras']['prefix'] . 'items` WHERE `feed_id` = :id');
  397. $query2->bindValue(':id', $this->id);
  398. return $query->execute() && $query2->execute();
  399. }
  400. }