PageRenderTime 60ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

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

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