PluginProbe
SQLite Object Cache / 1.6.2
SQLite Object Cache v1.6.2
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 +1070 -513 1.2.21.6.2 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,1149 @@
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 + /* Empty, intentionally. Don't crash on asset cleanup. */
141 + } finally {
142 + restore_current_blog();
143 + }
144 + }
145 + }
146 + }
197 147
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 - }
148 + /**
149 + * Filters the array of row meta for each plugin in the Plugins list table.
150 + *
151 + * @param array<int, string> $plugin_meta An array of the plugin's metadata.
152 + * @param string $plugin_file Path to the plugin file relative to the plugins directory.
153 + *
154 + * @return array<int, string> Updated array of the plugin's metadata.
155 + */
156 + public function filter_plugin_row_meta( array $plugin_meta, $plugin_file ) {
157 + if ( $this->plugin_file !== $plugin_file ) {
158 + return $plugin_meta;
159 + }
209 160
210 - $this->numeric_option( $option, 'samplerate', 10 );
211 - $this->numeric_option( $option, 'retainmeasurements', 2 );
161 + if ( is_multisite() && ( is_network_admin() || ! is_main_site() ) ) {
162 + $plugin_meta[] =
163 + esc_html__( 'See the main site\'s dashboard for settings.', 'sqlite-object-cache' );
164 + }
212 165
213 - if ( array_key_exists( 'capture', $option ) && $option['capture'] === 'on' ) {
214 - $option['previouscapture'] = 0;
215 - }
166 + /** @noinspection HtmlUnknownTarget */
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, including all transients) 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 + array(
252 + 'id' => 'adminbarflush',
253 + 'label' => __( 'Show', 'sqlite-object-cache' ),
254 + 'description' => __( 'Flush Object Cache button in the admin bar.', 'sqlite-object-cache' ),
255 + 'type' => 'checkbox',
256 + 'default' => '',
257 + )
258 + );
240 259
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 ) {
260 + if ( $this->parent->apcu_extension_is_enabled() ) {
261 + array_unshift( $fields, array(
262 + 'id' => 'use_apcu',
263 + 'label' => __( 'Use APCu', 'sqlite-object-cache' ),
264 + 'description' => __( 'Check to enable the use of php\'s APCu cache to improve performance.', 'sqlite-object-cache' ),
265 + 'type' => 'checkbox',
266 + 'default' => '',
267 + ) );
255 268
256 - global $wp_object_cache;
257 - if ( method_exists( $wp_object_cache, 'sqlite_reset_statistics' ) ) {
258 - $wp_object_cache->sqlite_reset_statistics();
259 - }
269 + }
260 270
261 - /* for this post operation, we don't want to change any plugin options. */
271 + $settings['standard'] = array(
272 + // Using core I18n.
273 + // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
274 + 'title' => __( 'Settings' ),
275 + // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
276 + 'submit' => __( 'Save Changes' ),
277 + 'description' => '',
278 + 'render_section_header' => array( $this, 'settings_section_header' ),
279 + 'form_post_callback' => array( $this, 'validate_settings' ),
280 + 'fields' => $fields,
281 + );
262 282
263 - return get_option( $name );
264 - }
283 + $settings['stats'] = array(
284 + 'title' => __( 'Statistics', 'sqlite-object-cache' ),
285 + 'submit' => __( 'Reset Statistics', 'sqlite-object-cache' ),
286 + 'render_section_header' => array( $this, 'stats_section_header' ),
287 + 'form_post_callback' => array( $this, 'validate_reset_stats' ),
288 + );
265 289
266 - /**
267 - * Add settings page to admin menu
268 - *
269 - * @return void
270 - */
271 - public function add_menu_item() {
290 + return apply_filters( 'sqlite_object_cache_settings_fields', $settings );
291 + }
272 292
273 - $args = $this->menu_settings();
293 + /**
294 + * Validate the options presented to the Settings tab.
295 + *
296 + * This is an options update filter. It filters an option value following sanitization.
297 + *
298 + * @param mixed $option The sanitized option value.
299 + * @param string $name The option name.
300 + * @param string $original_value The original value passed to the function.
301 + *
302 + * @throws Exception Announce SQLite failure.
303 + * @since 2.3.0
304 + * @since 4.3.0 Added the `$original_value` parameter.
305 + *
306 + * @noinspection PhpUnusedParameterInspection
307 + */
308 + public function validate_settings( $option, $name, $original_value ) {
309 + if ( ! is_array( $option ) ) {
310 + /* Weird. not an option array */
311 + return $option;
312 + }
313 + global $wp_object_cache;
314 + $former_value = get_option( $name, array() );
274 315
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 - }
316 + /* Opt in or opt out of the APCu. */
317 + if ( $this->parent->apcu_extension_is_enabled() ) {
318 + $apcu_choice = array_key_exists( 'use_apcu', $option ) && $option ['use_apcu'] === 'on';
319 + $apcu_former = array_key_exists( 'use_apcu', $former_value ) && $former_value ['use_apcu'] === 'on';
320 + if ( $apcu_choice !== $apcu_former ) {
321 + if ( method_exists( $wp_object_cache, 'apcu_clear_cache' ) ) {
322 + $wp_object_cache->apcu_clear_cache();
323 + }
324 + $this->update_wp_config_constant( 'WP_SQLITE_OBJECT_CACHE_APCU', $apcu_choice );
325 + $option['use_apcu_updated'] = true;
326 + }
327 + $option['use_apcu'] = $apcu_choice ? 'on' : 'off';
328 + }
329 + if ( array_key_exists( 'flush', $option ) && $option ['flush'] === 'on' ) {
330 + if ( method_exists( $wp_object_cache, 'flush' ) ) {
331 + try {
332 + $this->enter_maintenance_mode();
333 + $wp_object_cache->flush( true );
334 + } finally {
335 + $this->exit_maintenance_mode();
336 + }
337 + } else {
338 + wp_cache_flush();
339 + }
340 + unset ( $option['flush'] );
341 + }
342 + if ( array_key_exists( 'vacuum', $option ) && $option ['vacuum'] === 'on' ) {
343 + if ( method_exists( $wp_object_cache, 'vacuum' ) ) {
344 + try {
345 + $this->enter_maintenance_mode();
346 + $wp_object_cache->vacuum();
347 + } finally {
348 + $this->exit_maintenance_mode();
349 + }
350 + } else {
351 + wp_cache_flush();
352 + }
353 + unset ( $option['vacuum'] );
354 + }
355 + if ( array_key_exists( 'cleanup', $option ) && $option ['cleanup'] === 'on' ) {
356 + $this->parent->clean_job();
357 + unset ( $option['cleanup'] );
358 + }
293 359
294 - /**
295 - * Prepare default settings page arguments
296 - *
297 - * @return mixed|void
298 - */
299 - private function menu_settings() {
300 - $this->complain_if_sqlite3_unavailable();
360 + $this->numeric_option( $option, 'target_size', 4 );
361 + $this->numeric_option( $option, 'samplerate', 10 );
362 + $this->numeric_option( $option, 'retainmeasurements', 2 );
301 363
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 - }
364 + if ( array_key_exists( 'capture', $option ) && $option['capture'] === 'on' ) {
365 + $option['previouscapture'] = 0;
366 + }
317 367
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>';
368 + return $option;
369 + }
335 370
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 - }
371 + /**
372 + * Retrieve a numeric option, and set the default if it isn't present.
373 + *
374 + * @param array $option Option array value.
375 + * @param string $name Name of the element in the option array.
376 + * @param mixed $default Default value to use.
377 + *
378 + * @return float|int|mixed|string
379 + */
380 + private function numeric_option( &$option, $name, $default ) {
381 + $result = $default;
382 + if ( array_key_exists( $name, $option ) && is_numeric( $option[ $name ] ) ) {
383 + $result = $option[ $name ];
384 + $result = $result ?: $default;
385 + } else {
386 + $option[ $name ] = $default;
387 + }
340 388
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 - }
389 + return $result;
390 + }
348 391
349 - echo '</p></div>' . PHP_EOL;
350 - }
351 - }
352 - }
392 + /**
393 + * Filters an option value following sanitization.
394 + *
395 + * @param string $option The sanitized option value.
396 + * @param string $name The option name.
397 + * @param string $original_value The original value passed to the function.
398 + *
399 + * @throws Exception Announce SQLite failure.
400 + * @since 2.3.0
401 + * @since 4.3.0 Added the `$original_value` parameter.
402 + *
403 + * @noinspection PhpUnusedParameterInspection
404 + */
405 + public function validate_reset_stats( $option, $name, $original_value ) {
353 406
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 - }
407 + global $wp_object_cache;
408 + if ( method_exists( $wp_object_cache, 'sqlite_reset_statistics' ) ) {
409 + $wp_object_cache->sqlite_reset_statistics();
410 + }
364 411
365 - /**
366 - * Load settings JS & CSS
367 - *
368 - * @return void
369 - */
370 - public function settings_assets() {
412 + /* for this post operation, we don't want to change any plugin options. */
371 413
372 - }
414 + return get_option( $name );
415 + }
373 416
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 - }
417 + /**
418 + * Add settings page to admin menu
419 + *
420 + * @return void
421 + */
422 + public function add_menu_item() {
393 423
394 - return $links;
395 - }
424 + $args = $this->menu_settings();
396 425
397 - /**
398 - * Register plugin settings
399 - *
400 - * @return void
401 - */
402 - public function register_my_settings() {
403 - if ( is_array( $this->settings ) ) {
426 + // Do nothing if wrong location key is set.
427 + if ( is_array( $args ) && isset( $args['location'] ) && function_exists( 'add_' . $args['location'] . '_page' ) ) {
428 + switch ( $args['location'] ) {
429 + case 'options':
430 + case 'submenu':
431 + $page =
432 + add_submenu_page( $args['parent_slug'], $args['page_title'], $args['menu_title'], $args['capability'], $args['menu_slug'], $args['function'] );
433 + break;
434 + case 'menu':
435 + $page =
436 + add_menu_page( $args['page_title'], $args['menu_title'], $args['capability'], $args['menu_slug'], $args['function'], $args['icon_url'], $args['position'] );
437 + break;
438 + default:
439 + return;
440 + }
441 + add_action( 'admin_print_styles-' . $page, array( $this, 'settings_assets' ) );
442 + }
443 + }
404 444
405 - /* get the tab chosen by the user ('standard' or 'stats') */
406 - $tab = isset ( $_REQUEST['tab'] ) ? sanitize_key( $_REQUEST['tab'] ) : 'standard';
445 + /**
446 + * Prepare default settings page arguments
447 + *
448 + * @return mixed|void
449 + */
450 + private function menu_settings() {
451 + $this->complain_if_sqlite3_unavailable();
407 452
408 - $default_option = [];
409 - $option_name = $this->base . 'settings';
453 + return apply_filters(
454 + 'sqlite_object_cachemenu_settings',
455 + array(
456 + 'location' => 'options', // Possible settings: options, menu, submenu.
457 + 'parent_slug' => 'options-general.php',
458 + 'page_title' => __( 'SQLite Persistent Object Cache', 'sqlite-object-cache' ),
459 + 'menu_title' => __( 'Object Cache', 'sqlite-object-cache' ),
460 + 'capability' => 'manage_options',
461 + 'menu_slug' => $this->parent->_token . '_settings',
462 + 'function' => array( $this, 'settings_page' ),
463 + 'icon_url' => '',
464 + 'position' => null,
465 + )
466 + );
467 + }
410 468
411 - foreach ( $this->settings as $section => $data ) {
469 + /**
470 + * Emit a message if SQLite3 is not available.
471 + *
472 + * @param bool $verbose False means emit a notice. True means emit a longer explanation.
473 + *
474 + * @return void
475 + */
476 + private function complain_if_sqlite3_unavailable( $verbose = false ) {
477 + if ( true !== $this->has ) {
478 + if ( ! $verbose ) {
479 + echo '<div class="notice notice-error sql-object-cache">';
480 + echo esc_html( $this->has );
481 + echo '</div>';
482 + } else {
483 + echo '<div>';
484 + echo '<h3 style="color: #d63638">' . esc_html__( 'Server configuration problem', 'sqlite-object-cache' ) . '</h3>';
485 + echo '<p>';
412 486
413 - if ( $tab && $tab !== $section ) {
414 - continue;
415 - }
487 + if ( ! class_exists( 'SQLite3' ) || ! extension_loaded( 'sqlite3' ) ) {
488 + echo esc_html__( 'The SQLite Persistent Object Cache plugin requires php\'s SQLite3 extension.', 'sqlite-object-cache' ) . ' ';
489 + echo esc_html__( 'That extension is not installed in your server, so the plugin cannot work.', 'sqlite-object-cache' ) . ' ';
490 + }
416 491
417 - $setting_args = [
418 - 'description' => $data['title'],
419 - 'default' => $default_option,
420 - ];
492 + $sqlite_version = $this->parent->sqlite_get_version();
493 + if ( version_compare( $sqlite_version, $this->parent->minimum_sqlite_version ) < 0 ) {
494 + echo esc_html( sprintf(
495 + /* translators: 1 actual SQLite version. 2 required SQLite version) */
496 + __( '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' ),
497 + $sqlite_version, $this->parent->minimum_sqlite_version ) );
498 + }
421 499
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 - }
500 + echo '</p></div>' . PHP_EOL;
501 + }
502 + }
503 + }
426 504
427 - // Add section to page.
428 - add_settings_section( $section, '',
429 - $data ['render_section_header'],
430 - $this->parent->_token . '_settings' );
505 + /**
506 + * Container for settings page arguments
507 + *
508 + * @param array $settings Settings array.
509 + *
510 + * @return array
511 + */
512 + public function configure_settings( $settings = array() ) {
513 + return $settings;
514 + }
431 515
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 - }
516 + /**
517 + * Load settings JS & CSS
518 + *
519 + * @return void
520 + */
521 + public function settings_assets() {
436 522
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 - }
523 + }
457 524
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 ) {
525 + /**
526 + * Add settings link to plugin list table
527 + *
528 + * @param array $links Existing links.
529 + *
530 + * @return array Modified links.
531 + */
532 + public function add_settings_link( $links ) {
533 + if ( true !== $this->has ) {
534 + $settings_link =
535 + '<a style="color: #d63638" href="options-general.php?page=' . $this->parent->_token . '_settings">' . __( 'Settings', 'sqlite-object-cache' ) . '</a>';
536 + array_unshift( $links, $settings_link );
537 + } else {
538 + $settings_link =
539 + '<a href="options-general.php?page=' . $this->parent->_token . '_settings">' . __( 'Settings', 'sqlite-object-cache' ) . '</a>';
540 + $statistics_link =
541 + '<a href="options-general.php?page=' . $this->parent->_token . '_settings&tab=stats">' . __( 'Statistics', 'sqlite-object-cache' ) . '</a>';
542 + array_unshift( $links, $settings_link, $statistics_link );
543 + }
467 544
468 - $this->support_links();
469 - $this->versions();
545 + return $links;
546 + }
470 547
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 - }
548 + /**
549 + * Register plugin settings
550 + *
551 + * @return void
552 + */
553 + public function register_my_settings() {
554 + $this->parent->sync_apcu_global_to_option( false );
555 + $this->settings = $this->settings_fields();
475 556
476 - /**
477 - * Display support and rating links.
478 - *
479 - * @return void
480 - */
481 - private function support_links() {
557 + if ( is_array( $this->settings ) ) {
482 558
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 - }
559 + /* get the tab chosen by the user ('standard' or 'stats') */
560 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
561 + $tab = isset ( $_REQUEST['tab'] ) ? sanitize_key( $_REQUEST['tab'] ) : 'standard';
493 562
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' );
563 + $default_option = array();
564 + $option_name = $this->base . 'settings';
505 565
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 - }
566 + foreach ( $this->settings as $section => $data ) {
516 567
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 ) {
568 + if ( $tab && $tab !== $section ) {
569 + continue;
570 + }
527 571
528 - $this->support_links();
529 - $this->versions();
572 + $setting_args = array(
573 + 'description' => $data['title'],
574 + 'default' => $default_option,
575 + );
530 576
531 - $stats = new SQLite_Object_Cache_Statistics ( get_option( $this->parent->_token . '_settings', [] ) );
532 - $stats->init();
533 - $stats->render();
534 - $stats->render_usage();
535 - }
577 + // Validation callback for section.
578 + if ( isset( $data['form_post_callback'] ) ) {
579 + add_filter( "sanitize_option_$option_name", $data['form_post_callback'], 10, 3 );
580 + }
536 581
537 - /**
538 - * Load settings page content.
539 - *
540 - * @return void
541 - */
542 - public function settings_page() {
582 + // Add section to page.
583 + add_settings_section( $section, '',
584 + $data ['render_section_header'],
585 + $this->parent->_token . '_settings' );
543 586
544 - /* get the tab chosen by the user ('standard' by default or 'stats') */
545 - $tab = isset ( $_REQUEST['tab'] ) ? sanitize_key( $_REQUEST['tab'] ) : 'standard';
587 + if ( array_key_exists( 'fields', $data ) && is_array( $data['fields'] ) ) {
588 + foreach ( $data['fields'] as $field ) {
589 + $default_option [ $field['id'] ] = $field['default'];
590 + }
546 591
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;
592 + foreach ( $data['fields'] as $field ) {
593 + // Add field to page.
594 + add_settings_field(
595 + $field['id'],
596 + $field['label'],
597 + array( $this, 'echo_field' ),
598 + $this->parent->_token . '_settings',
599 + $section,
600 + array(
601 + 'field' => $field,
602 + 'option' => $option_name,
603 + )
604 + );
605 + }
606 + }
607 + /* register the settings object */
608 + register_setting( $this->parent->_token . '_settings', $option_name, $setting_args );
609 + }
610 + }
611 + }
550 612
551 - $submit_caption = __( 'Save Settings', 'sqlite-object-cache' );
552 - $this->complain_if_sqlite3_unavailable( true );
613 + /**
614 + * Settings section.
615 + *
616 + * @param array $section Array of section ids.
617 + *
618 + * @return void
619 + * @throws Exception Announce Database Failure.
620 + */
621 + public function settings_section_header( $section ) {
553 622
554 - // Show page tabs.
555 - if ( is_array( $this->settings ) && 1 < count( $this->settings ) ) {
623 + $this->support_links();
624 + $this->apcu_admonition();
625 + $this->versions();
556 626
557 - echo '<h2 class="nav-tab-wrapper">' . PHP_EOL;
627 + if ( array_key_exists( 'description', $this->settings[ $section['id'] ] ) ) {
628 + echo '<p> ' . esc_html( $this->settings[ $section['id'] ]['description'] ) . '</p>' . PHP_EOL;
629 + }
630 + }
558 631
559 - foreach ( $this->settings as $section => $data ) {
632 + /**
633 + * Display support and rating links.
634 + *
635 + * @return void
636 + */
637 + private function support_links() {
560 638
561 - // Set tab class.
562 - $class = 'nav-tab';
563 - if ( $section === $tab ) {
564 - $class .= ' nav-tab-active';
565 - $submit_caption = $data['submit'];
566 - }
639 + $supportUrl = "https://wordpress.org/support/plugin/sqlite-object-cache/";
640 + $reviewUrl = "https://wordpress.org/support/plugin/sqlite-object-cache/reviews/";
641 + echo '<p>';
642 + echo esc_html__( 'For support please', 'sqlite-object-cache' ) . ' ';
643 + echo '<a href="' . esc_url( $supportUrl ) . '">' . esc_html__( 'click here', 'sqlite-object-cache' ) . '</a>. ';
644 + echo esc_html__( 'To rate this plugin please', 'sqlite-object-cache' ) . ' ';
645 + echo '<a href="' . esc_url( $reviewUrl ) . '">' . esc_html__( 'click here', 'sqlite-object-cache' ) . '</a>. ';
646 + echo esc_html__( 'Your feedback helps make it better, faster, and more useful', 'sqlite-object-cache' ) . '.';
647 + echo '</p>';
567 648
568 - // Set tab link.
569 - $tab_link = add_query_arg( [ 'tab' => $section ] );
570 - $tab_link = remove_query_arg( 'settings-updated', $tab_link );
649 + }
571 650
572 - // Output tab.
573 - echo '<a href="' . esc_url( $tab_link ) . '" class="' . esc_attr( $class ) . '">' . esc_html( $data['title'] ) . '</a>' . PHP_EOL;
574 - }
651 + private function apcu_admonition() {
652 + echo '<p>';
653 + echo esc_html__( 'You can use WP-CLI to configure this plugin. Please type', 'sqlite-object-cache' ) . ' ';
654 + echo '<code>wp help sqlite-object-cache</code> ';
655 + echo esc_html__( 'into your shell for details', 'sqlite-object-cache' ) . '.';
656 + echo '</p>';
657 + $option = get_option( $this->parent->_token . '_settings', array() );
658 + $apcu_active = array_key_exists( 'use_apcu', $option ) && 'on' == $option['use_apcu'];
659 + $apcu_enabled = $this->parent->apcu_extension_is_enabled();
660 + if ( $apcu_enabled && ! $apcu_active ) {
661 + echo '<p>';
662 + echo esc_html__( 'On your site you can use php\'s', 'sqlite-object-cache' ) . ' ';
663 + echo '<a href="https://www.php.net/manual/en/book.apcu.php" target="_blank">' . esc_html__( 'APCu User Cache', 'sqlite-object-cache' ) . '</a> ';
664 + echo esc_html__( 'to improve the performance of this SQLite Object Cache. ', 'sqlite-object-cache' );
665 + echo esc_html__( 'To enable APCu caching please check the "Use APCu" box.', 'sqlite-object-cache' ) . ' ';
666 + echo '</p>';
667 + }
668 + if ( $apcu_enabled && $apcu_active ) {
669 + echo '<p>';
670 + echo esc_html__( 'You are using php\'s', 'sqlite-object-cache' ) . ' ';
671 + echo '<a href="https://www.php.net/manual/en/book.apcu.php" target="_blank">' . esc_html__( 'APCu User Cache', 'sqlite-object-cache' ) . '</a> ';
672 + echo esc_html__( 'to improve the performance of this SQLite Object Cache. ', 'sqlite-object-cache' ) . ' ';
673 + echo esc_html__( 'To disable APCu caching please uncheck the "Use APCu" box.', 'sqlite-object-cache' ) . ' ';
674 + echo '</p>';
675 + }
676 + if ( ! $apcu_enabled ) {
677 + echo '<p>';
678 + echo esc_html__( 'This object cache performs better when used with php\'s', 'sqlite-object-cache' ) . ' ';
679 + echo '<a href="https://www.php.net/manual/en/book.apcu.php" target="_blank">' . esc_html__( 'APCu User Cache', 'sqlite-object-cache' ) . '</a> ';
680 + echo esc_html__( 'extension. ', 'sqlite-object-cache' ) . ' ';
681 + echo esc_html__( 'However, it is not available on your server.', 'sqlite-object-cache' ) . ' ';
682 + echo esc_html__( 'It may be possible for you or your hosting service to install it.', 'sqlite-object-cache' ) . ' ';
683 + echo '</p>';
684 + }
685 + }
575 686
576 - echo '</h2>' . PHP_EOL;
577 - }
687 + /**
688 + * Display versions.
689 + *
690 + * @return void
691 + * @throws Exception Announce database failure.
692 + */
693 + private function versions() {
694 + global $wp_object_cache;
695 + global $wp_version;
696 + $igbinary = function_exists( 'igbinary_serialize' ) && function_exists( 'igbinary_unserialize' )
697 + ? phpversion( 'igbinary' )
698 + : __( 'unavailable', 'sqlite-object-cache' );
699 + $force_serialize = defined( 'WP_SQLITE_OBJECT_CACHE_SERIALIZE' ) && WP_SQLITE_OBJECT_CACHE_SERIALIZE;
700 + $igbinary .= $force_serialize ? esc_html__( '(disabled by WP_SQLITE_OBJECT_CACHE_SERIALIZE)', 'sqlite-object-cache' ) : '';
578 701
579 - /** @noinspection HtmlUnknownTarget */
580 - echo '<form method="post" action="options.php" enctype="multipart/form-data">' . PHP_EOL;
702 + $apcu_version = $this->parent->apcu_extension_is_enabled()
703 + ? phpversion( "apcu" )
704 + : __( 'unavailable', 'sqlite-object-cache' );
581 705
582 - // Get settings fields.
583 - settings_fields( $this->parent->_token . '_settings' );
584 - do_settings_sections( $this->parent->_token . '_settings' );
706 + if ( method_exists( $wp_object_cache, 'sqlite_get_version' ) ) {
707 + $server_software = 'unk';
708 + // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash
709 + if ( isset( $_SERVER['SERVER_SOFTWARE'] ) && is_string( $_SERVER['SERVER_SOFTWARE'] ) ) {
710 + $server_software = $_SERVER['SERVER_SOFTWARE'];
711 + }
712 + // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash
713 + echo '<p>' . esc_html( sprintf(
714 + /* translators: 1: version for sqlite 2: version for php 3: webserver version 4: version for plugin 5: igbinary 6:APCu 7:WordPress */
715 + __( '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' ),
716 + $wp_object_cache->sqlite_get_version(),
717 + PHP_VERSION,
718 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
719 + $server_software,
720 + $this->parent->_version,
721 + $igbinary,
722 + $apcu_version,
723 + $wp_version ) ) . '</p>';
724 + }
725 + }
585 726
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 - }
727 + /**
728 + * Statistics tab section.
729 + *
730 + * @param array $section Array of section ids.
731 + *
732 + * @return void
733 + * @throws Exception Announce Database Failure.
734 + * @noinspection PhpUnusedParameterInspection
735 + */
736 + public function stats_section_header( $section ) {
591 737
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 - }
738 + $this->support_links();
739 + $this->versions();
740 +
741 + $stats = new SQLite_Object_Cache_Statistics ( get_option( $this->parent->_token . '_settings', array() ) );
742 + $stats->init();
743 + $stats->render();
744 + $stats->render_usage();
745 + }
746 +
747 + /**
748 + * Load settings page content.
749 + *
750 + * @return void
751 + */
752 + public function settings_page() {
753 +
754 + $this->enqueue_assets();
755 + /* get the tab chosen by the user ('standard' by default or 'stats') */
756 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
757 + $tab = isset ( $_REQUEST['tab'] ) ? sanitize_key( $_REQUEST['tab'] ) : 'standard';
758 +
759 + // Build page HTML.
760 + echo '<div class="wrap" id="' . esc_attr( $this->parent->_token . '_settings' ) . '">' . PHP_EOL;
761 + echo '<h2>' . esc_html__( 'SQLite Persistent Object Cache', 'sqlite-object-cache' ) . '</h2>' . PHP_EOL;
762 +
763 + $submit_caption = __( 'Save Settings', 'sqlite-object-cache' );
764 + $this->complain_if_sqlite3_unavailable( true );
765 +
766 + // Show page tabs.
767 + if ( is_array( $this->settings ) && 1 < count( $this->settings ) ) {
768 +
769 + echo '<h2 class="nav-tab-wrapper">' . PHP_EOL;
770 +
771 + foreach ( $this->settings as $section => $data ) {
772 +
773 + // Set tab class.
774 + $class = 'nav-tab';
775 + if ( $section === $tab ) {
776 + $class .= ' nav-tab-active';
777 + $submit_caption = $data['submit'];
778 + }
779 +
780 + // Set tab link.
781 + $tab_link = add_query_arg( array( 'tab' => $section ) );
782 + $tab_link = remove_query_arg( 'settings-updated', $tab_link );
783 +
784 + // Output tab.
785 + echo '<a href="' . esc_url( $tab_link ) . '" class="' . esc_attr( $class ) . '">' . esc_html( $data['title'] ) . '</a>' . PHP_EOL;
786 + }
787 +
788 + if ( 'stats' === $tab ) {
789 + echo '<a href="' . esc_url( self::$statistics_help_url ) . '" class="nav-tab" target="_blank">' . esc_html__( 'Help', 'sqlite-object-cache' ) . '</a>' . PHP_EOL;
790 + }
791 +
792 + echo '</h2>' . PHP_EOL;
793 + }
794 +
795 + /** @noinspection HtmlUnknownTarget */
796 + echo '<form method="post" action="options.php" enctype="multipart/form-data">' . PHP_EOL;
797 +
798 + // Get settings fields.
799 + settings_fields( 'sqlite_object_cache_settings' );
800 + do_settings_sections( 'sqlite_object_cache_settings' );
801 +
802 + echo '<p class="submit">' . PHP_EOL;
803 + echo '<input type="hidden" name="tab" value="' . esc_attr( $tab ) . '" />' . PHP_EOL;
804 + echo '<input name="Submit" type="submit" class="button-primary" value="' . esc_attr( $submit_caption ) . '" />' . PHP_EOL;
805 + echo '</p></form></div>' . PHP_EOL;
806 + }
807 +
808 + /**
809 + * Admin enqueue assets
810 + *
811 + * @param string $hook Hook parameter.
812 + *
813 + * @return void
814 + * @noinspection PhpUnusedParameterInspection
815 + */
816 + public function enqueue_assets( $hook = '' ) {
817 + wp_register_style( $this->parent->_token . '-admin',
818 + esc_url( $this->parent->assets_url ) . 'css/admin.css',
819 + array(), $this->parent->_version );
820 + wp_enqueue_style( $this->parent->_token . '-admin' );
821 + }
822 +
823 + /**
824 + * Enters maintenance mode.
825 + *
826 + * @return void
827 + */
828 + private function enter_maintenance_mode() {
829 + $maintenanceFileName = ABSPATH . '.maintenance';
830 + $maintain = array();
831 + array_push( $maintain,
832 + '<?php',
833 + '$upgrading = ' . time() . ';',
834 + '?>' );
835 + file_put_contents( $maintenanceFileName, implode( PHP_EOL, $maintain ) );
836 + }
837 +
838 + /**
839 + * Exits maintenance mode.
840 + *
841 + * @return void
842 + */
843 + private function exit_maintenance_mode() {
844 + $maintenanceFileName = ABSPATH . '.maintenance';
845 + wp_delete_file( $maintenanceFileName );
846 + }
847 +
848 + /**
849 + * Update a variable in the wp-config.php file.
850 + *
851 + * If enabling, check if the variable is defined, and if not, define it.
852 + * Vice versa for disabling.
853 + */
854 + private function update_wp_config_constant( $symbol, $enable ) {
855 +
856 + /**
857 + * Follow WP's logic to locate wp-config file
858 + * @see wp-load.php
859 + */
860 + $conf_file = ABSPATH . 'wp-config.php';
861 + if ( ! file_exists( $conf_file ) ) {
862 + $conf_file = dirname( ABSPATH ) . '/wp-config.php';
863 + }
864 + $config = new SQLite_Object_Cache_File( $conf_file );
865 + // Remove the line `define('WHATEVER', true/false);` first
866 + $removed = $config->remove( $symbol );
867 + $inserted = false;
868 + if ( $enable ) {
869 + $cmd = "define( '" . $symbol . "', true );";
870 + $inserted = $config->insert_before( $cmd );
871 + }
872 + if ( $removed !== $inserted ) {
873 + $config->save();
874 + }
875 + return true;
876 + }
877 +
878 + /**
879 + * Put troubleshooting information into Site Health - Info
880 + *
881 + * @param array $info
882 + *
883 + * @return array
884 + */
885 + public function debug_information( $info ) {
886 + global $wp_object_cache;
887 +
888 + $stanza = array(
889 + 'label' => 'SQLite Object Cache',
890 + 'fields' => array()
891 + );
892 +
893 + try {
894 + $option = get_option( $this->parent->_token . '_settings', array() );
895 +
896 + $stanza['fields']['configuration'] = array(
897 + 'label' => __( 'Configuration', 'sqlite-object-cache' ),
898 + 'value' => $this->flatten( $option ),
899 + );
900 +
901 + } catch ( \Exception $e ) {
902 + /* empty, intentionally */
903 + }
904 +
905 + ob_start();
906 + try {
907 + $this->versions();
908 + $versions = wp_strip_all_tags( ob_get_clean() );
909 + $stanza['fields']['versions'] = array(
910 + 'label' => __( 'Versions', 'sqlite-object-cache' ),
911 + 'value' => $versions,
912 + );
913 + } catch ( \Exception $e ) {
914 + ob_get_clean();
915 + }
916 +
917 + try {
918 + $settings = array();
919 + if ( $this->parent->apcu_extension_is_enabled() ) {
920 + $settings = array_merge( $settings, ini_get_all( 'apcu' ) );
921 + }
922 + $settings = array_merge( $settings, ini_get_all( 'sqlite3' ) );
923 + $settings = array_merge( $settings, $this->inis_get( 'session.save_handler', 'session.auto_start', 'session.lifetime', 'session.gc_maxlifetime' ) );
924 +
925 + $stanza['fields']['settings'] = array(
926 + 'label' => __( 'Settings', 'sqlite-object-cache' ),
927 + 'value' => $this->flatten( $settings ),
928 + );
929 + } catch ( \Exception $e ) {
930 + /* empty, intentionally */
931 + }
932 +
933 + try {
934 + if ( $this->parent->apcu_extension_is_enabled() ) {
935 +
936 + $sizes = array();
937 + $counts = array();
938 + $totalsize = 0;
939 + $totalcount = 0;
940 + foreach ( new APCUIterator( null, APC_ITER_KEY | APC_ITER_MEM_SIZE ) as $item ) {
941 + $salt = '?';
942 + $splits = explode( '|', $item['key'], 2 );
943 + if ( count( $splits ) >= 2 ) {
944 + $salt = $splits[0];
945 + }
946 + if ( ! array_key_exists( $salt, $sizes ) ) {
947 + $sizes[ $salt ] = 0;
948 + $counts[ $salt ] = 0;
949 + }
950 + $sizes[ $salt ] += $item['mem_size'];
951 + $totalsize += $item['mem_size'];
952 + $counts [ $salt ] += 1;
953 + $totalcount += 1;
954 + }
955 +
956 + $apcusalt = trim( property_exists( $wp_object_cache, 'apcusalt' ) && is_string( $wp_object_cache->apcusalt ) ? $wp_object_cache->apcusalt : '', '|' );
957 + $settings = array();
958 + $settings ['salt'] = $apcusalt;
959 + $settings ['total'] = "$totalsize($totalcount)";
960 + $siteno = 1;
961 + foreach ( $sizes as $salt => $val ) {
962 + switch ( $salt ) {
963 + case $apcusalt:
964 + $csalt = 'mine';
965 + break;
966 + case '?':
967 + $csalt = 'no salt';
968 + break;
969 + default:
970 + $csalt = 'site' . $siteno ++;
971 + break;
972 + }
973 + $settings[ $csalt ] = "$val($counts[$salt])";
974 + }
975 +
976 +
977 + $stanza['fields']['apcu_utilization'] = array(
978 + 'label' => __( 'APCu utilization', 'sqlite-object-cache' ),
979 + 'value' => $this->flatten( $settings ),
980 + );
981 +
982 + $apcu_cache_info = array();
983 + $apcu_cache_info = array_merge( $apcu_cache_info, apcu_cache_info( true ) );
984 + $apcu_cache_info = array_merge( $apcu_cache_info, apcu_sma_info( true ) );
985 +
986 + $stanza['fields']['apcu_cache_info'] = array(
987 + 'label' => __( 'APCu info', 'sqlite-object-cache' ),
988 + 'value' => $this->flatten( $apcu_cache_info ),
989 + );
990 +
991 +
992 + }
993 + } catch ( \Exception $e ) {
994 + /* empty, intentionally */
995 + }
996 +
997 + try {
998 + global $wp_object_cache;
999 + if ( method_exists( $wp_object_cache, 'sqlite_sizes' ) ) {
1000 + $stanza['fields']['sqlite_sizes'] = array(
1001 + 'label' => __( 'SQLite utilization', 'sqlite-object-cache' ),
1002 + 'value' => $this->flatten( $wp_object_cache->sqlite_sizes() ),
1003 + );
1004 + }
1005 + } catch ( \Exception $e ) {
1006 + /* empty, intentionally */
1007 + }
1008 +
1009 +
1010 + $info['sqlite-object-cache'] = $stanza;
1011 + return $info;
1012 + }
1013 +
1014 + private function inis_get( ...$list ) {
1015 + $result = array();
1016 + foreach ( $list as $item ) {
1017 + $val = ini_get( $item );
1018 + $result[ $item ] = $val ?: '-missing-';
1019 + }
1020 + return $result;
1021 +
1022 + }
1023 +
1024 + private function flatten( $a ) {
1025 + $result = array();
1026 + foreach ( $a as $k => $v ) {
1027 + if ( is_array( $v ) ) {
1028 + if ( array_key_exists( 'local_value', $v ) ) {
1029 + $v = $v['local_value'];
1030 + } else {
1031 + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export
1032 + $v = var_export( $v, true );
1033 + }
1034 + }
1035 + $result [] = $k . ':' . $v;
1036 + }
1037 + return implode( '; ', $result );
1038 + }
1039 +
1040 + /**
1041 + * Generate HTML for displaying fields.
1042 + *
1043 + * @param mixed $idata Data array.
1044 + * @param object $post Post object.
1045 + */
1046 + public function echo_field( $idata = array(), $post = null ) {
1047 +
1048 + // Get field info.
1049 + if ( isset( $idata['field'] ) ) {
1050 + $field = $idata['field'];
1051 + } else {
1052 + $field = $idata;
1053 + }
1054 +
1055 + $option_name = '';
1056 + if ( isset( $idata['option'] ) ) {
1057 + $option_name = $idata['option'];
1058 + }
1059 +
1060 + /* Get saved data. */
1061 + $field_id = $field['id'];
1062 + $data = null;
1063 +
1064 + /* Data to display, if set */
1065 + $option = get_option( $option_name, array() );
1066 + if ( is_array( $option ) && array_key_exists( $field_id, $option ) ) {
1067 + $data = $option[ $field_id ];
1068 + }
1069 +
1070 + /* the reset element sets the value of the field, overriding whatever is in the option */
1071 + if ( array_key_exists( 'reset', $field ) ) {
1072 + $data = $field['reset'];
1073 + }
1074 +
1075 + /* Show default data if no option saved and default is supplied. */
1076 + if ( null === $data && isset( $field['default'] ) ) {
1077 + $data = $field['default'];
1078 + } elseif ( null === $data ) {
1079 + $data = '';
1080 + }
1081 +
1082 + /* CSS class array */
1083 + $classes = array();
1084 + if ( array_key_exists( 'cssclass', $field ) ) {
1085 + $classes = is_array( $field['cssclass'] ) ? $field['cssclass'] : array( $field['cssclass'] );
1086 + }
1087 +
1088 + echo '<input ';
1089 + echo 'id="' . esc_attr( $field['id'] ) . '" ';
1090 + $this->echo_classes( $classes ) . ' ';
1091 + echo 'name="' . esc_attr( $option_name ) . '[' . esc_attr( $field_id ) . ']" ';
1092 + if ( array_key_exists( 'placeholder', $field ) ) {
1093 + echo 'placeholder="' . esc_attr( $field['placeholder'] ) . '" ';
1094 + }
1095 + switch ( $field['type'] ) {
1096 +
1097 + case 'text':
1098 + case 'url':
1099 + case 'email':
1100 + echo 'type="text" ';
1101 + echo 'value="' . esc_attr( $data ) . '" ';
1102 + break;
1103 +
1104 + case 'number':
1105 + echo 'type="number" ';
1106 + echo 'value="' . esc_attr( $data ) . '" ';
1107 + if ( isset( $field['min'] ) ) {
1108 + echo 'min="' . esc_attr( $field['min'] ) . '" ';
1109 + }
1110 + if ( isset( $field['max'] ) ) {
1111 + echo 'max="' . esc_attr( $field['max'] ) . '" ';
1112 + }
1113 + if ( isset( $field['step'] ) ) {
1114 + echo 'step="' . esc_attr( $field['step'] ) . '" ';
1115 + }
1116 + break;
1117 +
1118 + case 'password':
1119 + case 'hidden':
1120 + echo 'type="' . esc_attr( $field['type'] ) . '" ';
1121 + echo 'value="' . esc_attr( $data ) . '" ';
1122 + break;
1123 +
1124 + case 'checkbox':
1125 + echo 'type="checkbox" ';
1126 + if ( 'on' === $data ) {
1127 + echo 'checked="checked" ';
1128 + }
1129 + break;
1130 + }
1131 + echo '>' . PHP_EOL;
1132 +
1133 + if ( ! $post ) {
1134 + echo '<label for="' . esc_attr( $field['id'] ) . '">' . PHP_EOL;
1135 + }
1136 +
1137 + echo '<span class="description">' . esc_html( $field['description'] ) . '</span>' . PHP_EOL;
1138 +
1139 + if ( ! $post ) {
1140 + echo '</label>' . PHP_EOL;
1141 + }
1142 + }
1143 +
1144 + /**
1145 + * Given an array of CSS class names ['foo', 'bar'] echo class="foo bar".
1146 + *
1147 + * If the array is empty just echo a space.
1148 + *
1149 + * @param array $classes
1150 + *
1151 + * @return void
1152 + */
1153 + private function echo_classes( array $classes ) {
1154 + if ( count( $classes ) > 0 ) {
1155 + echo 'class="';
1156 + echo esc_html( implode( ' ', array_map( 'esc_attr', $classes ) ) );
1157 + echo '"';
1158 + }
1159 + echo ' ';
1160 + }
1161 +
1162 +
606 1163 }