PageRenderTime 49ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/library/Zend/Cache/Backend/File.php

https://bitbucket.org/dbaltas/zend-framework-1.x-on-git
PHP | 1031 lines | 669 code | 38 blank | 324 comment | 63 complexity | 2af13d7ab0560fa35c00beb16e833480 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.0, MIT
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Cache
  17. * @subpackage Zend_Cache_Backend
  18. * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. * @version $Id: File.php 24844 2012-05-31 19:01:36Z rob $
  21. */
  22. /**
  23. * @see Zend_Cache_Backend_Interface
  24. */
  25. require_once 'Zend/Cache/Backend/ExtendedInterface.php';
  26. /**
  27. * @see Zend_Cache_Backend
  28. */
  29. require_once 'Zend/Cache/Backend.php';
  30. /**
  31. * @package Zend_Cache
  32. * @subpackage Zend_Cache_Backend
  33. * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
  34. * @license http://framework.zend.com/license/new-bsd New BSD License
  35. */
  36. class Zend_Cache_Backend_File extends Zend_Cache_Backend implements Zend_Cache_Backend_ExtendedInterface
  37. {
  38. /**
  39. * Available options
  40. *
  41. * =====> (string) cache_dir :
  42. * - Directory where to put the cache files
  43. *
  44. * =====> (boolean) file_locking :
  45. * - Enable / disable file_locking
  46. * - Can avoid cache corruption under bad circumstances but it doesn't work on multithread
  47. * webservers and on NFS filesystems for example
  48. *
  49. * =====> (boolean) read_control :
  50. * - Enable / disable read control
  51. * - If enabled, a control key is embeded in cache file and this key is compared with the one
  52. * calculated after the reading.
  53. *
  54. * =====> (string) read_control_type :
  55. * - Type of read control (only if read control is enabled). Available values are :
  56. * 'md5' for a md5 hash control (best but slowest)
  57. * 'crc32' for a crc32 hash control (lightly less safe but faster, better choice)
  58. * 'adler32' for an adler32 hash control (excellent choice too, faster than crc32)
  59. * 'strlen' for a length only test (fastest)
  60. *
  61. * =====> (int) hashed_directory_level :
  62. * - Hashed directory level
  63. * - Set the hashed directory structure level. 0 means "no hashed directory
  64. * structure", 1 means "one level of directory", 2 means "two levels"...
  65. * This option can speed up the cache only when you have many thousands of
  66. * cache file. Only specific benchs can help you to choose the perfect value
  67. * for you. Maybe, 1 or 2 is a good start.
  68. *
  69. * =====> (int) hashed_directory_umask :
  70. * - deprecated
  71. * - Permissions for hashed directory structure
  72. *
  73. * =====> (int) hashed_directory_perm :
  74. * - Permissions for hashed directory structure
  75. *
  76. * =====> (string) file_name_prefix :
  77. * - prefix for cache files
  78. * - be really carefull with this option because a too generic value in a system cache dir
  79. * (like /tmp) can cause disasters when cleaning the cache
  80. *
  81. * =====> (int) cache_file_umask :
  82. * - deprecated
  83. * - Permissions for cache files
  84. *
  85. * =====> (int) cache_file_perm :
  86. * - Permissions for cache files
  87. *
  88. * =====> (int) metatadatas_array_max_size :
  89. * - max size for the metadatas array (don't change this value unless you
  90. * know what you are doing)
  91. *
  92. * @var array available options
  93. */
  94. protected $_options = array(
  95. 'cache_dir' => null,
  96. 'file_locking' => true,
  97. 'read_control' => true,
  98. 'read_control_type' => 'crc32',
  99. 'hashed_directory_level' => 0,
  100. 'hashed_directory_perm' => 0700,
  101. 'file_name_prefix' => 'zend_cache',
  102. 'cache_file_perm' => 0600,
  103. 'metadatas_array_max_size' => 100
  104. );
  105. /**
  106. * Array of metadatas (each item is an associative array)
  107. *
  108. * @var array
  109. */
  110. protected $_metadatasArray = array();
  111. /**
  112. * Constructor
  113. *
  114. * @param array $options associative array of options
  115. * @throws Zend_Cache_Exception
  116. * @return void
  117. */
  118. public function __construct(array $options = array())
  119. {
  120. parent::__construct($options);
  121. if ($this->_options['cache_dir'] !== null) { // particular case for this option
  122. $this->setCacheDir($this->_options['cache_dir']);
  123. } else {
  124. $this->setCacheDir(self::getTmpDir() . DIRECTORY_SEPARATOR, false);
  125. }
  126. if (isset($this->_options['file_name_prefix'])) { // particular case for this option
  127. if (!preg_match('~^[a-zA-Z0-9_]+$~D', $this->_options['file_name_prefix'])) {
  128. Zend_Cache::throwException('Invalid file_name_prefix : must use only [a-zA-Z0-9_]');
  129. }
  130. }
  131. if ($this->_options['metadatas_array_max_size'] < 10) {
  132. Zend_Cache::throwException('Invalid metadatas_array_max_size, must be > 10');
  133. }
  134. if (isset($options['hashed_directory_umask'])) {
  135. // See #ZF-12047
  136. trigger_error("'hashed_directory_umask' is deprecated -> please use 'hashed_directory_perm' instead", E_USER_NOTICE);
  137. if (!isset($options['hashed_directory_perm'])) {
  138. $options['hashed_directory_perm'] = $options['hashed_directory_umask'];
  139. }
  140. }
  141. if (isset($options['hashed_directory_perm']) && is_string($options['hashed_directory_perm'])) {
  142. // See #ZF-4422
  143. $this->_options['hashed_directory_perm'] = octdec($this->_options['hashed_directory_perm']);
  144. }
  145. if (isset($options['cache_file_umask'])) {
  146. // See #ZF-12047
  147. trigger_error("'cache_file_umask' is deprecated -> please use 'cache_file_perm' instead", E_USER_NOTICE);
  148. if (!isset($options['cache_file_perm'])) {
  149. $options['cache_file_perm'] = $options['cache_file_umask'];
  150. }
  151. }
  152. if (isset($options['cache_file_perm']) && is_string($options['cache_file_perm'])) {
  153. // See #ZF-4422
  154. $this->_options['cache_file_perm'] = octdec($this->_options['cache_file_perm']);
  155. }
  156. }
  157. /**
  158. * Set the cache_dir (particular case of setOption() method)
  159. *
  160. * @param string $value
  161. * @param boolean $trailingSeparator If true, add a trailing separator is necessary
  162. * @throws Zend_Cache_Exception
  163. * @return void
  164. */
  165. public function setCacheDir($value, $trailingSeparator = true)
  166. {
  167. if (!is_dir($value)) {
  168. Zend_Cache::throwException(sprintf('cache_dir "%s" must be a directory', $value));
  169. }
  170. if (!is_writable($value)) {
  171. Zend_Cache::throwException(sprintf('cache_dir "%s" is not writable', $value));
  172. }
  173. if ($trailingSeparator) {
  174. // add a trailing DIRECTORY_SEPARATOR if necessary
  175. $value = rtrim(realpath($value), '\\/') . DIRECTORY_SEPARATOR;
  176. }
  177. $this->_options['cache_dir'] = $value;
  178. }
  179. /**
  180. * Test if a cache is available for the given id and (if yes) return it (false else)
  181. *
  182. * @param string $id cache id
  183. * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested
  184. * @return string|false cached datas
  185. */
  186. public function load($id, $doNotTestCacheValidity = false)
  187. {
  188. if (!($this->_test($id, $doNotTestCacheValidity))) {
  189. // The cache is not hit !
  190. return false;
  191. }
  192. $metadatas = $this->_getMetadatas($id);
  193. $file = $this->_file($id);
  194. $data = $this->_fileGetContents($file);
  195. if ($this->_options['read_control']) {
  196. $hashData = $this->_hash($data, $this->_options['read_control_type']);
  197. $hashControl = $metadatas['hash'];
  198. if ($hashData != $hashControl) {
  199. // Problem detected by the read control !
  200. $this->_log('Zend_Cache_Backend_File::load() / read_control : stored hash and computed hash do not match');
  201. $this->remove($id);
  202. return false;
  203. }
  204. }
  205. return $data;
  206. }
  207. /**
  208. * Test if a cache is available or not (for the given id)
  209. *
  210. * @param string $id cache id
  211. * @return mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record
  212. */
  213. public function test($id)
  214. {
  215. clearstatcache();
  216. return $this->_test($id, false);
  217. }
  218. /**
  219. * Save some string datas into a cache record
  220. *
  221. * Note : $data is always "string" (serialization is done by the
  222. * core not by the backend)
  223. *
  224. * @param string $data Datas to cache
  225. * @param string $id Cache id
  226. * @param array $tags Array of strings, the cache record will be tagged by each string entry
  227. * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
  228. * @return boolean true if no problem
  229. */
  230. public function save($data, $id, $tags = array(), $specificLifetime = false)
  231. {
  232. clearstatcache();
  233. $file = $this->_file($id);
  234. $path = $this->_path($id);
  235. if ($this->_options['hashed_directory_level'] > 0) {
  236. if (!is_writable($path)) {
  237. // maybe, we just have to build the directory structure
  238. $this->_recursiveMkdirAndChmod($id);
  239. }
  240. if (!is_writable($path)) {
  241. return false;
  242. }
  243. }
  244. if ($this->_options['read_control']) {
  245. $hash = $this->_hash($data, $this->_options['read_control_type']);
  246. } else {
  247. $hash = '';
  248. }
  249. $metadatas = array(
  250. 'hash' => $hash,
  251. 'mtime' => time(),
  252. 'expire' => $this->_expireTime($this->getLifetime($specificLifetime)),
  253. 'tags' => $tags
  254. );
  255. $res = $this->_setMetadatas($id, $metadatas);
  256. if (!$res) {
  257. $this->_log('Zend_Cache_Backend_File::save() / error on saving metadata');
  258. return false;
  259. }
  260. $res = $this->_filePutContents($file, $data);
  261. return $res;
  262. }
  263. /**
  264. * Remove a cache record
  265. *
  266. * @param string $id cache id
  267. * @return boolean true if no problem
  268. */
  269. public function remove($id)
  270. {
  271. $file = $this->_file($id);
  272. $boolRemove = $this->_remove($file);
  273. $boolMetadata = $this->_delMetadatas($id);
  274. return $boolMetadata && $boolRemove;
  275. }
  276. /**
  277. * Clean some cache records
  278. *
  279. * Available modes are :
  280. *
  281. * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used)
  282. * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used)
  283. * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags
  284. * ($tags can be an array of strings or a single string)
  285. * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags}
  286. * ($tags can be an array of strings or a single string)
  287. * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags
  288. * ($tags can be an array of strings or a single string)
  289. *
  290. * @param string $mode clean mode
  291. * @param tags array $tags array of tags
  292. * @return boolean true if no problem
  293. */
  294. public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
  295. {
  296. // We use this protected method to hide the recursive stuff
  297. clearstatcache();
  298. return $this->_clean($this->_options['cache_dir'], $mode, $tags);
  299. }
  300. /**
  301. * Return an array of stored cache ids
  302. *
  303. * @return array array of stored cache ids (string)
  304. */
  305. public function getIds()
  306. {
  307. return $this->_get($this->_options['cache_dir'], 'ids', array());
  308. }
  309. /**
  310. * Return an array of stored tags
  311. *
  312. * @return array array of stored tags (string)
  313. */
  314. public function getTags()
  315. {
  316. return $this->_get($this->_options['cache_dir'], 'tags', array());
  317. }
  318. /**
  319. * Return an array of stored cache ids which match given tags
  320. *
  321. * In case of multiple tags, a logical AND is made between tags
  322. *
  323. * @param array $tags array of tags
  324. * @return array array of matching cache ids (string)
  325. */
  326. public function getIdsMatchingTags($tags = array())
  327. {
  328. return $this->_get($this->_options['cache_dir'], 'matching', $tags);
  329. }
  330. /**
  331. * Return an array of stored cache ids which don't match given tags
  332. *
  333. * In case of multiple tags, a logical OR is made between tags
  334. *
  335. * @param array $tags array of tags
  336. * @return array array of not matching cache ids (string)
  337. */
  338. public function getIdsNotMatchingTags($tags = array())
  339. {
  340. return $this->_get($this->_options['cache_dir'], 'notMatching', $tags);
  341. }
  342. /**
  343. * Return an array of stored cache ids which match any given tags
  344. *
  345. * In case of multiple tags, a logical AND is made between tags
  346. *
  347. * @param array $tags array of tags
  348. * @return array array of any matching cache ids (string)
  349. */
  350. public function getIdsMatchingAnyTags($tags = array())
  351. {
  352. return $this->_get($this->_options['cache_dir'], 'matchingAny', $tags);
  353. }
  354. /**
  355. * Return the filling percentage of the backend storage
  356. *
  357. * @throws Zend_Cache_Exception
  358. * @return int integer between 0 and 100
  359. */
  360. public function getFillingPercentage()
  361. {
  362. $free = disk_free_space($this->_options['cache_dir']);
  363. $total = disk_total_space($this->_options['cache_dir']);
  364. if ($total == 0) {
  365. Zend_Cache::throwException('can\'t get disk_total_space');
  366. } else {
  367. if ($free >= $total) {
  368. return 100;
  369. }
  370. return ((int) (100. * ($total - $free) / $total));
  371. }
  372. }
  373. /**
  374. * Return an array of metadatas for the given cache id
  375. *
  376. * The array must include these keys :
  377. * - expire : the expire timestamp
  378. * - tags : a string array of tags
  379. * - mtime : timestamp of last modification time
  380. *
  381. * @param string $id cache id
  382. * @return array array of metadatas (false if the cache id is not found)
  383. */
  384. public function getMetadatas($id)
  385. {
  386. $metadatas = $this->_getMetadatas($id);
  387. if (!$metadatas) {
  388. return false;
  389. }
  390. if (time() > $metadatas['expire']) {
  391. return false;
  392. }
  393. return array(
  394. 'expire' => $metadatas['expire'],
  395. 'tags' => $metadatas['tags'],
  396. 'mtime' => $metadatas['mtime']
  397. );
  398. }
  399. /**
  400. * Give (if possible) an extra lifetime to the given cache id
  401. *
  402. * @param string $id cache id
  403. * @param int $extraLifetime
  404. * @return boolean true if ok
  405. */
  406. public function touch($id, $extraLifetime)
  407. {
  408. $metadatas = $this->_getMetadatas($id);
  409. if (!$metadatas) {
  410. return false;
  411. }
  412. if (time() > $metadatas['expire']) {
  413. return false;
  414. }
  415. $newMetadatas = array(
  416. 'hash' => $metadatas['hash'],
  417. 'mtime' => time(),
  418. 'expire' => $metadatas['expire'] + $extraLifetime,
  419. 'tags' => $metadatas['tags']
  420. );
  421. $res = $this->_setMetadatas($id, $newMetadatas);
  422. if (!$res) {
  423. return false;
  424. }
  425. return true;
  426. }
  427. /**
  428. * Return an associative array of capabilities (booleans) of the backend
  429. *
  430. * The array must include these keys :
  431. * - automatic_cleaning (is automating cleaning necessary)
  432. * - tags (are tags supported)
  433. * - expired_read (is it possible to read expired cache records
  434. * (for doNotTestCacheValidity option for example))
  435. * - priority does the backend deal with priority when saving
  436. * - infinite_lifetime (is infinite lifetime can work with this backend)
  437. * - get_list (is it possible to get the list of cache ids and the complete list of tags)
  438. *
  439. * @return array associative of with capabilities
  440. */
  441. public function getCapabilities()
  442. {
  443. return array(
  444. 'automatic_cleaning' => true,
  445. 'tags' => true,
  446. 'expired_read' => true,
  447. 'priority' => false,
  448. 'infinite_lifetime' => true,
  449. 'get_list' => true
  450. );
  451. }
  452. /**
  453. * PUBLIC METHOD FOR UNIT TESTING ONLY !
  454. *
  455. * Force a cache record to expire
  456. *
  457. * @param string $id cache id
  458. */
  459. public function ___expire($id)
  460. {
  461. $metadatas = $this->_getMetadatas($id);
  462. if ($metadatas) {
  463. $metadatas['expire'] = 1;
  464. $this->_setMetadatas($id, $metadatas);
  465. }
  466. }
  467. /**
  468. * Get a metadatas record
  469. *
  470. * @param string $id Cache id
  471. * @return array|false Associative array of metadatas
  472. */
  473. protected function _getMetadatas($id)
  474. {
  475. if (isset($this->_metadatasArray[$id])) {
  476. return $this->_metadatasArray[$id];
  477. } else {
  478. $metadatas = $this->_loadMetadatas($id);
  479. if (!$metadatas) {
  480. return false;
  481. }
  482. $this->_setMetadatas($id, $metadatas, false);
  483. return $metadatas;
  484. }
  485. }
  486. /**
  487. * Set a metadatas record
  488. *
  489. * @param string $id Cache id
  490. * @param array $metadatas Associative array of metadatas
  491. * @param boolean $save optional pass false to disable saving to file
  492. * @return boolean True if no problem
  493. */
  494. protected function _setMetadatas($id, $metadatas, $save = true)
  495. {
  496. if (count($this->_metadatasArray) >= $this->_options['metadatas_array_max_size']) {
  497. $n = (int) ($this->_options['metadatas_array_max_size'] / 10);
  498. $this->_metadatasArray = array_slice($this->_metadatasArray, $n);
  499. }
  500. if ($save) {
  501. $result = $this->_saveMetadatas($id, $metadatas);
  502. if (!$result) {
  503. return false;
  504. }
  505. }
  506. $this->_metadatasArray[$id] = $metadatas;
  507. return true;
  508. }
  509. /**
  510. * Drop a metadata record
  511. *
  512. * @param string $id Cache id
  513. * @return boolean True if no problem
  514. */
  515. protected function _delMetadatas($id)
  516. {
  517. if (isset($this->_metadatasArray[$id])) {
  518. unset($this->_metadatasArray[$id]);
  519. }
  520. $file = $this->_metadatasFile($id);
  521. return $this->_remove($file);
  522. }
  523. /**
  524. * Clear the metadatas array
  525. *
  526. * @return void
  527. */
  528. protected function _cleanMetadatas()
  529. {
  530. $this->_metadatasArray = array();
  531. }
  532. /**
  533. * Load metadatas from disk
  534. *
  535. * @param string $id Cache id
  536. * @return array|false Metadatas associative array
  537. */
  538. protected function _loadMetadatas($id)
  539. {
  540. $file = $this->_metadatasFile($id);
  541. $result = $this->_fileGetContents($file);
  542. if (!$result) {
  543. return false;
  544. }
  545. $tmp = @unserialize($result);
  546. return $tmp;
  547. }
  548. /**
  549. * Save metadatas to disk
  550. *
  551. * @param string $id Cache id
  552. * @param array $metadatas Associative array
  553. * @return boolean True if no problem
  554. */
  555. protected function _saveMetadatas($id, $metadatas)
  556. {
  557. $file = $this->_metadatasFile($id);
  558. $result = $this->_filePutContents($file, serialize($metadatas));
  559. if (!$result) {
  560. return false;
  561. }
  562. return true;
  563. }
  564. /**
  565. * Make and return a file name (with path) for metadatas
  566. *
  567. * @param string $id Cache id
  568. * @return string Metadatas file name (with path)
  569. */
  570. protected function _metadatasFile($id)
  571. {
  572. $path = $this->_path($id);
  573. $fileName = $this->_idToFileName('internal-metadatas---' . $id);
  574. return $path . $fileName;
  575. }
  576. /**
  577. * Check if the given filename is a metadatas one
  578. *
  579. * @param string $fileName File name
  580. * @return boolean True if it's a metadatas one
  581. */
  582. protected function _isMetadatasFile($fileName)
  583. {
  584. $id = $this->_fileNameToId($fileName);
  585. if (substr($id, 0, 21) == 'internal-metadatas---') {
  586. return true;
  587. } else {
  588. return false;
  589. }
  590. }
  591. /**
  592. * Remove a file
  593. *
  594. * If we can't remove the file (because of locks or any problem), we will touch
  595. * the file to invalidate it
  596. *
  597. * @param string $file Complete file path
  598. * @return boolean True if ok
  599. */
  600. protected function _remove($file)
  601. {
  602. if (!is_file($file)) {
  603. return false;
  604. }
  605. if (!@unlink($file)) {
  606. # we can't remove the file (because of locks or any problem)
  607. $this->_log("Zend_Cache_Backend_File::_remove() : we can't remove $file");
  608. return false;
  609. }
  610. return true;
  611. }
  612. /**
  613. * Clean some cache records (protected method used for recursive stuff)
  614. *
  615. * Available modes are :
  616. * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used)
  617. * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used)
  618. * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags
  619. * ($tags can be an array of strings or a single string)
  620. * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags}
  621. * ($tags can be an array of strings or a single string)
  622. * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags
  623. * ($tags can be an array of strings or a single string)
  624. *
  625. * @param string $dir Directory to clean
  626. * @param string $mode Clean mode
  627. * @param array $tags Array of tags
  628. * @throws Zend_Cache_Exception
  629. * @return boolean True if no problem
  630. */
  631. protected function _clean($dir, $mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
  632. {
  633. if (!is_dir($dir)) {
  634. return false;
  635. }
  636. $result = true;
  637. $prefix = $this->_options['file_name_prefix'];
  638. $glob = @glob($dir . $prefix . '--*');
  639. if ($glob === false) {
  640. // On some systems it is impossible to distinguish between empty match and an error.
  641. return true;
  642. }
  643. foreach ($glob as $file) {
  644. if (is_file($file)) {
  645. $fileName = basename($file);
  646. if ($this->_isMetadatasFile($fileName)) {
  647. // in CLEANING_MODE_ALL, we drop anything, even remainings old metadatas files
  648. if ($mode != Zend_Cache::CLEANING_MODE_ALL) {
  649. continue;
  650. }
  651. }
  652. $id = $this->_fileNameToId($fileName);
  653. $metadatas = $this->_getMetadatas($id);
  654. if ($metadatas === FALSE) {
  655. $metadatas = array('expire' => 1, 'tags' => array());
  656. }
  657. switch ($mode) {
  658. case Zend_Cache::CLEANING_MODE_ALL:
  659. $res = $this->remove($id);
  660. if (!$res) {
  661. // in this case only, we accept a problem with the metadatas file drop
  662. $res = $this->_remove($file);
  663. }
  664. $result = $result && $res;
  665. break;
  666. case Zend_Cache::CLEANING_MODE_OLD:
  667. if (time() > $metadatas['expire']) {
  668. $result = $this->remove($id) && $result;
  669. }
  670. break;
  671. case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
  672. $matching = true;
  673. foreach ($tags as $tag) {
  674. if (!in_array($tag, $metadatas['tags'])) {
  675. $matching = false;
  676. break;
  677. }
  678. }
  679. if ($matching) {
  680. $result = $this->remove($id) && $result;
  681. }
  682. break;
  683. case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
  684. $matching = false;
  685. foreach ($tags as $tag) {
  686. if (in_array($tag, $metadatas['tags'])) {
  687. $matching = true;
  688. break;
  689. }
  690. }
  691. if (!$matching) {
  692. $result = $this->remove($id) && $result;
  693. }
  694. break;
  695. case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
  696. $matching = false;
  697. foreach ($tags as $tag) {
  698. if (in_array($tag, $metadatas['tags'])) {
  699. $matching = true;
  700. break;
  701. }
  702. }
  703. if ($matching) {
  704. $result = $this->remove($id) && $result;
  705. }
  706. break;
  707. default:
  708. Zend_Cache::throwException('Invalid mode for clean() method');
  709. break;
  710. }
  711. }
  712. if ((is_dir($file)) and ($this->_options['hashed_directory_level']>0)) {
  713. // Recursive call
  714. $result = $this->_clean($file . DIRECTORY_SEPARATOR, $mode, $tags) && $result;
  715. if ($mode == Zend_Cache::CLEANING_MODE_ALL) {
  716. // we try to drop the structure too
  717. @rmdir($file);
  718. }
  719. }
  720. }
  721. return $result;
  722. }
  723. protected function _get($dir, $mode, $tags = array())
  724. {
  725. if (!is_dir($dir)) {
  726. return false;
  727. }
  728. $result = array();
  729. $prefix = $this->_options['file_name_prefix'];
  730. $glob = @glob($dir . $prefix . '--*');
  731. if ($glob === false) {
  732. // On some systems it is impossible to distinguish between empty match and an error.
  733. return array();
  734. }
  735. foreach ($glob as $file) {
  736. if (is_file($file)) {
  737. $fileName = basename($file);
  738. $id = $this->_fileNameToId($fileName);
  739. $metadatas = $this->_getMetadatas($id);
  740. if ($metadatas === FALSE) {
  741. continue;
  742. }
  743. if (time() > $metadatas['expire']) {
  744. continue;
  745. }
  746. switch ($mode) {
  747. case 'ids':
  748. $result[] = $id;
  749. break;
  750. case 'tags':
  751. $result = array_unique(array_merge($result, $metadatas['tags']));
  752. break;
  753. case 'matching':
  754. $matching = true;
  755. foreach ($tags as $tag) {
  756. if (!in_array($tag, $metadatas['tags'])) {
  757. $matching = false;
  758. break;
  759. }
  760. }
  761. if ($matching) {
  762. $result[] = $id;
  763. }
  764. break;
  765. case 'notMatching':
  766. $matching = false;
  767. foreach ($tags as $tag) {
  768. if (in_array($tag, $metadatas['tags'])) {
  769. $matching = true;
  770. break;
  771. }
  772. }
  773. if (!$matching) {
  774. $result[] = $id;
  775. }
  776. break;
  777. case 'matchingAny':
  778. $matching = false;
  779. foreach ($tags as $tag) {
  780. if (in_array($tag, $metadatas['tags'])) {
  781. $matching = true;
  782. break;
  783. }
  784. }
  785. if ($matching) {
  786. $result[] = $id;
  787. }
  788. break;
  789. default:
  790. Zend_Cache::throwException('Invalid mode for _get() method');
  791. break;
  792. }
  793. }
  794. if ((is_dir($file)) and ($this->_options['hashed_directory_level']>0)) {
  795. // Recursive call
  796. $recursiveRs = $this->_get($file . DIRECTORY_SEPARATOR, $mode, $tags);
  797. if ($recursiveRs === false) {
  798. $this->_log('Zend_Cache_Backend_File::_get() / recursive call : can\'t list entries of "'.$file.'"');
  799. } else {
  800. $result = array_unique(array_merge($result, $recursiveRs));
  801. }
  802. }
  803. }
  804. return array_unique($result);
  805. }
  806. /**
  807. * Compute & return the expire time
  808. *
  809. * @return int expire time (unix timestamp)
  810. */
  811. protected function _expireTime($lifetime)
  812. {
  813. if ($lifetime === null) {
  814. return 9999999999;
  815. }
  816. return time() + $lifetime;
  817. }
  818. /**
  819. * Make a control key with the string containing datas
  820. *
  821. * @param string $data Data
  822. * @param string $controlType Type of control 'md5', 'crc32' or 'strlen'
  823. * @throws Zend_Cache_Exception
  824. * @return string Control key
  825. */
  826. protected function _hash($data, $controlType)
  827. {
  828. switch ($controlType) {
  829. case 'md5':
  830. return md5($data);
  831. case 'crc32':
  832. return crc32($data);
  833. case 'strlen':
  834. return strlen($data);
  835. case 'adler32':
  836. return hash('adler32', $data);
  837. default:
  838. Zend_Cache::throwException("Incorrect hash function : $controlType");
  839. }
  840. }
  841. /**
  842. * Transform a cache id into a file name and return it
  843. *
  844. * @param string $id Cache id
  845. * @return string File name
  846. */
  847. protected function _idToFileName($id)
  848. {
  849. $prefix = $this->_options['file_name_prefix'];
  850. $result = $prefix . '---' . $id;
  851. return $result;
  852. }
  853. /**
  854. * Make and return a file name (with path)
  855. *
  856. * @param string $id Cache id
  857. * @return string File name (with path)
  858. */
  859. protected function _file($id)
  860. {
  861. $path = $this->_path($id);
  862. $fileName = $this->_idToFileName($id);
  863. return $path . $fileName;
  864. }
  865. /**
  866. * Return the complete directory path of a filename (including hashedDirectoryStructure)
  867. *
  868. * @param string $id Cache id
  869. * @param boolean $parts if true, returns array of directory parts instead of single string
  870. * @return string Complete directory path
  871. */
  872. protected function _path($id, $parts = false)
  873. {
  874. $partsArray = array();
  875. $root = $this->_options['cache_dir'];
  876. $prefix = $this->_options['file_name_prefix'];
  877. if ($this->_options['hashed_directory_level']>0) {
  878. $hash = hash('adler32', $id);
  879. for ($i=0 ; $i < $this->_options['hashed_directory_level'] ; $i++) {
  880. $root = $root . $prefix . '--' . substr($hash, 0, $i + 1) . DIRECTORY_SEPARATOR;
  881. $partsArray[] = $root;
  882. }
  883. }
  884. if ($parts) {
  885. return $partsArray;
  886. } else {
  887. return $root;
  888. }
  889. }
  890. /**
  891. * Make the directory strucuture for the given id
  892. *
  893. * @param string $id cache id
  894. * @return boolean true
  895. */
  896. protected function _recursiveMkdirAndChmod($id)
  897. {
  898. if ($this->_options['hashed_directory_level'] <=0) {
  899. return true;
  900. }
  901. $partsArray = $this->_path($id, true);
  902. foreach ($partsArray as $part) {
  903. if (!is_dir($part)) {
  904. @mkdir($part, $this->_options['hashed_directory_perm']);
  905. @chmod($part, $this->_options['hashed_directory_perm']); // see #ZF-320 (this line is required in some configurations)
  906. }
  907. }
  908. return true;
  909. }
  910. /**
  911. * Test if the given cache id is available (and still valid as a cache record)
  912. *
  913. * @param string $id Cache id
  914. * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
  915. * @return boolean|mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record
  916. */
  917. protected function _test($id, $doNotTestCacheValidity)
  918. {
  919. $metadatas = $this->_getMetadatas($id);
  920. if (!$metadatas) {
  921. return false;
  922. }
  923. if ($doNotTestCacheValidity || (time() <= $metadatas['expire'])) {
  924. return $metadatas['mtime'];
  925. }
  926. return false;
  927. }
  928. /**
  929. * Return the file content of the given file
  930. *
  931. * @param string $file File complete path
  932. * @return string File content (or false if problem)
  933. */
  934. protected function _fileGetContents($file)
  935. {
  936. $result = false;
  937. if (!is_file($file)) {
  938. return false;
  939. }
  940. $f = @fopen($file, 'rb');
  941. if ($f) {
  942. if ($this->_options['file_locking']) @flock($f, LOCK_SH);
  943. $result = stream_get_contents($f);
  944. if ($this->_options['file_locking']) @flock($f, LOCK_UN);
  945. @fclose($f);
  946. }
  947. return $result;
  948. }
  949. /**
  950. * Put the given string into the given file
  951. *
  952. * @param string $file File complete path
  953. * @param string $string String to put in file
  954. * @return boolean true if no problem
  955. */
  956. protected function _filePutContents($file, $string)
  957. {
  958. $result = false;
  959. $f = @fopen($file, 'ab+');
  960. if ($f) {
  961. if ($this->_options['file_locking']) @flock($f, LOCK_EX);
  962. fseek($f, 0);
  963. ftruncate($f, 0);
  964. $tmp = @fwrite($f, $string);
  965. if (!($tmp === FALSE)) {
  966. $result = true;
  967. }
  968. @fclose($f);
  969. }
  970. @chmod($file, $this->_options['cache_file_perm']);
  971. return $result;
  972. }
  973. /**
  974. * Transform a file name into cache id and return it
  975. *
  976. * @param string $fileName File name
  977. * @return string Cache id
  978. */
  979. protected function _fileNameToId($fileName)
  980. {
  981. $prefix = $this->_options['file_name_prefix'];
  982. return preg_replace('~^' . $prefix . '---(.*)$~', '$1', $fileName);
  983. }
  984. }