PluginProbe
SQLite Object Cache / 1.5.4
SQLite Object Cache v1.5.4
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.4, at includes/class-sqlite-object-cache.php

556 lines 14.9 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.4' ) {
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 // Handle localization.
151 $this->load_plugin_textdomain();
152 add_action( 'admin-init', array( $this, 'load_localization' ), 0 );
153 add_action( 'admin_init', array( $this, 'maybe_update_dropin' ) );
154
155 /* handle cron cache cleanup */
156 add_action( self::CLEAN_EVENT_HOOK, array( $this, 'clean_job' ), 10, 0 );
157 if ( ! wp_next_scheduled( self::CLEAN_EVENT_HOOK ) ) {
158 wp_schedule_event( time() + HOUR_IN_SECONDS, 'hourly', self::CLEAN_EVENT_HOOK );
159 }
160 /* Handle probabilistic non-cron cleanup, one request in 2000. */
161 if ( 1 === rand( 1, 2000 ) ) {
162 add_action( 'shutdown', function () {
163 $this->clean_job( 1.25 );
164 }, 999, 0 );
165 }
166 }
167
168 /**
169 * Load plugin textdomain
170 *
171 * @access public
172 * @return void
173 * @since 1.0.0
174 */
175 public function load_plugin_textdomain() {
176 $domain = 'sqlite-object-cache';
177
178 $locale = apply_filters( 'plugin_locale', get_locale(), $domain );
179
180 load_textdomain( $domain, WP_LANG_DIR . '/' . $domain . '/' . $domain . '-' . $locale . '.mo' );
181 load_plugin_textdomain( $domain, false, dirname( plugin_basename( $this->file ) ) . '/languages/' );
182 }
183
184 /**
185 * WP_Cron task to clean cache entries.
186 *
187 * @param float $grace_factor Default 1.0
188 *
189 * @return void
190 */
191 public function clean_job( $grace_factor = 1.0 ) {
192 $option = get_option( $this->_token . '_settings', array() );
193 $target_size = empty ( $option['target_size'] ) ? 16 : $option['target_size'];
194 $target_size *= ( 1024 * 1024 );
195 $threshold_size = (int) ( $target_size * $grace_factor );
196
197 global $wp_object_cache;
198 if ( ! method_exists( $wp_object_cache, 'sqlite_get_size' ) ) {
199 return;
200 }
201 /* Clean up old statistics. Do this even when the cache is not over size. */
202 $retention = empty ( $option['retainmeasurements'] ) ? 24 : $option['retainmeasurements'];
203 $wp_object_cache->sqlite_reset_statistics( $retention * HOUR_IN_SECONDS );
204
205 $current_size = $wp_object_cache->sqlite_get_size();
206 /* Skip this if the current size is small enough. */
207 if ( $current_size <= $threshold_size ) {
208 return;
209 }
210
211 /* Remove expired items (transients mostly). */
212 if ( $wp_object_cache->sqlite_remove_expired() ) {
213 /* If anything was removed, get the size again. */
214 $current_size = $wp_object_cache->sqlite_get_size();
215 }
216
217 /* Skip this if the size is small enough, after removing expired items. */
218 if ( $current_size <= $threshold_size ) {
219 return;
220 }
221
222 /* Delete the least-recently-updated items to get to the target size. */
223 $wp_object_cache->sqlite_delete_old( $target_size, $current_size );
224 }
225
226 /**
227 * Plugin activation hook
228 *
229 * @return void
230 */
231 public function on_activation() {
232
233 $this->sync_apcu_global_to_option( true );
234 if ( true === $this->has_sqlite() ) {
235 add_action( 'shutdown', array( $this, 'update_dropin' ) );
236 }
237 }
238
239 /**
240 * Determine whether we can use SQLite3.
241 *
242 * @return bool|string true, or an error message.
243 */
244 public function has_sqlite() {
245 if ( ! class_exists( 'SQLite3' ) || ! extension_loaded( 'sqlite3' ) ) {
246 return __( 'You cannot use the SQLite Object Cache plugin. Your server does not have php\'s SQLite3 extension installed.', 'sqlite-object-cache' );
247 }
248
249 $sqlite_version = $this->sqlite_get_version();
250 if ( version_compare( $sqlite_version, $this->minimum_sqlite_version ) < 0 ) {
251 return sprintf(
252 /* translators: 1 actual SQLite version. 2 required SQLite version) */
253 __( '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' ),
254 $sqlite_version, $this->minimum_sqlite_version );
255 }
256
257 return true;
258 }
259
260 /**
261 * Get the version number for the SQLite extension.
262 *
263 * @return string|false SQLite's version number.
264 */
265 public function sqlite_get_version() {
266
267 if ( class_exists( 'SQLite3' ) ) {
268 $version = SQLite3::version();
269
270 return $version['versionString'];
271 }
272
273 return false;
274 }
275
276 /**
277 * Load plugin localization
278 *
279 * @access public
280 * @return void
281 * @since 1.0.0
282 */
283 public function load_localization() {
284 load_plugin_textdomain( 'sqlite-object-cache', false, dirname( plugin_basename( $this->file ) ) . '/languages/' );
285 }
286
287 /**
288 * Test if we can write in the WP_CONTENT_DIR and modify the `object-cache.php` drop-in
289 *
290 * @return true|WP_Error
291 * @author Till Krüss
292 *
293 */
294 public function test_filesystem_writing() {
295 global $wp_filesystem;
296
297 if ( ! $this->initialize_filesystem( '', true ) ) {
298 return new WP_Error( 'fs', __( 'Could not initialize filesystem.', 'sqlite-object-cache' ) );
299 }
300
301 $testfiledest = WP_CONTENT_DIR . '/sqlite-write-test.tmp';
302
303 if ( ! $wp_filesystem->exists( $this->dropinfilesource ) ) {
304 return new WP_Error( 'exists', __( 'Object cache drop-in file doesn’t exist.', 'sqlite-object-cache' ) );
305 }
306
307 if ( $wp_filesystem->exists( $testfiledest ) && ! $wp_filesystem->delete( $testfiledest ) ) {
308 return new WP_Error( 'delete', __( 'Test file exists, but couldn’t be deleted.', 'sqlite-object-cache' ) );
309 }
310
311 if ( ! $wp_filesystem->is_writable( WP_CONTENT_DIR ) ) {
312 return new WP_Error( 'copy', __( 'Content directory is not writable.', 'sqlite-object-cache' ) );
313 }
314
315 if ( ! $wp_filesystem->copy( $this->dropinfilesource, $testfiledest, true, FS_CHMOD_FILE ) ) {
316 return new WP_Error( 'copy', __( 'Failed to copy test file.', 'sqlite-object-cache' ) );
317 }
318
319 if ( ! $wp_filesystem->exists( $testfiledest ) ) {
320 return new WP_Error( 'exists', __( 'Copied test file doesn’t exist.', 'sqlite-object-cache' ) );
321 }
322
323 $meta = get_file_data( $testfiledest, array( 'Version' => 'Version' ) );
324
325 if ( $meta['Version'] !== $this->_version ) {
326 return new WP_Error( 'version', __( 'Couldn’t verify test file contents.', 'sqlite-object-cache' ) );
327 }
328
329 if ( ! $wp_filesystem->delete( $testfiledest ) ) {
330 return new WP_Error( 'delete', __( 'Copied test file couldn’t be deleted.', 'sqlite-object-cache' ) );
331 }
332
333 return true;
334 }
335
336 /**
337 * Initializes the WP filesystem API to be ready for use
338 *
339 * @param string $url The URL to post the form to.
340 * @param bool $silent Whether to ask the user for credentials if necessary or not.
341 *
342 * @return bool
343 * @author Till Krüss
344 *
345 */
346 public function initialize_filesystem( $url, $silent = false ) {
347 require_once ABSPATH . 'wp-admin/includes/file.php';
348 if ( $silent ) {
349 ob_start();
350 }
351
352 $credentials = request_filesystem_credentials( $url );
353
354 if ( false === $credentials ) {
355 if ( $silent ) {
356 ob_end_clean();
357 }
358
359 return false;
360 }
361
362 if ( ! WP_Filesystem( $credentials ) ) {
363 request_filesystem_credentials( $url );
364
365 if ( $silent ) {
366 ob_end_clean();
367 }
368
369 return false;
370 }
371
372 return true;
373 }
374
375 /**
376 * Calls the drop-in update method if necessary
377 *
378 * @return void
379 * @author Till Krüss
380 */
381 public function maybe_update_dropin() {
382 if ( defined( 'WP_SQLITE_OBJECT_CACHE_DISABLE_DROPIN_AUTOUPDATE' ) && WP_SQLITE_OBJECT_CACHE_DISABLE_DROPIN_AUTOUPDATE ) {
383 return;
384 }
385
386 $has = $this->has_sqlite();
387 if ( ( true === $has ) && $this->object_cache_dropin_needs_updating() ) {
388 add_action( 'shutdown', array( $this, 'update_dropin' ) );
389 }
390 }
391
392 /**
393 * Checks if the `object-cache.php` drop-in is outdated
394 * @return bool
395 * @author Till Krüss
396 *
397 */
398 public function object_cache_dropin_needs_updating() {
399 global $wp_object_cache;
400 if ( ! method_exists( $wp_object_cache, 'dropin_get_version' ) ) {
401 return true;
402 }
403 return version_compare( $wp_object_cache->dropin_get_version(), $this->_version, '<' );
404 }
405
406 /**
407 * Updates the `object-cache.php` drop-in
408 *
409 * @return void
410 * @author Till Krüss
411 */
412 public function update_dropin() {
413 global $wp_filesystem;
414 $has = $this->has_sqlite();
415 if ( true === $has && $this->initialize_filesystem( '', true ) ) {
416
417 $this->delete_sqlite_files();
418 $result = $wp_filesystem->copy( $this->dropinfilesource, $this->dropinfiledest, true, FS_CHMOD_FILE );
419 /**
420 * Fires on cache enable event
421 *
422 * @param bool $result Whether the filesystem event (copy of the `object-cache.php` file) was successful.
423 *
424 * @since 0.1.0
425 */
426 do_action( 'sqlite_object_cache_update_dropin', $result );
427 }
428 }
429
430 /**
431 * Validates the `object-cache.php` drop-in
432 *
433 * @return bool
434 * @author Till Krüss
435 */
436 public function validate_object_cache_dropin() {
437 global $wp_object_cache;
438
439 if ( ! method_exists( $wp_object_cache, 'dropin_get_version' ) ) {
440 return false;
441 }
442
443 $dropin = get_plugin_data( $this->dropinfiledest );
444 $plugin = get_plugin_data( $this->dropinfilesource );
445
446 /**
447 * Filters the drop-in validation state
448 *
449 * @param bool $state The validation state of the drop-in.
450 * @param string $dropin The `PluginURI` of the drop-in.
451 * @param string $plugin The `PluginURI` of the plugin.
452 *
453 * @since 2.0.16
454 */
455 return apply_filters(
456 'sqlite_object_cache_validate_dropin',
457 $dropin['PluginURI'] === $plugin['PluginURI'],
458 $dropin['PluginURI'],
459 $plugin['PluginURI']
460 );
461 }
462
463 /**
464 * Plugin deactivation hook
465 *
466 * @param string $plugin Plugin basename.
467 *
468 * @return void
469 * @author Till Krüss
470 */
471 public function on_deactivation( $plugin ) {
472
473 wp_cache_flush();
474
475 wp_unschedule_hook( self::CLEAN_EVENT_HOOK );
476 $this->delete_sqlite_files();
477 $this->delete_dropin();
478 }
479
480 private function delete_sqlite_files() {
481 global $wp_filesystem;
482 global $wp_object_cache;
483
484 if ( method_exists( $wp_object_cache, 'sqlite_files' ) ) {
485 if ( $this->initialize_filesystem( '', true ) ) {
486 foreach ( $wp_object_cache->sqlite_files() as $file ) {
487 $wp_filesystem->delete( $file );
488 }
489 }
490 }
491 }
492
493 private function delete_dropin() {
494 global $wp_filesystem;
495 ob_start();
496
497 if ( $this->validate_object_cache_dropin() && $this->initialize_filesystem( '', true ) ) {
498 $wp_filesystem->delete( $this->dropinfiledest );
499 }
500
501 ob_end_clean();
502 }
503
504 /**
505 * @return bool True if APCu support is activated for this plugin.
506 */
507 public function apcu_is_activated() {
508 return defined( 'WP_SQLITE_OBJECT_CACHE_APCU' ) && WP_SQLITE_OBJECT_CACHE_APCU
509 && $this->apcu_extension_is_enabled();
510 }
511
512 /**
513 * @return bool True if the APCu extension is loaded and enabled.
514 */
515 public function apcu_extension_is_enabled() {
516 return function_exists( 'apcu_enabled' ) && apcu_enabled();
517 }
518
519 /**
520 * Make sure WP_SQLITE_OBJECT_CACHE_APCU and $option['use_apcu'] match.
521 *
522 * @param bool $unconditional true if we should always change the option to match WP_SQLITE_OBJECT_CACHE_APCU.
523 *
524 * @return string 'on' or 'off': the current activation state of epcu.
525 */
526 public function sync_apcu_global_to_option( $unconditional = true ) {
527 global $wp_object_cache;
528 $option = get_option( $this->_token . '_settings', array() );
529 $option_dirty = false;
530 $updated_flag = array_key_exists( 'use_apcu_updated', $option );
531 $use_apcu = array_key_exists( 'use_apcu', $option ) && 'on' === $option['use_apcu'] ? 'on' : 'off';
532 if ( $updated_flag ) {
533 unset ( $option['use_apcu_updated'] );
534 $option_dirty = true;
535 }
536 $target_use_apcu = $this->apcu_is_activated() ? 'on' : 'off';
537 if ( ! $unconditional && $updated_flag ) {
538 $target_use_apcu = $use_apcu;
539 }
540 if ( $target_use_apcu !== $use_apcu ) {
541 $option['use_apcu'] = $target_use_apcu;
542 $option_dirty = true;
543 }
544 if ( $option_dirty ) {
545 if ( method_exists( $wp_object_cache, 'apcu-clear_cache' ) ) {
546 $wp_object_cache->apcu_clear_cache();
547 }
548 update_option( $this->_token . '_settings', $option, true );
549 }
550
551 return $target_use_apcu;
552 }
553
554
555 }
556