Teknik is a suite of services with attractive and functional interfaces. https://www.teknik.io/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

CacheFile.php 5.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. <?php
  2. /**
  3. * Piwik - free/libre analytics platform
  4. *
  5. * @link http://piwik.org
  6. * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  7. *
  8. */
  9. namespace Piwik;
  10. use Exception;
  11. /**
  12. * This class is used to cache data on the filesystem.
  13. *
  14. * It is for example used by the Tracker process to cache various settings and websites attributes in tmp/cache/tracker/*
  15. *
  16. */
  17. class CacheFile
  18. {
  19. // for testing purposes since tests run on both CLI/FPM (changes in CLI can't invalidate
  20. // opcache in FPM, so we have to invalidate before reading)
  21. public static $invalidateOpCacheBeforeRead = false;
  22. /**
  23. * @var string
  24. */
  25. protected $cachePath;
  26. /**
  27. * @var
  28. */
  29. protected $cachePrefix;
  30. /**
  31. * Minimum enforced TTL in seconds
  32. */
  33. const MINIMUM_TTL = 60;
  34. /**
  35. * @var \Callable[]
  36. */
  37. private static $onDeleteCallback = array();
  38. /**
  39. * @param string $directory directory to use
  40. * @param int $timeToLiveInSeconds TTL
  41. */
  42. public function __construct($directory, $timeToLiveInSeconds = 300)
  43. {
  44. $cachePath = PIWIK_USER_PATH . '/tmp/cache/' . $directory . '/';
  45. $this->cachePath = SettingsPiwik::rewriteTmpPathWithInstanceId($cachePath);
  46. if ($timeToLiveInSeconds < self::MINIMUM_TTL) {
  47. $timeToLiveInSeconds = self::MINIMUM_TTL;
  48. }
  49. $this->ttl = $timeToLiveInSeconds;
  50. }
  51. /**
  52. * Function to fetch a cache entry
  53. *
  54. * @param string $id The cache entry ID
  55. * @return array|bool False on error, or array the cache content
  56. */
  57. public function get($id)
  58. {
  59. if (empty($id)) {
  60. return false;
  61. }
  62. $id = $this->cleanupId($id);
  63. $cache_complete = false;
  64. $content = '';
  65. $expires_on = false;
  66. // We are assuming that most of the time cache will exists
  67. $cacheFilePath = $this->cachePath . $id . '.php';
  68. if (self::$invalidateOpCacheBeforeRead) {
  69. $this->opCacheInvalidate($cacheFilePath);
  70. }
  71. $ok = @include($cacheFilePath);
  72. if ($ok && $cache_complete == true) {
  73. if (empty($expires_on)
  74. || $expires_on < time()
  75. ) {
  76. return false;
  77. }
  78. return $content;
  79. }
  80. return false;
  81. }
  82. private function getExpiresTime()
  83. {
  84. return time() + $this->ttl;
  85. }
  86. protected function cleanupId($id)
  87. {
  88. if (!Filesystem::isValidFilename($id)) {
  89. throw new Exception("Invalid cache ID request $id");
  90. }
  91. return $id;
  92. }
  93. /**
  94. * A function to store content a cache entry.
  95. *
  96. * @param string $id The cache entry ID
  97. * @param array $content The cache content
  98. * @throws \Exception
  99. * @return bool True if the entry was succesfully stored
  100. */
  101. public function set($id, $content)
  102. {
  103. if (empty($id)) {
  104. return false;
  105. }
  106. if (!is_dir($this->cachePath)) {
  107. Filesystem::mkdir($this->cachePath);
  108. }
  109. if (!is_writable($this->cachePath)) {
  110. return false;
  111. }
  112. $id = $this->cleanupId($id);
  113. $id = $this->cachePath . $id . '.php';
  114. if (is_object($content)) {
  115. throw new \Exception('You cannot use the CacheFile to cache an object, only arrays, strings and numbers.');
  116. }
  117. $cache_literal = "<" . "?php\n";
  118. $cache_literal .= "$" . "content = " . var_export($content, true) . ";\n";
  119. $cache_literal .= "$" . "expires_on = " . $this->getExpiresTime() . ";\n";
  120. $cache_literal .= "$" . "cache_complete = true;\n";
  121. $cache_literal .= "?" . ">";
  122. // Write cache to a temp file, then rename it, overwriting the old cache
  123. // On *nix systems this should guarantee atomicity
  124. $tmp_filename = tempnam($this->cachePath, 'tmp_');
  125. @chmod($tmp_filename, 0640);
  126. if ($fp = @fopen($tmp_filename, 'wb')) {
  127. @fwrite($fp, $cache_literal, strlen($cache_literal));
  128. @fclose($fp);
  129. if (!@rename($tmp_filename, $id)) {
  130. // On some systems rename() doesn't overwrite destination
  131. @unlink($id);
  132. if (!@rename($tmp_filename, $id)) {
  133. // Make sure that no temporary file is left over
  134. // if the destination is not writable
  135. @unlink($tmp_filename);
  136. }
  137. }
  138. $this->opCacheInvalidate($id);
  139. return true;
  140. }
  141. return false;
  142. }
  143. /**
  144. * A function to delete a single cache entry
  145. *
  146. * @param string $id The cache entry ID
  147. * @return bool True if the entry was succesfully deleted
  148. */
  149. public function delete($id)
  150. {
  151. if (empty($id)) {
  152. return false;
  153. }
  154. $id = $this->cleanupId($id);
  155. $filename = $this->cachePath . $id . '.php';
  156. if (file_exists($filename)) {
  157. $this->opCacheInvalidate($filename);
  158. @unlink($filename);
  159. return true;
  160. }
  161. return false;
  162. }
  163. public function addOnDeleteCallback($onDeleteCallback)
  164. {
  165. self::$onDeleteCallback[] = $onDeleteCallback;
  166. }
  167. /**
  168. * A function to delete all cache entries in the directory
  169. */
  170. public function deleteAll()
  171. {
  172. $self = $this;
  173. $beforeUnlink = function ($path) use ($self) {
  174. $self->opCacheInvalidate($path);
  175. };
  176. Filesystem::unlinkRecursive($this->cachePath, $deleteRootToo = false, $beforeUnlink);
  177. if (!empty(self::$onDeleteCallback)) {
  178. foreach (self::$onDeleteCallback as $callback) {
  179. $callback();
  180. }
  181. }
  182. }
  183. public function opCacheInvalidate($filepath)
  184. {
  185. if (function_exists('opcache_invalidate')
  186. && is_file($filepath)
  187. ) {
  188. opcache_invalidate($filepath, $force = true);
  189. }
  190. }
  191. }