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

541 lines 13.1 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 funtion.
120 *
121 * @param string $file File constructor.
122 * @param string $version Plugin version.
123 */
124 public function __construct( $file = '', $version = '1.3.1' ) {
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'] ) ? 4 : $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 $current_size = $wp_object_cache->sqlite_get_size();
202 /* Skip this if the current size is small enough. */
203 if ( $current_size <= $threshold_size ) {
204 return;
205 }
206
207 /* Remove expired items (transients mostly). */
208 if ( $wp_object_cache->sqlite_remove_expired( true ) ) {
209 /* If anything was removed, get the size again. */
210 $current_size = $wp_object_cache->sqlite_get_size();
211 }
212
213 /* Skip this if the size is small enough, after removing expired items. */
214 if ( $current_size <= $threshold_size ) {
215 return;
216 }
217
218 /* Clean up old statistics. */
219 $retention = empty ( $option['retention'] ) ? 24 : $option['retention'];
220 $wp_object_cache->sqlite_reset_statistics( $retention * HOUR_IN_SECONDS );
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
232 function on_activation() {
233 /* make sure the autoloaded option is set when activating; avoid an extra dbms or cache hit to fetch it */
234 $option = get_option( $this->_token . '_settings', 'default' );
235 if ( 'default' === $option ) {
236 update_option( $this->_token . '_settings', array(), true );
237 }
238 if ( true === $this->has_sqlite() ) {
239 add_action( 'shutdown', array( $this, 'update_dropin' ) );
240 }
241 }
242
243 /**
244 * Determine whether we can use SQLite3.
245 *
246 * @return bool|string true, or an error message.
247 */
248 public
249 function has_sqlite() {
250 if ( ! class_exists( 'SQLite3' ) || ! extension_loaded( 'sqlite3' ) ) {
251 return __( 'You cannot use the SQLite Object Cache plugin. Your server does not have php\'s SQLite3 extension installed.', 'sqlite-object-cache' );
252 }
253
254 $sqlite_version = $this->sqlite_get_version();
255 if ( version_compare( $sqlite_version, $this->minimum_sqlite_version ) < 0 ) {
256 return sprintf(
257 /* translators: 1 actual SQLite version. 2 required SQLite version) */
258 __( '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' ),
259 $sqlite_version, $this->minimum_sqlite_version );
260 }
261
262 return true;
263 }
264
265 /**
266 * Get the version number for the SQLite extension.
267 *
268 * @return string|false SQLite's version number.
269 */
270 public
271 function sqlite_get_version() {
272
273 if ( class_exists( 'SQLite3' ) ) {
274 $version = SQLite3::version();
275
276 return $version['versionString'];
277 }
278
279 return false;
280 }
281
282 /**
283 * Load plugin localization
284 *
285 * @access public
286 * @return void
287 * @since 1.0.0
288 */
289 public
290 function load_localization() {
291 load_plugin_textdomain( 'sqlite-object-cache', false, dirname( plugin_basename( $this->file ) ) . '/languages/' );
292 }
293
294 /**
295 * Test if we can write in the WP_CONTENT_DIR and modify the `object-cache.php` drop-in
296 *
297 * @return true|WP_Error
298 * @author Till Krüss
299 *
300 */
301 public
302 function test_filesystem_writing() {
303 global $wp_filesystem;
304
305 if ( ! $this->initialize_filesystem( '', true ) ) {
306 return new WP_Error( 'fs', __( 'Could not initialize filesystem.', 'sqlite-object-cache' ) );
307 }
308
309 $testfiledest = WP_CONTENT_DIR . '/sqlite-write-test.tmp';
310
311 if ( ! $wp_filesystem->exists( $this->dropinfilesource ) ) {
312 return new WP_Error( 'exists', __( 'Object cache drop-in file doesn’t exist.', 'sqlite-object-cache' ) );
313 }
314
315 if ( $wp_filesystem->exists( $testfiledest ) && ! $wp_filesystem->delete( $testfiledest ) ) {
316 return new WP_Error( 'delete', __( 'Test file exists, but couldn’t be deleted.', 'sqlite-object-cache' ) );
317 }
318
319 if ( ! $wp_filesystem->is_writable( WP_CONTENT_DIR ) ) {
320 return new WP_Error( 'copy', __( 'Content directory is not writable.', 'sqlite-object-cache' ) );
321 }
322
323 if ( ! $wp_filesystem->copy( $this->dropinfilesource, $testfiledest, true, FS_CHMOD_FILE ) ) {
324 return new WP_Error( 'copy', __( 'Failed to copy test file.', 'sqlite-object-cache' ) );
325 }
326
327 if ( ! $wp_filesystem->exists( $testfiledest ) ) {
328 return new WP_Error( 'exists', __( 'Copied test file doesn’t exist.', 'sqlite-object-cache' ) );
329 }
330
331 $meta = get_file_data( $testfiledest, array( 'Version' => 'Version' ) );
332
333 if ( $meta['Version'] !== $this->_version ) {
334 return new WP_Error( 'version', __( 'Couldn’t verify test file contents.', 'sqlite-object-cache' ) );
335 }
336
337 if ( ! $wp_filesystem->delete( $testfiledest ) ) {
338 return new WP_Error( 'delete', __( 'Copied test file couldn’t be deleted.', 'sqlite-object-cache' ) );
339 }
340
341 return true;
342 }
343
344 /**
345 * Initializes the WP filesystem API to be ready for use
346 *
347 * @param string $url The URL to post the form to.
348 * @param bool $silent Whether to ask the user for credentials if necessary or not.
349 *
350 * @return bool
351 * @author Till Krüss
352 *
353 */
354 public
355 function initialize_filesystem(
356 $url, $silent = false
357 ) {
358 require_once ABSPATH . 'wp-admin/includes/file.php';
359 if ( $silent ) {
360 ob_start();
361 }
362
363 $credentials = request_filesystem_credentials( $url );
364
365 if ( false === $credentials ) {
366 if ( $silent ) {
367 ob_end_clean();
368 }
369
370 return false;
371 }
372
373 if ( ! WP_Filesystem( $credentials ) ) {
374 request_filesystem_credentials( $url );
375
376 if ( $silent ) {
377 ob_end_clean();
378 }
379
380 return false;
381 }
382
383 return true;
384 }
385
386 /**
387 * Calls the drop-in update method if necessary
388 *
389 * @return void
390 * @author Till Krüss
391 */
392 public
393 function maybe_update_dropin() {
394 if ( defined( 'WP_SQLITE_OBJECT_CACHE_DISABLE_DROPIN_AUTOUPDATE' ) && WP_SQLITE_OBJECT_CACHE_DISABLE_DROPIN_AUTOUPDATE ) {
395 return;
396 }
397
398 $has = $this->has_sqlite();
399 if ( ( true === $has ) && $this->object_cache_dropin_needs_updating() ) {
400 add_action( 'shutdown', array( $this, 'update_dropin' ) );
401 }
402 }
403
404 /**
405 * Checks if the `object-cache.php` drop-in is outdated
406 * @return bool
407 * @author Till Krüss
408 *
409 */
410 public
411 function object_cache_dropin_needs_updating() {
412 if ( ! $this->object_cache_dropin_exists() ) {
413 return true;
414 }
415
416 $dropin = get_plugin_data( $this->dropinfiledest );
417 $plugin = get_plugin_data( $this->dropinfilesource );
418
419 if ( $dropin['PluginURI'] === $plugin['PluginURI'] ) {
420 return version_compare( $dropin['Version'], $plugin['Version'], '<' );
421 }
422
423 return false;
424 }
425
426 /**
427 * Checks if the `object-cache.php` drop-in exists
428 *
429 * @return bool
430 * @author Till Krüss
431 */
432 public
433 function object_cache_dropin_exists() {
434 return @file_exists( $this->dropinfiledest );
435 }
436
437 /**
438 * Updates the `object-cache.php` drop-in
439 *
440 * @return void
441 * @author Till Krüss
442 */
443 public
444 function update_dropin() {
445 global $wp_filesystem;
446 $has = $this->has_sqlite();
447 if ( true === $has && $this->initialize_filesystem( '', true ) ) {
448
449 $this->delete_sqlite_files();
450 $result = $wp_filesystem->copy( $this->dropinfilesource, $this->dropinfiledest, true, FS_CHMOD_FILE );
451 /**
452 * Fires on cache enable event
453 *
454 * @param bool $result Whether the filesystem event (copy of the `object-cache.php` file) was successful.
455 *
456 * @since 0.1.0
457 */
458 do_action( 'sqlite_object_cache_update_dropin', $result );
459 }
460 }
461
462 /**
463 * Validates the `object-cache.php` drop-in
464 *
465 * @return bool
466 * @author Till Krüss
467 */
468 public function validate_object_cache_dropin() {
469 if ( ! $this->object_cache_dropin_exists() ) {
470 return false;
471 }
472
473 $dropin = get_plugin_data( $this->dropinfiledest );
474 $plugin = get_plugin_data( $this->dropinfilesource );
475
476 /**
477 * Filters the drop-in validation state
478 *
479 * @param bool $state The validation state of the drop-in.
480 * @param string $dropin The `PluginURI` of the drop-in.
481 * @param string $plugin The `PluginURI` of the plugin.
482 *
483 * @since 2.0.16
484 */
485 return apply_filters(
486 'sqlite_object_cache_validate_dropin',
487 $dropin['PluginURI'] === $plugin['PluginURI'],
488 $dropin['PluginURI'],
489 $plugin['PluginURI']
490 );
491 }
492
493 /**
494 * Plugin deactivation hook
495 *
496 * @param string $plugin Plugin basename.
497 *
498 * @return void
499 * @author Till Krüss
500 */
501 public
502 function on_deactivation(
503 $plugin
504 ) {
505 wp_cache_flush();
506
507 wp_unschedule_hook( self::CLEAN_EVENT_HOOK );
508 $this->delete_sqlite_files();
509 $this->delete_dropin();
510 }
511
512 private function delete_sqlite_files() {
513 global $wp_filesystem;
514 global $wp_object_cache;
515
516 ob_start();
517
518 if ( method_exists( $wp_object_cache, 'sqlite_files' ) ) {
519 if ( $this->validate_object_cache_dropin() && $this->initialize_filesystem( '', true ) ) {
520 foreach ( $wp_object_cache->sqlite_files() as $file ) {
521 $wp_filesystem->delete( $file );
522 }
523 }
524 }
525
526 ob_end_clean();
527 }
528
529 private
530 function delete_dropin() {
531 global $wp_filesystem;
532 ob_start();
533
534 if ( $this->validate_object_cache_dropin() && $this->initialize_filesystem( '', true ) ) {
535 $wp_filesystem->delete( $this->dropinfiledest );
536 }
537
538 ob_end_clean();
539 }
540 }
541