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