[ Index ] |
PHP Cross Reference of moodle-2.8 |
[Summary view] [Print] [Text view]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * The library file for the file cache store. 19 * 20 * This file is part of the file cache store, it contains the API for interacting with an instance of the store. 21 * This is used as a default cache store within the Cache API. It should never be deleted. 22 * 23 * @package cachestore_file 24 * @category cache 25 * @copyright 2012 Sam Hemelryk 26 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 27 */ 28 29 /** 30 * The file store class. 31 * 32 * Configuration options 33 * path: string: path to the cache directory, if left empty one will be created in the cache directory 34 * autocreate: true, false 35 * prescan: true, false 36 * 37 * @copyright 2012 Sam Hemelryk 38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 39 */ 40 class cachestore_file extends cache_store implements cache_is_key_aware, cache_is_configurable, cache_is_searchable { 41 42 /** 43 * The name of the store. 44 * @var string 45 */ 46 protected $name; 47 48 /** 49 * The path used to store files for this store and the definition it was initialised with. 50 * @var string 51 */ 52 protected $path = false; 53 54 /** 55 * The path in which definition specific sub directories will be created for caching. 56 * @var string 57 */ 58 protected $filestorepath = false; 59 60 /** 61 * Set to true when a prescan has been performed. 62 * @var bool 63 */ 64 protected $prescan = false; 65 66 /** 67 * Set to true if we should store files within a single directory. 68 * By default we use a nested structure in order to reduce the chance of conflicts and avoid any file system 69 * limitations such as maximum files per directory. 70 * @var bool 71 */ 72 protected $singledirectory = false; 73 74 /** 75 * Set to true when the path should be automatically created if it does not yet exist. 76 * @var bool 77 */ 78 protected $autocreate = false; 79 80 /** 81 * Set to true if a custom path is being used. 82 * @var bool 83 */ 84 protected $custompath = false; 85 86 /** 87 * An array of keys we are sure about presently. 88 * @var array 89 */ 90 protected $keys = array(); 91 92 /** 93 * True when the store is ready to be initialised. 94 * @var bool 95 */ 96 protected $isready = false; 97 98 /** 99 * The cache definition this instance has been initialised with. 100 * @var cache_definition 101 */ 102 protected $definition; 103 104 /** 105 * A reference to the global $CFG object. 106 * 107 * You may be asking yourself why on earth this is here, but there is a good reason. 108 * By holding onto a reference of the $CFG object we can be absolutely sure that it won't be destroyed before 109 * we are done with it. 110 * This makes it possible to use a cache within a destructor method for the purposes of 111 * delayed writes. Like how the session mechanisms work. 112 * 113 * @var stdClass 114 */ 115 private $cfg = null; 116 117 /** 118 * Constructs the store instance. 119 * 120 * Noting that this function is not an initialisation. It is used to prepare the store for use. 121 * The store will be initialised when required and will be provided with a cache_definition at that time. 122 * 123 * @param string $name 124 * @param array $configuration 125 */ 126 public function __construct($name, array $configuration = array()) { 127 global $CFG; 128 129 if (isset($CFG)) { 130 // Hold onto a reference of the global $CFG object. 131 $this->cfg = $CFG; 132 } 133 134 $this->name = $name; 135 if (array_key_exists('path', $configuration) && $configuration['path'] !== '') { 136 $this->custompath = true; 137 $this->autocreate = !empty($configuration['autocreate']); 138 $path = (string)$configuration['path']; 139 if (!is_dir($path)) { 140 if ($this->autocreate) { 141 if (!make_writable_directory($path, false)) { 142 $path = false; 143 debugging('Error trying to autocreate file store path. '.$path, DEBUG_DEVELOPER); 144 } 145 } else { 146 $path = false; 147 debugging('The given file cache store path does not exist. '.$path, DEBUG_DEVELOPER); 148 } 149 } 150 if ($path !== false && !is_writable($path)) { 151 $path = false; 152 debugging('The file cache store path is not writable for `'.$name.'`', DEBUG_DEVELOPER); 153 } 154 } else { 155 $path = make_cache_directory('cachestore_file/'.preg_replace('#[^a-zA-Z0-9\.\-_]+#', '', $name)); 156 } 157 $this->isready = $path !== false; 158 $this->filestorepath = $path; 159 // This will be updated once the store has been initialised for a definition. 160 $this->path = $path; 161 162 // Check if we should prescan the directory. 163 if (array_key_exists('prescan', $configuration)) { 164 $this->prescan = (bool)$configuration['prescan']; 165 } else { 166 // Default is no, we should not prescan. 167 $this->prescan = false; 168 } 169 // Check if we should be storing in a single directory. 170 if (array_key_exists('singledirectory', $configuration)) { 171 $this->singledirectory = (bool)$configuration['singledirectory']; 172 } else { 173 // Default: No, we will use multiple directories. 174 $this->singledirectory = false; 175 } 176 } 177 178 /** 179 * Performs any necessary operation when the file store instance has been created. 180 */ 181 public function instance_created() { 182 if ($this->isready && !$this->prescan) { 183 // It is supposed the store instance to expect an empty folder. 184 $this->purge_all_definitions(); 185 } 186 } 187 188 /** 189 * Returns true if this store instance is ready to be used. 190 * @return bool 191 */ 192 public function is_ready() { 193 return $this->isready; 194 } 195 196 /** 197 * Returns true once this instance has been initialised. 198 * 199 * @return bool 200 */ 201 public function is_initialised() { 202 return true; 203 } 204 205 /** 206 * Returns the supported features as a combined int. 207 * 208 * @param array $configuration 209 * @return int 210 */ 211 public static function get_supported_features(array $configuration = array()) { 212 $supported = self::SUPPORTS_DATA_GUARANTEE + 213 self::SUPPORTS_NATIVE_TTL + 214 self::IS_SEARCHABLE; 215 return $supported; 216 } 217 218 /** 219 * Returns false as this store does not support multiple identifiers. 220 * (This optional function is a performance optimisation; it must be 221 * consistent with the value from get_supported_features.) 222 * 223 * @return bool False 224 */ 225 public function supports_multiple_identifiers() { 226 return false; 227 } 228 229 /** 230 * Returns the supported modes as a combined int. 231 * 232 * @param array $configuration 233 * @return int 234 */ 235 public static function get_supported_modes(array $configuration = array()) { 236 return self::MODE_APPLICATION + self::MODE_SESSION; 237 } 238 239 /** 240 * Returns true if the store requirements are met. 241 * 242 * @return bool 243 */ 244 public static function are_requirements_met() { 245 return true; 246 } 247 248 /** 249 * Returns true if the given mode is supported by this store. 250 * 251 * @param int $mode One of cache_store::MODE_* 252 * @return bool 253 */ 254 public static function is_supported_mode($mode) { 255 return ($mode === self::MODE_APPLICATION || $mode === self::MODE_SESSION); 256 } 257 258 /** 259 * Initialises the cache. 260 * 261 * Once this has been done the cache is all set to be used. 262 * 263 * @param cache_definition $definition 264 */ 265 public function initialise(cache_definition $definition) { 266 $this->definition = $definition; 267 $hash = preg_replace('#[^a-zA-Z0-9]+#', '_', $this->definition->get_id()); 268 $this->path = $this->filestorepath.'/'.$hash; 269 make_writable_directory($this->path, false); 270 if ($this->prescan && $definition->get_mode() !== self::MODE_REQUEST) { 271 $this->prescan = false; 272 } 273 if ($this->prescan) { 274 $this->prescan_keys(); 275 } 276 } 277 278 /** 279 * Pre-scan the cache to see which keys are present. 280 */ 281 protected function prescan_keys() { 282 $files = glob($this->glob_keys_pattern(), GLOB_MARK | GLOB_NOSORT); 283 if (is_array($files)) { 284 foreach ($files as $filename) { 285 $this->keys[basename($filename)] = filemtime($filename); 286 } 287 } 288 } 289 290 /** 291 * Gets a pattern suitable for use with glob to find all keys in the cache. 292 * 293 * @param string $prefix A prefix to use. 294 * @return string The pattern. 295 */ 296 protected function glob_keys_pattern($prefix = '') { 297 if ($this->singledirectory) { 298 return $this->path . '/'.$prefix.'*.cache'; 299 } else { 300 return $this->path . '/*/'.$prefix.'*.cache'; 301 } 302 } 303 304 /** 305 * Returns the file path to use for the given key. 306 * 307 * @param string $key The key to generate a file path for. 308 * @param bool $create If set to the true the directory structure the key requires will be created. 309 * @return string The full path to the file that stores a particular cache key. 310 */ 311 protected function file_path_for_key($key, $create = false) { 312 if ($this->singledirectory) { 313 // Its a single directory, easy, just the store instances path + the file name. 314 return $this->path . '/' . $key . '.cache'; 315 } else { 316 // We are using a single subdirectory to achieve 1 level. 317 // We suffix the subdir so it does not clash with any windows 318 // reserved filenames like 'con'. 319 $subdir = substr($key, 0, 3) . '-cache'; 320 $dir = $this->path . '/' . $subdir; 321 if ($create) { 322 // Create the directory. This function does it recursivily! 323 make_writable_directory($dir, false); 324 } 325 return $dir . '/' . $key . '.cache'; 326 } 327 } 328 329 /** 330 * Retrieves an item from the cache store given its key. 331 * 332 * @param string $key The key to retrieve 333 * @return mixed The data that was associated with the key, or false if the key did not exist. 334 */ 335 public function get($key) { 336 $filename = $key.'.cache'; 337 $file = $this->file_path_for_key($key); 338 $ttl = $this->definition->get_ttl(); 339 $maxtime = 0; 340 if ($ttl) { 341 $maxtime = cache::now() - $ttl; 342 } 343 $readfile = false; 344 if ($this->prescan && array_key_exists($key, $this->keys)) { 345 if (!$ttl || $this->keys[$filename] >= $maxtime && file_exists($file)) { 346 $readfile = true; 347 } else { 348 $this->delete($key); 349 } 350 } else if (file_exists($file) && (!$ttl || filemtime($file) >= $maxtime)) { 351 $readfile = true; 352 } 353 if (!$readfile) { 354 return false; 355 } 356 // Open ensuring the file for reading in binary format. 357 if (!$handle = fopen($file, 'rb')) { 358 return false; 359 } 360 // Lock it up! 361 // We don't care if this succeeds or not, on some systems it will, on some it won't, meah either way. 362 flock($handle, LOCK_SH); 363 $data = ''; 364 // Read the data in 1Mb chunks. Small caches will not loop more than once. We don't use filesize as it may 365 // be cached with a different value than what we need to read from the file. 366 do { 367 $data .= fread($handle, 1048576); 368 } while (!feof($handle)); 369 // Unlock it. 370 flock($handle, LOCK_UN); 371 // Return it unserialised. 372 return $this->prep_data_after_read($data); 373 } 374 375 /** 376 * Retrieves several items from the cache store in a single transaction. 377 * 378 * If not all of the items are available in the cache then the data value for those that are missing will be set to false. 379 * 380 * @param array $keys The array of keys to retrieve 381 * @return array An array of items from the cache. There will be an item for each key, those that were not in the store will 382 * be set to false. 383 */ 384 public function get_many($keys) { 385 $result = array(); 386 foreach ($keys as $key) { 387 $result[$key] = $this->get($key); 388 } 389 return $result; 390 } 391 392 /** 393 * Deletes an item from the cache store. 394 * 395 * @param string $key The key to delete. 396 * @return bool Returns true if the operation was a success, false otherwise. 397 */ 398 public function delete($key) { 399 $filename = $key.'.cache'; 400 $file = $this->file_path_for_key($key); 401 if (@unlink($file)) { 402 unset($this->keys[$filename]); 403 return true; 404 } 405 406 return false; 407 } 408 409 /** 410 * Deletes several keys from the cache in a single action. 411 * 412 * @param array $keys The keys to delete 413 * @return int The number of items successfully deleted. 414 */ 415 public function delete_many(array $keys) { 416 $count = 0; 417 foreach ($keys as $key) { 418 if ($this->delete($key)) { 419 $count++; 420 } 421 } 422 return $count; 423 } 424 425 /** 426 * Sets an item in the cache given its key and data value. 427 * 428 * @param string $key The key to use. 429 * @param mixed $data The data to set. 430 * @return bool True if the operation was a success false otherwise. 431 */ 432 public function set($key, $data) { 433 $this->ensure_path_exists(); 434 $filename = $key.'.cache'; 435 $file = $this->file_path_for_key($key, true); 436 $result = $this->write_file($file, $this->prep_data_before_save($data)); 437 if (!$result) { 438 // Couldn't write the file. 439 return false; 440 } 441 // Record the key if required. 442 if ($this->prescan) { 443 $this->keys[$filename] = cache::now() + 1; 444 } 445 // Return true.. it all worked **miracles**. 446 return true; 447 } 448 449 /** 450 * Prepares data to be stored in a file. 451 * 452 * @param mixed $data 453 * @return string 454 */ 455 protected function prep_data_before_save($data) { 456 return serialize($data); 457 } 458 459 /** 460 * Prepares the data it has been read from the cache. Undoing what was done in prep_data_before_save. 461 * 462 * @param string $data 463 * @return mixed 464 * @throws coding_exception 465 */ 466 protected function prep_data_after_read($data) { 467 $result = @unserialize($data); 468 if ($result === false) { 469 throw new coding_exception('Failed to unserialise data from file. Either failed to read, or failed to write.'); 470 } 471 return $result; 472 } 473 474 /** 475 * Sets many items in the cache in a single transaction. 476 * 477 * @param array $keyvaluearray An array of key value pairs. Each item in the array will be an associative array with two 478 * keys, 'key' and 'value'. 479 * @return int The number of items successfully set. It is up to the developer to check this matches the number of items 480 * sent ... if they care that is. 481 */ 482 public function set_many(array $keyvaluearray) { 483 $count = 0; 484 foreach ($keyvaluearray as $pair) { 485 if ($this->set($pair['key'], $pair['value'])) { 486 $count++; 487 } 488 } 489 return $count; 490 } 491 492 /** 493 * Checks if the store has a record for the given key and returns true if so. 494 * 495 * @param string $key 496 * @return bool 497 */ 498 public function has($key) { 499 $filename = $key.'.cache'; 500 $maxtime = cache::now() - $this->definition->get_ttl(); 501 if ($this->prescan) { 502 return array_key_exists($filename, $this->keys) && $this->keys[$filename] >= $maxtime; 503 } 504 $file = $this->file_path_for_key($key); 505 return (file_exists($file) && ($this->definition->get_ttl() == 0 || filemtime($file) >= $maxtime)); 506 } 507 508 /** 509 * Returns true if the store contains records for all of the given keys. 510 * 511 * @param array $keys 512 * @return bool 513 */ 514 public function has_all(array $keys) { 515 foreach ($keys as $key) { 516 if (!$this->has($key)) { 517 return false; 518 } 519 } 520 return true; 521 } 522 523 /** 524 * Returns true if the store contains records for any of the given keys. 525 * 526 * @param array $keys 527 * @return bool 528 */ 529 public function has_any(array $keys) { 530 foreach ($keys as $key) { 531 if ($this->has($key)) { 532 return true; 533 } 534 } 535 return false; 536 } 537 538 /** 539 * Purges the cache definition deleting all the items within it. 540 * 541 * @return boolean True on success. False otherwise. 542 */ 543 public function purge() { 544 if ($this->isready) { 545 $files = glob($this->glob_keys_pattern(), GLOB_MARK | GLOB_NOSORT); 546 if (is_array($files)) { 547 foreach ($files as $filename) { 548 @unlink($filename); 549 } 550 } 551 $this->keys = array(); 552 } 553 return true; 554 } 555 556 /** 557 * Purges all the cache definitions deleting all items within them. 558 * 559 * @return boolean True on success. False otherwise. 560 */ 561 protected function purge_all_definitions() { 562 // Warning: limit the deletion to what file store is actually able 563 // to create using the internal {@link purge()} providing the 564 // {@link $path} with a wildcard to perform a purge action over all the definitions. 565 $currpath = $this->path; 566 $this->path = $this->filestorepath.'/*'; 567 $result = $this->purge(); 568 $this->path = $currpath; 569 return $result; 570 } 571 572 /** 573 * Given the data from the add instance form this function creates a configuration array. 574 * 575 * @param stdClass $data 576 * @return array 577 */ 578 public static function config_get_configuration_array($data) { 579 $config = array(); 580 581 if (isset($data->path)) { 582 $config['path'] = $data->path; 583 } 584 if (isset($data->autocreate)) { 585 $config['autocreate'] = $data->autocreate; 586 } 587 if (isset($data->singledirectory)) { 588 $config['singledirectory'] = $data->singledirectory; 589 } 590 if (isset($data->prescan)) { 591 $config['prescan'] = $data->prescan; 592 } 593 594 return $config; 595 } 596 597 /** 598 * Allows the cache store to set its data against the edit form before it is shown to the user. 599 * 600 * @param moodleform $editform 601 * @param array $config 602 */ 603 public static function config_set_edit_form_data(moodleform $editform, array $config) { 604 $data = array(); 605 if (!empty($config['path'])) { 606 $data['path'] = $config['path']; 607 } 608 if (isset($config['autocreate'])) { 609 $data['autocreate'] = (bool)$config['autocreate']; 610 } 611 if (isset($config['singledirectory'])) { 612 $data['singledirectory'] = (bool)$config['singledirectory']; 613 } 614 if (isset($config['prescan'])) { 615 $data['prescan'] = (bool)$config['prescan']; 616 } 617 $editform->set_data($data); 618 } 619 620 /** 621 * Checks to make sure that the path for the file cache exists. 622 * 623 * @return bool 624 * @throws coding_exception 625 */ 626 protected function ensure_path_exists() { 627 global $CFG; 628 if (!is_writable($this->path)) { 629 if ($this->custompath && !$this->autocreate) { 630 throw new coding_exception('File store path does not exist. It must exist and be writable by the web server.'); 631 } 632 $createdcfg = false; 633 if (!isset($CFG)) { 634 // This can only happen during destruction of objects. 635 // A cache is being used within a destructor, php is ending a request and $CFG has 636 // already being cleaned up. 637 // Rebuild $CFG with directory permissions just to complete this write. 638 $CFG = $this->cfg; 639 $createdcfg = true; 640 } 641 if (!make_writable_directory($this->path, false)) { 642 throw new coding_exception('File store path does not exist and can not be created.'); 643 } 644 if ($createdcfg) { 645 // We re-created it so we'll clean it up. 646 unset($CFG); 647 } 648 } 649 return true; 650 } 651 652 /** 653 * Performs any necessary clean up when the file store instance is being deleted. 654 * 655 * 1. Purges the cache directory. 656 * 2. Deletes the directory we created for the given definition. 657 */ 658 public function instance_deleted() { 659 $this->purge_all_definitions(); 660 @rmdir($this->filestorepath); 661 } 662 663 /** 664 * Generates an instance of the cache store that can be used for testing. 665 * 666 * Returns an instance of the cache store, or false if one cannot be created. 667 * 668 * @param cache_definition $definition 669 * @return cachestore_file 670 */ 671 public static function initialise_test_instance(cache_definition $definition) { 672 $name = 'File test'; 673 $path = make_cache_directory('cachestore_file_test'); 674 $cache = new cachestore_file($name, array('path' => $path)); 675 $cache->initialise($definition); 676 return $cache; 677 } 678 679 /** 680 * Writes your madness to a file. 681 * 682 * There are several things going on in this function to try to ensure what we don't end up with partial writes etc. 683 * 1. Files for writing are opened with the mode xb, the file must be created and can not already exist. 684 * 2. Renaming, data is written to a temporary file, where it can be verified using md5 and is then renamed. 685 * 686 * @param string $file Absolute file path 687 * @param string $content The content to write. 688 * @return bool 689 */ 690 protected function write_file($file, $content) { 691 // Generate a temp file that is going to be unique. We'll rename it at the end to the desired file name. 692 // in this way we avoid partial writes. 693 $path = dirname($file); 694 while (true) { 695 $tempfile = $path.'/'.uniqid(sesskey().'.', true) . '.temp'; 696 if (!file_exists($tempfile)) { 697 break; 698 } 699 } 700 701 // Open the file with mode=x. This acts to create and open the file for writing only. 702 // If the file already exists this will return false. 703 // We also force binary. 704 $handle = @fopen($tempfile, 'xb+'); 705 if ($handle === false) { 706 // File already exists... lock already exists, return false. 707 return false; 708 } 709 fwrite($handle, $content); 710 fflush($handle); 711 // Close the handle, we're done. 712 fclose($handle); 713 714 if (md5_file($tempfile) !== md5($content)) { 715 // The md5 of the content of the file must match the md5 of the content given to be written. 716 @unlink($tempfile); 717 return false; 718 } 719 720 // Finally rename the temp file to the desired file, returning the true|false result. 721 $result = rename($tempfile, $file); 722 @chmod($file, $this->cfg->filepermissions); 723 if (!$result) { 724 // Failed to rename, don't leave files lying around. 725 @unlink($tempfile); 726 } 727 return $result; 728 } 729 730 /** 731 * Returns the name of this instance. 732 * @return string 733 */ 734 public function my_name() { 735 return $this->name; 736 } 737 738 /** 739 * Finds all of the keys being used by this cache store instance. 740 * 741 * @return array 742 */ 743 public function find_all() { 744 $this->ensure_path_exists(); 745 $files = glob($this->glob_keys_pattern(), GLOB_MARK | GLOB_NOSORT); 746 $return = array(); 747 if ($files === false) { 748 return $return; 749 } 750 foreach ($files as $file) { 751 $return[] = substr(basename($file), 0, -6); 752 } 753 return $return; 754 } 755 756 /** 757 * Finds all of the keys whose keys start with the given prefix. 758 * 759 * @param string $prefix 760 */ 761 public function find_by_prefix($prefix) { 762 $this->ensure_path_exists(); 763 $prefix = preg_replace('#(\*|\?|\[)#', '[$1]', $prefix); 764 $files = glob($this->glob_keys_pattern($prefix), GLOB_MARK | GLOB_NOSORT); 765 $return = array(); 766 if ($files === false) { 767 return $return; 768 } 769 foreach ($files as $file) { 770 // Trim off ".cache" from the end. 771 $return[] = substr(basename($file), 0, -6); 772 } 773 return $return; 774 } 775 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |