PluginProbe
SQLite Object Cache / 1.6.0
SQLite Object Cache v1.6.0
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
← All changes | includes/class-sqlite-object-cache.php +512 -448 0.1.71.6.0 View file →
@@ -5,9 +5,9 @@
5 5 * @package WordPress Plugin Template/Includes
6 6 */
7 7
8 8 if ( ! defined( 'ABSPATH' ) ) {
9 - exit;
9 + exit;
10 10 }
11 11
12 12 /**
13 13 * Main plugin class.
@@ -14,529 +14,593 @@
14 14 *
15 15 * Only make one instance of this, please.
16 16 */
17 17 class SQLite_Object_Cache {
18 - const CLEAN_EVENT_HOOK = 'sqlite_object_cache_clean';
18 + const CLEAN_EVENT_HOOK = 'sqlite_object_cache_clean';
19 19
20 - /**
21 - * Local instance of SQLite_Object_Cache_Admin_API
22 - *
23 - * @var SQLite_Object_Cache_Admin_API|null
24 - */
25 - public $admin;
20 + /**
21 + * Local instance of SQLite_Object_Cache_Admin_API
22 + *
23 + * @var SQLite_Object_Cache_Admin_API|null
24 + */
25 + public $admin;
26 26
27 - /**
28 - * Settings class object
29 - *
30 - * @var object
31 - * @access public
32 - * @since 1.0.0
33 - */
34 - public $settings;
27 + /**
28 + * Settings class object
29 + *
30 + * @var object
31 + * @access public
32 + * @since 1.0.0
33 + */
34 + public $settings;
35 35
36 - /**
37 - * The version number.
38 - *
39 - * @var string
40 - * @access public
41 - * @since 1.0.0
42 - */
43 - public $_version;
36 + /**
37 + * The version number.
38 + *
39 + * @var string
40 + * @access public
41 + * @since 1.0.0
42 + */
43 + public $_version;
44 44
45 - /**
46 - * The token.
47 - *
48 - * @var string
49 - * @access public
50 - * @since 1.0.0
51 - */
52 - public $_token;
45 + /**
46 + * The token.
47 + *
48 + * @var string
49 + * @access public
50 + * @since 1.0.0
51 + */
52 + public $_token;
53 53
54 - /**
55 - * The main plugin file.
56 - *
57 - * @var string
58 - * @access public
59 - * @since 1.0.0
60 - */
61 - public $file;
54 + /**
55 + * The main plugin file.
56 + *
57 + * @var string
58 + * @access public
59 + * @since 1.0.0
60 + */
61 + public $file;
62 62
63 - /**
64 - * The main plugin directory.
65 - *
66 - * @var string
67 - * @access public
68 - * @since 1.0.0
69 - */
70 - public $dir;
63 + /**
64 + * The main plugin directory.
65 + *
66 + * @var string
67 + * @access public
68 + * @since 1.0.0
69 + */
70 + public $dir;
71 71
72 - /**
73 - * The plugin assets directory.
74 - *
75 - * @var string
76 - * @access public
77 - * @since 1.0.0
78 - */
79 - public $assets_dir;
72 + /**
73 + * The plugin assets directory.
74 + *
75 + * @var string
76 + * @access public
77 + * @since 1.0.0
78 + */
79 + public $assets_dir;
80 80
81 - /**
82 - * The plugin assets URL.
83 - *
84 - * @var string
85 - * @access public
86 - * @since 1.0.0
87 - */
88 - public $assets_url;
81 + /**
82 + * The plugin assets URL.
83 + *
84 + * @var string
85 + * @access public
86 + * @since 1.0.0
87 + */
88 + public $assets_url;
89 89
90 - /**
91 - * Suffix for JavaScripts.
92 - *
93 - * @var string
94 - * @access public
95 - * @since 1.0.0
96 - */
97 - public $script_suffix;
90 + /**
91 + * Suffix for JavaScripts.
92 + *
93 + * @var string
94 + * @access public
95 + * @since 1.0.0
96 + */
97 + public $script_suffix;
98 98
99 - /**
100 - * Path for the drop-in when it is working.
101 - * @var string
102 - */
103 - public $dropinfiledest;
99 + /**
100 + * Path for the drop-in when it is working.
101 + * @var string
102 + */
103 + public $dropinfiledest;
104 104
105 - /**
106 - * Path for the drop-in in the plugin tree.
107 - * @var string
108 - */
109 - public $dropinfilesource;
105 + /**
106 + * Path for the drop-in in the plugin tree.
107 + * @var string
108 + */
109 + public $dropinfilesource;
110 110
111 - /**
112 - * Minimum required sqlite version.
113 - *
114 - * @var string
115 - */
116 - public $minimum_sqlite_version = '3.7.0';
111 + /**
112 + * Minimum required sqlite version.
113 + *
114 + * @var string
115 + */
116 + public $minimum_sqlite_version = '3.7.0';
117 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';
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.6.0' ) {
125 + $this->_version = $version;
126 + $this->_token = 'sqlite_object_cache';
127 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';
128 + // Load plugin environment variables.
129 + $this->file = $file;
130 + $this->dir =
131 + trailingslashit( dirname( trailingslashit( WP_PLUGIN_DIR ) . 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 135
136 - $this->assets_url = esc_url( trailingslashit( plugins_url( '/assets/', $this->file ) ) );
136 + $this->assets_url = esc_url( trailingslashit( plugins_url( '/assets/', $this->file ) ) );
137 137
138 - $this->script_suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
138 + $this->script_suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
139 139
140 - register_activation_hook( $this->file, [ $this, 'on_activation' ] );
141 - register_deactivation_hook( $this->file, [ $this, 'on_deactivation' ] );
140 + register_activation_hook( $this->file, array( $this, 'on_activation' ) );
141 + register_deactivation_hook( $this->file, array( $this, 'on_deactivation' ) );
142 142
143 - // Load API for generic admin functions.
144 - if ( is_admin() ) {
145 - $this->admin = new SQLite_Object_Cache_Admin_API();
146 - }
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();
147 148
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' ] );
149 + add_action( 'admin_notices', function () {
150 + $purged = get_site_transient( 'sqlite-object-cache-flush-on-update' );
151 + if ( $purged ) {
152 + delete_site_transient( 'sqlite-object-cache-flush-on-update' );
153 + $message = esc_html( __( 'A software installation or upgrade operation flushed the SQLite Object Cache.', 'sqlite-object-cache' ) )
154 + ?>
155 + <div class="notice notice-info sqlite-object-cache is-dismissible">
156 + <p><?php echo $message ?></p>
157 + </div>
158 + <?php
159 + }
160 + } );
161 + }
152 162
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 - }
163 + add_action( 'admin_init', array( $this, 'maybe_update_dropin' ) );
158 164
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 + /* Ensure nothing is cached after software upgrades. */
166 + add_action( 'upgrader_process_complete', function () {
167 + add_action( 'shutdown', function () {
168 + wp_cache_flush();
169 + set_site_transient( 'sqlite-object-cache-flush-on-update', true, HOUR_IN_SECONDS );
170 + }, 999 );
171 + wp_cache_flush();
172 + }, 0 );
165 173
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 174
176 - $locale = apply_filters( 'plugin_locale', get_locale(), $domain );
175 + /* handle cron cache cleanup */
176 + add_action( self::CLEAN_EVENT_HOOK, array( $this, 'clean_job' ), 10, 0 );
177 + if ( ! wp_next_scheduled( self::CLEAN_EVENT_HOOK ) ) {
178 + wp_schedule_event( time() + HOUR_IN_SECONDS, 'hourly', self::CLEAN_EVENT_HOOK );
179 + }
180 + /* Handle probabilistic non-cron cleanup, one request in 2000. */
181 + if ( 1 === rand( 1, 2000 ) ) {
182 + add_action( 'shutdown', function () {
183 + $this->clean_job( 1.25 );
184 + }, 999, 0 );
185 + }
186 + }
177 187
178 - load_textdomain( $domain, WP_LANG_DIR . '/' . $domain . '/' . $domain . '-' . $locale . '.mo' );
179 - load_plugin_textdomain( $domain, false, dirname( plugin_basename( $this->file ) ) . '/languages/' );
180 - }
188 + /**
189 + * WP_Cron task to clean cache entries.
190 + *
191 + * @param float $grace_factor Default 1.0
192 + *
193 + * @return void
194 + */
195 + public function clean_job( $grace_factor = 1.0 ) {
196 + $option = get_option( $this->_token . '_settings', array() );
197 + $target_size = empty ( $option['target_size'] ) ? 16 : $option['target_size'];
198 + $target_size *= ( 1024 * 1024 );
199 + $threshold_size = (int) ( $target_size * $grace_factor );
181 200
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 - }
201 + global $wp_object_cache;
202 + if ( ! method_exists( $wp_object_cache, 'sqlite_get_size' ) ) {
203 + return;
204 + }
205 + /* Clean up old statistics. Do this even when the cache is not over size. */
206 + $retention = empty ( $option['retainmeasurements'] ) ? 24 : $option['retainmeasurements'];
207 + $wp_object_cache->sqlite_reset_statistics( $retention * HOUR_IN_SECONDS );
198 208
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 ()
209 + $current_size = $wp_object_cache->sqlite_get_size();
210 + /* Skip this if the current size is small enough. */
211 + if ( $current_size <= $threshold_size ) {
212 + return;
213 + }
208 214
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 - }
215 + /* Remove expired items (transients mostly). */
216 + if ( $wp_object_cache->sqlite_remove_expired() ) {
217 + /* If anything was removed, get the size again. */
218 + $current_size = $wp_object_cache->sqlite_get_size();
219 + }
220 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 - }
221 + /* Skip this if the size is small enough, after removing expired items. */
222 + if ( $current_size <= $threshold_size ) {
223 + return;
224 + }
231 225
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 - }
226 + /* Delete the least-recently-updated items to get to the target size. */
227 + $wp_object_cache->sqlite_delete_old( $target_size, $current_size );
228 + }
243 229
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 - }
230 + /**
231 + * Plugin activation hook
232 + *
233 + * @return void
234 + */
235 + public function on_activation() {
251 236
252 - return true;
253 - }
237 + $this->sync_apcu_global_to_option( true );
238 + if ( true === $this->has_sqlite() ) {
239 + add_action( 'shutdown', array( $this, 'update_dropin' ) );
240 + add_action( 'shutdown', array( $this, 'delete_all_transients_from_db' ) );
241 + }
242 + }
254 243
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() {
244 + /**
245 + * Determine whether we can use SQLite3.
246 + *
247 + * @return bool|string true, or an error message.
248 + */
249 + public 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 + }
261 253
262 - if ( class_exists( 'SQLite3' ) ) {
263 - $version = SQLite3::version();
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 + }
264 261
265 - return $version['versionString'];
266 - }
262 + return true;
263 + }
267 264
268 - return false;
269 - }
265 + /**
266 + * Get the version number for the SQLite extension.
267 + *
268 + * @return string|false SQLite's version number.
269 + */
270 + public function sqlite_get_version() {
270 271
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 - }
272 + if ( class_exists( 'SQLite3' ) ) {
273 + $version = SQLite3::version();
281 274
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 - }
275 + return $version['versionString'];
276 + }
316 277
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;
278 + return false;
279 + }
326 280
327 - if ( ! $this->initialize_filesystem( '', true ) ) {
328 - return new WP_Error( 'fs', __( 'Could not initialize filesystem.', 'sqlite-object-cache' ) );
329 - }
281 + /**
282 + * Test if we can write in the WP_CONTENT_DIR and modify the `object-cache.php` drop-in
283 + *
284 + * @return true|WP_Error
285 + * @author Till Krüss
286 + *
287 + */
288 + public function test_filesystem_writing() {
289 + global $wp_filesystem;
330 290
331 - $testfiledest = WP_CONTENT_DIR . '/sqlite-write-test.tmp';
291 + if ( ! $this->initialize_filesystem( '', true ) ) {
292 + return new WP_Error( 'fs', __( 'Could not initialize filesystem.', 'sqlite-object-cache' ) );
293 + }
332 294
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 - }
295 + $testfiledest = WP_CONTENT_DIR . '/sqlite-write-test.tmp';
336 296
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 - }
297 + if ( ! $wp_filesystem->exists( $this->dropinfilesource ) ) {
298 + return new WP_Error( 'exists', __( 'Object cache drop-in file doesn’t exist.', 'sqlite-object-cache' ) );
299 + }
340 300
341 - if ( ! $wp_filesystem->is_writable( WP_CONTENT_DIR ) ) {
342 - return new WP_Error( 'copy', __( 'Content directory is not writable.', 'sqlite-object-cache' ) );
343 - }
301 + if ( $wp_filesystem->exists( $testfiledest ) && ! $wp_filesystem->delete( $testfiledest ) ) {
302 + return new WP_Error( 'delete', __( 'Test file exists, but couldn’t be deleted.', 'sqlite-object-cache' ) );
303 + }
344 304
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 - }
305 + if ( ! $wp_filesystem->is_writable( WP_CONTENT_DIR ) ) {
306 + return new WP_Error( 'copy', __( 'Content directory is not writable.', 'sqlite-object-cache' ) );
307 + }
348 308
349 - if ( ! $wp_filesystem->exists( $testfiledest ) ) {
350 - return new WP_Error( 'exists', __( 'Copied test file doesn’t exist.', 'sqlite-object-cache' ) );
351 - }
309 + if ( ! $wp_filesystem->copy( $this->dropinfilesource, $testfiledest, true, FS_CHMOD_FILE ) ) {
310 + return new WP_Error( 'copy', __( 'Failed to copy test file.', 'sqlite-object-cache' ) );
311 + }
352 312
353 - $meta = get_file_data( $testfiledest, [ 'Version' => 'Version' ] );
313 + if ( ! $wp_filesystem->exists( $testfiledest ) ) {
314 + return new WP_Error( 'exists', __( 'Copied test file doesn’t exist.', 'sqlite-object-cache' ) );
315 + }
354 316
355 - if ( $meta['Version'] !== $this->_version ) {
356 - return new WP_Error( 'version', __( 'Couldn’t verify test file contents.', 'sqlite-object-cache' ) );
357 - }
317 + $meta = get_file_data( $testfiledest, array( 'Version' => 'Version' ) );
358 318
359 - if ( ! $wp_filesystem->delete( $testfiledest ) ) {
360 - return new WP_Error( 'delete', __( 'Copied test file couldn’t be deleted.', 'sqlite-object-cache' ) );
361 - }
319 + if ( $meta['Version'] !== $this->_version ) {
320 + return new WP_Error( 'version', __( 'Couldn’t verify test file contents.', 'sqlite-object-cache' ) );
321 + }
362 322
363 - return true;
364 - }
323 + if ( ! $wp_filesystem->delete( $testfiledest ) ) {
324 + return new WP_Error( 'delete', __( 'Copied test file couldn’t be deleted.', 'sqlite-object-cache' ) );
325 + }
365 326
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 - }
327 + return true;
328 + }
382 329
383 - $credentials = request_filesystem_credentials( $url );
330 + /**
331 + * Initializes the WP filesystem API to be ready for use
332 + *
333 + * @param string $url The URL to post the form to.
334 + * @param bool $silent Whether to ask the user for credentials if necessary or not.
335 + *
336 + * @return bool
337 + * @author Till Krüss
338 + *
339 + */
340 + public function initialize_filesystem( $url, $silent = false ) {
341 + require_once ABSPATH . 'wp-admin/includes/file.php';
342 + if ( $silent ) {
343 + ob_start();
344 + }
384 345
385 - if ( false === $credentials ) {
386 - if ( $silent ) {
387 - ob_end_clean();
388 - }
346 + $credentials = request_filesystem_credentials( $url );
389 347
390 - return false;
391 - }
348 + if ( false === $credentials ) {
349 + if ( $silent ) {
350 + ob_end_clean();
351 + }
392 352
393 - if ( ! WP_Filesystem( $credentials ) ) {
394 - request_filesystem_credentials( $url );
353 + return false;
354 + }
395 355
396 - if ( $silent ) {
397 - ob_end_clean();
398 - }
356 + if ( ! WP_Filesystem( $credentials ) ) {
357 + request_filesystem_credentials( $url );
399 358
400 - return false;
401 - }
359 + if ( $silent ) {
360 + ob_end_clean();
361 + }
402 362
403 - return true;
404 - }
363 + return false;
364 + }
405 365
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 - }
366 + return true;
367 + }
416 368
417 - $has = $this->has_sqlite();
418 - if ( ( true === $has ) && $this->object_cache_dropin_needs_updating() ) {
419 - add_action( 'shutdown', [ $this, 'update_dropin' ] );
420 - }
421 - }
369 + /**
370 + * Calls the drop-in update method if necessary
371 + *
372 + * @return void
373 + * @author Till Krüss
374 + */
375 + public function maybe_update_dropin() {
376 + if ( defined( 'WP_SQLITE_OBJECT_CACHE_DISABLE_DROPIN_AUTOUPDATE' ) && WP_SQLITE_OBJECT_CACHE_DISABLE_DROPIN_AUTOUPDATE ) {
377 + return;
378 + }
422 379
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 - }
380 + $has = $this->has_sqlite();
381 + if ( ( true === $has ) && $this->object_cache_dropin_needs_updating() ) {
382 + add_action( 'shutdown', array( $this, 'update_dropin' ) );
383 + }
384 + }
433 385
434 - $dropin = get_plugin_data( $this->dropinfiledest );
435 - $plugin = get_plugin_data( $this->dropinfilesource );
386 + /**
387 + * Checks if the `object-cache.php` drop-in is outdated
388 + * @return bool
389 + * @author Till Krüss
390 + *
391 + */
392 + public function object_cache_dropin_needs_updating() {
393 + global $wp_object_cache;
394 + if ( ! method_exists( $wp_object_cache, 'dropin_get_version' ) ) {
395 + return true;
396 + }
397 + return version_compare( $wp_object_cache->dropin_get_version(), $this->_version, '<' );
398 + }
436 399
437 - if ( $dropin['PluginURI'] === $plugin['PluginURI'] ) {
438 - return version_compare( $dropin['Version'], $plugin['Version'], '<' );
439 - }
400 + /**
401 + * Updates the `object-cache.php` drop-in
402 + *
403 + * @return void
404 + * @author Till Krüss
405 + */
406 + public function update_dropin() {
407 + global $wp_filesystem;
408 + $has = $this->has_sqlite();
409 + if ( true === $has && $this->initialize_filesystem( '', true ) ) {
440 410
441 - return false;
442 - }
411 + $this->delete_sqlite_files();
412 + $result = $wp_filesystem->copy( $this->dropinfilesource, $this->dropinfiledest, true, FS_CHMOD_FILE );
413 + /**
414 + * Fires on cache enable event
415 + *
416 + * @param bool $result Whether the filesystem event (copy of the `object-cache.php` file) was successful.
417 + *
418 + * @since 0.1.0
419 + */
420 + do_action( 'sqlite_object_cache_update_dropin', $result );
421 + }
422 + }
443 423
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 - }
424 + /**
425 + * Validates the `object-cache.php` drop-in
426 + *
427 + * @return bool
428 + * @author Till Krüss
429 + */
430 + public function validate_object_cache_dropin() {
431 + global $wp_object_cache;
453 432
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 );
433 + if ( ! method_exists( $wp_object_cache, 'dropin_get_version' ) ) {
434 + return false;
435 + }
465 436
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 - }
437 + $dropin = get_plugin_data( $this->dropinfiledest );
438 + $plugin = get_plugin_data( $this->dropinfilesource );
476 439
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;
440 + /**
441 + * Filters the drop-in validation state
442 + *
443 + * @param bool $state The validation state of the drop-in.
444 + * @param string $dropin The `PluginURI` of the drop-in.
445 + * @param string $plugin The `PluginURI` of the plugin.
446 + *
447 + * @since 2.0.16
448 + */
449 + return apply_filters(
450 + 'sqlite_object_cache_validate_dropin',
451 + $dropin['PluginURI'] === $plugin['PluginURI'],
452 + $dropin['PluginURI'],
453 + $plugin['PluginURI']
454 + );
455 + }
487 456
488 - wp_unschedule_hook( self::CLEAN_EVENT_HOOK );
457 + /**
458 + * Plugin deactivation hook
459 + *
460 + * @param string $plugin Plugin basename.
461 + *
462 + * @return void
463 + * @author Till Krüss
464 + */
465 + public function on_deactivation( $plugin ) {
489 466
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 - }
467 + wp_cache_flush();
494 468
495 - $dont_delete_db_file =
496 - defined( 'WP_SQLITE_OBJECT_CACHE_DB_FILE_DISABLE_DELETE' ) && WP_SQLITE_OBJECT_CACHE_DB_FILE_DISABLE_DELETE;
469 + wp_unschedule_hook( self::CLEAN_EVENT_HOOK );
470 + $this->delete_sqlite_files();
471 + $this->delete_dropin();
472 + $this->delete_all_transients_from_db();
473 + }
497 474
498 - ob_start();
475 + private function delete_sqlite_files() {
476 + global $wp_filesystem;
477 + global $wp_object_cache;
499 478
500 - wp_cache_flush();
479 + if ( method_exists( $wp_object_cache, 'sqlite_files' ) ) {
480 + if ( $this->initialize_filesystem( '', true ) ) {
481 + foreach ( $wp_object_cache->sqlite_files() as $file ) {
482 + $wp_filesystem->delete( $file );
483 + }
484 + }
485 + }
486 + }
501 487
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 - }
488 + private function delete_dropin() {
489 + global $wp_filesystem;
490 + ob_start();
508 491
509 - ob_end_clean();
510 - }
492 + if ( $this->validate_object_cache_dropin() && $this->initialize_filesystem( '', true ) ) {
493 + $wp_filesystem->delete( $this->dropinfiledest );
494 + }
511 495
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 - }
496 + ob_end_clean();
497 + }
522 498
523 - $dropin = get_plugin_data( $this->dropinfiledest );
524 - $plugin = get_plugin_data( $this->dropinfilesource );
499 + /**
500 + * Deletes all transients from the database.
501 + *
502 + * It is necessary to do this when deactivating a persistent object cache
503 + * because the transients are kept there, and the ones in the database are
504 + * possibly stale.
505 + *
506 + * This hammers performance. But deactivating a persistent object cache is
507 + * not a common operation.
508 + *
509 + * @global wpdb $wpdb WordPress database abstraction object.
510 + */
511 + public function delete_all_transients_from_db() {
512 + global $wpdb;
513 + if ( ! is_multisite() ) {
514 + $this->clear_blog_transients();
515 + // Single site stores site transients in the options table.
516 + $this->clear_blog_transients( '_site_transient_' );
517 + } else {
518 + foreach ( get_sites( array( 'number' => 0, 'fields' => 'ids', 'no_found_rows' => true, 'orderby' => false ) ) as $site_id ) {
519 + try {
520 + switch_to_blog( $site_id );
521 + $this->clear_blog_transients();
522 + } catch ( Exception $ex ) {
523 + /* Avoid crashes on transient clearing */
524 + error_log( 'SQLite Object Cache problem clearing transients. Blog ' . $site_id . ':' . $ex->getMessage() );
525 + } finally {
526 + restore_current_blog();
527 + }
528 + }
529 + // Multisite stores site transients in the sitemeta table.
530 + $wpdb->query(
531 + $wpdb->prepare(
532 + "DELETE FROM {$wpdb->sitemeta} WHERE a.meta_key LIKE %s",
533 + $wpdb->esc_like( '_site_transient_' ) . '%'
534 + )
535 + );
536 + }
537 + }
525 538
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 - }
539 + /**
540 + * DELETE transients from the current blog's options table.
541 + *
542 + * @param string $tag _site_transient_ or _transient_, the default.
543 + *
544 + * @return void
545 + */
546 + private function clear_blog_transients( $tag = '_transient_') {
547 + global $wpdb;
548 + $wpdb->query(
549 + $wpdb->prepare(
550 + "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
551 + $wpdb->esc_like( $tag ) . '%'
552 + )
553 + );
554 + }
555 +
556 + /**
557 + * @return bool True if APCu support is activated for this plugin.
558 + */
559 + public function apcu_is_activated() {
560 + return defined( 'WP_SQLITE_OBJECT_CACHE_APCU' ) && WP_SQLITE_OBJECT_CACHE_APCU
561 + && $this->apcu_extension_is_enabled();
562 + }
563 +
564 + /**
565 + * @return bool True if the APCu extension is loaded and enabled.
566 + */
567 + public function apcu_extension_is_enabled() {
568 + return function_exists( 'apcu_enabled' ) && apcu_enabled();
569 + }
570 +
571 + /**
572 + * Make sure WP_SQLITE_OBJECT_CACHE_APCU and $option['use_apcu'] match.
573 + *
574 + * @param bool $unconditional true if we should always change the option to match WP_SQLITE_OBJECT_CACHE_APCU.
575 + *
576 + * @return string 'on' or 'off': the current activation state of epcu.
577 + */
578 + public function sync_apcu_global_to_option( $unconditional = true ) {
579 + global $wp_object_cache;
580 + $option = get_option( $this->_token . '_settings', array() );
581 + $option_dirty = false;
582 + $updated_flag = array_key_exists( 'use_apcu_updated', $option );
583 + $use_apcu = array_key_exists( 'use_apcu', $option ) && 'on' === $option['use_apcu'] ? 'on' : 'off';
584 + if ( $updated_flag ) {
585 + unset ( $option['use_apcu_updated'] );
586 + $option_dirty = true;
587 + }
588 + $target_use_apcu = $this->apcu_is_activated() ? 'on' : 'off';
589 + if ( ! $unconditional && $updated_flag ) {
590 + $target_use_apcu = $use_apcu;
591 + }
592 + if ( $target_use_apcu !== $use_apcu ) {
593 + $option['use_apcu'] = $target_use_apcu;
594 + $option_dirty = true;
595 + }
596 + if ( $option_dirty ) {
597 + if ( method_exists( $wp_object_cache, 'apcu-clear_cache' ) ) {
598 + $wp_object_cache->apcu_clear_cache();
599 + }
600 + update_option( $this->_token . '_settings', $option, true );
601 + }
602 +
603 + return $target_use_apcu;
604 + }
605 +
542 606 }