PageRenderTime 45ms CodeModel.GetById 4ms RepoModel.GetById 0ms app.codeStats 0ms

/cache/stores/file/lib.php

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