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-settings.php +928 -513 1.2.21.6.0 View file →
@@ -5,9 +5,9 @@
5 5 * @package SQLite Object Cache
6 6 */
7 7
8 8 if ( ! defined( 'ABSPATH' ) ) {
9 - exit;
9 + exit;
10 10 }
11 11
12 12 /**
13 13 * Only make one instance of this, please.
@@ -15,592 +15,1007 @@
15 15 * Settings class.
16 16 */
17 17 class SQLite_Object_Cache_Settings {
18 18
19 - /**
20 - * The main plugin object.
21 - *
22 - * @var object
23 - */
24 - public $parent;
19 + static $statistics_help_url = 'https://www.plumislandmedia.net/wordpress-plugins/sqlite-object-cache/statistics-from-sqlite-object-cache/';
25 20
26 - /**
27 - * Prefix for plugin settings.
28 - *
29 - * @var string
30 - */
31 - public $base = '';
21 + /**
22 + * The main plugin object.
23 + *
24 + * @var object
25 + */
26 + public $parent;
32 27
33 - /**
34 - * Available settings for plugin.
35 - *
36 - * @var array
37 - */
38 - public $settings = [];
28 + /**
29 + * Prefix for plugin settings.
30 + *
31 + * @var string
32 + */
33 + public $base = '';
39 34
40 - /**
41 - * SQLite3 is available.
42 - *
43 - * @var string|bool If it's true, we're good. If it's a string, an explanation.
44 - */
45 - public $has = '';
35 + /**
36 + * Available settings for plugin.
37 + *
38 + * @var array
39 + */
40 + public $settings = array();
46 41
47 - /**
48 - * Constructor function.
49 - *
50 - * @param object $parent Parent object.
51 - */
52 - public function __construct( $parent ) {
53 - $this->parent = $parent;
54 - $this->has = $parent->has_sqlite();
55 - $this->base = 'sqlite_object_cache_';
42 + /**
43 + * SQLite3 is available.
44 + *
45 + * @var string|bool If it's true, we're good. If it's a string, an explanation.
46 + */
47 + public $has = '';
56 48
57 - // Initialise settings.
58 - add_action( 'init', [ $this, 'init_settings' ], 11 );
49 + /**
50 + * @var string The plugin file name.
51 + */
52 + private $plugin_file;
53 + /**
54 + * @var mixed
55 + */
56 + private $caught_option_value = false;
59 57
60 - // Register plugin settings.
61 - add_action( 'admin_init', [ $this, 'register_my_settings' ] );
58 + /**
59 + * Constructor function.
60 + *
61 + * @param object $parent Parent object.
62 + * @param string $plugin_file Plugin top-level file name.
63 + */
64 + public function __construct( $parent, $plugin_file ) {
65 + $this->parent = $parent;
66 + $this->has = $parent->has_sqlite();
67 + $this->base = 'sqlite_object_cache_';
68 + $this->plugin_file = $plugin_file;
62 69
63 - // Add settings page to menu.
64 - add_action( 'admin_menu', [ $this, 'add_menu_item' ] );
70 + // Register plugin settings.
71 + add_action( 'admin_init', array( $this, 'register_my_settings' ) );
65 72
66 - // Add settings link to plugins page.
67 - add_filter(
68 - 'plugin_action_links_' . plugin_basename( $this->parent->file ),
69 - [
70 - $this,
71 - 'add_settings_link',
72 - ]
73 - );
73 + if ( is_main_site() ) {
74 + // Information for Site Health Info
75 + add_filter( 'debug_information', array( $this, 'debug_information' ) );
74 76
75 - // Configure placement of plugin settings page. See readme for implementation.
76 - add_filter( $this->base . 'menu_settings', [ $this, 'configure_settings' ] );
77 + // Add settings page to menu.
78 + add_action( 'admin_menu', array( $this, 'add_menu_item' ) );
77 79
78 - // Load admin JS & CSS.
79 - add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ], 10, 1 );
80 - }
80 + // Add settings link to plugins page.
81 + add_filter(
82 + 'plugin_action_links_' . plugin_basename( $this->parent->file ),
83 + array(
84 + $this,
85 + 'add_settings_link',
86 + )
87 + );
88 + }
81 89
82 - /**
83 - * Initialize settings
84 - *
85 - * @return void
86 - */
87 - public function init_settings() {
88 - $this->settings = $this->settings_fields();
89 - }
90 + // For multisite, propagate the option value to all subsites.
91 + if ( is_multisite() ) {
92 + add_action( 'update_option_' . $this->parent->_token . '_settings', array( $this, 'catch_option' ), 20, 3 );
93 + }
90 94
91 - /**
92 - * Build settings fields
93 - *
94 - * @return array Fields to be displayed on settings page
95 - */
96 - private function settings_fields() {
95 + // Configure placement of plugin settings page. See readme for implementation.
96 + add_filter( $this->base . 'menu_settings', array( $this, 'configure_settings' ) );
97 97
98 - $settings['standard'] = [
99 - 'title' => __( 'Settings', 'sqlite-object-cache' ),
100 - 'submit' => __( 'Save Settings', 'sqlite-object-cache' ),
101 - 'description' => '',
102 - 'render_section_header' => [ $this, 'settings_section_header' ],
103 - 'form_post_callback' => [ $this, 'validate_settings' ],
104 - 'fields' => [
105 - [
106 - 'id' => 'flush',
107 - 'label' => __( 'Flush now', 'sqlite-object-cache' ),
108 - 'description' => __( 'Check to flush the cache (delete all its entries).', 'sqlite-object-cache' ),
109 - 'type' => 'checkbox',
110 - 'default' => '',
111 - 'reset' => '',
112 - ],
113 - [
114 - 'id' => 'retention',
115 - 'label' => __( 'Cached data expires after', 'sqlite-object-cache' ),
116 - 'description' => __( 'hours.', 'sqlite-object-cache' ),
117 - 'type' => 'number',
118 - 'default' => 24,
119 - 'max' => 24 * 7,
120 - 'min' => 1,
121 - 'step' => 'any',
122 - 'cssclass' => 'narrow',
123 - 'placeholder' => __( 'Hours to retain.', 'sqlite-object-cache' ),
124 - ],
125 - [
126 - 'id' => 'cleanup',
127 - 'label' => __( 'Clean up now', 'sqlite-object-cache' ),
128 - 'description' => __( 'Check to clean up the cache (delete expired data).', 'sqlite-object-cache' ),
129 - 'type' => 'checkbox',
130 - 'default' => '',
131 - 'reset' => '',
132 - ],
133 - [
134 - 'id' => 'capture',
135 - 'label' => __( 'Measure performance', 'sqlite-object-cache' ),
136 - 'description' => __( 'Check to measure cache performance. ', 'sqlite-object-cache' ),
137 - 'type' => 'checkbox',
138 - 'default' => '',
139 - ],
140 - [
141 - 'id' => 'samplerate',
142 - 'label' => __( 'Measure', 'sqlite-object-cache' ),
143 - 'description' => __( 'percent of requests, randomly sampled.', 'sqlite-object-cache' ),
144 - 'type' => 'number',
145 - 'default' => 1,
146 - 'max' => 100,
147 - 'min' => 0,
148 - 'step' => 'any',
149 - 'cssclass' => 'narrow',
150 - 'placeholder' => __( 'Sampling percentage.', 'sqlite-object-cache' ),
151 - ],
152 - [
153 - 'id' => 'retainmeasurements',
154 - 'label' => __( 'Retain measurements for', 'sqlite-object-cache' ),
155 - 'description' => __( 'hours.', 'sqlite-object-cache' ),
156 - 'type' => 'number',
157 - 'default' => 2,
158 - 'min' => 0.1,
159 - 'step' => 'any',
160 - 'cssclass' => 'narrow',
161 - 'placeholder' => __( 'Hours to retain.', 'sqlite-object-cache' ),
162 - ],
163 - ],
164 - ];
98 + // Spoonsor link.
99 + add_filter( 'plugin_row_meta', array( $this, 'filter_plugin_row_meta' ), 10, 2 );
165 100
166 - $settings['stats'] = [
167 - 'title' => __( 'Statistics', 'sqlite-object-cache' ),
168 - 'submit' => __( 'Reset Statistics', 'sqlite-object-cache' ),
169 - 'render_section_header' => [ $this, 'stats_section_header' ],
170 - 'form_post_callback' => [ $this, 'validate_reset_stats' ],
171 - ];
101 + }
172 102
173 - return apply_filters( $this->parent->_token . '_settings_fields', $settings );
174 - }
103 + /**
104 + * Fires after the value of our option has been successfully updated.
105 + *
106 + * @param mixed $old_value The old option value.
107 + * @param mixed $value The new option value.
108 + * @param string $option Option name.
109 + */
110 + public function catch_option( $old_value, $value, $option ) {
111 + if ( false === $this->caught_option_value ) {
112 + add_action( 'shutdown', array( $this, 'propagate_option' ) );
113 + }
114 + $this->caught_option_value = $value;
115 + }
175 116
176 - /**
177 - * Validate the options presented to the Settings tab.
178 - *
179 - * This is an options update filter. It filters an option value following sanitization.
180 - *
181 - * @param mixed $option The sanitized option value.
182 - * @param string $name The option name.
183 - * @param string $original_value The original value passed to the function.
184 - *
185 - * @throws Exception Announce SQLite failure.
186 - * @since 2.3.0
187 - * @since 4.3.0 Added the `$original_value` parameter.
188 - *
189 - * @noinspection PhpUnusedParameterInspection
190 - */
191 - public function validate_settings( $option, $name, $original_value ) {
192 - if ( ! is_array( $option ) ) {
193 - /* weird. not an option array */
194 - return $option;
195 - }
196 - global $wp_object_cache;
117 + /**
118 + * Shutdown action handler to copy our option to all subsites.
119 + *
120 + * We use this rather than a site option because this gets us autoloading.
121 + *
122 + * @return void
123 + */
124 + public function propagate_option() {
125 + remove_action( 'update_option_' . $this->parent->_token . '_settings', array( $this, 'catch_option' ), 20 );
126 + $current_site = get_current_blog_id();
127 + foreach (
128 + get_sites( array(
129 + 'number' => 0,
130 + 'fields' => 'ids',
131 + 'no_found_rows' => true,
132 + 'orderby' => false
133 + ) ) as $site_id
134 + ) {
135 + if ( $site_id !== $current_site ) {
136 + try {
137 + switch_to_blog( $site_id );
138 + update_option( $this->parent->_token . '_settings', $this->caught_option_value, true );
139 + } catch ( Exception $ex ) {
140 + /* Avoid crashes on option propagation */
141 + error_log( 'SQLite Object Cache problem propagaging option value. Blog ' . $site_id . ':' . $ex->getMessage() );
142 + } finally {
143 + restore_current_blog();
144 + }
145 + }
146 + }
147 + }
197 148
198 - if ( array_key_exists( 'flush', $option ) && $option ['flush'] === 'on' ) {
199 - if ( method_exists( $wp_object_cache, 'flush' ) ) {
200 - $wp_object_cache->flush( true );
201 - }
202 - unset ( $option['flush'] );
203 - }
204 - $retention = $this->numeric_option( $option, 'retention', 7 );
205 - if ( array_key_exists( 'cleanup', $option ) && $option ['cleanup'] === 'on' ) {
206 - $this->parent->clean();
207 - unset ( $option['cleanup'] );
208 - }
149 + /**
150 + * Filters the array of row meta for each plugin in the Plugins list table.
151 + *
152 + * @param array<int, string> $plugin_meta An array of the plugin's metadata.
153 + * @param string $plugin_file Path to the plugin file relative to the plugins directory.
154 + *
155 + * @return array<int, string> Updated array of the plugin's metadata.
156 + */
157 + public function filter_plugin_row_meta( array $plugin_meta, $plugin_file ) {
158 + if ( $this->plugin_file !== $plugin_file ) {
159 + return $plugin_meta;
160 + }
209 161
210 - $this->numeric_option( $option, 'samplerate', 10 );
211 - $this->numeric_option( $option, 'retainmeasurements', 2 );
162 + if ( is_multisite() && (is_network_admin() || ! is_main_site() ) ) {
163 + $plugin_meta[] =
164 + esc_html__( 'See the main site\'s dashboard for settings.', 'sqlite-object-cache' );
165 + }
212 166
213 - if ( array_key_exists( 'capture', $option ) && $option['capture'] === 'on' ) {
214 - $option['previouscapture'] = 0;
215 - }
167 + $plugin_meta[] = sprintf(
168 + '<a href="%1$s"><span class="dashicons dashicons-star-filled" aria-hidden="true" style="font-size:14px;line-height:1.3"></span>%2$s</a>',
169 + 'https://github.com/sponsors/OllieJones',
170 + esc_html_x( 'Sponsor', 'verb', 'sqlite-object-cache' )
171 + );
216 172
217 - return $option;
218 - }
173 + return $plugin_meta;
174 + }
219 175
220 - /**
221 - * Retrieve a numeric option, and set the default if it isn't present.
222 - *
223 - * @param array $option Option array value.
224 - * @param string $name Name of the element in the option array.
225 - * @param mixed $default Default value to use.
226 - *
227 - * @return float|int|mixed|string
228 - */
229 - private function numeric_option( &$option, $name, $default ) {
230 - $result = $default;
231 - if ( array_key_exists( $name, $option ) && is_numeric( $option[ $name ] ) ) {
232 - $result = $option[ $name ];
233 - $result = $result ?: $default;
234 - } else {
235 - $option[ $name ] = $default;
236 - }
176 + /**
177 + * Build settings fields
178 + *
179 + * @return array Fields to be displayed on settings page
180 + */
181 + private function settings_fields() {
237 182
238 - return $result;
239 - }
183 + $fields = array(
184 + array(
185 + 'id' => 'flush',
186 + 'label' => __( 'Flush now', 'sqlite-object-cache' ),
187 + 'description' => __( 'Check to flush the cache (delete all its entries) now.', 'sqlite-object-cache' ) . ' ' .
188 + __( 'This briefly puts your site into maintenance mode.', 'sqlite-object-cache' ),
189 + 'type' => 'checkbox',
190 + 'default' => '',
191 + 'reset' => '',
192 + ),
193 + array(
194 + 'id' => 'vacuum',
195 + 'label' => __( 'Vacuum now', 'sqlite-object-cache' ),
196 + 'description' => __( 'Check to vacuum (defragment) the cache now.', 'sqlite-object-cache' ) . ' ' .
197 + __( 'This briefly puts your site into maintenance mode.', 'sqlite-object-cache' ),
198 + 'type' => 'checkbox',
199 + 'default' => '',
200 + 'reset' => '',
201 + ),
202 + array(
203 + 'id' => 'target_size',
204 + 'label' => __( 'Cached data size', 'sqlite-object-cache' ),
205 + 'description' => __( 'MiB. When data in the cache grows larger than this, hourly cleanup removes the oldest entries.', 'sqlite-object-cache' ),
206 + 'type' => 'number',
207 + 'default' => 16,
208 + 'min' => 1,
209 + 'step' => 'any',
210 + 'cssclass' => 'narrow',
211 + 'placeholder' => __( 'MiB.', 'sqlite-object-cache' ),
212 + ),
213 + array(
214 + 'id' => 'cleanup',
215 + 'label' => __( 'Clean up now', 'sqlite-object-cache' ),
216 + 'description' => __( 'Check to clean up the cache (delete old data) now.', 'sqlite-object-cache' ),
217 + 'type' => 'checkbox',
218 + 'default' => '',
219 + 'reset' => '',
220 + ),
221 + array(
222 + 'id' => 'capture',
223 + 'label' => __( 'Measure performance', 'sqlite-object-cache' ),
224 + 'description' => __( 'Check to enable cache performance measurement. ', 'sqlite-object-cache' ),
225 + 'type' => 'checkbox',
226 + 'default' => '',
227 + ),
228 + array(
229 + 'id' => 'samplerate',
230 + 'label' => __( 'Measure', 'sqlite-object-cache' ),
231 + 'description' => __( 'percent of requests, randomly sampled.', 'sqlite-object-cache' ),
232 + 'type' => 'number',
233 + 'default' => 1,
234 + 'max' => 100,
235 + 'min' => 0,
236 + 'step' => 'any',
237 + 'cssclass' => 'narrow',
238 + 'placeholder' => __( 'Sampling percentage.', 'sqlite-object-cache' ),
239 + ),
240 + array(
241 + 'id' => 'retainmeasurements',
242 + 'label' => __( 'Retain measurements for', 'sqlite-object-cache' ),
243 + 'description' => __( 'hours.', 'sqlite-object-cache' ),
244 + 'type' => 'number',
245 + 'default' => 2,
246 + 'min' => 0.1,
247 + 'step' => 'any',
248 + 'cssclass' => 'narrow',
249 + 'placeholder' => __( 'Hours to retain.', 'sqlite-object-cache' ),
250 + )
251 + );
240 252
241 - /**
242 - * Filters an option value following sanitization.
243 - *
244 - * @param string $option The sanitized option value.
245 - * @param string $name The option name.
246 - * @param string $original_value The original value passed to the function.
247 - *
248 - * @throws Exception Announce SQLite failure.
249 - * @since 2.3.0
250 - * @since 4.3.0 Added the `$original_value` parameter.
251 - *
252 - * @noinspection PhpUnusedParameterInspection
253 - */
254 - public function validate_reset_stats( $option, $name, $original_value ) {
253 + if ( $this->parent->apcu_extension_is_enabled() ) {
254 + array_unshift( $fields, array(
255 + 'id' => 'use_apcu',
256 + 'label' => __( 'Use APCu', 'sqlite-object-cache' ),
257 + 'description' => __( 'Check to enable the use of php\'s APCu cache to improve performance.', 'sqlite-object-cache' ),
258 + 'type' => 'checkbox',
259 + 'default' => '',
260 + ) );
255 261
256 - global $wp_object_cache;
257 - if ( method_exists( $wp_object_cache, 'sqlite_reset_statistics' ) ) {
258 - $wp_object_cache->sqlite_reset_statistics();
259 - }
262 + }
260 263
261 - /* for this post operation, we don't want to change any plugin options. */
264 + $settings['standard'] = array(
265 + 'title' => __( 'Settings' ),
266 + 'submit' => __( 'Save Changes' ),
267 + 'description' => '',
268 + 'render_section_header' => array( $this, 'settings_section_header' ),
269 + 'form_post_callback' => array( $this, 'validate_settings' ),
270 + 'fields' => $fields,
271 + );
262 272
263 - return get_option( $name );
264 - }
273 + $settings['stats'] = array(
274 + 'title' => __( 'Statistics', 'sqlite-object-cache' ),
275 + 'submit' => __( 'Reset Statistics', 'sqlite-object-cache' ),
276 + 'render_section_header' => array( $this, 'stats_section_header' ),
277 + 'form_post_callback' => array( $this, 'validate_reset_stats' ),
278 + );
265 279
266 - /**
267 - * Add settings page to admin menu
268 - *
269 - * @return void
270 - */
271 - public function add_menu_item() {
280 + return apply_filters( $this->parent->_token . '_settings_fields', $settings );
281 + }
272 282
273 - $args = $this->menu_settings();
283 + /**
284 + * Validate the options presented to the Settings tab.
285 + *
286 + * This is an options update filter. It filters an option value following sanitization.
287 + *
288 + * @param mixed $option The sanitized option value.
289 + * @param string $name The option name.
290 + * @param string $original_value The original value passed to the function.
291 + *
292 + * @throws Exception Announce SQLite failure.
293 + * @since 2.3.0
294 + * @since 4.3.0 Added the `$original_value` parameter.
295 + *
296 + * @noinspection PhpUnusedParameterInspection
297 + */
298 + public function validate_settings( $option, $name, $original_value ) {
299 + if ( ! is_array( $option ) ) {
300 + /* Weird. not an option array */
301 + return $option;
302 + }
303 + global $wp_object_cache;
304 + $former_value = get_option( $name, array() );
274 305
275 - // Do nothing if wrong location key is set.
276 - if ( is_array( $args ) && isset( $args['location'] ) && function_exists( 'add_' . $args['location'] . '_page' ) ) {
277 - switch ( $args['location'] ) {
278 - case 'options':
279 - case 'submenu':
280 - $page =
281 - add_submenu_page( $args['parent_slug'], $args['page_title'], $args['menu_title'], $args['capability'], $args['menu_slug'], $args['function'] );
282 - break;
283 - case 'menu':
284 - $page =
285 - add_menu_page( $args['page_title'], $args['menu_title'], $args['capability'], $args['menu_slug'], $args['function'], $args['icon_url'], $args['position'] );
286 - break;
287 - default:
288 - return;
289 - }
290 - add_action( 'admin_print_styles-' . $page, [ $this, 'settings_assets' ] );
291 - }
292 - }
306 + /* Opt in or opt out of the APCu. */
307 + if ( $this->parent->apcu_extension_is_enabled() ) {
308 + $apcu_choice = array_key_exists( 'use_apcu', $option ) && $option ['use_apcu'] === 'on';
309 + $apcu_former = array_key_exists( 'use_apcu', $former_value ) && $former_value ['use_apcu'] === 'on';
310 + if ( $apcu_choice !== $apcu_former ) {
311 + if ( method_exists( $wp_object_cache, 'apcu_clear_cache' ) ) {
312 + $wp_object_cache->apcu_clear_cache();
313 + }
314 + $this->update_wp_config_constant( 'WP_SQLITE_OBJECT_CACHE_APCU', $apcu_choice );
315 + $option['use_apcu_updated'] = true;
316 + }
317 + $option['use_apcu'] = $apcu_choice ? 'on' : 'off';
318 + }
319 + if ( array_key_exists( 'flush', $option ) && $option ['flush'] === 'on' ) {
320 + if ( method_exists( $wp_object_cache, 'flush' ) ) {
321 + try {
322 + $this->enter_maintenance_mode();
323 + $wp_object_cache->flush( true );
324 + } finally {
325 + $this->exit_maintenance_mode();
326 + }
327 + } else {
328 + wp_cache_flush();
329 + }
330 + unset ( $option['flush'] );
331 + }
332 + if ( array_key_exists( 'vacuum', $option ) && $option ['vacuum'] === 'on' ) {
333 + if ( method_exists( $wp_object_cache, 'vacuum' ) ) {
334 + try {
335 + $this->enter_maintenance_mode();
336 + $wp_object_cache->vacuum();
337 + } finally {
338 + $this->exit_maintenance_mode();
339 + }
340 + } else {
341 + wp_cache_flush();
342 + }
343 + unset ( $option['vacuum'] );
344 + }
345 + if ( array_key_exists( 'cleanup', $option ) && $option ['cleanup'] === 'on' ) {
346 + $this->parent->clean_job();
347 + unset ( $option['cleanup'] );
348 + }
293 349
294 - /**
295 - * Prepare default settings page arguments
296 - *
297 - * @return mixed|void
298 - */
299 - private function menu_settings() {
300 - $this->complain_if_sqlite3_unavailable();
350 + $this->numeric_option( $option, 'target_size', 4 );
351 + $this->numeric_option( $option, 'samplerate', 10 );
352 + $this->numeric_option( $option, 'retainmeasurements', 2 );
301 353
302 - return apply_filters(
303 - $this->base . 'menu_settings',
304 - [
305 - 'location' => 'options', // Possible settings: options, menu, submenu.
306 - 'parent_slug' => 'options-general.php',
307 - 'page_title' => __( 'SQLite Persistent Object Cache', 'sqlite-object-cache' ),
308 - 'menu_title' => __( 'Object Cache', 'sqlite-object-cache' ),
309 - 'capability' => 'manage_options',
310 - 'menu_slug' => $this->parent->_token . '_settings',
311 - 'function' => [ $this, 'settings_page' ],
312 - 'icon_url' => '',
313 - 'position' => null,
314 - ]
315 - );
316 - }
354 + if ( array_key_exists( 'capture', $option ) && $option['capture'] === 'on' ) {
355 + $option['previouscapture'] = 0;
356 + }
317 357
318 - /**
319 - * Emit a message if SQLite3 is not available.
320 - *
321 - * @param bool $verbose False means emit a notice. True means emit a longer explanation.
322 - *
323 - * @return void
324 - */
325 - private function complain_if_sqlite3_unavailable( $verbose = false ) {
326 - if ( true !== $this->has ) {
327 - if ( ! $verbose ) {
328 - echo '<div class="notice notice-error sql-object-cache">';
329 - echo esc_html( $this->has );
330 - echo '</div>';
331 - } else {
332 - echo '<div>';
333 - echo '<h3 style="color: #d63638">' . esc_html__( 'Server configuration problem', 'sqlite-object-cache' ) . '</h3>';
334 - echo '<p>';
358 + return $option;
359 + }
335 360
336 - if ( ! class_exists( 'SQLite3' ) || ! extension_loaded( 'sqlite3' ) ) {
337 - echo esc_html__( 'The SQLite Persistent Object Cache plugin requires php\'s SQLite3 extension.', 'sqlite-object-cache' ) . ' ';
338 - echo esc_html__( 'That extension is not installed in your server, so the plugin cannot work.', 'sqlite-object-cache' ) . ' ';
339 - }
361 + /**
362 + * Retrieve a numeric option, and set the default if it isn't present.
363 + *
364 + * @param array $option Option array value.
365 + * @param string $name Name of the element in the option array.
366 + * @param mixed $default Default value to use.
367 + *
368 + * @return float|int|mixed|string
369 + */
370 + private function numeric_option( &$option, $name, $default ) {
371 + $result = $default;
372 + if ( array_key_exists( $name, $option ) && is_numeric( $option[ $name ] ) ) {
373 + $result = $option[ $name ];
374 + $result = $result ?: $default;
375 + } else {
376 + $option[ $name ] = $default;
377 + }
340 378
341 - $sqlite_version = $this->parent->sqlite_get_version();
342 - if ( version_compare( $sqlite_version, $this->parent->minimum_sqlite_version ) < 0 ) {
343 - echo esc_html( sprintf(
344 - /* translators: 1 actual SQLite version. 2 required SQLite version) */
345 - __( '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' ),
346 - $sqlite_version, $this->parent->minimum_sqlite_version ) );
347 - }
379 + return $result;
380 + }
348 381
349 - echo '</p></div>' . PHP_EOL;
350 - }
351 - }
352 - }
382 + /**
383 + * Filters an option value following sanitization.
384 + *
385 + * @param string $option The sanitized option value.
386 + * @param string $name The option name.
387 + * @param string $original_value The original value passed to the function.
388 + *
389 + * @throws Exception Announce SQLite failure.
390 + * @since 2.3.0
391 + * @since 4.3.0 Added the `$original_value` parameter.
392 + *
393 + * @noinspection PhpUnusedParameterInspection
394 + */
395 + public function validate_reset_stats( $option, $name, $original_value ) {
353 396
354 - /**
355 - * Container for settings page arguments
356 - *
357 - * @param array $settings Settings array.
358 - *
359 - * @return array
360 - */
361 - public function configure_settings( $settings = [] ) {
362 - return $settings;
363 - }
397 + global $wp_object_cache;
398 + if ( method_exists( $wp_object_cache, 'sqlite_reset_statistics' ) ) {
399 + $wp_object_cache->sqlite_reset_statistics();
400 + }
364 401
365 - /**
366 - * Load settings JS & CSS
367 - *
368 - * @return void
369 - */
370 - public function settings_assets() {
402 + /* for this post operation, we don't want to change any plugin options. */
371 403
372 - }
404 + return get_option( $name );
405 + }
373 406
374 - /**
375 - * Add settings link to plugin list table
376 - *
377 - * @param array $links Existing links.
378 - *
379 - * @return array Modified links.
380 - */
381 - public function add_settings_link( $links ) {
382 - if ( true !== $this->has ) {
383 - $settings_link =
384 - '<a style="color: #d63638" href="options-general.php?page=' . $this->parent->_token . '_settings">' . __( 'Settings', 'sqlite-object-cache' ) . '</a>';
385 - array_unshift( $links, $settings_link );
386 - } else {
387 - $settings_link =
388 - '<a href="options-general.php?page=' . $this->parent->_token . '_settings">' . __( 'Settings', 'sqlite-object-cache' ) . '</a>';
389 - $statistics_link =
390 - '<a href="options-general.php?page=' . $this->parent->_token . '_settings&tab=stats">' . __( 'Statistics', 'sqlite-object-cache' ) . '</a>';
391 - array_unshift( $links, $settings_link, $statistics_link );
392 - }
407 + /**
408 + * Add settings page to admin menu
409 + *
410 + * @return void
411 + */
412 + public function add_menu_item() {
393 413
394 - return $links;
395 - }
414 + $args = $this->menu_settings();
396 415
397 - /**
398 - * Register plugin settings
399 - *
400 - * @return void
401 - */
402 - public function register_my_settings() {
403 - if ( is_array( $this->settings ) ) {
416 + // Do nothing if wrong location key is set.
417 + if ( is_array( $args ) && isset( $args['location'] ) && function_exists( 'add_' . $args['location'] . '_page' ) ) {
418 + switch ( $args['location'] ) {
419 + case 'options':
420 + case 'submenu':
421 + $page =
422 + add_submenu_page( $args['parent_slug'], $args['page_title'], $args['menu_title'], $args['capability'], $args['menu_slug'], $args['function'] );
423 + break;
424 + case 'menu':
425 + $page =
426 + add_menu_page( $args['page_title'], $args['menu_title'], $args['capability'], $args['menu_slug'], $args['function'], $args['icon_url'], $args['position'] );
427 + break;
428 + default:
429 + return;
430 + }
431 + add_action( 'admin_print_styles-' . $page, array( $this, 'settings_assets' ) );
432 + }
433 + }
404 434
405 - /* get the tab chosen by the user ('standard' or 'stats') */
406 - $tab = isset ( $_REQUEST['tab'] ) ? sanitize_key( $_REQUEST['tab'] ) : 'standard';
435 + /**
436 + * Prepare default settings page arguments
437 + *
438 + * @return mixed|void
439 + */
440 + private function menu_settings() {
441 + $this->complain_if_sqlite3_unavailable();
407 442
408 - $default_option = [];
409 - $option_name = $this->base . 'settings';
443 + return apply_filters(
444 + $this->base . 'menu_settings',
445 + array(
446 + 'location' => 'options', // Possible settings: options, menu, submenu.
447 + 'parent_slug' => 'options-general.php',
448 + 'page_title' => __( 'SQLite Persistent Object Cache', 'sqlite-object-cache' ),
449 + 'menu_title' => __( 'Object Cache', 'sqlite-object-cache' ),
450 + 'capability' => 'manage_options',
451 + 'menu_slug' => $this->parent->_token . '_settings',
452 + 'function' => array( $this, 'settings_page' ),
453 + 'icon_url' => '',
454 + 'position' => null,
455 + )
456 + );
457 + }
410 458
411 - foreach ( $this->settings as $section => $data ) {
459 + /**
460 + * Emit a message if SQLite3 is not available.
461 + *
462 + * @param bool $verbose False means emit a notice. True means emit a longer explanation.
463 + *
464 + * @return void
465 + */
466 + private function complain_if_sqlite3_unavailable( $verbose = false ) {
467 + if ( true !== $this->has ) {
468 + if ( ! $verbose ) {
469 + echo '<div class="notice notice-error sql-object-cache">';
470 + echo esc_html( $this->has );
471 + echo '</div>';
472 + } else {
473 + echo '<div>';
474 + echo '<h3 style="color: #d63638">' . esc_html__( 'Server configuration problem', 'sqlite-object-cache' ) . '</h3>';
475 + echo '<p>';
412 476
413 - if ( $tab && $tab !== $section ) {
414 - continue;
415 - }
477 + if ( ! class_exists( 'SQLite3' ) || ! extension_loaded( 'sqlite3' ) ) {
478 + echo esc_html__( 'The SQLite Persistent Object Cache plugin requires php\'s SQLite3 extension.', 'sqlite-object-cache' ) . ' ';
479 + echo esc_html__( 'That extension is not installed in your server, so the plugin cannot work.', 'sqlite-object-cache' ) . ' ';
480 + }
416 481
417 - $setting_args = [
418 - 'description' => $data['title'],
419 - 'default' => $default_option,
420 - ];
482 + $sqlite_version = $this->parent->sqlite_get_version();
483 + if ( version_compare( $sqlite_version, $this->parent->minimum_sqlite_version ) < 0 ) {
484 + echo esc_html( sprintf(
485 + /* translators: 1 actual SQLite version. 2 required SQLite version) */
486 + __( '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' ),
487 + $sqlite_version, $this->parent->minimum_sqlite_version ) );
488 + }
421 489
422 - // Validation callback for section.
423 - if ( isset( $data['form_post_callback'] ) ) {
424 - add_filter( "sanitize_option_$option_name", $data['form_post_callback'], 10, 3 );
425 - }
490 + echo '</p></div>' . PHP_EOL;
491 + }
492 + }
493 + }
426 494
427 - // Add section to page.
428 - add_settings_section( $section, '',
429 - $data ['render_section_header'],
430 - $this->parent->_token . '_settings' );
495 + /**
496 + * Container for settings page arguments
497 + *
498 + * @param array $settings Settings array.
499 + *
500 + * @return array
501 + */
502 + public function configure_settings( $settings = array() ) {
503 + return $settings;
504 + }
431 505
432 - if ( array_key_exists( 'fields', $data ) && is_array( $data['fields'] ) ) {
433 - foreach ( $data['fields'] as $field ) {
434 - $default_option [ $field['id'] ] = $field['default'];
435 - }
506 + /**
507 + * Load settings JS & CSS
508 + *
509 + * @return void
510 + */
511 + public function settings_assets() {
436 512
437 - foreach ( $data['fields'] as $field ) {
438 - // Add field to page.
439 - add_settings_field(
440 - $field['id'],
441 - $field['label'],
442 - [ $this->parent->admin, 'echo_field' ],
443 - $this->parent->_token . '_settings',
444 - $section,
445 - [
446 - 'field' => $field,
447 - 'option' => $option_name,
448 - ]
449 - );
450 - }
451 - }
452 - /* register the settings object */
453 - register_setting( $this->parent->_token . '_settings', $option_name, $setting_args );
454 - }
455 - }
456 - }
513 + }
457 514
458 - /**
459 - * Settings section.
460 - *
461 - * @param array $section Array of section ids.
462 - *
463 - * @return void
464 - * @throws Exception Announce Database Failure.
465 - */
466 - public function settings_section_header( $section ) {
515 + /**
516 + * Add settings link to plugin list table
517 + *
518 + * @param array $links Existing links.
519 + *
520 + * @return array Modified links.
521 + */
522 + public function add_settings_link( $links ) {
523 + if ( true !== $this->has ) {
524 + $settings_link =
525 + '<a style="color: #d63638" href="options-general.php?page=' . $this->parent->_token . '_settings">' . __( 'Settings', 'sqlite-object-cache' ) . '</a>';
526 + array_unshift( $links, $settings_link );
527 + } else {
528 + $settings_link =
529 + '<a href="options-general.php?page=' . $this->parent->_token . '_settings">' . __( 'Settings', 'sqlite-object-cache' ) . '</a>';
530 + $statistics_link =
531 + '<a href="options-general.php?page=' . $this->parent->_token . '_settings&tab=stats">' . __( 'Statistics', 'sqlite-object-cache' ) . '</a>';
532 + array_unshift( $links, $settings_link, $statistics_link );
533 + }
467 534
468 - $this->support_links();
469 - $this->versions();
535 + return $links;
536 + }
470 537
471 - if ( array_key_exists( 'description', $this->settings[ $section['id'] ] ) ) {
472 - echo '<p> ' . esc_html( $this->settings[ $section['id'] ]['description'] ) . '</p>' . PHP_EOL;
473 - }
474 - }
538 + /**
539 + * Register plugin settings
540 + *
541 + * @return void
542 + */
543 + public function register_my_settings() {
544 + $this->parent->sync_apcu_global_to_option( false );
545 + $this->settings = $this->settings_fields();
475 546
476 - /**
477 - * Display support and rating links.
478 - *
479 - * @return void
480 - */
481 - private function support_links() {
547 + if ( is_array( $this->settings ) ) {
482 548
483 - $supportUrl = "https://wordpress.org/support/plugin/sqlite-object-cache/";
484 - $reviewUrl = "https://wordpress.org/support/plugin/sqlite-object-cache/reviews/";
485 - echo '<p>';
486 - echo esc_html__( 'For support please', 'sqlite-object-cache' ) . ' ';
487 - echo '<a href="' . esc_url( $supportUrl ) . '">' . esc_html__( 'click here', 'sqlite-object-cache' ) . '</a>. ';
488 - echo esc_html__( 'To rate this plugin please', 'sqlite-object-cache' ) . ' ';
489 - echo '<a href="' . esc_url( $reviewUrl ) . '">' . esc_html__( 'click here', 'sqlite-object-cache' ) . '</a>. ';
490 - echo esc_html__( 'Your feedback helps make it better, faster, and more useful', 'sqlite-object-cache' ) . '.';
491 - echo '</p>';
492 - }
549 + /* get the tab chosen by the user ('standard' or 'stats') */
550 + $tab = isset ( $_REQUEST['tab'] ) ? sanitize_key( $_REQUEST['tab'] ) : 'standard';
493 551
494 - /**
495 - * Display versions.
496 - *
497 - * @return void
498 - * @throws Exception Announce database failure.
499 - */
500 - private function versions() {
501 - global $wp_object_cache;
502 - $igbinary = function_exists( 'igbinary_serialize' ) && function_exists( 'igbinary_unserialize' )
503 - ? __( 'available', 'sqlite-object-cache' )
504 - : __( 'unavailable', 'sqlite-object-cache' );
552 + $default_option = array();
553 + $option_name = $this->base . 'settings';
505 554
506 - if ( method_exists( $wp_object_cache, 'sqlite_get_version' ) ) {
507 - echo '<p>' . esc_html( sprintf(
508 - /* translators: 1: version for sqlite 2: version for php 3: version for plugin 4: status of igbinary */
509 - __( 'Versions: SQLite: %1$s php: %2$s Plugin: %3$s igbinary: %4$s.', 'sqlite-object-cache' ),
510 - $wp_object_cache->sqlite_get_version(),
511 - PHP_VERSION,
512 - $this->parent->_version,
513 - $igbinary ) ) . '</p>';
514 - }
515 - }
555 + foreach ( $this->settings as $section => $data ) {
516 556
517 - /**
518 - * Statistics tab section.
519 - *
520 - * @param array $section Array of section ids.
521 - *
522 - * @return void
523 - * @throws Exception Announce Database Failure.
524 - * @noinspection PhpUnusedParameterInspection
525 - */
526 - public function stats_section_header( $section ) {
557 + if ( $tab && $tab !== $section ) {
558 + continue;
559 + }
527 560
528 - $this->support_links();
529 - $this->versions();
561 + $setting_args = array(
562 + 'description' => $data['title'],
563 + 'default' => $default_option,
564 + );
530 565
531 - $stats = new SQLite_Object_Cache_Statistics ( get_option( $this->parent->_token . '_settings', [] ) );
532 - $stats->init();
533 - $stats->render();
534 - $stats->render_usage();
535 - }
566 + // Validation callback for section.
567 + if ( isset( $data['form_post_callback'] ) ) {
568 + add_filter( "sanitize_option_$option_name", $data['form_post_callback'], 10, 3 );
569 + }
536 570
537 - /**
538 - * Load settings page content.
539 - *
540 - * @return void
541 - */
542 - public function settings_page() {
571 + // Add section to page.
572 + add_settings_section( $section, '',
573 + $data ['render_section_header'],
574 + $this->parent->_token . '_settings' );
543 575
544 - /* get the tab chosen by the user ('standard' by default or 'stats') */
545 - $tab = isset ( $_REQUEST['tab'] ) ? sanitize_key( $_REQUEST['tab'] ) : 'standard';
576 + if ( array_key_exists( 'fields', $data ) && is_array( $data['fields'] ) ) {
577 + foreach ( $data['fields'] as $field ) {
578 + $default_option [ $field['id'] ] = $field['default'];
579 + }
546 580
547 - // Build page HTML.
548 - echo '<div class="wrap" id="' . esc_attr( $this->parent->_token . '_settings' ) . '">' . PHP_EOL;
549 - echo '<h2>' . esc_html__( 'SQLite Persistent Object Cache', 'sqlite-object-cache' ) . '</h2>' . PHP_EOL;
581 + foreach ( $data['fields'] as $field ) {
582 + // Add field to page.
583 + add_settings_field(
584 + $field['id'],
585 + $field['label'],
586 + array( $this->parent->admin, 'echo_field' ),
587 + $this->parent->_token . '_settings',
588 + $section,
589 + array(
590 + 'field' => $field,
591 + 'option' => $option_name,
592 + )
593 + );
594 + }
595 + }
596 + /* register the settings object */
597 + register_setting( $this->parent->_token . '_settings', $option_name, $setting_args );
598 + }
599 + }
600 + }
550 601
551 - $submit_caption = __( 'Save Settings', 'sqlite-object-cache' );
552 - $this->complain_if_sqlite3_unavailable( true );
602 + /**
603 + * Settings section.
604 + *
605 + * @param array $section Array of section ids.
606 + *
607 + * @return void
608 + * @throws Exception Announce Database Failure.
609 + */
610 + public function settings_section_header( $section ) {
553 611
554 - // Show page tabs.
555 - if ( is_array( $this->settings ) && 1 < count( $this->settings ) ) {
612 + $this->support_links();
613 + $this->apcu_admonition();
614 + $this->versions();
556 615
557 - echo '<h2 class="nav-tab-wrapper">' . PHP_EOL;
616 + if ( array_key_exists( 'description', $this->settings[ $section['id'] ] ) ) {
617 + echo '<p> ' . esc_html( $this->settings[ $section['id'] ]['description'] ) . '</p>' . PHP_EOL;
618 + }
619 + }
558 620
559 - foreach ( $this->settings as $section => $data ) {
621 + /**
622 + * Display support and rating links.
623 + *
624 + * @return void
625 + */
626 + private function support_links() {
560 627
561 - // Set tab class.
562 - $class = 'nav-tab';
563 - if ( $section === $tab ) {
564 - $class .= ' nav-tab-active';
565 - $submit_caption = $data['submit'];
566 - }
628 + $supportUrl = "https://wordpress.org/support/plugin/sqlite-object-cache/";
629 + $reviewUrl = "https://wordpress.org/support/plugin/sqlite-object-cache/reviews/";
630 + echo '<p>';
631 + echo esc_html__( 'For support please', 'sqlite-object-cache' ) . ' ';
632 + echo '<a href="' . esc_url( $supportUrl ) . '">' . esc_html__( 'click here', 'sqlite-object-cache' ) . '</a>. ';
633 + echo esc_html__( 'To rate this plugin please', 'sqlite-object-cache' ) . ' ';
634 + echo '<a href="' . esc_url( $reviewUrl ) . '">' . esc_html__( 'click here', 'sqlite-object-cache' ) . '</a>. ';
635 + echo esc_html__( 'Your feedback helps make it better, faster, and more useful', 'sqlite-object-cache' ) . '.';
636 + echo '</p>';
567 637
568 - // Set tab link.
569 - $tab_link = add_query_arg( [ 'tab' => $section ] );
570 - $tab_link = remove_query_arg( 'settings-updated', $tab_link );
638 + }
571 639
572 - // Output tab.
573 - echo '<a href="' . esc_url( $tab_link ) . '" class="' . esc_attr( $class ) . '">' . esc_html( $data['title'] ) . '</a>' . PHP_EOL;
574 - }
640 + private function apcu_admonition() {
641 + echo '<p>';
642 + echo esc_html__( 'You can use WP-CLI to configure this plugin. Please type', 'sqlite-object-cache' ) . ' ';
643 + echo '<code>wp help sqlite-object-cache</code> ';
644 + echo esc_html__( 'into your shell for details', 'sqlite-object-cache' ) . '.';
645 + echo '</p>';
646 + $option = get_option( $this->parent->_token . '_settings', array() );
647 + $apcu_active = array_key_exists( 'use_apcu', $option ) && 'on' == $option['use_apcu'];
648 + $apcu_enabled = $this->parent->apcu_extension_is_enabled();
649 + if ( $apcu_enabled && ! $apcu_active ) {
650 + echo '<p>';
651 + echo esc_html__( 'On your site you can use php\'s', 'sqlite-object-cache' ) . ' ';
652 + echo '<a href="https://www.php.net/manual/en/book.apcu.php" target="_blank">' . esc_html__( 'APCu User Cache', 'sqlite-object-cache' ) . '</a> ';
653 + echo esc_html__( 'to improve the performance of this SQLite Object Cache. ', 'sqlite-object-cache' );
654 + echo esc_html__( 'To enable APCu caching please check the "Use APCu" box.', 'sqlite-object-cache' ) . ' ';
655 + echo '</p>';
656 + }
657 + if ( $apcu_enabled && $apcu_active ) {
658 + echo '<p>';
659 + echo esc_html__( 'You are using php\'s', 'sqlite-object-cache' ) . ' ';
660 + echo '<a href="https://www.php.net/manual/en/book.apcu.php" target="_blank">' . esc_html__( 'APCu User Cache', 'sqlite-object-cache' ) . '</a> ';
661 + echo esc_html__( 'to improve the performance of this SQLite Object Cache. ', 'sqlite-object-cache' ) . ' ';
662 + echo esc_html__( 'To disable APCu caching please uncheck the "Use APCu" box.', 'sqlite-object-cache' ) . ' ';
663 + echo '</p>';
664 + }
665 + if ( ! $apcu_enabled ) {
666 + echo '<p>';
667 + echo esc_html__( 'This object cache performs better when used with php\'s', 'sqlite-object-cache' ) . ' ';
668 + echo '<a href="https://www.php.net/manual/en/book.apcu.php" target="_blank">' . esc_html__( 'APCu User Cache', 'sqlite-object-cache' ) . '</a> ';
669 + echo esc_html__( 'extension. ', 'sqlite-object-cache' ) . ' ';
670 + echo esc_html__( 'However, it is not available on your server.', 'sqlite-object-cache' ) . ' ';
671 + echo esc_html__( 'It may be possible for you or your hosting service to install it.', 'sqlite-object-cache' ) . ' ';
672 + echo '</p>';
673 + }
674 + }
575 675
576 - echo '</h2>' . PHP_EOL;
577 - }
676 + /**
677 + * Display versions.
678 + *
679 + * @return void
680 + * @throws Exception Announce database failure.
681 + */
682 + private function versions() {
683 + global $wp_object_cache;
684 + global $wp_version;
685 + $igbinary = function_exists( 'igbinary_serialize' ) && function_exists( 'igbinary_unserialize' )
686 + ? phpversion( 'igbinary' )
687 + : __( 'unavailable', 'sqlite-object-cache' );
688 + $force_serialize = defined( 'WP_SQLITE_OBJECT_CACHE_SERIALIZE' ) && WP_SQLITE_OBJECT_CACHE_SERIALIZE;
689 + $igbinary .= $force_serialize ? esc_html__( '(disabled by WP_SQLITE_OBJECT_CACHE_SERIALIZE)', 'sqlite-object-cache' ) : '';
578 690
579 - /** @noinspection HtmlUnknownTarget */
580 - echo '<form method="post" action="options.php" enctype="multipart/form-data">' . PHP_EOL;
691 + $apcu_version = $this->parent->apcu_extension_is_enabled()
692 + ? phpversion( "apcu" )
693 + : __( 'unavailable', 'sqlite-object-cache' );
581 694
582 - // Get settings fields.
583 - settings_fields( $this->parent->_token . '_settings' );
584 - do_settings_sections( $this->parent->_token . '_settings' );
695 + if ( method_exists( $wp_object_cache, 'sqlite_get_version' ) ) {
696 + echo '<p>' . esc_html( sprintf(
697 + /* translators: 1: version for sqlite 2: version for php 3: webserver version 4: version for plugin 5: igbinary 6:APCu 7:WordPress */
698 + __( 'Versions: WordPress: %7$s SQLite: %1$s php: %2$s Server: %3$s Plugin: %4$s APCu: %6$s igbinary: %5$s.', 'sqlite-object-cache' ),
699 + $wp_object_cache->sqlite_get_version(),
700 + PHP_VERSION,
701 + $_SERVER['SERVER_SOFTWARE'],
702 + $this->parent->_version,
703 + $igbinary,
704 + $apcu_version,
705 + $wp_version ) ) . '</p>';
706 + }
707 + }
585 708
586 - echo '<p class="submit">' . PHP_EOL;
587 - echo '<input type="hidden" name="tab" value="' . esc_attr( $tab ) . '" />' . PHP_EOL;
588 - echo '<input name="Submit" type="submit" class="button-primary" value="' . esc_attr( $submit_caption ) . '" />' . PHP_EOL;
589 - echo '</p></form></div>' . PHP_EOL;
590 - }
709 + /**
710 + * Statistics tab section.
711 + *
712 + * @param array $section Array of section ids.
713 + *
714 + * @return void
715 + * @throws Exception Announce Database Failure.
716 + * @noinspection PhpUnusedParameterInspection
717 + */
718 + public function stats_section_header( $section ) {
591 719
592 - /**
593 - * Admin enqueue assets
594 - *
595 - * @param string $hook Hook parameter.
596 - *
597 - * @return void
598 - * @noinspection PhpUnusedParameterInspection
599 - */
600 - public function enqueue_assets( $hook = '' ) {
601 - wp_register_style( $this->parent->_token . '-admin',
602 - esc_url( $this->parent->assets_url ) . 'css/admin.css',
603 - [], $this->parent->_version );
604 - wp_enqueue_style( $this->parent->_token . '-admin' );
605 - }
720 + $this->support_links();
721 + $this->versions();
722 +
723 + $stats = new SQLite_Object_Cache_Statistics ( get_option( $this->parent->_token . '_settings', array() ) );
724 + $stats->init();
725 + $stats->render();
726 + $stats->render_usage();
727 + }
728 +
729 + /**
730 + * Load settings page content.
731 + *
732 + * @return void
733 + */
734 + public function settings_page() {
735 +
736 + $this->enqueue_assets();
737 + /* get the tab chosen by the user ('standard' by default or 'stats') */
738 + $tab = isset ( $_REQUEST['tab'] ) ? sanitize_key( $_REQUEST['tab'] ) : 'standard';
739 +
740 + // Build page HTML.
741 + echo '<div class="wrap" id="' . esc_attr( $this->parent->_token . '_settings' ) . '">' . PHP_EOL;
742 + echo '<h2>' . esc_html__( 'SQLite Persistent Object Cache', 'sqlite-object-cache' ) . '</h2>' . PHP_EOL;
743 +
744 + $submit_caption = __( 'Save Settings', 'sqlite-object-cache' );
745 + $this->complain_if_sqlite3_unavailable( true );
746 +
747 + // Show page tabs.
748 + if ( is_array( $this->settings ) && 1 < count( $this->settings ) ) {
749 +
750 + echo '<h2 class="nav-tab-wrapper">' . PHP_EOL;
751 +
752 + foreach ( $this->settings as $section => $data ) {
753 +
754 + // Set tab class.
755 + $class = 'nav-tab';
756 + if ( $section === $tab ) {
757 + $class .= ' nav-tab-active';
758 + $submit_caption = $data['submit'];
759 + }
760 +
761 + // Set tab link.
762 + $tab_link = add_query_arg( array( 'tab' => $section ) );
763 + $tab_link = remove_query_arg( 'settings-updated', $tab_link );
764 +
765 + // Output tab.
766 + echo '<a href="' . esc_url( $tab_link ) . '" class="' . esc_attr( $class ) . '">' . esc_html( $data['title'] ) . '</a>' . PHP_EOL;
767 + }
768 +
769 + if ( 'stats' === $tab ) {
770 + echo '<a href="' . esc_url( self::$statistics_help_url ) . '" class="nav-tab" target="_blank">' . esc_html__( 'Help', 'sqlite-object-cache' ) . '</a>' . PHP_EOL;
771 + }
772 +
773 + echo '</h2>' . PHP_EOL;
774 + }
775 +
776 + /** @noinspection HtmlUnknownTarget */
777 + echo '<form method="post" action="options.php" enctype="multipart/form-data">' . PHP_EOL;
778 +
779 + // Get settings fields.
780 + settings_fields( $this->parent->_token . '_settings' );
781 + do_settings_sections( $this->parent->_token . '_settings' );
782 +
783 + echo '<p class="submit">' . PHP_EOL;
784 + echo '<input type="hidden" name="tab" value="' . esc_attr( $tab ) . '" />' . PHP_EOL;
785 + echo '<input name="Submit" type="submit" class="button-primary" value="' . esc_attr( $submit_caption ) . '" />' . PHP_EOL;
786 + echo '</p></form></div>' . PHP_EOL;
787 + }
788 +
789 + /**
790 + * Admin enqueue assets
791 + *
792 + * @param string $hook Hook parameter.
793 + *
794 + * @return void
795 + * @noinspection PhpUnusedParameterInspection
796 + */
797 + public function enqueue_assets( $hook = '' ) {
798 + wp_register_style( $this->parent->_token . '-admin',
799 + esc_url( $this->parent->assets_url ) . 'css/admin.css',
800 + array(), $this->parent->_version );
801 + wp_enqueue_style( $this->parent->_token . '-admin' );
802 + }
803 +
804 + /**
805 + * Enters maintenance mode.
806 + *
807 + * @return void
808 + */
809 + private function enter_maintenance_mode() {
810 + $maintenanceFileName = ABSPATH . '.maintenance';
811 + $maintain = array();
812 + array_push( $maintain,
813 + '<?php',
814 + '$upgrading = ' . time() . ';',
815 + '?>' );
816 + file_put_contents( $maintenanceFileName, implode( PHP_EOL, $maintain ) );
817 + }
818 +
819 + /**
820 + * Exits maintenance mode.
821 + *
822 + * @return void
823 + */
824 + private function exit_maintenance_mode() {
825 + $maintenanceFileName = ABSPATH . '.maintenance';
826 + unlink( $maintenanceFileName );
827 + }
828 +
829 + /**
830 + * Update a variable in the wp-config.php file.
831 + *
832 + * If enabling, check if the variable is defined, and if not, define it.
833 + * Vice versa for disabling.
834 + */
835 + private function update_wp_config_constant( $symbol, $enable ) {
836 +
837 + /**
838 + * Follow WP's logic to locate wp-config file
839 + * @see wp-load.php
840 + */
841 + $conf_file = ABSPATH . 'wp-config.php';
842 + if ( ! file_exists( $conf_file ) ) {
843 + $conf_file = dirname( ABSPATH ) . '/wp-config.php';
844 + }
845 + $config = new SQLite_Object_Cache_File( $conf_file );
846 + // Remove the line `define('WHATEVER', true/false);` first
847 + $removed = $config->remove( $symbol );
848 + $inserted = false;
849 + if ( $enable ) {
850 + $cmd = "define( '" . $symbol . "', true );";
851 + $inserted = $config->insert_before( $cmd );
852 + }
853 + if ( $removed !== $inserted ) {
854 + $config->save();
855 + }
856 + return true;
857 + }
858 +
859 + /**
860 + * Put troubleshooting information into Site Health - Info
861 + *
862 + * @param array $info
863 + *
864 + * @return array
865 + */
866 + public function debug_information( $info ) {
867 + global $wp_object_cache;
868 +
869 + $stanza = array(
870 + 'label' => 'SQLite Object Cache',
871 + 'fields' => array()
872 + );
873 +
874 + try {
875 + $option = get_option( $this->parent->_token . '_settings', array() );
876 +
877 + $stanza['fields']['configuration'] = array(
878 + 'label' => __( 'Configuration', 'sqlite-object-cache' ),
879 + 'value' => $this->flatten( $option ),
880 + );
881 +
882 + } catch ( \Exception $e ) {
883 + /* empty, intentionally */
884 + }
885 +
886 + ob_start();
887 + try {
888 + $this->versions();
889 + $versions = wp_strip_all_tags( ob_get_clean() );
890 + $stanza['fields']['versions'] = array(
891 + 'label' => __( 'Versions', 'sqlite-object-cache' ),
892 + 'value' => $versions,
893 + );
894 + } catch ( \Exception $e ) {
895 + ob_get_clean();
896 + }
897 +
898 + try {
899 + $settings = array();
900 + if ( $this->parent->apcu_extension_is_enabled() ) {
901 + $settings = array_merge( $settings, ini_get_all( 'apcu' ) );
902 + }
903 + $settings = array_merge( $settings, ini_get_all( 'sqlite3' ) );
904 + $settings = array_merge( $settings, $this->inis_get( 'session.save_handler', 'session.auto_start', 'session.lifetime', 'session.gc_maxlifetime' ) );
905 +
906 + $stanza['fields']['settings'] = array(
907 + 'label' => __( 'Settings', 'sqlite-object-cache' ),
908 + 'value' => $this->flatten( $settings ),
909 + );
910 + } catch ( \Exception $e ) {
911 + /* empty, intentionally */
912 + }
913 +
914 + try {
915 + if ( $this->parent->apcu_extension_is_enabled() ) {
916 +
917 + $sizes = array();
918 + $counts = array();
919 + $totalsize = 0;
920 + $totalcount = 0;
921 + foreach ( new APCUIterator( null, APC_ITER_KEY | APC_ITER_MEM_SIZE ) as $item ) {
922 + $salt = '?';
923 + $splits = explode( '|', $item['key'], 2 );
924 + if ( count( $splits ) >= 2 ) {
925 + $salt = $splits[0];
926 + }
927 + if ( ! array_key_exists( $salt, $sizes ) ) {
928 + $sizes[ $salt ] = 0;
929 + $counts[ $salt ] = 0;
930 + }
931 + $sizes[ $salt ] += $item['mem_size'];
932 + $totalsize += $item['mem_size'];
933 + $counts [ $salt ] += 1;
934 + $totalcount += 1;
935 + }
936 +
937 + $apcusalt = trim( property_exists( $wp_object_cache, 'apcusalt' ) ? $wp_object_cache->apcusalt : '', '|' );
938 + $settings = array();
939 + $settings ['salt'] = $apcusalt;
940 + $settings ['total'] = "$totalsize($totalcount)";
941 + $siteno = 1;
942 + foreach ( $sizes as $salt => $val ) {
943 + switch ( $salt ) {
944 + case $apcusalt:
945 + $csalt = 'mine';
946 + break;
947 + case '?':
948 + $csalt = 'no salt';
949 + break;
950 + default:
951 + $csalt = 'site' . $siteno ++;
952 + break;
953 + }
954 + $settings[ $csalt ] = "$val($counts[$salt])";
955 + }
956 +
957 +
958 + $stanza['fields']['apcu_utilization'] = array(
959 + 'label' => __( 'APCu utilization', 'sqlite-object-cache' ),
960 + 'value' => $this->flatten( $settings ),
961 + );
962 +
963 + $apcu_cache_info = array();
964 + $apcu_cache_info = array_merge( $apcu_cache_info, apcu_cache_info( true ) );
965 + $apcu_cache_info = array_merge( $apcu_cache_info, apcu_sma_info( true ) );
966 +
967 + $stanza['fields']['apcu_cache_info'] = array(
968 + 'label' => __( 'APCu info', 'sqlite-object-cache' ),
969 + 'value' => $this->flatten( $apcu_cache_info ),
970 + );
971 +
972 +
973 + }
974 + } catch ( \Exception $e ) {
975 + /* empty, intentionally */
976 + }
977 +
978 + try {
979 + global $wp_object_cache;
980 + if ( method_exists( $wp_object_cache, 'sqlite_sizes' ) ) {
981 + $stanza['fields']['sqlite_sizes'] = array(
982 + 'label' => __( 'SQLite utilization', 'sqlite-object-cache' ),
983 + 'value' => $this->flatten( $wp_object_cache->sqlite_sizes() ),
984 + );
985 + }
986 + } catch ( \Exception $e ) {
987 + /* empty, intentionally */
988 + }
989 +
990 +
991 + $info['sqlite-object-cache'] = $stanza;
992 + return $info;
993 + }
994 +
995 + private function inis_get( ...$list ) {
996 + $result = array();
997 + foreach ( $list as $item ) {
998 + $val = ini_get( $item );
999 + $result[ $item ] = $val ?: '-missing-';
1000 + }
1001 + return $result;
1002 +
1003 + }
1004 +
1005 + private function flatten( $a ) {
1006 + $result = array();
1007 + foreach ( $a as $k => $v ) {
1008 + if ( is_array( $v ) ) {
1009 + if ( array_key_exists( 'local_value', $v ) ) {
1010 + $v = $v['local_value'];
1011 + } else {
1012 + $v = var_export( $v, true );
1013 + }
1014 + }
1015 + $result [] = $k . ':' . $v;
1016 + }
1017 + return implode( '; ', $result );
1018 + }
1019 +
1020 +
606 1021 }