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 +1068 -543 1.3.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,624 +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 = array();
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', array( $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', array( $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', array( $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 - array(
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', array( $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', array( $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'] = array(
99 - 'title' => __( 'Settings', 'sqlite-object-cache' ),
100 - 'submit' => __( 'Save Settings', 'sqlite-object-cache' ),
101 - 'description' => '',
102 - 'render_section_header' => array( $this, 'settings_section_header' ),
103 - 'form_post_callback' => array( $this, 'validate_settings' ),
104 - 'fields' => array(
105 - array(
106 - 'id' => 'flush',
107 - 'label' => __( 'Flush now', 'sqlite-object-cache' ),
108 - 'description' => __( 'Check to flush the cache (delete all its entries) now.', 'sqlite-object-cache' ) . ' ' .
109 - __( 'This briefly puts your site into maintenance mode.', 'sqlite-object-cache' ),
110 - 'type' => 'checkbox',
111 - 'default' => '',
112 - 'reset' => '',
113 - ),
114 - array(
115 - 'id' => 'target_size',
116 - 'label' => __( 'Cache Size', 'sqlite-object-cache' ),
117 - 'description' => __( 'MiB. When the cache grows larger than this, hourly cleanup removes the oldest entries.', 'sqlite-object-cache' ),
118 - 'type' => 'number',
119 - 'default' => 4,
120 - 'min' => 0.5,
121 - 'step' => 'any',
122 - 'cssclass' => 'narrow',
123 - 'placeholder' => __( 'MiB.', 'sqlite-object-cache' ),
124 - ),
125 - array(
126 - 'id' => 'cleanup',
127 - 'label' => __( 'Clean up now', 'sqlite-object-cache' ),
128 - 'description' => __( 'Check to clean up the cache (delete old data) now.', 'sqlite-object-cache' ),
129 - 'type' => 'checkbox',
130 - 'default' => '',
131 - 'reset' => '',
132 - ),
133 - array(
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 - array(
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 - array(
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'] = array(
167 - 'title' => __( 'Statistics', 'sqlite-object-cache' ),
168 - 'submit' => __( 'Reset Statistics', 'sqlite-object-cache' ),
169 - 'render_section_header' => array( $this, 'stats_section_header' ),
170 - 'form_post_callback' => array( $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 - try {
201 - $this->enter_maintenance_mode();
202 - $wp_object_cache->flush( true );
203 - } finally {
204 - $this->exit_maintenance_mode();
205 - }
206 - } else {
207 - wp_cache_flush();
208 - }
209 - unset ( $option['flush'] );
210 - }
211 - if ( array_key_exists( 'cleanup', $option ) && $option ['cleanup'] === 'on' ) {
212 - $this->parent->clean_job();
213 - unset ( $option['cleanup'] );
214 - }
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 + }
215 160
216 - $this->numeric_option( $option, 'target_size', 4 );
217 - $this->numeric_option( $option, 'samplerate', 10 );
218 - $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 + }
219 165
220 - if ( array_key_exists( 'capture', $option ) && $option['capture'] === 'on' ) {
221 - $option['previouscapture'] = 0;
222 - }
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 + );
223 172
224 - return $option;
225 - }
173 + return $plugin_meta;
174 + }
226 175
227 - /**
228 - * Retrieve a numeric option, and set the default if it isn't present.
229 - *
230 - * @param array $option Option array value.
231 - * @param string $name Name of the element in the option array.
232 - * @param mixed $default Default value to use.
233 - *
234 - * @return float|int|mixed|string
235 - */
236 - private function numeric_option( &$option, $name, $default ) {
237 - $result = $default;
238 - if ( array_key_exists( $name, $option ) && is_numeric( $option[ $name ] ) ) {
239 - $result = $option[ $name ];
240 - $result = $result ?: $default;
241 - } else {
242 - $option[ $name ] = $default;
243 - }
176 + /**
177 + * Build settings fields
178 + *
179 + * @return array Fields to be displayed on settings page
180 + */
181 + private function settings_fields() {
244 182
245 - return $result;
246 - }
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 + );
247 259
248 - /**
249 - * Filters an option value following sanitization.
250 - *
251 - * @param string $option The sanitized option value.
252 - * @param string $name The option name.
253 - * @param string $original_value The original value passed to the function.
254 - *
255 - * @throws Exception Announce SQLite failure.
256 - * @since 2.3.0
257 - * @since 4.3.0 Added the `$original_value` parameter.
258 - *
259 - * @noinspection PhpUnusedParameterInspection
260 - */
261 - 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 + ) );
262 268
263 - global $wp_object_cache;
264 - if ( method_exists( $wp_object_cache, 'sqlite_reset_statistics' ) ) {
265 - $wp_object_cache->sqlite_reset_statistics();
266 - }
269 + }
267 270
268 - /* 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 + );
269 282
270 - return get_option( $name );
271 - }
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 + );
272 289
273 - /**
274 - * Add settings page to admin menu
275 - *
276 - * @return void
277 - */
278 - public function add_menu_item() {
290 + return apply_filters( 'sqlite_object_cache_settings_fields', $settings );
291 + }
279 292
280 - $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() );
281 315
282 - // Do nothing if wrong location key is set.
283 - if ( is_array( $args ) && isset( $args['location'] ) && function_exists( 'add_' . $args['location'] . '_page' ) ) {
284 - switch ( $args['location'] ) {
285 - case 'options':
286 - case 'submenu':
287 - $page =
288 - add_submenu_page( $args['parent_slug'], $args['page_title'], $args['menu_title'], $args['capability'], $args['menu_slug'], $args['function'] );
289 - break;
290 - case 'menu':
291 - $page =
292 - add_menu_page( $args['page_title'], $args['menu_title'], $args['capability'], $args['menu_slug'], $args['function'], $args['icon_url'], $args['position'] );
293 - break;
294 - default:
295 - return;
296 - }
297 - add_action( 'admin_print_styles-' . $page, array( $this, 'settings_assets' ) );
298 - }
299 - }
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 + }
300 359
301 - /**
302 - * Prepare default settings page arguments
303 - *
304 - * @return mixed|void
305 - */
306 - private function menu_settings() {
307 - $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 );
308 363
309 - return apply_filters(
310 - $this->base . 'menu_settings',
311 - array(
312 - 'location' => 'options', // Possible settings: options, menu, submenu.
313 - 'parent_slug' => 'options-general.php',
314 - 'page_title' => __( 'SQLite Persistent Object Cache', 'sqlite-object-cache' ),
315 - 'menu_title' => __( 'Object Cache', 'sqlite-object-cache' ),
316 - 'capability' => 'manage_options',
317 - 'menu_slug' => $this->parent->_token . '_settings',
318 - 'function' => array( $this, 'settings_page' ),
319 - 'icon_url' => '',
320 - 'position' => null,
321 - )
322 - );
323 - }
364 + if ( array_key_exists( 'capture', $option ) && $option['capture'] === 'on' ) {
365 + $option['previouscapture'] = 0;
366 + }
324 367
325 - /**
326 - * Emit a message if SQLite3 is not available.
327 - *
328 - * @param bool $verbose False means emit a notice. True means emit a longer explanation.
329 - *
330 - * @return void
331 - */
332 - private function complain_if_sqlite3_unavailable( $verbose = false ) {
333 - if ( true !== $this->has ) {
334 - if ( ! $verbose ) {
335 - echo '<div class="notice notice-error sql-object-cache">';
336 - echo esc_html( $this->has );
337 - echo '</div>';
338 - } else {
339 - echo '<div>';
340 - echo '<h3 style="color: #d63638">' . esc_html__( 'Server configuration problem', 'sqlite-object-cache' ) . '</h3>';
341 - echo '<p>';
368 + return $option;
369 + }
342 370
343 - if ( ! class_exists( 'SQLite3' ) || ! extension_loaded( 'sqlite3' ) ) {
344 - echo esc_html__( 'The SQLite Persistent Object Cache plugin requires php\'s SQLite3 extension.', 'sqlite-object-cache' ) . ' ';
345 - echo esc_html__( 'That extension is not installed in your server, so the plugin cannot work.', 'sqlite-object-cache' ) . ' ';
346 - }
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 + }
347 388
348 - $sqlite_version = $this->parent->sqlite_get_version();
349 - if ( version_compare( $sqlite_version, $this->parent->minimum_sqlite_version ) < 0 ) {
350 - echo esc_html( sprintf(
351 - /* translators: 1 actual SQLite version. 2 required SQLite version) */
352 - __( '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' ),
353 - $sqlite_version, $this->parent->minimum_sqlite_version ) );
354 - }
389 + return $result;
390 + }
355 391
356 - echo '</p></div>' . PHP_EOL;
357 - }
358 - }
359 - }
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 ) {
360 406
361 - /**
362 - * Container for settings page arguments
363 - *
364 - * @param array $settings Settings array.
365 - *
366 - * @return array
367 - */
368 - public function configure_settings( $settings = array() ) {
369 - return $settings;
370 - }
407 + global $wp_object_cache;
408 + if ( method_exists( $wp_object_cache, 'sqlite_reset_statistics' ) ) {
409 + $wp_object_cache->sqlite_reset_statistics();
410 + }
371 411
372 - /**
373 - * Load settings JS & CSS
374 - *
375 - * @return void
376 - */
377 - public function settings_assets() {
412 + /* for this post operation, we don't want to change any plugin options. */
378 413
379 - }
414 + return get_option( $name );
415 + }
380 416
381 - /**
382 - * Add settings link to plugin list table
383 - *
384 - * @param array $links Existing links.
385 - *
386 - * @return array Modified links.
387 - */
388 - public function add_settings_link( $links ) {
389 - if ( true !== $this->has ) {
390 - $settings_link =
391 - '<a style="color: #d63638" href="options-general.php?page=' . $this->parent->_token . '_settings">' . __( 'Settings', 'sqlite-object-cache' ) . '</a>';
392 - array_unshift( $links, $settings_link );
393 - } else {
394 - $settings_link =
395 - '<a href="options-general.php?page=' . $this->parent->_token . '_settings">' . __( 'Settings', 'sqlite-object-cache' ) . '</a>';
396 - $statistics_link =
397 - '<a href="options-general.php?page=' . $this->parent->_token . '_settings&tab=stats">' . __( 'Statistics', 'sqlite-object-cache' ) . '</a>';
398 - array_unshift( $links, $settings_link, $statistics_link );
399 - }
417 + /**
418 + * Add settings page to admin menu
419 + *
420 + * @return void
421 + */
422 + public function add_menu_item() {
400 423
401 - return $links;
402 - }
424 + $args = $this->menu_settings();
403 425
404 - /**
405 - * Register plugin settings
406 - *
407 - * @return void
408 - */
409 - public function register_my_settings() {
410 - 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 + }
411 444
412 - /* get the tab chosen by the user ('standard' or 'stats') */
413 - $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();
414 452
415 - $default_option = array();
416 - $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 + }
417 468
418 - 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>';
419 486
420 - if ( $tab && $tab !== $section ) {
421 - continue;
422 - }
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 + }
423 491
424 - $setting_args = array(
425 - 'description' => $data['title'],
426 - 'default' => $default_option,
427 - );
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 + }
428 499
429 - // Validation callback for section.
430 - if ( isset( $data['form_post_callback'] ) ) {
431 - add_filter( "sanitize_option_$option_name", $data['form_post_callback'], 10, 3 );
432 - }
500 + echo '</p></div>' . PHP_EOL;
501 + }
502 + }
503 + }
433 504
434 - // Add section to page.
435 - add_settings_section( $section, '',
436 - $data ['render_section_header'],
437 - $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 + }
438 515
439 - if ( array_key_exists( 'fields', $data ) && is_array( $data['fields'] ) ) {
440 - foreach ( $data['fields'] as $field ) {
441 - $default_option [ $field['id'] ] = $field['default'];
442 - }
516 + /**
517 + * Load settings JS & CSS
518 + *
519 + * @return void
520 + */
521 + public function settings_assets() {
443 522
444 - foreach ( $data['fields'] as $field ) {
445 - // Add field to page.
446 - add_settings_field(
447 - $field['id'],
448 - $field['label'],
449 - array( $this->parent->admin, 'echo_field' ),
450 - $this->parent->_token . '_settings',
451 - $section,
452 - array(
453 - 'field' => $field,
454 - 'option' => $option_name,
455 - )
456 - );
457 - }
458 - }
459 - /* register the settings object */
460 - register_setting( $this->parent->_token . '_settings', $option_name, $setting_args );
461 - }
462 - }
463 - }
523 + }
464 524
465 - /**
466 - * Settings section.
467 - *
468 - * @param array $section Array of section ids.
469 - *
470 - * @return void
471 - * @throws Exception Announce Database Failure.
472 - */
473 - 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 + }
474 544
475 - $this->support_links();
476 - $this->versions();
545 + return $links;
546 + }
477 547
478 - if ( array_key_exists( 'description', $this->settings[ $section['id'] ] ) ) {
479 - echo '<p> ' . esc_html( $this->settings[ $section['id'] ]['description'] ) . '</p>' . PHP_EOL;
480 - }
481 - }
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();
482 556
483 - /**
484 - * Display support and rating links.
485 - *
486 - * @return void
487 - */
488 - private function support_links() {
557 + if ( is_array( $this->settings ) ) {
489 558
490 - $supportUrl = "https://wordpress.org/support/plugin/sqlite-object-cache/";
491 - $reviewUrl = "https://wordpress.org/support/plugin/sqlite-object-cache/reviews/";
492 - echo '<p>';
493 - echo esc_html__( 'For support please', 'sqlite-object-cache' ) . ' ';
494 - echo '<a href="' . esc_url( $supportUrl ) . '">' . esc_html__( 'click here', 'sqlite-object-cache' ) . '</a>. ';
495 - echo esc_html__( 'To rate this plugin please', 'sqlite-object-cache' ) . ' ';
496 - echo '<a href="' . esc_url( $reviewUrl ) . '">' . esc_html__( 'click here', 'sqlite-object-cache' ) . '</a>. ';
497 - echo esc_html__( 'Your feedback helps make it better, faster, and more useful', 'sqlite-object-cache' ) . '.';
498 - echo '</p>';
499 - }
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';
500 562
501 - /**
502 - * Display versions.
503 - *
504 - * @return void
505 - * @throws Exception Announce database failure.
506 - */
507 - private function versions() {
508 - global $wp_object_cache;
509 - $igbinary = function_exists( 'igbinary_serialize' ) && function_exists( 'igbinary_unserialize' )
510 - ? __( 'available', 'sqlite-object-cache' )
511 - : __( 'unavailable', 'sqlite-object-cache' );
563 + $default_option = array();
564 + $option_name = $this->base . 'settings';
512 565
513 - if ( method_exists( $wp_object_cache, 'sqlite_get_version' ) ) {
514 - echo '<p>' . esc_html( sprintf(
515 - /* translators: 1: version for sqlite 2: version for php 3: version for plugin 4: status of igbinary */
516 - __( 'Versions: SQLite: %1$s php: %2$s Plugin: %3$s igbinary: %4$s.', 'sqlite-object-cache' ),
517 - $wp_object_cache->sqlite_get_version(),
518 - PHP_VERSION,
519 - $this->parent->_version,
520 - $igbinary ) ) . '</p>';
521 - }
522 - }
566 + foreach ( $this->settings as $section => $data ) {
523 567
524 - /**
525 - * Statistics tab section.
526 - *
527 - * @param array $section Array of section ids.
528 - *
529 - * @return void
530 - * @throws Exception Announce Database Failure.
531 - * @noinspection PhpUnusedParameterInspection
532 - */
533 - public function stats_section_header( $section ) {
568 + if ( $tab && $tab !== $section ) {
569 + continue;
570 + }
534 571
535 - $this->support_links();
536 - $this->versions();
572 + $setting_args = array(
573 + 'description' => $data['title'],
574 + 'default' => $default_option,
575 + );
537 576
538 - $stats = new SQLite_Object_Cache_Statistics ( get_option( $this->parent->_token . '_settings', array() ) );
539 - $stats->init();
540 - $stats->render();
541 - $stats->render_usage();
542 - }
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 + }
543 581
544 - /**
545 - * Load settings page content.
546 - *
547 - * @return void
548 - */
549 - public function settings_page() {
582 + // Add section to page.
583 + add_settings_section( $section, '',
584 + $data ['render_section_header'],
585 + $this->parent->_token . '_settings' );
550 586
551 - /* get the tab chosen by the user ('standard' by default or 'stats') */
552 - $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 + }
553 591
554 - // Build page HTML.
555 - echo '<div class="wrap" id="' . esc_attr( $this->parent->_token . '_settings' ) . '">' . PHP_EOL;
556 - 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 + }
557 612
558 - $submit_caption = __( 'Save Settings', 'sqlite-object-cache' );
559 - $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 ) {
560 622
561 - // Show page tabs.
562 - if ( is_array( $this->settings ) && 1 < count( $this->settings ) ) {
623 + $this->support_links();
624 + $this->apcu_admonition();
625 + $this->versions();
563 626
564 - 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 + }
565 631
566 - foreach ( $this->settings as $section => $data ) {
632 + /**
633 + * Display support and rating links.
634 + *
635 + * @return void
636 + */
637 + private function support_links() {
567 638
568 - // Set tab class.
569 - $class = 'nav-tab';
570 - if ( $section === $tab ) {
571 - $class .= ' nav-tab-active';
572 - $submit_caption = $data['submit'];
573 - }
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>';
574 648
575 - // Set tab link.
576 - $tab_link = add_query_arg( array( 'tab' => $section ) );
577 - $tab_link = remove_query_arg( 'settings-updated', $tab_link );
649 + }
578 650
579 - // Output tab.
580 - echo '<a href="' . esc_url( $tab_link ) . '" class="' . esc_attr( $class ) . '">' . esc_html( $data['title'] ) . '</a>' . PHP_EOL;
581 - }
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 + }
582 686
583 - echo '</h2>' . PHP_EOL;
584 - }
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' ) : '';
585 701
586 - /** @noinspection HtmlUnknownTarget */
587 - 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' );
588 705
589 - // Get settings fields.
590 - settings_fields( $this->parent->_token . '_settings' );
591 - 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 + }
592 726
593 - echo '<p class="submit">' . PHP_EOL;
594 - echo '<input type="hidden" name="tab" value="' . esc_attr( $tab ) . '" />' . PHP_EOL;
595 - echo '<input name="Submit" type="submit" class="button-primary" value="' . esc_attr( $submit_caption ) . '" />' . PHP_EOL;
596 - echo '</p></form></div>' . PHP_EOL;
597 - }
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 ) {
598 737
599 - /**
600 - * Admin enqueue assets
601 - *
602 - * @param string $hook Hook parameter.
603 - *
604 - * @return void
605 - * @noinspection PhpUnusedParameterInspection
606 - */
607 - public function enqueue_assets( $hook = '' ) {
608 - wp_register_style( $this->parent->_token . '-admin',
609 - esc_url( $this->parent->assets_url ) . 'css/admin.css',
610 - array(), $this->parent->_version );
611 - wp_enqueue_style( $this->parent->_token . '-admin' );
612 - }
738 + $this->support_links();
739 + $this->versions();
613 740
614 - /**
615 - * Enters maintenance mode.
616 - *
617 - * @return void
618 - */
619 - private function enter_maintenance_mode() {
620 - $maintenanceFileName = ABSPATH . '.maintenance';
621 - $maintain = array();
622 - array_push( $maintain,
623 - '<?php',
624 - '$upgrading = ' . time() . ';',
625 - '?>' );
626 - file_put_contents( $maintenanceFileName, implode( PHP_EOL, $maintain ) );
627 - }
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 + }
628 746
629 - /**
630 - * Exits maintenance mode.
631 - *
632 - * @return void
633 - */
634 - private function exit_maintenance_mode() {
635 - $maintenanceFileName = ABSPATH . '.maintenance';
636 - unlink( $maintenanceFileName );
637 - }
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 +
638 1163 }