MDL-56001 lib: Upgrade simplepie to 1.4.2
[moodle.git] / lib / simplepie / library / SimplePie / Cache / MySQL.php
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, Geoffrey 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, Geoffrey Sneddon, Ryan McCue
37  * @author Ryan Parman
38  * @author Geoffrey Sneddon
39  * @author Ryan McCue
40  * @link http://simplepie.org/ SimplePie
41  * @license http://www.opensource.org/licenses/bsd-license.php BSD License
42  */
44 /**
45  * Caches data to a MySQL database
46  *
47  * Registered for URLs with the "mysql" protocol
48  *
49  * For example, `mysql://root:password@localhost:3306/mydb?prefix=sp_` will
50  * connect to the `mydb` database on `localhost` on port 3306, with the user
51  * `root` and the password `password`. All tables will be prefixed with `sp_`
52  *
53  * @package SimplePie
54  * @subpackage Caching
55  */
56 class SimplePie_Cache_MySQL extends SimplePie_Cache_DB
57 {
58         /**
59          * PDO instance
60          *
61          * @var PDO
62          */
63         protected $mysql;
65         /**
66          * Options
67          *
68          * @var array
69          */
70         protected $options;
72         /**
73          * Cache ID
74          *
75          * @var string
76          */
77         protected $id;
79         /**
80          * Create a new cache object
81          *
82          * @param string $location Location string (from SimplePie::$cache_location)
83          * @param string $name Unique ID for the cache
84          * @param string $type Either TYPE_FEED for SimplePie data, or TYPE_IMAGE for image data
85          */
86         public function __construct($location, $name, $type)
87         {
88                 $this->options = array(
89                         'user' => null,
90                         'pass' => null,
91                         'host' => '127.0.0.1',
92                         'port' => '3306',
93                         'path' => '',
94                         'extras' => array(
95                                 'prefix' => '',
96                                 'cache_purge_time' => 2592000
97                         ),
98                 );
99                 
100                 $this->options = SimplePie_Misc::array_merge_recursive($this->options, SimplePie_Cache::parse_URL($location));
102                 // Path is prefixed with a "/"
103                 $this->options['dbname'] = substr($this->options['path'], 1);
105                 try
106                 {
107                         $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'));
108                 }
109                 catch (PDOException $e)
110                 {
111                         $this->mysql = null;
112                         return;
113                 }
115                 $this->id = $name . $type;
117                 if (!$query = $this->mysql->query('SHOW TABLES'))
118                 {
119                         $this->mysql = null;
120                         return;
121                 }
123                 $db = array();
124                 while ($row = $query->fetchColumn())
125                 {
126                         $db[] = $row;
127                 }
129                 if (!in_array($this->options['extras']['prefix'] . 'cache_data', $db))
130                 {
131                         $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)))');
132                         if ($query === false)
133                         {
134                                 trigger_error("Can't create " . $this->options['extras']['prefix'] . "cache_data table, check permissions", E_USER_WARNING);
135                                 $this->mysql = null;
136                                 return;
137                         }
138                 }
140                 if (!in_array($this->options['extras']['prefix'] . 'items', $db))
141                 {
142                         $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)))');
143                         if ($query === false)
144                         {
145                                 trigger_error("Can't create " . $this->options['extras']['prefix'] . "items table, check permissions", E_USER_WARNING);
146                                 $this->mysql = null;
147                                 return;
148                         }
149                 }
150         }
152         /**
153          * Save data to the cache
154          *
155          * @param array|SimplePie $data Data to store in the cache. If passed a SimplePie object, only cache the $data property
156          * @return bool Successfulness
157          */
158         public function save($data)
159         {
160                 if ($this->mysql === null)
161                 {
162                         return false;
163                 }
165                 $query = $this->mysql->prepare('DELETE i, cd FROM `' . $this->options['extras']['prefix'] . 'cache_data` cd, ' .
166                         '`' . $this->options['extras']['prefix'] . 'items` i ' .
167                         'WHERE cd.id = i.feed_id ' .
168                         'AND cd.mtime < (unix_timestamp() - :purge_time)');
169                 $query->bindValue(':purge_time', $this->options['extras']['cache_purge_time']);
171                 if (!$query->execute())
172                 {
173                         return false;
174                 }
176                 if ($data instanceof SimplePie)
177                 {
178                         $data = clone $data;
180                         $prepared = self::prepare_simplepie_object_for_cache($data);
182                         $query = $this->mysql->prepare('SELECT COUNT(*) FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :feed');
183                         $query->bindValue(':feed', $this->id);
184                         if ($query->execute())
185                         {
186                                 if ($query->fetchColumn() > 0)
187                                 {
188                                         $items = count($prepared[1]);
189                                         if ($items)
190                                         {
191                                                 $sql = 'UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `items` = :items, `data` = :data, `mtime` = :time WHERE `id` = :feed';
192                                                 $query = $this->mysql->prepare($sql);
193                                                 $query->bindValue(':items', $items);
194                                         }
195                                         else
196                                         {
197                                                 $sql = 'UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `data` = :data, `mtime` = :time WHERE `id` = :feed';
198                                                 $query = $this->mysql->prepare($sql);
199                                         }
201                                         $query->bindValue(':data', $prepared[0]);
202                                         $query->bindValue(':time', time());
203                                         $query->bindValue(':feed', $this->id);
204                                         if (!$query->execute())
205                                         {
206                                                 return false;
207                                         }
208                                 }
209                                 else
210                                 {
211                                         $query = $this->mysql->prepare('INSERT INTO `' . $this->options['extras']['prefix'] . 'cache_data` (`id`, `items`, `data`, `mtime`) VALUES(:feed, :count, :data, :time)');
212                                         $query->bindValue(':feed', $this->id);
213                                         $query->bindValue(':count', count($prepared[1]));
214                                         $query->bindValue(':data', $prepared[0]);
215                                         $query->bindValue(':time', time());
216                                         if (!$query->execute())
217                                         {
218                                                 return false;
219                                         }
220                                 }
222                                 $ids = array_keys($prepared[1]);
223                                 if (!empty($ids))
224                                 {
225                                         foreach ($ids as $id)
226                                         {
227                                                 $database_ids[] = $this->mysql->quote($id);
228                                         }
230                                         $query = $this->mysql->prepare('SELECT `id` FROM `' . $this->options['extras']['prefix'] . 'items` WHERE `id` = ' . implode(' OR `id` = ', $database_ids) . ' AND `feed_id` = :feed');
231                                         $query->bindValue(':feed', $this->id);
233                                         if ($query->execute())
234                                         {
235                                                 $existing_ids = array();
236                                                 while ($row = $query->fetchColumn())
237                                                 {
238                                                         $existing_ids[] = $row;
239                                                 }
241                                                 $new_ids = array_diff($ids, $existing_ids);
243                                                 foreach ($new_ids as $new_id)
244                                                 {
245                                                         if (!($date = $prepared[1][$new_id]->get_date('U')))
246                                                         {
247                                                                 $date = time();
248                                                         }
250                                                         $query = $this->mysql->prepare('INSERT INTO `' . $this->options['extras']['prefix'] . 'items` (`feed_id`, `id`, `data`, `posted`) VALUES(:feed, :id, :data, :date)');
251                                                         $query->bindValue(':feed', $this->id);
252                                                         $query->bindValue(':id', $new_id);
253                                                         $query->bindValue(':data', serialize($prepared[1][$new_id]->data));
254                                                         $query->bindValue(':date', $date);
255                                                         if (!$query->execute())
256                                                         {
257                                                                 return false;
258                                                         }
259                                                 }
260                                                 return true;
261                                         }
262                                 }
263                                 else
264                                 {
265                                         return true;
266                                 }
267                         }
268                 }
269                 else
270                 {
271                         $query = $this->mysql->prepare('SELECT `id` FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :feed');
272                         $query->bindValue(':feed', $this->id);
273                         if ($query->execute())
274                         {
275                                 if ($query->rowCount() > 0)
276                                 {
277                                         $query = $this->mysql->prepare('UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `items` = 0, `data` = :data, `mtime` = :time WHERE `id` = :feed');
278                                         $query->bindValue(':data', serialize($data));
279                                         $query->bindValue(':time', time());
280                                         $query->bindValue(':feed', $this->id);
281                                         if ($this->execute())
282                                         {
283                                                 return true;
284                                         }
285                                 }
286                                 else
287                                 {
288                                         $query = $this->mysql->prepare('INSERT INTO `' . $this->options['extras']['prefix'] . 'cache_data` (`id`, `items`, `data`, `mtime`) VALUES(:id, 0, :data, :time)');
289                                         $query->bindValue(':id', $this->id);
290                                         $query->bindValue(':data', serialize($data));
291                                         $query->bindValue(':time', time());
292                                         if ($query->execute())
293                                         {
294                                                 return true;
295                                         }
296                                 }
297                         }
298                 }
299                 return false;
300         }
302         /**
303          * Retrieve the data saved to the cache
304          *
305          * @return array Data for SimplePie::$data
306          */
307         public function load()
308         {
309                 if ($this->mysql === null)
310                 {
311                         return false;
312                 }
314                 $query = $this->mysql->prepare('SELECT `items`, `data` FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :id');
315                 $query->bindValue(':id', $this->id);
316                 if ($query->execute() && ($row = $query->fetch()))
317                 {
318                         $data = unserialize($row[1]);
320                         if (isset($this->options['items'][0]))
321                         {
322                                 $items = (int) $this->options['items'][0];
323                         }
324                         else
325                         {
326                                 $items = (int) $row[0];
327                         }
329                         if ($items !== 0)
330                         {
331                                 if (isset($data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]))
332                                 {
333                                         $feed =& $data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0];
334                                 }
335                                 elseif (isset($data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]))
336                                 {
337                                         $feed =& $data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0];
338                                 }
339                                 elseif (isset($data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]))
340                                 {
341                                         $feed =& $data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0];
342                                 }
343                                 elseif (isset($data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]))
344                                 {
345                                         $feed =& $data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0];
346                                 }
347                                 else
348                                 {
349                                         $feed = null;
350                                 }
352                                 if ($feed !== null)
353                                 {
354                                         $sql = 'SELECT `data` FROM `' . $this->options['extras']['prefix'] . 'items` WHERE `feed_id` = :feed ORDER BY `posted` DESC';
355                                         if ($items > 0)
356                                         {
357                                                 $sql .= ' LIMIT ' . $items;
358                                         }
360                                         $query = $this->mysql->prepare($sql);
361                                         $query->bindValue(':feed', $this->id);
362                                         if ($query->execute())
363                                         {
364                                                 while ($row = $query->fetchColumn())
365                                                 {
366                                                         $feed['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['entry'][] = unserialize($row);
367                                                 }
368                                         }
369                                         else
370                                         {
371                                                 return false;
372                                         }
373                                 }
374                         }
375                         return $data;
376                 }
377                 return false;
378         }
380         /**
381          * Retrieve the last modified time for the cache
382          *
383          * @return int Timestamp
384          */
385         public function mtime()
386         {
387                 if ($this->mysql === null)
388                 {
389                         return false;
390                 }
392                 $query = $this->mysql->prepare('SELECT `mtime` FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :id');
393                 $query->bindValue(':id', $this->id);
394                 if ($query->execute() && ($time = $query->fetchColumn()))
395                 {
396                         return $time;
397                 }
398                 else
399                 {
400                         return false;
401                 }
402         }
404         /**
405          * Set the last modified time to the current time
406          *
407          * @return bool Success status
408          */
409         public function touch()
410         {
411                 if ($this->mysql === null)
412                 {
413                         return false;
414                 }
416                 $query = $this->mysql->prepare('UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `mtime` = :time WHERE `id` = :id');
417                 $query->bindValue(':time', time());
418                 $query->bindValue(':id', $this->id);
419                 if ($query->execute() && $query->rowCount() > 0)
420                 {
421                         return true;
422                 }
423                 else
424                 {
425                         return false;
426                 }
427         }
429         /**
430          * Remove the cache
431          *
432          * @return bool Success status
433          */
434         public function unlink()
435         {
436                 if ($this->mysql === null)
437                 {
438                         return false;
439                 }
441                 $query = $this->mysql->prepare('DELETE FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :id');
442                 $query->bindValue(':id', $this->id);
443                 $query2 = $this->mysql->prepare('DELETE FROM `' . $this->options['extras']['prefix'] . 'items` WHERE `feed_id` = :id');
444                 $query2->bindValue(':id', $this->id);
445                 if ($query->execute() && $query2->execute())
446                 {
447                         return true;
448                 }
449                 else
450                 {
451                         return false;
452                 }
453         }