admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('fastless_cache_admin'), 'siteUrl' => home_url('/'), 'labels' => array( 'cacheCleared' => __('Cache cleared.', 'fastless-cache'), 'settingsSaved' => __('Settings saved.', 'fastless-cache'), 'imported' => __('Settings imported.', 'fastless-cache'), 'error' => __('Something went wrong.', 'fastless-cache'), ), 'preload' => array( 'start' => __('Preload cache', 'fastless-cache'), 'starting' => __('Starting preload…', 'fastless-cache'), 'running' => __('Preloading…', 'fastless-cache'), 'stop' => __('Stop', 'fastless-cache'), 'stopping' => __('Stopping…', 'fastless-cache'), 'confirmStop' => __('Stop the preload? Progress so far is kept and you can start again later.', 'fastless-cache'), 'resumed' => __('Preload resumed.', 'fastless-cache'), 'connectionLost' => __('Connection lost - the preload is paused. Reload this page to resume it.', 'fastless-cache'), ), )); } } /** * Render the admin page. * * @return void */ public function render_page() { $nav = Fastless_Cache_Tabs::nav(); $tab = isset($_GET['tab']) ? sanitize_key($_GET['tab']) : 'dashboard'; // phpcs:ignore WordPress.Security.NonceVerification -- read-only display parameter, sanitized with sanitize_key() and restricted to the manage_options screen. if (! array_key_exists($tab, $nav)) { $tab = 'dashboard'; } // Populate the dynamic tools fields before rendering that tab. if ('tools' === $tab) { $this->render_tools_content(); } $version = defined('FASTLESS_CACHE_VERSION') ? FASTLESS_CACHE_VERSION : ''; ?>
render_notices(); ?>
'success', 'message' => __('Settings saved.', 'fastless-cache')); } if (isset($_GET['fastless_cache_cleared'])) { // phpcs:ignore WordPress.Security.NonceVerification -- isset-only check of post-redirect-get notice flags; no form processing occurs here. $toasts[] = array('type' => 'success', 'message' => __('Cache cleared.', 'fastless-cache')); } if (isset($_GET['fastless_reset'])) { // phpcs:ignore WordPress.Security.NonceVerification -- isset-only check of post-redirect-get notice flags; no form processing occurs here. $toasts[] = array('type' => 'success', 'message' => __('Settings reset.', 'fastless-cache')); } if (isset($_GET['fastless_imported'])) { // phpcs:ignore WordPress.Security.NonceVerification -- isset-only check of post-redirect-get notice flags; no form processing occurs here. $toasts[] = array('type' => 'success', 'message' => __('Settings imported.', 'fastless-cache')); } if (isset($_GET['fastless_error'])) { // phpcs:ignore WordPress.Security.NonceVerification -- isset-only check of post-redirect-get notice flags; no form processing occurs here. $toasts[] = array('type' => 'error', 'message' => sanitize_text_field(wp_unslash($_GET['fastless_error']))); // phpcs:ignore WordPress.Security.NonceVerification -- isset-only check of post-redirect-get notice flags; no form processing occurs here. } if (empty($toasts)) { return; } ?>
>

render_export_import()); } /** * The export / import block. * * The export is not on screen until asked for: the JSON rides along in an * inert application/json block (hex-escaped so it can never break out of * the script element) and the button copies it into the textarea, ready to * select and copy. * * @return string */ private function render_export_import() { $export = wp_json_encode(Fastless_Cache_Options::all(), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_HEX_APOS); ob_start(); ?>
save($sections); if (is_wp_error($result)) { wp_safe_redirect(add_query_arg('fastless_error', urlencode($result->get_error_message()), $this->admin_url())); exit; } // update_option() skips its hooks when the new value equals the old // one, so a re-save of unchanged values would leave a stale settings // file behind (e.g. a footprint value that diverged on an earlier // install). Rewrite the file from the stored settings every time. if (class_exists('Fastless_Cache')) { Fastless_Cache::on_update_backend('fastless_cache', Fastless_Cache::get_settings(false)); } if (Fastless_Cache_Options::enabled('clearing', 'clear_site_cache_on_saved_settings')) { Fastless_Cache::clear_site_cache(); } wp_safe_redirect(add_query_arg('fastless_saved', 1, $this->admin_url())); exit; } /** * Handle a clear-cache request. * * @return void */ public function handle_clear() { $this->verify_action_nonce(); Fastless_Cache::clear_site_cache(); wp_safe_redirect(add_query_arg('fastless_cache_cleared', 1, $this->admin_url())); exit; } /** * Handle a settings reset. * * @return void */ public function handle_reset() { $this->verify_action_nonce(); Fastless_Cache_Options::reset(); Fastless_Cache::update_backend(); wp_safe_redirect(add_query_arg('fastless_reset', 1, $this->admin_url())); exit; } /** * Handle an export request: download the settings as a JSON file. * * @return void */ public function handle_export() { if (! current_user_can('manage_options')) { wp_die(esc_html__('You do not have permission to do that.', 'fastless-cache')); } check_admin_referer('fastless_cache_admin', 'fastless_cache_nonce'); $data = wp_json_encode(Fastless_Cache_Options::all()); header('Content-Type: application/json'); header('Content-Disposition: attachment; filename="fastless-cache-settings.json"'); echo $data; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped exit; } /** * Handle an import request. * * @return void */ public function handle_import() { if (! current_user_can('manage_options')) { wp_die(esc_html__('You do not have permission to do that.', 'fastless-cache')); } check_admin_referer('fastless_cache_admin', 'fastless_cache_nonce'); $raw = isset($_POST['fastless_cache_import']) ? wp_unslash($_POST['fastless_cache_import']) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- raw JSON input validated via json_decode() and schema-driven sanitization in sanitize_import_values(). // Validate JSON: check for parse errors, ensure it's an array, allowlist keys. $data = json_decode($raw, true); if (json_last_error() !== JSON_ERROR_NONE || ! is_array($data)) { wp_safe_redirect(add_query_arg('fastless_error', urlencode(__('Invalid import data.', 'fastless-cache')), $this->admin_url())); exit; } // Allowlist expected top-level sections (schema-driven). $allowed_sections = array('general', 'clearing', 'exclusions', 'query_strings', 'cookies', 'tools'); $filtered_data = array(); foreach ($data as $section => $values) { if (! in_array($section, $allowed_sections, true) || ! is_array($values)) { continue; } $filtered_data[$section] = $this->sanitize_import_values($values); } if (empty($filtered_data)) { wp_safe_redirect(add_query_arg('fastless_error', urlencode(__('No valid settings found in import data.', 'fastless-cache')), $this->admin_url())); exit; } $result = Fastless_Cache_Options::instance()->save($filtered_data); if (is_wp_error($result)) { wp_safe_redirect(add_query_arg('fastless_error', urlencode($result->get_error_message()), $this->admin_url())); exit; } // Same stale-file reasoning as handle_save(): force the rewrite even // when the imported values match what is already stored. if (class_exists('Fastless_Cache')) { Fastless_Cache::on_update_backend('fastless_cache', Fastless_Cache::get_settings(false)); } wp_safe_redirect(add_query_arg('fastless_imported', 1, $this->admin_url())); exit; } /** * Recursively sanitize import values by type. * * @param array $values Raw values from JSON import. * @return array Sanitized values. */ private function sanitize_import_values($values) { $sanitized = array(); foreach ($values as $key => $value) { if (is_array($value)) { $sanitized[$key] = $this->sanitize_import_values($value); } elseif (is_bool($value)) { $sanitized[$key] = (bool) $value; } elseif (is_int($value)) { $sanitized[$key] = (int) $value; } elseif (is_float($value)) { $sanitized[$key] = (float) $value; } elseif (is_string($value)) { // Length limit for safety. $sanitized[$key] = substr(sanitize_text_field($value), 0, 4000); } else { // Skip null, objects, resources. continue; } } return $sanitized; } /** * Verify the capability and nonce shared by the action handlers. * * @return void */ private function verify_action_nonce() { if (! current_user_can('manage_options')) { wp_die(esc_html__('You do not have permission to do that.', 'fastless-cache')); } check_admin_referer('fastless_cache_admin', 'fastless_cache_nonce'); } /** * Build a URL to the panel with extra query args. * * @param array $args Query args. * @return string */ private function admin_url($args = array()) { return add_query_arg(array_merge(array('page' => self::SLUG), $args), admin_url('admin.php')); } }