PageRenderTime 51ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/Zend/Cache/Backend/File.php

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