PageRenderTime 57ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/content/plugins/w3-total-cache/Cache_File.php

https://gitlab.com/karlen/ayo_wp
PHP | 455 lines | 246 code | 84 blank | 125 comment | 55 complexity | aafa778f56ec73edd4a84cdca51a1583 MD5 | raw file
  1. <?php
  2. namespace W3TC;
  3. /**
  4. * class Cache_File
  5. */
  6. class Cache_File extends Cache_Base {
  7. /**
  8. * Path to cache dir
  9. *
  10. * @var string
  11. */
  12. protected $_cache_dir = '';
  13. /**
  14. * Directory to flush
  15. *
  16. * @var string
  17. */
  18. protected $_flush_dir = '';
  19. /**
  20. * Exclude files
  21. *
  22. * @var array
  23. */
  24. protected $_exclude = array();
  25. /**
  26. * Flush time limit
  27. *
  28. * @var int
  29. */
  30. protected $_flush_timelimit = 0;
  31. /**
  32. * File locking
  33. *
  34. * @var boolean
  35. */
  36. protected $_locking = false;
  37. /**
  38. * If path should be generated based on wp_hash
  39. *
  40. * @var bool
  41. */
  42. protected $_use_wp_hash = false;
  43. /**
  44. * Constructor
  45. *
  46. * @param array $config
  47. */
  48. function __construct( $config = array() ) {
  49. parent::__construct( $config );
  50. if ( isset( $config['cache_dir'] ) )
  51. $this->_cache_dir = trim( $config['cache_dir'] );
  52. else
  53. $this->_cache_dir = Util_Environment::cache_blog_dir( $config['section'], $config['blog_id'] );
  54. $this->_exclude = isset( $config['exclude'] ) ? (array) $config['exclude'] : array();
  55. $this->_flush_timelimit = isset( $config['flush_timelimit'] ) ? (int) $config['flush_timelimit'] : 180;
  56. $this->_locking = isset( $config['locking'] ) ? (boolean) $config['locking'] : false;
  57. if ( isset( $config['flush_dir'] ) )
  58. $this->_flush_dir = $config['flush_dir'];
  59. else {
  60. if ( $config['blog_id'] <= 0 && !isset( $config['cache_dir'] ) ) {
  61. // clear whole section if we operate on master cache
  62. // and in a mode when cache_dir not strictly specified
  63. $this->_flush_dir = Util_Environment::cache_dir( $config['section'] );
  64. } else
  65. $this->_flush_dir = $this->_cache_dir;
  66. }
  67. if ( isset( $config['use_wp_hash'] ) && $config['use_wp_hash'] )
  68. $this->_use_wp_hash = true;
  69. }
  70. /**
  71. * Adds data
  72. *
  73. * @param string $key
  74. * @param mixed $var
  75. * @param integer $expire
  76. * @param string $group Used to differentiate between groups of cache values
  77. * @return boolean
  78. */
  79. function add( $key, &$var, $expire = 0, $group = '' ) {
  80. if ( $this->get( $key, $group ) === false ) {
  81. return $this->set( $key, $var, $expire, $group );
  82. }
  83. return false;
  84. }
  85. /**
  86. * Sets data
  87. *
  88. * @param string $key
  89. * @param mixed $var
  90. * @param integer $expire
  91. * @param string $group Used to differentiate between groups of cache values
  92. * @return boolean
  93. */
  94. function set( $key, $var, $expire = 0, $group = '' ) {
  95. $fp = $this->fopen_write( $key, $group, 'wb' );
  96. if ( !$fp )
  97. return false;
  98. if ( $this->_locking )
  99. @flock( $fp, LOCK_EX );
  100. if ( $expire <= 0 || $expire > W3TC_CACHE_FILE_EXPIRE_MAX )
  101. $expire = W3TC_CACHE_FILE_EXPIRE_MAX;
  102. $expires_at = time() + $expire;
  103. @fputs( $fp, pack( 'L', $expires_at ) );
  104. @fputs( $fp, '<?php exit; ?>' );
  105. @fputs( $fp, @serialize( $var ) );
  106. @fclose( $fp );
  107. if ( $this->_locking )
  108. @flock( $fp, LOCK_UN );
  109. return true;
  110. }
  111. /**
  112. * Returns data
  113. *
  114. * @param string $key
  115. * @param string $group Used to differentiate between groups of cache values
  116. * @return mixed
  117. */
  118. function get_with_old( $key, $group = '' ) {
  119. list( $data, $has_old_data ) = $this->_get_with_old_raw( $key, $group );
  120. if ( !empty( $data ) )
  121. $data_unserialized = @unserialize( $data );
  122. else
  123. $data_unserialized = $data;
  124. return array( $data_unserialized, $has_old_data );
  125. }
  126. private function _get_with_old_raw( $key, $group = '' ) {
  127. $has_old_data = false;
  128. $storage_key = $this->get_item_key( $key );
  129. $path = $this->_cache_dir . DIRECTORY_SEPARATOR .
  130. ( $group ? $group . DIRECTORY_SEPARATOR : '' ) .
  131. $this->_get_path( $storage_key );
  132. if ( !is_readable( $path ) )
  133. return array( null, $has_old_data );
  134. $fp = @fopen( $path, 'rb' );
  135. if ( !$fp )
  136. return array( null, $has_old_data );
  137. if ( $this->_locking )
  138. @flock( $fp, LOCK_SH );
  139. $expires_at = @fread( $fp, 4 );
  140. $data = null;
  141. if ( $expires_at !== false ) {
  142. list( , $expires_at ) = @unpack( 'L', $expires_at );
  143. if ( time() > $expires_at ) {
  144. if ( $this->_use_expired_data ) {
  145. // update expiration so other threads will use old data
  146. $fp2 = @fopen( $path, 'cb' );
  147. if ( $fp2 ) {
  148. @fputs( $fp2, pack( 'L', time() + 30 ) );
  149. @fclose( $fp2 );
  150. }
  151. $has_old_data = true;
  152. }
  153. } else {
  154. $data = '';
  155. while ( !@feof( $fp ) ) {
  156. $data .= @fread( $fp, 4096 );
  157. }
  158. $data = substr( $data, 14 );
  159. }
  160. }
  161. if ( $this->_locking )
  162. @flock( $fp, LOCK_UN );
  163. @fclose( $fp );
  164. return array( $data, $has_old_data );
  165. }
  166. /**
  167. * Replaces data
  168. *
  169. * @param string $key
  170. * @param mixed $var
  171. * @param integer $expire
  172. * @param string $group Used to differentiate between groups of cache values
  173. * @return boolean
  174. */
  175. function replace( $key, &$var, $expire = 0, $group = '' ) {
  176. if ( $this->get( $key, $group ) !== false ) {
  177. return $this->set( $key, $var, $expire, $group );
  178. }
  179. return false;
  180. }
  181. /**
  182. * Deletes data
  183. *
  184. * @param string $key
  185. * @param string $group Used to differentiate between groups of cache values
  186. * @return boolean
  187. */
  188. function delete( $key, $group = '' ) {
  189. $storage_key = $this->get_item_key( $key );
  190. $path = $this->_cache_dir . DIRECTORY_SEPARATOR .
  191. ( $group ? $group . DIRECTORY_SEPARATOR : '' ) .
  192. $this->_get_path( $storage_key );
  193. if ( !file_exists( $path ) )
  194. return true;
  195. if ( $this->_use_expired_data ) {
  196. $fp = @fopen( $path, 'cb' );
  197. if ( $fp ) {
  198. if ( $this->_locking )
  199. @flock( $fp, LOCK_EX );
  200. @fputs( $fp, pack( 'L', 0 ) ); // make it expired
  201. @fclose( $fp );
  202. if ( $this->_locking )
  203. @flock( $fp, LOCK_UN );
  204. return true;
  205. }
  206. }
  207. return @unlink( $path );
  208. }
  209. /**
  210. * Key to delete, deletes .old and primary if exists.
  211. *
  212. * @param string $key
  213. *
  214. * @return bool
  215. */
  216. function hard_delete( $key ) {
  217. $key = $this->get_item_key( $key );
  218. $path = $this->_cache_dir . DIRECTORY_SEPARATOR . $this->_get_path( $key );
  219. return @unlink( $path );
  220. }
  221. /**
  222. * Flushes all data
  223. *
  224. * @param string $group Used to differentiate between groups of cache values
  225. * @return boolean
  226. */
  227. function flush( $group = '' ) {
  228. @set_time_limit( $this->_flush_timelimit );
  229. $flush_dir = $group ?
  230. $this->_cache_dir . DIRECTORY_SEPARATOR . $group .
  231. DIRECTORY_SEPARATOR :
  232. $this->_flush_dir;
  233. Util_File::emptydir( $flush_dir, $this->_exclude );
  234. return true;
  235. }
  236. /**
  237. * Returns modification time of cache file
  238. *
  239. * @param integer $key
  240. * @param string $group Used to differentiate between groups of cache values
  241. * @return boolean|string
  242. */
  243. function mtime( $key, $group = '' ) {
  244. $path =
  245. $this->_cache_dir . DIRECTORY_SEPARATOR .
  246. ( $group ? $group . DIRECTORY_SEPARATOR : '' ) .
  247. $this->_get_path( $key );
  248. if ( file_exists( $path ) ) {
  249. return @filemtime( $path );
  250. }
  251. return false;
  252. }
  253. /**
  254. * Returns file path for key
  255. *
  256. * @param string $key
  257. * @return string
  258. */
  259. function _get_path( $key ) {
  260. if ( $this->_use_wp_hash && function_exists( 'wp_hash' ) )
  261. $hash = wp_hash( $key );
  262. else
  263. $hash = md5( $key );
  264. $path = sprintf( '%s/%s/%s.php', substr( $hash, 0, 3 ), substr( $hash, 3, 3 ), $hash );
  265. return $path;
  266. }
  267. public function get_stats_size( $timeout_time ) {
  268. $size = array(
  269. 'bytes' => 0,
  270. 'items' => 0,
  271. 'timeout_occurred' => false
  272. );
  273. $size = $this->dirsize( $this->_cache_dir, $size, $timeout_time );
  274. return $size;
  275. }
  276. private function dirsize( $path, $size, $timeout_time ) {
  277. $dir = @opendir( $path );
  278. if ( $dir ) {
  279. while ( !$size['timeout_occurred'] && ( $entry = @readdir( $dir ) ) !== false ) {
  280. if ( $entry == '.' || $entry == '..' ) {
  281. continue;
  282. }
  283. $full_path = $path . DIRECTORY_SEPARATOR . $entry;
  284. if ( @is_dir( $full_path ) ) {
  285. $size = $this->dirsize( $full_path, $size, $timeout_time );
  286. } else {
  287. $size['bytes'] += @filesize( $full_path );
  288. // dont check time() for each file, quite expensive
  289. $size['items']++;
  290. if ( $size['items'] % 1000 == 0 )
  291. $size['timeout_occurred'] |= ( time() > $timeout_time );
  292. }
  293. }
  294. @closedir( $dir );
  295. }
  296. return $size;
  297. }
  298. /**
  299. * Used to replace as atomically as possible known value to new one
  300. */
  301. public function set_if_maybe_equals( $key, $old_value, $new_value ) {
  302. // cant guarantee atomic action here, filelocks fail often
  303. $value = $this->get( $key );
  304. if ( isset( $old_value['content'] ) &&
  305. $value['content'] != $old_value['content'] )
  306. return false;
  307. return $this->set( $key, $new_value );
  308. }
  309. /**
  310. * Use key as a counter and add integet value to it
  311. */
  312. public function counter_add( $key, $value ) {
  313. if ( $value == 0 )
  314. return true;
  315. $fp = $this->fopen_write( $key, '', 'a' );
  316. if ( !$fp )
  317. return false;
  318. // use "x" to store increment, since it's most often case
  319. // and it will save 50% of size if only increments are used
  320. if ( $value == 1 )
  321. @fputs( $fp, 'x' );
  322. else
  323. @fputs( $fp, ' ' . (int)$value );
  324. @fclose( $fp );
  325. return true;
  326. }
  327. /**
  328. * Use key as a counter and add integet value to it
  329. */
  330. public function counter_set( $key, $value ) {
  331. $fp = $this->fopen_write( $key, '', 'wb' );
  332. if ( !$fp )
  333. return false;
  334. $expire = W3TC_CACHE_FILE_EXPIRE_MAX;
  335. $expires_at = time() + $expire;
  336. @fputs( $fp, pack( 'L', $expires_at ) );
  337. @fputs( $fp, '<?php exit; ?>' );
  338. @fputs( $fp, (int)$value );
  339. @fclose( $fp );
  340. return true;
  341. }
  342. /**
  343. * Get counter's value
  344. */
  345. public function counter_get( $key ) {
  346. list( $value, $old_data ) = $this->_get_with_old_raw( $key );
  347. if ( empty( $value ) )
  348. return 0;
  349. $original_length = strlen( $value );
  350. $cut_value = str_replace( 'x', '', $value );
  351. $count = $original_length - strlen( $cut_value );
  352. // values more than 1 are stored as <space>value
  353. $a = explode( ' ', $cut_value );
  354. foreach ( $a as $counter_value )
  355. $count += (int)$counter_value;
  356. return $count;
  357. }
  358. private function fopen_write( $key, $group, $mode ) {
  359. $storage_key = $this->get_item_key( $key );
  360. $sub_path = $this->_get_path( $storage_key );
  361. $path = $this->_cache_dir . DIRECTORY_SEPARATOR .
  362. ( $group ? $group . DIRECTORY_SEPARATOR : '' ) . $sub_path;
  363. $dir = dirname( $path );
  364. if ( !@is_dir( $dir ) ) {
  365. if ( !Util_File::mkdir_from( $dir, W3TC_CACHE_DIR ) )
  366. return false;
  367. }
  368. $fp = @fopen( $path, $mode );
  369. return $fp;
  370. }
  371. }