PluginProbe
SQLite Object Cache / 1.5.5
SQLite Object Cache v1.5.5
1.6.5 trunk 0.1.7 1.0.0 1.1.0 1.1.1 1.2.0 1.2.1 1.2.2 1.2.3 1.3.0 1.3.1 1.3.2 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.4.0 1.4.1 1.5.1 1.5.4 1.5.5 1.5.6 1.5.7 All 30 releases
sqlite-object-cache / includes / class-sqlite-object-cache.php

class-sqlite-object-cache.php in SQLite Object Cache 1.5.5, at includes/class-sqlite-object-cache.php

526 lines 14.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Main plugin class file.
4 *
5 * @package WordPress Plugin Template/Includes
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit;
10 }
11
12 /**
13 * Main plugin class.
14 *
15 * Only make one instance of this, please.
16 */
17 class SQLite_Object_Cache {
18 const CLEAN_EVENT_HOOK = 'sqlite_object_cache_clean';
19
20 /**
21 * Local instance of SQLite_Object_Cache_Admin_API
22 *
23 * @var SQLite_Object_Cache_Admin_API|null
24 */
25 public $admin;
26
27 /**
28 * Settings class object
29 *
30 * @var object
31 * @access public
32 * @since 1.0.0
33 */
34 public $settings;
35
36 /**
37 * The version number.
38 *
39 * @var string
40 * @access public
41 * @since 1.0.0
42 */
43 public $_version;
44
45 /**
46 * The token.
47 *
48 * @var string
49 * @access public
50 * @since 1.0.0
51 */
52 public $_token;
53
54 /**
55 * The main plugin file.
56 *
57 * @var string
58 * @access public
59 * @since 1.0.0
60 */
61 public $file;
62
63 /**
64 * The main plugin directory.
65 *
66 * @var string
67 * @access public
68 * @since 1.0.0
69 */
70 public $dir;
71
72 /**
73 * The plugin assets directory.
74 *
75 * @var string
76 * @access public
77 * @since 1.0.0
78 */
79 public $assets_dir;
80
81 /**
82 * The plugin assets URL.
83 *
84 * @var string
85 * @access public
86 * @since 1.0.0
87 */
88 public $assets_url;
89
90 /**
91 * Suffix for JavaScripts.
92 *
93 * @var string
94 * @access public
95 * @since 1.0.0
96 */
97 public $script_suffix;
98
99 /**
100 * Path for the drop-in when it is working.
101 * @var string
102 */
103 public $dropinfiledest;
104
105 /**
106 * Path for the drop-in in the plugin tree.
107 * @var string
108 */
109 public $dropinfilesource;
110
111 /**
112 * Minimum required sqlite version.
113 *
114 * @var string
115 */
116 public $minimum_sqlite_version = '3.7.0';
117
118 /**
119 * Constructor function.
120 *
121 * @param string $file File constructor.
122 * @param string $version Plugin version.
123 */
124 public function __construct( $file = '', $version = '1.5.5' ) {
125 $this->_version = $version;
126 $this->_token = 'sqlite_object_cache';
127
128 // Load plugin environment variables.
129 $this->file = $file;
130 $this->dir =
131 trailingslashit( dirname( WP_CONTENT_DIR . '/plugins/' . plugin_basename( $this->file ) ) );
132 $this->assets_dir = trailingslashit( $this->dir . 'assets' );
133 $this->dropinfilesource = $this->assets_dir . 'drop-in/object-cache.php';
134 $this->dropinfiledest = trailingslashit( WP_CONTENT_DIR ) . 'object-cache.php';
135
136 $this->assets_url = esc_url( trailingslashit( plugins_url( '/assets/', $this->file ) ) );
137
138 $this->script_suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
139
140 register_activation_hook( $this->file, array( $this, 'on_activation' ) );
141 register_deactivation_hook( $this->file, array( $this, 'on_deactivation' ) );
142
143 if ( is_admin() ) {
144 // Load API for generic admin functions.
145 $this->admin = new SQLite_Object_Cache_Admin_API();
146 // Suppress backups
147 new SQLite_Backup_Exclusion();
148 }
149
150 add_action( 'admin_init', array( $this, 'maybe_update_dropin' ) );
151
152 /* handle cron cache cleanup */
153 add_action( self::CLEAN_EVENT_HOOK, array( $this, 'clean_job' ), 10, 0 );
154 if ( ! wp_next_scheduled( self::CLEAN_EVENT_HOOK ) ) {
155 wp_schedule_event( time() + HOUR_IN_SECONDS, 'hourly', self::CLEAN_EVENT_HOOK );
156 }
157 /* Handle probabilistic non-cron cleanup, one request in 2000. */
158 if ( 1 === rand( 1, 2000 ) ) {
159 add_action( 'shutdown', function () {
160 $this->clean_job( 1.25 );
161 }, 999, 0 );
162 }
163 }
164
165 /**
166 * WP_Cron task to clean cache entries.
167 *
168 * @param float $grace_factor Default 1.0
169 *
170 * @return void
171 */
172 public function clean_job( $grace_factor = 1.0 ) {
173 $option = get_option( $this->_token . '_settings', array() );
174 $target_size = empty ( $option['target_size'] ) ? 16 : $option['target_size'];
175 $target_size *= ( 1024 * 1024 );
176 $threshold_size = (int) ( $target_size * $grace_factor );
177
178 global $wp_object_cache;
179 if ( ! method_exists( $wp_object_cache, 'sqlite_get_size' ) ) {
180 return;
181 }
182 /* Clean up old statistics. Do this even when the cache is not over size. */
183 $retention = empty ( $option['retainmeasurements'] ) ? 24 : $option['retainmeasurements'];
184 $wp_object_cache->sqlite_reset_statistics( $retention * HOUR_IN_SECONDS );
185
186 $current_size = $wp_object_cache->sqlite_get_size();
187 /* Skip this if the current size is small enough. */
188 if ( $current_size <= $threshold_size ) {
189 return;
190 }
191
192 /* Remove expired items (transients mostly). */
193 if ( $wp_object_cache->sqlite_remove_expired() ) {
194 /* If anything was removed, get the size again. */
195 $current_size = $wp_object_cache->sqlite_get_size();
196 }
197
198 /* Skip this if the size is small enough, after removing expired items. */
199 if ( $current_size <= $threshold_size ) {
200 return;
201 }
202
203 /* Delete the least-recently-updated items to get to the target size. */
204 $wp_object_cache->sqlite_delete_old( $target_size, $current_size );
205 }
206
207 /**
208 * Plugin activation hook
209 *
210 * @return void
211 */
212 public function on_activation() {
213
214 $this->sync_apcu_global_to_option( true );
215 if ( true === $this->has_sqlite() ) {
216 add_action( 'shutdown', array( $this, 'update_dropin' ) );
217 }
218 }
219
220 /**
221 * Determine whether we can use SQLite3.
222 *
223 * @return bool|string true, or an error message.
224 */
225 public function has_sqlite() {
226 if ( ! class_exists( 'SQLite3' ) || ! extension_loaded( 'sqlite3' ) ) {
227 return __( 'You cannot use the SQLite Object Cache plugin. Your server does not have php\'s SQLite3 extension installed.', 'sqlite-object-cache' );
228 }
229
230 $sqlite_version = $this->sqlite_get_version();
231 if ( version_compare( $sqlite_version, $this->minimum_sqlite_version ) < 0 ) {
232 return sprintf(
233 /* translators: 1 actual SQLite version. 2 required SQLite version) */
234 __( 'You cannot use the SQLite Object Cache plugin. Your server only offers SQLite3 version %1$s, but at least %2$s is required.', 'sqlite-object-cache' ),
235 $sqlite_version, $this->minimum_sqlite_version );
236 }
237
238 return true;
239 }
240
241 /**
242 * Get the version number for the SQLite extension.
243 *
244 * @return string|false SQLite's version number.
245 */
246 public function sqlite_get_version() {
247
248 if ( class_exists( 'SQLite3' ) ) {
249 $version = SQLite3::version();
250
251 return $version['versionString'];
252 }
253
254 return false;
255 }
256
257 /**
258 * Test if we can write in the WP_CONTENT_DIR and modify the `object-cache.php` drop-in
259 *
260 * @return true|WP_Error
261 * @author Till Krüss
262 *
263 */
264 public function test_filesystem_writing() {
265 global $wp_filesystem;
266
267 if ( ! $this->initialize_filesystem( '', true ) ) {
268 return new WP_Error( 'fs', __( 'Could not initialize filesystem.', 'sqlite-object-cache' ) );
269 }
270
271 $testfiledest = WP_CONTENT_DIR . '/sqlite-write-test.tmp';
272
273 if ( ! $wp_filesystem->exists( $this->dropinfilesource ) ) {
274 return new WP_Error( 'exists', __( 'Object cache drop-in file doesn’t exist.', 'sqlite-object-cache' ) );
275 }
276
277 if ( $wp_filesystem->exists( $testfiledest ) && ! $wp_filesystem->delete( $testfiledest ) ) {
278 return new WP_Error( 'delete', __( 'Test file exists, but couldn’t be deleted.', 'sqlite-object-cache' ) );
279 }
280
281 if ( ! $wp_filesystem->is_writable( WP_CONTENT_DIR ) ) {
282 return new WP_Error( 'copy', __( 'Content directory is not writable.', 'sqlite-object-cache' ) );
283 }
284
285 if ( ! $wp_filesystem->copy( $this->dropinfilesource, $testfiledest, true, FS_CHMOD_FILE ) ) {
286 return new WP_Error( 'copy', __( 'Failed to copy test file.', 'sqlite-object-cache' ) );
287 }
288
289 if ( ! $wp_filesystem->exists( $testfiledest ) ) {
290 return new WP_Error( 'exists', __( 'Copied test file doesn’t exist.', 'sqlite-object-cache' ) );
291 }
292
293 $meta = get_file_data( $testfiledest, array( 'Version' => 'Version' ) );
294
295 if ( $meta['Version'] !== $this->_version ) {
296 return new WP_Error( 'version', __( 'Couldn’t verify test file contents.', 'sqlite-object-cache' ) );
297 }
298
299 if ( ! $wp_filesystem->delete( $testfiledest ) ) {
300 return new WP_Error( 'delete', __( 'Copied test file couldn’t be deleted.', 'sqlite-object-cache' ) );
301 }
302
303 return true;
304 }
305
306 /**
307 * Initializes the WP filesystem API to be ready for use
308 *
309 * @param string $url The URL to post the form to.
310 * @param bool $silent Whether to ask the user for credentials if necessary or not.
311 *
312 * @return bool
313 * @author Till Krüss
314 *
315 */
316 public function initialize_filesystem( $url, $silent = false ) {
317 require_once ABSPATH . 'wp-admin/includes/file.php';
318 if ( $silent ) {
319 ob_start();
320 }
321
322 $credentials = request_filesystem_credentials( $url );
323
324 if ( false === $credentials ) {
325 if ( $silent ) {
326 ob_end_clean();
327 }
328
329 return false;
330 }
331
332 if ( ! WP_Filesystem( $credentials ) ) {
333 request_filesystem_credentials( $url );
334
335 if ( $silent ) {
336 ob_end_clean();
337 }
338
339 return false;
340 }
341
342 return true;
343 }
344
345 /**
346 * Calls the drop-in update method if necessary
347 *
348 * @return void
349 * @author Till Krüss
350 */
351 public function maybe_update_dropin() {
352 if ( defined( 'WP_SQLITE_OBJECT_CACHE_DISABLE_DROPIN_AUTOUPDATE' ) && WP_SQLITE_OBJECT_CACHE_DISABLE_DROPIN_AUTOUPDATE ) {
353 return;
354 }
355
356 $has = $this->has_sqlite();
357 if ( ( true === $has ) && $this->object_cache_dropin_needs_updating() ) {
358 add_action( 'shutdown', array( $this, 'update_dropin' ) );
359 }
360 }
361
362 /**
363 * Checks if the `object-cache.php` drop-in is outdated
364 * @return bool
365 * @author Till Krüss
366 *
367 */
368 public function object_cache_dropin_needs_updating() {
369 global $wp_object_cache;
370 if ( ! method_exists( $wp_object_cache, 'dropin_get_version' ) ) {
371 return true;
372 }
373 return version_compare( $wp_object_cache->dropin_get_version(), $this->_version, '<' );
374 }
375
376 /**
377 * Updates the `object-cache.php` drop-in
378 *
379 * @return void
380 * @author Till Krüss
381 */
382 public function update_dropin() {
383 global $wp_filesystem;
384 $has = $this->has_sqlite();
385 if ( true === $has && $this->initialize_filesystem( '', true ) ) {
386
387 $this->delete_sqlite_files();
388 $result = $wp_filesystem->copy( $this->dropinfilesource, $this->dropinfiledest, true, FS_CHMOD_FILE );
389 /**
390 * Fires on cache enable event
391 *
392 * @param bool $result Whether the filesystem event (copy of the `object-cache.php` file) was successful.
393 *
394 * @since 0.1.0
395 */
396 do_action( 'sqlite_object_cache_update_dropin', $result );
397 }
398 }
399
400 /**
401 * Validates the `object-cache.php` drop-in
402 *
403 * @return bool
404 * @author Till Krüss
405 */
406 public function validate_object_cache_dropin() {
407 global $wp_object_cache;
408
409 if ( ! method_exists( $wp_object_cache, 'dropin_get_version' ) ) {
410 return false;
411 }
412
413 $dropin = get_plugin_data( $this->dropinfiledest );
414 $plugin = get_plugin_data( $this->dropinfilesource );
415
416 /**
417 * Filters the drop-in validation state
418 *
419 * @param bool $state The validation state of the drop-in.
420 * @param string $dropin The `PluginURI` of the drop-in.
421 * @param string $plugin The `PluginURI` of the plugin.
422 *
423 * @since 2.0.16
424 */
425 return apply_filters(
426 'sqlite_object_cache_validate_dropin',
427 $dropin['PluginURI'] === $plugin['PluginURI'],
428 $dropin['PluginURI'],
429 $plugin['PluginURI']
430 );
431 }
432
433 /**
434 * Plugin deactivation hook
435 *
436 * @param string $plugin Plugin basename.
437 *
438 * @return void
439 * @author Till Krüss
440 */
441 public function on_deactivation( $plugin ) {
442
443 wp_cache_flush();
444
445 wp_unschedule_hook( self::CLEAN_EVENT_HOOK );
446 $this->delete_sqlite_files();
447 $this->delete_dropin();
448 }
449
450 private function delete_sqlite_files() {
451 global $wp_filesystem;
452 global $wp_object_cache;
453
454 if ( method_exists( $wp_object_cache, 'sqlite_files' ) ) {
455 if ( $this->initialize_filesystem( '', true ) ) {
456 foreach ( $wp_object_cache->sqlite_files() as $file ) {
457 $wp_filesystem->delete( $file );
458 }
459 }
460 }
461 }
462
463 private function delete_dropin() {
464 global $wp_filesystem;
465 ob_start();
466
467 if ( $this->validate_object_cache_dropin() && $this->initialize_filesystem( '', true ) ) {
468 $wp_filesystem->delete( $this->dropinfiledest );
469 }
470
471 ob_end_clean();
472 }
473
474 /**
475 * @return bool True if APCu support is activated for this plugin.
476 */
477 public function apcu_is_activated() {
478 return defined( 'WP_SQLITE_OBJECT_CACHE_APCU' ) && WP_SQLITE_OBJECT_CACHE_APCU
479 && $this->apcu_extension_is_enabled();
480 }
481
482 /**
483 * @return bool True if the APCu extension is loaded and enabled.
484 */
485 public function apcu_extension_is_enabled() {
486 return function_exists( 'apcu_enabled' ) && apcu_enabled();
487 }
488
489 /**
490 * Make sure WP_SQLITE_OBJECT_CACHE_APCU and $option['use_apcu'] match.
491 *
492 * @param bool $unconditional true if we should always change the option to match WP_SQLITE_OBJECT_CACHE_APCU.
493 *
494 * @return string 'on' or 'off': the current activation state of epcu.
495 */
496 public function sync_apcu_global_to_option( $unconditional = true ) {
497 global $wp_object_cache;
498 $option = get_option( $this->_token . '_settings', array() );
499 $option_dirty = false;
500 $updated_flag = array_key_exists( 'use_apcu_updated', $option );
501 $use_apcu = array_key_exists( 'use_apcu', $option ) && 'on' === $option['use_apcu'] ? 'on' : 'off';
502 if ( $updated_flag ) {
503 unset ( $option['use_apcu_updated'] );
504 $option_dirty = true;
505 }
506 $target_use_apcu = $this->apcu_is_activated() ? 'on' : 'off';
507 if ( ! $unconditional && $updated_flag ) {
508 $target_use_apcu = $use_apcu;
509 }
510 if ( $target_use_apcu !== $use_apcu ) {
511 $option['use_apcu'] = $target_use_apcu;
512 $option_dirty = true;
513 }
514 if ( $option_dirty ) {
515 if ( method_exists( $wp_object_cache, 'apcu-clear_cache' ) ) {
516 $wp_object_cache->apcu_clear_cache();
517 }
518 update_option( $this->_token . '_settings', $option, true );
519 }
520
521 return $target_use_apcu;
522 }
523
524
525 }
526