File.php 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749
  1. <?php
  2. /*
  3. ==New BSD License==
  4. Copyright (c) 2012, Colin Mollenhour
  5. All rights reserved.
  6. Redistribution and use in source and binary forms, with or without
  7. modification, are permitted provided that the following conditions are met:
  8. * Redistributions of source code must retain the above copyright
  9. notice, this list of conditions and the following disclaimer.
  10. * Redistributions in binary form must reproduce the above copyright
  11. notice, this list of conditions and the following disclaimer in the
  12. documentation and/or other materials provided with the distribution.
  13. * The name of Colin Mollenhour may not be used to endorse or promote products
  14. derived from this software without specific prior written permission.
  15. * The class name must remain as Cm_Cache_Backend_File.
  16. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  17. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
  20. DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. /**
  28. * Cm_Cache_Backend_File
  29. *
  30. * @copyright Copyright (c) 2013 Colin Mollenhour (http://colin.mollenhour.com)
  31. * @license http://framework.zend.com/license/new-bsd New BSD License
  32. */
  33. class Cm_Cache_Backend_File extends Zend_Cache_Backend_File
  34. {
  35. /** @var array */
  36. protected $_options = array(
  37. 'cache_dir' => null, // Path to cache files
  38. 'file_name_prefix' => 'cm', // Prefix for cache directories created
  39. 'file_locking' => true, // Best to keep enabled
  40. 'read_control' => false, // Use a checksum to detect corrupt data
  41. 'read_control_type' => 'crc32', // If read_control is enabled, which checksum algorithm to use
  42. 'hashed_directory_level' => 2, // How many characters should be used to create sub-directories
  43. 'use_chmod' => FALSE, // Do not use chmod on files and directories (should use umask() to control permissions)
  44. 'directory_mode' => 0770, // Filesystem permissions for created directories (requires use_chmod)
  45. 'file_mode' => 0660, // Filesystem permissions for created files (requires use_chmod)
  46. );
  47. /** @var bool */
  48. protected $_isTagDirChecked;
  49. /**
  50. * @param array $options
  51. */
  52. public function __construct(array $options = array())
  53. {
  54. // Magento-friendly cache dir
  55. if (empty($options['cache_dir']) && class_exists('Mage', false)) {
  56. $options['cache_dir'] = Mage::getBaseDir('cache');
  57. }
  58. // Backwards compatibility ZF 1.11 and ZF 1.12
  59. if (isset($options['hashed_directory_umask'])) {
  60. $options['directory_mode'] = $options['hashed_directory_umask'];
  61. }
  62. if (isset($options['cache_file_umask'])) {
  63. $options['file_mode'] = $options['cache_file_umask'];
  64. }
  65. // Auto-enable chmod if modes are specified.
  66. if (isset($options['directory_mode']) || isset($options['file_mode'])) {
  67. $options['use_chmod'] = TRUE;
  68. }
  69. // Don't use parent constructor
  70. foreach ($options as $name => $value) {
  71. $this->setOption($name, $value);
  72. }
  73. // Check cache dir
  74. if ($this->_options['cache_dir'] !== null) { // particular case for this option
  75. $this->setCacheDir($this->_options['cache_dir']);
  76. } else {
  77. $this->setCacheDir(self::getTmpDir() . DIRECTORY_SEPARATOR, false);
  78. }
  79. // Validate prefix
  80. if (isset($this->_options['file_name_prefix']) && !preg_match('~^[a-zA-Z0-9_]+$~D', $this->_options['file_name_prefix'])) {
  81. Zend_Cache::throwException('Invalid file_name_prefix : must use only [a-zA-Z0-9_]');
  82. }
  83. // See #ZF-4422
  84. if (is_string($this->_options['directory_mode'])) {
  85. $this->_options['directory_mode'] = octdec($this->_options['directory_mode']);
  86. }
  87. if (is_string($this->_options['file_mode'])) {
  88. $this->_options['file_mode'] = octdec($this->_options['file_mode']);
  89. }
  90. $this->_options['hashed_directory_umask'] = $this->_options['directory_mode'];
  91. $this->_options['cache_file_umask'] = $this->_options['file_mode'];
  92. }
  93. /**
  94. * OVERRIDDEN to remove use of each() which is deprecated in PHP 7.2
  95. *
  96. * Set the frontend directives
  97. *
  98. * @param array $directives Assoc of directives
  99. * @throws Zend_Cache_Exception
  100. * @return void
  101. */
  102. public function setDirectives($directives)
  103. {
  104. if (!is_array($directives)) Zend_Cache::throwException('Directives parameter must be an array');
  105. foreach ($directives as $name => $value) {
  106. if (!is_string($name)) {
  107. Zend_Cache::throwException("Incorrect option name : $name");
  108. }
  109. $name = strtolower($name);
  110. if (array_key_exists($name, $this->_directives)) {
  111. $this->_directives[$name] = $value;
  112. }
  113. }
  114. $this->_loggerSanity();
  115. }
  116. /**
  117. * Test if a cache is available for the given id and (if yes) return it (false else)
  118. *
  119. * @param string $id cache id
  120. * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested
  121. * @return string|bool cached datas
  122. */
  123. public function load($id, $doNotTestCacheValidity = false)
  124. {
  125. $file = $this->_file($id);
  126. $cache = $this->_getCache($file, true);
  127. if ( ! $cache) {
  128. return false;
  129. }
  130. list($metadatas, $data) = $cache;
  131. if ( ! $doNotTestCacheValidity && (time() > $metadatas['expire'])) {
  132. // ?? $this->remove($id);
  133. return false;
  134. }
  135. if ($this->_options['read_control']) {
  136. $hashData = $this->_hash($data, $this->_options['read_control_type']);
  137. $hashControl = $metadatas['hash'];
  138. if ($hashData != $hashControl) {
  139. // Problem detected by the read control !
  140. $this->_log('Zend_Cache_Backend_File::load() / read_control : stored hash and computed hash do not match');
  141. $this->remove($id);
  142. return false;
  143. }
  144. }
  145. return $data;
  146. }
  147. /**
  148. * Save some string datas into a cache record
  149. *
  150. * Note : $data is always "string" (serialization is done by the
  151. * core not by the backend)
  152. *
  153. * @param string $data Datas to cache
  154. * @param string $id Cache id
  155. * @param array $tags Array of strings, the cache record will be tagged by each string entry
  156. * @param bool|int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
  157. * @return boolean true if no problem
  158. */
  159. public function save($data, $id, $tags = array(), $specificLifetime = false)
  160. {
  161. $file = $this->_file($id);
  162. $path = $this->_path($id);
  163. if ($this->_options['hashed_directory_level'] > 0) {
  164. if (!is_writable($path)) {
  165. // maybe, we just have to build the directory structure
  166. $this->_recursiveMkdirAndChmod($id);
  167. }
  168. if (!is_writable($path)) {
  169. return false;
  170. }
  171. }
  172. if ($this->_options['read_control']) {
  173. $hash = $this->_hash($data, $this->_options['read_control_type']);
  174. } else {
  175. $hash = '';
  176. }
  177. $metadatas = array(
  178. 'hash' => $hash,
  179. 'mtime' => time(),
  180. 'expire' => $this->_expireTime($this->getLifetime($specificLifetime)),
  181. 'tags' => implode(',', $tags),
  182. );
  183. $res = $this->_filePutContents($file, serialize($metadatas)."\n".$data);
  184. $res = $res && $this->_updateIdsTags(array($id), $tags, 'merge');
  185. return $res;
  186. }
  187. /**
  188. * Remove a cache record
  189. *
  190. * @param string $id cache id
  191. * @return boolean true if no problem
  192. */
  193. public function remove($id)
  194. {
  195. $file = $this->_file($id);
  196. $metadatas = $this->_getCache($file, false);
  197. if ($metadatas) {
  198. $boolRemove = $this->_remove($file);
  199. $boolTags = $this->_updateIdsTags(array($id), explode(',', $metadatas['tags']), 'diff');
  200. return $boolRemove && $boolTags;
  201. }
  202. return false;
  203. }
  204. /**
  205. * Clean some cache records
  206. *
  207. * Available modes are :
  208. * 'all' (default) => remove all cache entries ($tags is not used)
  209. * 'old' => remove too old cache entries ($tags is not used)
  210. * 'matchingTag' => remove cache entries matching all given tags
  211. * ($tags can be an array of strings or a single string)
  212. * 'notMatchingTag' => remove cache entries not matching one of the given tags
  213. * ($tags can be an array of strings or a single string)
  214. * 'matchingAnyTag' => remove cache entries matching any given tags
  215. * ($tags can be an array of strings or a single string)
  216. *
  217. * @param string $mode
  218. * @param array $tags
  219. * @return boolean true if no problem
  220. */
  221. public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
  222. {
  223. // We use this protected method to hide the recursive stuff
  224. clearstatcache();
  225. switch($mode) {
  226. case Zend_Cache::CLEANING_MODE_ALL:
  227. case Zend_Cache::CLEANING_MODE_OLD:
  228. return $this->_clean($this->_options['cache_dir'], $mode);
  229. default:
  230. return $this->_cleanNew($mode, $tags);
  231. }
  232. }
  233. /**
  234. * Return an array of stored tags
  235. *
  236. * @return array array of stored tags (string)
  237. */
  238. public function getTags()
  239. {
  240. $prefix = $this->_tagFile('');
  241. $prefixLen = strlen($prefix);
  242. $tags = array();
  243. foreach (@glob($prefix . '*') as $tagFile) {
  244. $tags[] = substr($tagFile, $prefixLen);
  245. }
  246. return $tags;
  247. }
  248. /**
  249. * Return an array of stored cache ids which match given tags
  250. *
  251. * In case of multiple tags, a logical AND is made between tags
  252. *
  253. * @param array $tags array of tags
  254. * @return array array of matching cache ids (string)
  255. */
  256. public function getIdsMatchingTags($tags = array())
  257. {
  258. return $this->_getIdsByTags(Zend_Cache::CLEANING_MODE_MATCHING_TAG, $tags, FALSE);
  259. }
  260. /**
  261. * Return an array of stored cache ids which don't match given tags
  262. *
  263. * In case of multiple tags, a logical OR is made between tags
  264. *
  265. * @param array $tags array of tags
  266. * @return array array of not matching cache ids (string)
  267. */
  268. public function getIdsNotMatchingTags($tags = array())
  269. {
  270. return $this->_getIdsByTags(Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG, $tags, FALSE);
  271. }
  272. /**
  273. * Return an array of stored cache ids which match any given tags
  274. *
  275. * In case of multiple tags, a logical AND is made between tags
  276. *
  277. * @param array $tags array of tags
  278. * @return array array of any matching cache ids (string)
  279. */
  280. public function getIdsMatchingAnyTags($tags = array())
  281. {
  282. return $this->_getIdsByTags(Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG, $tags, FALSE);
  283. }
  284. /**
  285. * Return an array of metadatas for the given cache id
  286. *
  287. * The array must include these keys :
  288. * - expire : the expire timestamp
  289. * - tags : a string array of tags
  290. * - mtime : timestamp of last modification time
  291. *
  292. * @param string $id cache id
  293. * @return array array of metadatas (false if the cache id is not found)
  294. */
  295. public function getMetadatas($id)
  296. {
  297. $metadatas = $this->_getCache($this->_file($id), false);
  298. if ($metadatas) {
  299. $metadatas['tags'] = explode(',' ,$metadatas['tags']);
  300. }
  301. return $metadatas;
  302. }
  303. /**
  304. * Give (if possible) an extra lifetime to the given cache id
  305. *
  306. * @param string $id cache id
  307. * @param int $extraLifetime
  308. * @return boolean true if ok
  309. */
  310. public function touch($id, $extraLifetime)
  311. {
  312. $file = $this->_file($id);
  313. $cache = $this->_getCache($file, true);
  314. if (!$cache) {
  315. return false;
  316. }
  317. list($metadatas, $data) = $cache;
  318. if (time() > $metadatas['expire']) {
  319. return false;
  320. }
  321. $newMetadatas = array(
  322. 'hash' => $metadatas['hash'],
  323. 'mtime' => time(),
  324. 'expire' => $metadatas['expire'] + $extraLifetime,
  325. 'tags' => $metadatas['tags']
  326. );
  327. return !! $this->_filePutContents($file, serialize($newMetadatas)."\n".$data);
  328. }
  329. /**
  330. * Get a metadatas record and optionally the data as well
  331. *
  332. * @param string $file Cache file
  333. * @param bool $withData
  334. * @return array|bool
  335. */
  336. protected function _getCache($file, $withData)
  337. {
  338. if (!is_file($file) || ! ($fd = @fopen($file, 'rb'))) {
  339. return false;
  340. }
  341. if ($this->_options['file_locking']) flock($fd, LOCK_SH);
  342. $metadata = fgets($fd);
  343. if ( ! $metadata) {
  344. if ($this->_options['file_locking']) flock($fd, LOCK_UN);
  345. fclose($fd);
  346. return false;
  347. }
  348. if ($withData) {
  349. $data = stream_get_contents($fd);
  350. }
  351. if ($this->_options['file_locking']) flock($fd, LOCK_UN);
  352. fclose($fd);
  353. $metadata = @unserialize(rtrim($metadata,"\n"));
  354. if ($withData) {
  355. return array($metadata, $data);
  356. }
  357. return $metadata;
  358. }
  359. /**
  360. * Get meta data from a cache record
  361. *
  362. * @param string $id Cache id
  363. * @return array|bool Associative array of meta data
  364. */
  365. protected function _getMetadatas($id)
  366. {
  367. return $this->_getCache($this->_file($id), false);
  368. }
  369. /**
  370. * Set a metadatas record
  371. *
  372. * @param string $id Cache id
  373. * @param array $metadatas Associative array of metadatas
  374. * @param boolean $save optional pass false to disable saving to file
  375. * @return boolean True if no problem
  376. */
  377. protected function _setMetadatas($id, $metadatas, $save = true)
  378. {
  379. // TODO - implement for unit tests ___expire method
  380. return true;
  381. }
  382. /**
  383. * Return the complete directory path of a filename (including hashedDirectoryStructure)
  384. *
  385. * Uses multiple letters for a single-level hash rather than multiple levels
  386. *
  387. * @param string $id Cache id
  388. * @param boolean $parts if true, returns array of directory parts instead of single string
  389. * @return string|array Complete directory path
  390. */
  391. protected function _path($id, $parts = false)
  392. {
  393. $partsArray = array();
  394. $root = $this->_options['cache_dir'];
  395. $prefix = $this->_options['file_name_prefix'];
  396. if ($this->_options['hashed_directory_level']>0) {
  397. $root .= $prefix . '--' . substr(md5($id), -$this->_options['hashed_directory_level']) . DIRECTORY_SEPARATOR;
  398. $partsArray[] = $root;
  399. }
  400. if ($parts){
  401. return $partsArray;
  402. }
  403. return $root;
  404. }
  405. /**
  406. * Clean some cache records (protected method used for recursive stuff)
  407. *
  408. * Available modes are :
  409. * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used)
  410. * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used)
  411. * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags
  412. * ($tags can be an array of strings or a single string)
  413. * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags}
  414. * ($tags can be an array of strings or a single string)
  415. * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags
  416. * ($tags can be an array of strings or a single string)
  417. *
  418. * @param string $dir Directory to clean
  419. * @param string $mode Clean mode
  420. * @param array $tags
  421. * @throws Zend_Cache_Exception
  422. * @return boolean True if no problem
  423. */
  424. protected function _clean($dir, $mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
  425. {
  426. if (!is_dir($dir)) {
  427. return false;
  428. }
  429. $result = true;
  430. $glob = @glob($dir . $this->_options['file_name_prefix'] . '--*');
  431. if ($glob === false) {
  432. return true;
  433. }
  434. foreach ($glob as $file) {
  435. if (is_file($file)) {
  436. if ($mode == Zend_Cache::CLEANING_MODE_ALL) {
  437. $result = @unlink($file) && $result;
  438. continue;
  439. }
  440. $id = $this->_fileNameToId(basename($file));
  441. $_file = $this->_file($id);
  442. if ($file != $_file) {
  443. @unlink($file);
  444. continue;
  445. }
  446. $metadatas = $this->_getCache($file, false);
  447. if ( ! $metadatas) {
  448. @unlink($file);
  449. continue;
  450. }
  451. if ($mode == Zend_Cache::CLEANING_MODE_OLD) {
  452. if (time() > $metadatas['expire']) {
  453. $result = $this->_remove($file) && $result;
  454. $result = $this->_updateIdsTags(array($id), explode(',', $metadatas['tags']), 'diff') && $result;
  455. }
  456. continue;
  457. } else {
  458. Zend_Cache::throwException('Invalid mode for clean() method.');
  459. }
  460. }
  461. if (is_dir($file) && $this->_options['hashed_directory_level'] > 0) {
  462. // Recursive call
  463. $result = $this->_clean($file . DIRECTORY_SEPARATOR, $mode) && $result;
  464. if ($mode == 'all') {
  465. // if mode=='all', we try to drop the structure too
  466. @rmdir($file);
  467. }
  468. }
  469. }
  470. if ($mode == 'all') {
  471. foreach (glob($this->_tagFile('*')) as $tagFile) {
  472. @unlink($tagFile);
  473. }
  474. }
  475. return $result;
  476. }
  477. /**
  478. * Clean some cache records (protected method used for recursive stuff)
  479. *
  480. * Available modes are :
  481. * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used)
  482. * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used)
  483. * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags
  484. * ($tags can be an array of strings or a single string)
  485. * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags}
  486. * ($tags can be an array of strings or a single string)
  487. * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags
  488. * ($tags can be an array of strings or a single string)
  489. *
  490. * @param string $mode Clean mode
  491. * @param array $tags Array of tags
  492. * @throws Zend_Cache_Exception
  493. * @return boolean True if no problem
  494. */
  495. protected function _cleanNew($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
  496. {
  497. $result = true;
  498. $ids = $this->_getIdsByTags($mode, $tags, TRUE);
  499. switch($mode)
  500. {
  501. case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
  502. case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
  503. $this->_updateIdsTags($ids, $tags, 'diff');
  504. break;
  505. }
  506. foreach ($ids as $id) {
  507. $idFile = $this->_file($id);
  508. if (is_file($idFile)) {
  509. $result = $this->_remove($idFile) && $result;
  510. }
  511. }
  512. return $result;
  513. }
  514. /**
  515. * @param string $mode
  516. * @param array $tags
  517. * @param boolean $delete
  518. * @return array
  519. */
  520. protected function _getIdsByTags($mode, $tags, $delete)
  521. {
  522. $ids = array();
  523. switch($mode) {
  524. case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
  525. $ids = $this->getIds();
  526. if ($tags) {
  527. foreach ($tags as $tag) {
  528. if ( ! $ids) {
  529. break; // early termination optimization
  530. }
  531. $ids = array_diff($ids, $this->_getTagIds($tag));
  532. }
  533. }
  534. break;
  535. case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
  536. if ($tags) {
  537. $tag = array_shift($tags);
  538. $ids = $this->_getTagIds($tag);
  539. foreach ($tags as $tag) {
  540. if ( ! $ids) {
  541. break; // early termination optimization
  542. }
  543. $ids = array_intersect($ids, $this->_getTagIds($tag));
  544. }
  545. $ids = array_unique($ids);
  546. }
  547. break;
  548. case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
  549. foreach ($tags as $tag) {
  550. $file = $this->_tagFile($tag);
  551. if ( ! is_file($file) || ! ($fd = @fopen($file, 'rb+'))) {
  552. continue;
  553. }
  554. if ($this->_options['file_locking']) flock($fd, LOCK_EX);
  555. $ids = array_merge($ids,$this->_getTagIds($fd));
  556. if ($delete) {
  557. fseek($fd, 0);
  558. ftruncate($fd, 0);
  559. }
  560. if ($this->_options['file_locking']) flock($fd, LOCK_UN);
  561. fclose($fd);
  562. }
  563. $ids = array_unique($ids);
  564. break;
  565. }
  566. return $ids;
  567. }
  568. /**
  569. * Make and return a file name (with path)
  570. *
  571. * @param string $id Cache id
  572. * @return string File name (with path)
  573. */
  574. protected function _tagFile($id)
  575. {
  576. $path = $this->_tagPath();
  577. $fileName = $this->_idToFileName($id);
  578. return $path . $fileName;
  579. }
  580. /**
  581. * Return the complete directory path where tags are stored
  582. *
  583. * @return string Complete directory path
  584. */
  585. protected function _tagPath()
  586. {
  587. $path = $this->_options['cache_dir'] . DIRECTORY_SEPARATOR . $this->_options['file_name_prefix']. '-tags' . DIRECTORY_SEPARATOR;
  588. if ( ! $this->_isTagDirChecked) {
  589. if ( ! is_dir($path)) {
  590. if (@mkdir($path, $this->_options['use_chmod'] ? $this->_options['directory_mode'] : 0777) && $this->_options['use_chmod']) {
  591. @chmod($path, $this->_options['directory_mode']); // see #ZF-320 (this line is required in some configurations)
  592. }
  593. }
  594. $this->_isTagDirChecked = true;
  595. }
  596. return $path;
  597. }
  598. /**
  599. * @param string|resource $tag
  600. * @return array
  601. */
  602. protected function _getTagIds($tag)
  603. {
  604. if (is_resource($tag)) {
  605. $ids = stream_get_contents($tag);
  606. } elseif(file_exists($this->_tagFile($tag))) {
  607. $ids = @file_get_contents($this->_tagFile($tag));
  608. } else {
  609. $ids = false;
  610. }
  611. if( ! $ids) {
  612. return array();
  613. }
  614. $ids = trim(substr($ids, 0, strrpos($ids, "\n")));
  615. return $ids ? explode("\n", $ids) : array();
  616. }
  617. /**
  618. * @param array $ids
  619. * @param array $tags
  620. * @param string $mode
  621. * @return bool
  622. */
  623. protected function _updateIdsTags($ids, $tags, $mode)
  624. {
  625. $result = true;
  626. if (empty($ids)) {
  627. return $result;
  628. }
  629. foreach($tags as $tag) {
  630. $file = $this->_tagFile($tag);
  631. if (file_exists($file)) {
  632. if ($mode == 'diff' || (mt_rand(1,100) == 1 && filesize($file) > 4096)) {
  633. $file = $this->_tagFile($tag);
  634. if ( ! ($fd = @fopen($file, 'rb+'))) {
  635. $result = false;
  636. continue;
  637. }
  638. if ($this->_options['file_locking']) flock($fd, LOCK_EX);
  639. if ($mode == 'diff') {
  640. $_ids = array_diff($this->_getTagIds($fd), $ids);
  641. } else {
  642. $_ids = array_merge($this->_getTagIds($fd), $ids);
  643. }
  644. fseek($fd, 0);
  645. ftruncate($fd, 0);
  646. $result = fwrite($fd, implode("\n", array_unique($_ids))."\n") && $result;
  647. if ($this->_options['file_locking']) flock($fd, LOCK_UN);
  648. fclose($fd);
  649. }
  650. else {
  651. $result = file_put_contents($file, implode("\n", $ids)."\n", FILE_APPEND | ($this->_options['file_locking'] ? LOCK_EX : 0)) && $result;
  652. }
  653. } else if ($mode == 'merge') {
  654. $result = $this->_filePutContents($file, implode("\n", $ids)."\n") && $result;
  655. }
  656. }
  657. return $result;
  658. }
  659. /**
  660. * Put the given string into the given file
  661. *
  662. * @param string $file File complete path
  663. * @param string $string String to put in file
  664. * @return boolean true if no problem
  665. */
  666. protected function _filePutContents($file, $string)
  667. {
  668. $result = @file_put_contents($file, $string, $this->_options['file_locking'] ? LOCK_EX : 0);
  669. if ($result && $this->_options['use_chmod']) {
  670. @chmod($file, $this->_options['file_mode']);
  671. }
  672. return $result;
  673. }
  674. /**
  675. * Make the directory structure for the given id
  676. *
  677. * @param string $id cache id
  678. * @return boolean true
  679. */
  680. protected function _recursiveMkdirAndChmod($id)
  681. {
  682. if ($this->_options['hashed_directory_level'] <=0) {
  683. return true;
  684. }
  685. $partsArray = $this->_path($id, true);
  686. foreach ($partsArray as $part) {
  687. if (!is_dir($part)) {
  688. @mkdir($part, $this->_options['use_chmod'] ? $this->_options['directory_mode'] : 0777);
  689. if ($this->_options['use_chmod']) {
  690. @chmod($part, $this->_options['directory_mode']); // see #ZF-320 (this line is required in some configurations)
  691. }
  692. }
  693. }
  694. return true;
  695. }
  696. /**
  697. * For unit testing only
  698. * @param $id
  699. */
  700. public function ___expire($id)
  701. {
  702. $metadata = $this->_getMetadatas($id);
  703. $this->touch($id, 1 - $metadata['expire']);
  704. }
  705. }