[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/cache/stores/file/ -> lib.php (source)

   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  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1