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  // 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                       self::DEREFERENCES_OBJECTS;
 216          return $supported;
 217      }
 218  
 219      /**
 220       * Returns false as this store does not support multiple identifiers.
 221       * (This optional function is a performance optimisation; it must be
 222       * consistent with the value from get_supported_features.)
 223       *
 224       * @return bool False
 225       */
 226      public function supports_multiple_identifiers() {
 227          return false;
 228      }
 229  
 230      /**
 231       * Returns the supported modes as a combined int.
 232       *
 233       * @param array $configuration
 234       * @return int
 235       */
 236      public static function get_supported_modes(array $configuration = array()) {
 237          return self::MODE_APPLICATION + self::MODE_SESSION;
 238      }
 239  
 240      /**
 241       * Returns true if the store requirements are met.
 242       *
 243       * @return bool
 244       */
 245      public static function are_requirements_met() {
 246          return true;
 247      }
 248  
 249      /**
 250       * Returns true if the given mode is supported by this store.
 251       *
 252       * @param int $mode One of cache_store::MODE_*
 253       * @return bool
 254       */
 255      public static function is_supported_mode($mode) {
 256          return ($mode === self::MODE_APPLICATION || $mode === self::MODE_SESSION);
 257      }
 258  
 259      /**
 260       * Initialises the cache.
 261       *
 262       * Once this has been done the cache is all set to be used.
 263       *
 264       * @param cache_definition $definition
 265       */
 266      public function initialise(cache_definition $definition) {
 267          $this->definition = $definition;
 268          $hash = preg_replace('#[^a-zA-Z0-9]+#', '_', $this->definition->get_id());
 269          $this->path = $this->filestorepath.'/'.$hash;
 270          make_writable_directory($this->path, false);
 271          if ($this->prescan && $definition->get_mode() !== self::MODE_REQUEST) {
 272              $this->prescan = false;
 273          }
 274          if ($this->prescan) {
 275              $this->prescan_keys();
 276          }
 277      }
 278  
 279      /**
 280       * Pre-scan the cache to see which keys are present.
 281       */
 282      protected function prescan_keys() {
 283          $files = glob($this->glob_keys_pattern(), GLOB_MARK | GLOB_NOSORT);
 284          if (is_array($files)) {
 285              foreach ($files as $filename) {
 286                  $this->keys[basename($filename)] = filemtime($filename);
 287              }
 288          }
 289      }
 290  
 291      /**
 292       * Gets a pattern suitable for use with glob to find all keys in the cache.
 293       *
 294       * @param string $prefix A prefix to use.
 295       * @return string The pattern.
 296       */
 297      protected function glob_keys_pattern($prefix = '') {
 298          if ($this->singledirectory) {
 299              return $this->path . '/'.$prefix.'*.cache';
 300          } else {
 301              return $this->path . '/*/'.$prefix.'*.cache';
 302          }
 303      }
 304  
 305      /**
 306       * Returns the file path to use for the given key.
 307       *
 308       * @param string $key The key to generate a file path for.
 309       * @param bool $create If set to the true the directory structure the key requires will be created.
 310       * @return string The full path to the file that stores a particular cache key.
 311       */
 312      protected function file_path_for_key($key, $create = false) {
 313          if ($this->singledirectory) {
 314              // Its a single directory, easy, just the store instances path + the file name.
 315              return $this->path . '/' . $key . '.cache';
 316          } else {
 317              // We are using a single subdirectory to achieve 1 level.
 318             // We suffix the subdir so it does not clash with any windows
 319             // reserved filenames like 'con'.
 320              $subdir = substr($key, 0, 3) . '-cache';
 321              $dir = $this->path . '/' . $subdir;
 322              if ($create) {
 323                  // Create the directory. This function does it recursivily!
 324                  make_writable_directory($dir, false);
 325              }
 326              return $dir . '/' . $key . '.cache';
 327          }
 328      }
 329  
 330      /**
 331       * Retrieves an item from the cache store given its key.
 332       *
 333       * @param string $key The key to retrieve
 334       * @return mixed The data that was associated with the key, or false if the key did not exist.
 335       */
 336      public function get($key) {
 337          $filename = $key.'.cache';
 338          $file = $this->file_path_for_key($key);
 339          $ttl = $this->definition->get_ttl();
 340          $maxtime = 0;
 341          if ($ttl) {
 342              $maxtime = cache::now() - $ttl;
 343          }
 344          $readfile = false;
 345          if ($this->prescan && array_key_exists($filename, $this->keys)) {
 346              if ((!$ttl || $this->keys[$filename] >= $maxtime) && file_exists($file)) {
 347                  $readfile = true;
 348              } else {
 349                  $this->delete($key);
 350              }
 351          } else if (file_exists($file) && (!$ttl || filemtime($file) >= $maxtime)) {
 352              $readfile = true;
 353          }
 354          if (!$readfile) {
 355              return false;
 356          }
 357          // Open ensuring the file for reading in binary format.
 358          if (!$handle = fopen($file, 'rb')) {
 359              return false;
 360          }
 361          // Lock it up!
 362          // We don't care if this succeeds or not, on some systems it will, on some it won't, meah either way.
 363          flock($handle, LOCK_SH);
 364          $data = '';
 365          // Read the data in 1Mb chunks. Small caches will not loop more than once.  We don't use filesize as it may
 366          // be cached with a different value than what we need to read from the file.
 367          do {
 368              $data .= fread($handle, 1048576);
 369          } while (!feof($handle));
 370          // Unlock it.
 371          flock($handle, LOCK_UN);
 372          // Return it unserialised.
 373          return $this->prep_data_after_read($data);
 374      }
 375  
 376      /**
 377       * Retrieves several items from the cache store in a single transaction.
 378       *
 379       * 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.
 380       *
 381       * @param array $keys The array of keys to retrieve
 382       * @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
 383       *      be set to false.
 384       */
 385      public function get_many($keys) {
 386          $result = array();
 387          foreach ($keys as $key) {
 388              $result[$key] = $this->get($key);
 389          }
 390          return $result;
 391      }
 392  
 393      /**
 394       * Deletes an item from the cache store.
 395       *
 396       * @param string $key The key to delete.
 397       * @return bool Returns true if the operation was a success, false otherwise.
 398       */
 399      public function delete($key) {
 400          $filename = $key.'.cache';
 401          $file = $this->file_path_for_key($key);
 402          if (@unlink($file)) {
 403              unset($this->keys[$filename]);
 404              return true;
 405          }
 406  
 407          return false;
 408      }
 409  
 410      /**
 411       * Deletes several keys from the cache in a single action.
 412       *
 413       * @param array $keys The keys to delete
 414       * @return int The number of items successfully deleted.
 415       */
 416      public function delete_many(array $keys) {
 417          $count = 0;
 418          foreach ($keys as $key) {
 419              if ($this->delete($key)) {
 420                  $count++;
 421              }
 422          }
 423          return $count;
 424      }
 425  
 426      /**
 427       * Sets an item in the cache given its key and data value.
 428       *
 429       * @param string $key The key to use.
 430       * @param mixed $data The data to set.
 431       * @return bool True if the operation was a success false otherwise.
 432       */
 433      public function set($key, $data) {
 434          $this->ensure_path_exists();
 435          $filename = $key.'.cache';
 436          $file = $this->file_path_for_key($key, true);
 437          $result = $this->write_file($file, $this->prep_data_before_save($data));
 438          if (!$result) {
 439              // Couldn't write the file.
 440              return false;
 441          }
 442          // Record the key if required.
 443          if ($this->prescan) {
 444              $this->keys[$filename] = cache::now() + 1;
 445          }
 446          // Return true.. it all worked **miracles**.
 447          return true;
 448      }
 449  
 450      /**
 451       * Prepares data to be stored in a file.
 452       *
 453       * @param mixed $data
 454       * @return string
 455       */
 456      protected function prep_data_before_save($data) {
 457          return serialize($data);
 458      }
 459  
 460      /**
 461       * Prepares the data it has been read from the cache. Undoing what was done in prep_data_before_save.
 462       *
 463       * @param string $data
 464       * @return mixed
 465       * @throws coding_exception
 466       */
 467      protected function prep_data_after_read($data) {
 468          $result = @unserialize($data);
 469          if ($result === false) {
 470              throw new coding_exception('Failed to unserialise data from file. Either failed to read, or failed to write.');
 471          }
 472          return $result;
 473      }
 474  
 475      /**
 476       * Sets many items in the cache in a single transaction.
 477       *
 478       * @param array $keyvaluearray An array of key value pairs. Each item in the array will be an associative array with two
 479       *      keys, 'key' and 'value'.
 480       * @return int The number of items successfully set. It is up to the developer to check this matches the number of items
 481       *      sent ... if they care that is.
 482       */
 483      public function set_many(array $keyvaluearray) {
 484          $count = 0;
 485          foreach ($keyvaluearray as $pair) {
 486              if ($this->set($pair['key'], $pair['value'])) {
 487                  $count++;
 488              }
 489          }
 490          return $count;
 491      }
 492  
 493      /**
 494       * Checks if the store has a record for the given key and returns true if so.
 495       *
 496       * @param string $key
 497       * @return bool
 498       */
 499      public function has($key) {
 500          $filename = $key.'.cache';
 501          $maxtime = cache::now() - $this->definition->get_ttl();
 502          if ($this->prescan) {
 503              return array_key_exists($filename, $this->keys) && $this->keys[$filename] >= $maxtime;
 504          }
 505          $file = $this->file_path_for_key($key);
 506          return (file_exists($file) && ($this->definition->get_ttl() == 0 || filemtime($file) >= $maxtime));
 507      }
 508  
 509      /**
 510       * Returns true if the store contains records for all of the given keys.
 511       *
 512       * @param array $keys
 513       * @return bool
 514       */
 515      public function has_all(array $keys) {
 516          foreach ($keys as $key) {
 517              if (!$this->has($key)) {
 518                  return false;
 519              }
 520          }
 521          return true;
 522      }
 523  
 524      /**
 525       * Returns true if the store contains records for any of the given keys.
 526       *
 527       * @param array $keys
 528       * @return bool
 529       */
 530      public function has_any(array $keys) {
 531          foreach ($keys as $key) {
 532              if ($this->has($key)) {
 533                  return true;
 534              }
 535          }
 536          return false;
 537      }
 538  
 539      /**
 540       * Purges the cache definition deleting all the items within it.
 541       *
 542       * @return boolean True on success. False otherwise.
 543       */
 544      public function purge() {
 545          if ($this->isready) {
 546              $files = glob($this->glob_keys_pattern(), GLOB_MARK | GLOB_NOSORT);
 547              if (is_array($files)) {
 548                  foreach ($files as $filename) {
 549                      @unlink($filename);
 550                  }
 551              }
 552              $this->keys = array();
 553          }
 554          return true;
 555      }
 556  
 557      /**
 558       * Purges all the cache definitions deleting all items within them.
 559       *
 560       * @return boolean True on success. False otherwise.
 561       */
 562      protected function purge_all_definitions() {
 563          // Warning: limit the deletion to what file store is actually able
 564          // to create using the internal {@link purge()} providing the
 565          // {@link $path} with a wildcard to perform a purge action over all the definitions.
 566          $currpath = $this->path;
 567          $this->path = $this->filestorepath.'/*';
 568          $result = $this->purge();
 569          $this->path = $currpath;
 570          return $result;
 571      }
 572  
 573      /**
 574       * Given the data from the add instance form this function creates a configuration array.
 575       *
 576       * @param stdClass $data
 577       * @return array
 578       */
 579      public static function config_get_configuration_array($data) {
 580          $config = array();
 581  
 582          if (isset($data->path)) {
 583              $config['path'] = $data->path;
 584          }
 585          if (isset($data->autocreate)) {
 586              $config['autocreate'] = $data->autocreate;
 587          }
 588          if (isset($data->singledirectory)) {
 589              $config['singledirectory'] = $data->singledirectory;
 590          }
 591          if (isset($data->prescan)) {
 592              $config['prescan'] = $data->prescan;
 593          }
 594  
 595          return $config;
 596      }
 597  
 598      /**
 599       * Allows the cache store to set its data against the edit form before it is shown to the user.
 600       *
 601       * @param moodleform $editform
 602       * @param array $config
 603       */
 604      public static function config_set_edit_form_data(moodleform $editform, array $config) {
 605          $data = array();
 606          if (!empty($config['path'])) {
 607              $data['path'] = $config['path'];
 608          }
 609          if (isset($config['autocreate'])) {
 610              $data['autocreate'] = (bool)$config['autocreate'];
 611          }
 612          if (isset($config['singledirectory'])) {
 613              $data['singledirectory'] = (bool)$config['singledirectory'];
 614          }
 615          if (isset($config['prescan'])) {
 616              $data['prescan'] = (bool)$config['prescan'];
 617          }
 618          $editform->set_data($data);
 619      }
 620  
 621      /**
 622       * Checks to make sure that the path for the file cache exists.
 623       *
 624       * @return bool
 625       * @throws coding_exception
 626       */
 627      protected function ensure_path_exists() {
 628          global $CFG;
 629          if (!is_writable($this->path)) {
 630              if ($this->custompath && !$this->autocreate) {
 631                  throw new coding_exception('File store path does not exist. It must exist and be writable by the web server.');
 632              }
 633              $createdcfg = false;
 634              if (!isset($CFG)) {
 635                  // This can only happen during destruction of objects.
 636                  // A cache is being used within a destructor, php is ending a request and $CFG has
 637                  // already being cleaned up.
 638                  // Rebuild $CFG with directory permissions just to complete this write.
 639                  $CFG = $this->cfg;
 640                  $createdcfg = true;
 641              }
 642              if (!make_writable_directory($this->path, false)) {
 643                  throw new coding_exception('File store path does not exist and can not be created.');
 644              }
 645              if ($createdcfg) {
 646                  // We re-created it so we'll clean it up.
 647                  unset($CFG);
 648              }
 649          }
 650          return true;
 651      }
 652  
 653      /**
 654       * Performs any necessary clean up when the file store instance is being deleted.
 655       *
 656       * 1. Purges the cache directory.
 657       * 2. Deletes the directory we created for the given definition.
 658       */
 659      public function instance_deleted() {
 660          $this->purge_all_definitions();
 661          @rmdir($this->filestorepath);
 662      }
 663  
 664      /**
 665       * Generates an instance of the cache store that can be used for testing.
 666       *
 667       * Returns an instance of the cache store, or false if one cannot be created.
 668       *
 669       * @param cache_definition $definition
 670       * @return cachestore_file
 671       */
 672      public static function initialise_test_instance(cache_definition $definition) {
 673          $name = 'File test';
 674          $path = make_cache_directory('cachestore_file_test');
 675          $cache = new cachestore_file($name, array('path' => $path));
 676          if ($cache->is_ready()) {
 677              $cache->initialise($definition);
 678          }
 679          return $cache;
 680      }
 681  
 682      /**
 683       * Generates the appropriate configuration required for unit testing.
 684       *
 685       * @return array Array of unit test configuration data to be used by initialise().
 686       */
 687      public static function unit_test_configuration() {
 688          return array();
 689      }
 690  
 691      /**
 692       * Writes your madness to a file.
 693       *
 694       * There are several things going on in this function to try to ensure what we don't end up with partial writes etc.
 695       *   1. Files for writing are opened with the mode xb, the file must be created and can not already exist.
 696       *   2. Renaming, data is written to a temporary file, where it can be verified using md5 and is then renamed.
 697       *
 698       * @param string $file Absolute file path
 699       * @param string $content The content to write.
 700       * @return bool
 701       */
 702      protected function write_file($file, $content) {
 703          // Generate a temp file that is going to be unique. We'll rename it at the end to the desired file name.
 704          // in this way we avoid partial writes.
 705          $path = dirname($file);
 706          while (true) {
 707              $tempfile = $path.'/'.uniqid(sesskey().'.', true) . '.temp';
 708              if (!file_exists($tempfile)) {
 709                  break;
 710              }
 711          }
 712  
 713          // Open the file with mode=x. This acts to create and open the file for writing only.
 714          // If the file already exists this will return false.
 715          // We also force binary.
 716          $handle = @fopen($tempfile, 'xb+');
 717          if ($handle === false) {
 718              // File already exists... lock already exists, return false.
 719              return false;
 720          }
 721          fwrite($handle, $content);
 722          fflush($handle);
 723          // Close the handle, we're done.
 724          fclose($handle);
 725  
 726          if (md5_file($tempfile) !== md5($content)) {
 727              // The md5 of the content of the file must match the md5 of the content given to be written.
 728              @unlink($tempfile);
 729              return false;
 730          }
 731  
 732          // Finally rename the temp file to the desired file, returning the true|false result.
 733          $result = rename($tempfile, $file);
 734          @chmod($file, $this->cfg->filepermissions);
 735          if (!$result) {
 736              // Failed to rename, don't leave files lying around.
 737              @unlink($tempfile);
 738          }
 739          return $result;
 740      }
 741  
 742      /**
 743       * Returns the name of this instance.
 744       * @return string
 745       */
 746      public function my_name() {
 747          return $this->name;
 748      }
 749  
 750      /**
 751       * Finds all of the keys being used by this cache store instance.
 752       *
 753       * @return array
 754       */
 755      public function find_all() {
 756          $this->ensure_path_exists();
 757          $files = glob($this->glob_keys_pattern(), GLOB_MARK | GLOB_NOSORT);
 758          $return = array();
 759          if ($files === false) {
 760              return $return;
 761          }
 762          foreach ($files as $file) {
 763              $return[] = substr(basename($file), 0, -6);
 764          }
 765          return $return;
 766      }
 767  
 768      /**
 769       * Finds all of the keys whose keys start with the given prefix.
 770       *
 771       * @param string $prefix
 772       */
 773      public function find_by_prefix($prefix) {
 774          $this->ensure_path_exists();
 775          $prefix = preg_replace('#(\*|\?|\[)#', '[$1]', $prefix);
 776          $files = glob($this->glob_keys_pattern($prefix), GLOB_MARK | GLOB_NOSORT);
 777          $return = array();
 778          if ($files === false) {
 779              return $return;
 780          }
 781          foreach ($files as $file) {
 782              // Trim off ".cache" from the end.
 783              $return[] = substr(basename($file), 0, -6);
 784          }
 785          return $return;
 786      }
 787  }