PluginProbe
SQLite Object Cache / 0.1.7
SQLite Object Cache v0.1.7
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 0.1.7, at includes/class-sqlite-object-cache.php

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