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

507 lines 12.5 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.1.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, [ $this, 'on_activation' ] );
141 register_deactivation_hook( $this->file, [ $this, 'on_deactivation' ] );
142
143 // Load API for generic admin functions.
144 if ( is_admin() ) {
145 $this->admin = new SQLite_Object_Cache_Admin_API();
146 }
147
148 // Handle localization.
149 $this->load_plugin_textdomain();
150 add_action( 'admin-init', [ $this, 'load_localization' ], 0 );
151 add_action( 'admin_init', [ $this, 'maybe_update_dropin' ] );
152
153 /* handle cron cache cleanup */
154 add_action( self::CLEAN_EVENT_HOOK, [ $this, 'clean' ], 10, 0 );
155 if ( ! wp_next_scheduled( self::CLEAN_EVENT_HOOK ) ) {
156 wp_schedule_event( time() + HOUR_IN_SECONDS, 'hourly', self::CLEAN_EVENT_HOOK );
157 }
158 }
159
160 /**
161 * Load plugin textdomain
162 *
163 * @access public
164 * @return void
165 * @since 1.0.0
166 */
167 public function load_plugin_textdomain() {
168 $domain = 'sqlite-object-cache';
169
170 $locale = apply_filters( 'plugin_locale', get_locale(), $domain );
171
172 load_textdomain( $domain, WP_LANG_DIR . '/' . $domain . '/' . $domain . '-' . $locale . '.mo' );
173 load_plugin_textdomain( $domain, false, dirname( plugin_basename( $this->file ) ) . '/languages/' );
174 }
175
176 /**
177 * WP_Cron task to clean cache entries.
178 *
179 * @return void
180 */
181 public function clean() {
182 global $wp_object_cache;
183 $option = get_option( $this->_token . '_settings', [] );
184 if ( method_exists( $wp_object_cache, 'sqlite_clean_up_cache' ) ) {
185 $retention = array_key_exists( 'retention', $option ) ? $option['retention'] : 24;
186 try {
187 $wp_object_cache->sqlite_clean_up_cache( $retention * HOUR_IN_SECONDS, true, true );
188 } catch ( Exception $ex ) {
189 error_log( 'sqlite_object_cache cache cleanup failure: ', $ex->getMessage() );
190 }
191 }
192
193 if ( method_exists( $wp_object_cache, 'sqlite_reset_statistics' ) ) {
194 $retention = array_key_exists( 'retainmeasurements', $option ) ? $option['retainmeasurements'] : 24;
195 try {
196 $wp_object_cache->sqlite_reset_statistics( $retention * HOUR_IN_SECONDS );
197 } catch ( Exception $ex ) {
198 error_log( 'sqlite_object_cache stats cleanup failure: ', $ex->getMessage() );
199 }
200 }
201 } // End enqueue_styles ()
202
203 /**
204 * Plugin activation hook
205 *
206 * @return void
207 */
208 public function on_activation() {
209 /* make sure the autoloaded option is set when activating; avoid an extra dbms or cache hit to fetch it */
210 $option = get_option( $this->_token . '_settings', 'default' );
211 if ( 'default' === $option ) {
212 update_option( $this->_token . '_settings', [], true );
213 }
214 if ( true === $this->has_sqlite() ) {
215 add_action( 'shutdown', [ $this, 'update_dropin' ] );
216 }
217 }
218
219 /**
220 * Determine whether we can use SQLite3.
221 *
222 * @return bool|string true, or an error message.
223 */
224 public function has_sqlite() {
225 if ( ! class_exists( 'SQLite3' ) || ! extension_loaded( 'sqlite3' ) ) {
226 return __( 'You cannot use the SQLite Object Cache plugin. Your server does not have php\'s SQLite3 extension installed.', 'sqlite-object-cache' );
227 }
228
229 $sqlite_version = $this->sqlite_get_version();
230 if ( version_compare( $sqlite_version, $this->minimum_sqlite_version ) < 0 ) {
231 return sprintf(
232 /* translators: 1 actual SQLite version. 2 required SQLite version) */
233 __( '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' ),
234 $sqlite_version, $this->minimum_sqlite_version );
235 }
236
237 return true;
238 }
239
240 /**
241 * Get the version number for the SQLite extension.
242 *
243 * @return string|false SQLite's version number.
244 */
245 public function sqlite_get_version() {
246
247 if ( class_exists( 'SQLite3' ) ) {
248 $version = SQLite3::version();
249
250 return $version['versionString'];
251 }
252
253 return false;
254 }
255
256 /**
257 * Load plugin localization
258 *
259 * @access public
260 * @return void
261 * @since 1.0.0
262 */
263 public function load_localization() {
264 load_plugin_textdomain( 'sqlite-object-cache', false, dirname( plugin_basename( $this->file ) ) . '/languages/' );
265 }
266
267 /**
268 * Test if we can write in the WP_CONTENT_DIR and modify the `object-cache.php` drop-in
269 *
270 * @return true|WP_Error
271 * @author Till Krüss
272 *
273 */
274 public function test_filesystem_writing() {
275 global $wp_filesystem;
276
277 if ( ! $this->initialize_filesystem( '', true ) ) {
278 return new WP_Error( 'fs', __( 'Could not initialize filesystem.', 'sqlite-object-cache' ) );
279 }
280
281 $testfiledest = WP_CONTENT_DIR . '/sqlite-write-test.tmp';
282
283 if ( ! $wp_filesystem->exists( $this->dropinfilesource ) ) {
284 return new WP_Error( 'exists', __( 'Object cache drop-in file doesn’t exist.', 'sqlite-object-cache' ) );
285 }
286
287 if ( $wp_filesystem->exists( $testfiledest ) && ! $wp_filesystem->delete( $testfiledest ) ) {
288 return new WP_Error( 'delete', __( 'Test file exists, but couldn’t be deleted.', 'sqlite-object-cache' ) );
289 }
290
291 if ( ! $wp_filesystem->is_writable( WP_CONTENT_DIR ) ) {
292 return new WP_Error( 'copy', __( 'Content directory is not writable.', 'sqlite-object-cache' ) );
293 }
294
295 if ( ! $wp_filesystem->copy( $this->dropinfilesource, $testfiledest, true, FS_CHMOD_FILE ) ) {
296 return new WP_Error( 'copy', __( 'Failed to copy test file.', 'sqlite-object-cache' ) );
297 }
298
299 if ( ! $wp_filesystem->exists( $testfiledest ) ) {
300 return new WP_Error( 'exists', __( 'Copied test file doesn’t exist.', 'sqlite-object-cache' ) );
301 }
302
303 $meta = get_file_data( $testfiledest, [ 'Version' => 'Version' ] );
304
305 if ( $meta['Version'] !== $this->_version ) {
306 return new WP_Error( 'version', __( 'Couldn’t verify test file contents.', 'sqlite-object-cache' ) );
307 }
308
309 if ( ! $wp_filesystem->delete( $testfiledest ) ) {
310 return new WP_Error( 'delete', __( 'Copied test file couldn’t be deleted.', 'sqlite-object-cache' ) );
311 }
312
313 return true;
314 }
315
316 /**
317 * Initializes the WP filesystem API to be ready for use
318 *
319 * @param string $url The URL to post the form to.
320 * @param bool $silent Whether to ask the user for credentials if necessary or not.
321 *
322 * @return bool
323 * @author Till Krüss
324 *
325 */
326 public function initialize_filesystem( $url, $silent = false ) {
327 require_once ABSPATH . 'wp-admin/includes/file.php';
328 if ( $silent ) {
329 ob_start();
330 }
331
332 $credentials = request_filesystem_credentials( $url );
333
334 if ( false === $credentials ) {
335 if ( $silent ) {
336 ob_end_clean();
337 }
338
339 return false;
340 }
341
342 if ( ! WP_Filesystem( $credentials ) ) {
343 request_filesystem_credentials( $url );
344
345 if ( $silent ) {
346 ob_end_clean();
347 }
348
349 return false;
350 }
351
352 return true;
353 }
354
355 /**
356 * Calls the drop-in update method if necessary
357 *
358 * @return void
359 * @author Till Krüss
360 */
361 public function maybe_update_dropin() {
362 if ( defined( 'WP_SQLITE_OBJECT_CACHE_DISABLE_DROPIN_AUTOUPDATE' ) && WP_SQLITE_OBJECT_CACHE_DISABLE_DROPIN_AUTOUPDATE ) {
363 return;
364 }
365
366 $has = $this->has_sqlite();
367 if ( ( true === $has ) && $this->object_cache_dropin_needs_updating() ) {
368 add_action( 'shutdown', [ $this, 'update_dropin' ] );
369 }
370 }
371
372 /**
373 * Checks if the `object-cache.php` drop-in is outdated
374 * @return bool
375 * @author Till Krüss
376 *
377 */
378 public function object_cache_dropin_needs_updating() {
379 if ( ! $this->object_cache_dropin_exists() ) {
380 return true;
381 }
382
383 $dropin = get_plugin_data( $this->dropinfiledest );
384 $plugin = get_plugin_data( $this->dropinfilesource );
385
386 if ( $dropin['PluginURI'] === $plugin['PluginURI'] ) {
387 return version_compare( $dropin['Version'], $plugin['Version'], '<' );
388 }
389
390 return false;
391 }
392
393 /**
394 * Checks if the `object-cache.php` drop-in exists
395 *
396 * @return bool
397 * @author Till Krüss
398 */
399 public function object_cache_dropin_exists() {
400 return @file_exists( $this->dropinfiledest );
401 }
402
403 /**
404 * Updates the `object-cache.php` drop-in
405 *
406 * @return void
407 * @author Till Krüss
408 */
409 public function update_dropin() {
410 global $wp_filesystem;
411 $has = $this->has_sqlite();
412 if ( true === $has && $this->initialize_filesystem( '', true ) ) {
413
414 $this->delete_sqlite_files();
415 $result = $wp_filesystem->copy( $this->dropinfilesource, $this->dropinfiledest, true, FS_CHMOD_FILE );
416 /**
417 * Fires on cache enable event
418 *
419 * @param bool $result Whether the filesystem event (copy of the `object-cache.php` file) was successful.
420 *
421 * @since 0.1.0
422 */
423 do_action( 'sqlite_object_cache_update_dropin', $result );
424 }
425 }
426
427 private function delete_sqlite_files() {
428 global $wp_filesystem;
429
430 /* the former filename */
431 $old_file = WP_CONTENT_DIR . '/object-cache.sqlite';
432 $db_file = WP_CONTENT_DIR . '/.ht.object-cache.sqlite';
433 if ( defined( 'WP_SQLITE_OBJECT_CACHE_DB_FILE' ) ) {
434 $db_file = WP_SQLITE_OBJECT_CACHE_DB_FILE;
435 }
436
437 ob_start();
438
439 if ( $this->validate_object_cache_dropin() && $this->initialize_filesystem( '', true ) ) {
440 foreach ( [ '', '-shm', '-wal' ] as $suffix ) {
441 $wp_filesystem->delete( $db_file . $suffix );
442 $wp_filesystem->delete( $old_file . $suffix );
443 }
444 }
445
446 ob_end_clean();
447 }
448
449 /**
450 * Validates the `object-cache.php` drop-in
451 *
452 * @return bool
453 * @author Till Krüss
454 */
455 public function validate_object_cache_dropin() {
456 if ( ! $this->object_cache_dropin_exists() ) {
457 return false;
458 }
459
460 $dropin = get_plugin_data( $this->dropinfiledest );
461 $plugin = get_plugin_data( $this->dropinfilesource );
462
463 /**
464 * Filters the drop-in validation state
465 *
466 * @param bool $state The validation state of the drop-in.
467 * @param string $dropin The `PluginURI` of the drop-in.
468 * @param string $plugin The `PluginURI` of the plugin.
469 *
470 * @since 2.0.16
471 */
472 return apply_filters(
473 'sqlite_object_cache_validate_dropin',
474 $dropin['PluginURI'] === $plugin['PluginURI'],
475 $dropin['PluginURI'],
476 $plugin['PluginURI']
477 );
478 }
479
480 /**
481 * Plugin deactivation hook
482 *
483 * @param string $plugin Plugin basename.
484 *
485 * @return void
486 * @author Till Krüss
487 */
488 public function on_deactivation( $plugin ) {
489 wp_cache_flush();
490
491 wp_unschedule_hook( self::CLEAN_EVENT_HOOK );
492 $this->delete_sqlite_files();
493 $this->delete_dropin();
494 }
495
496 private function delete_dropin() {
497 global $wp_filesystem;
498 ob_start();
499
500 if ( $this->validate_object_cache_dropin() && $this->initialize_filesystem( '', true ) ) {
501 $wp_filesystem->delete( $this->dropinfiledest );
502 }
503
504 ob_end_clean();
505 }
506 }
507