Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]

   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   */
  43  
  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;
  64  
  65  	 /**
  66  	  * Options
  67  	  *
  68  	  * @var array
  69  	  */
  70  	 protected $options;
  71  
  72  	 /**
  73  	  * Cache ID
  74  	  *
  75  	  * @var string
  76  	  */
  77  	 protected $id;
  78  
  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));
 101  
 102  	 	 // Path is prefixed with a "/"
 103  	 	 $this->options['dbname'] = substr($this->options['path'], 1);
 104  
 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  	 	 }
 114  
 115  	 	 $this->id = $name . $type;
 116  
 117  	 	 if (!$query = $this->mysql->query('SHOW TABLES'))
 118  	 	 {
 119  	 	 	 $this->mysql = null;
 120  	 	 	 return;
 121  	 	 }
 122  
 123  	 	 $db = array();
 124  	 	 while ($row = $query->fetchColumn())
 125  	 	 {
 126  	 	 	 $db[] = $row;
 127  	 	 }
 128  
 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  	 	 }
 139  
 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  	 }
 151  
 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  	 	 }
 164  
 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']);
 170  
 171  	 	 if (!$query->execute())
 172  	 	 {
 173  	 	 	 return false;
 174  	 	 }
 175  
 176  	 	 if ($data instanceof SimplePie)
 177  	 	 {
 178  	 	 	 $data = clone $data;
 179  
 180  	 	 	 $prepared = self::prepare_simplepie_object_for_cache($data);
 181  
 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  	 	 	 	 	 }
 200  
 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  	 	 	 	 }
 221  
 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  	 	 	 	 	 }
 229  
 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);
 232  
 233  	 	 	 	 	 if ($query->execute())
 234  	 	 	 	 	 {
 235  	 	 	 	 	 	 $existing_ids = array();
 236  	 	 	 	 	 	 while ($row = $query->fetchColumn())
 237  	 	 	 	 	 	 {
 238  	 	 	 	 	 	 	 $existing_ids[] = $row;
 239  	 	 	 	 	 	 }
 240  
 241  	 	 	 	 	 	 $new_ids = array_diff($ids, $existing_ids);
 242  
 243  	 	 	 	 	 	 foreach ($new_ids as $new_id)
 244  	 	 	 	 	 	 {
 245  	 	 	 	 	 	 	 if (!($date = $prepared[1][$new_id]->get_date('U')))
 246  	 	 	 	 	 	 	 {
 247  	 	 	 	 	 	 	 	 $date = time();
 248  	 	 	 	 	 	 	 }
 249  
 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  	 }
 301  
 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  	 	 }
 313  
 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]);
 319  
 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  	 	 	 }
 328  
 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  	 	 	 	 }
 351  
 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  	 	 	 	 	 }
 359  
 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  	 }
 379  
 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  	 	 }
 391  
 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  
 399  	 	 return false;
 400  	 }
 401  
 402  	 /**
 403  	  * Set the last modified time to the current time
 404  	  *
 405  	  * @return bool Success status
 406  	  */
 407  	public function touch()
 408  	 {
 409  	 	 if ($this->mysql === null)
 410  	 	 {
 411  	 	 	 return false;
 412  	 	 }
 413  
 414  	 	 $query = $this->mysql->prepare('UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `mtime` = :time WHERE `id` = :id');
 415  	 	 $query->bindValue(':time', time());
 416  	 	 $query->bindValue(':id', $this->id);
 417  
 418  	 	 return $query->execute() && $query->rowCount() > 0;
 419  	 }
 420  
 421  	 /**
 422  	  * Remove the cache
 423  	  *
 424  	  * @return bool Success status
 425  	  */
 426  	public function unlink()
 427  	 {
 428  	 	 if ($this->mysql === null)
 429  	 	 {
 430  	 	 	 return false;
 431  	 	 }
 432  
 433  	 	 $query = $this->mysql->prepare('DELETE FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :id');
 434  	 	 $query->bindValue(':id', $this->id);
 435  	 	 $query2 = $this->mysql->prepare('DELETE FROM `' . $this->options['extras']['prefix'] . 'items` WHERE `feed_id` = :id');
 436  	 	 $query2->bindValue(':id', $this->id);
 437  
 438  	 	 return $query->execute() && $query2->execute();
 439  	 }
 440  }