Source: admin/dashboard.php

<?php
/**
 * Dashboard Page
 *
 * @package PoweredCache
 */

namespace PoweredCache\Admin\Dashboard;

use PoweredCache\Async\CachePurger;
use PoweredCache\Async\DatabaseOptimizer;
use PoweredCache\Config;
use PoweredCache\Encryption;
use PoweredCache\SettingsManifest;
use PoweredCache\SettingsRepository;
use PoweredCache\SettingsSaveService;
use function PoweredCache\Utils\is_dev_mode_active;
use function PoweredCache\Utils\mask_string;
use const PoweredCache\Constants\ALLOPTIONS_CRITICAL_THRESHOLD;
use const PoweredCache\Constants\ALLOPTIONS_WARNING_THRESHOLD;
use const PoweredCache\Constants\ICON_BASE64;
use const PoweredCache\Constants\MENU_SLUG;
use const PoweredCache\Constants\PURGE_CACHE_PLUGIN_NOTICE_TRANSIENT;
use function PoweredCache\Utils\can_configure_htaccess;
use function PoweredCache\Utils\can_configure_object_cache;
use function PoweredCache\Utils\can_control_all_settings;
use function PoweredCache\Utils\cdn_zones;
use function PoweredCache\Utils\get_available_object_caches;
use function PoweredCache\Utils\get_decrypted_setting;
use function PoweredCache\Utils\get_timeout_with_interval;
use function PoweredCache\Utils\is_premium;
use function PoweredCache\Utils\powered_cache_flush;
use function PoweredCache\Utils\remove_dir;
use function PoweredCache\Utils\sanitize_css;
use const PoweredCache\Constants\UNMASK_CHARACTER_LENGTH;

// phpcs:disable WordPress.WhiteSpace.PrecisionAlignment.Found
// phpcs:disable Generic.WhiteSpace.DisallowSpaceIndent.SpacesUsed
// phpcs:disable WordPress.WP.I18n.MissingTranslatorsComment

/**
 * Default setup routine
 *
 * @return void
 */
function setup() {
	if ( POWERED_CACHE_IS_NETWORK ) {
		add_action( 'network_admin_menu', __NAMESPACE__ . '\\admin_menu' );
		add_action( 'network_admin_notices', __NAMESPACE__ . '\\maybe_display_message' );
	} else {
		add_action( 'admin_menu', __NAMESPACE__ . '\\admin_menu' );
	}

	add_action( 'admin_notices', __NAMESPACE__ . '\\maybe_display_message' );
	add_action( 'admin_head', __NAMESPACE__ . '\\admin_menu_icon_styles' );
	add_action( 'network_admin_head', __NAMESPACE__ . '\\admin_menu_icon_styles' );

	add_action( 'admin_init', __NAMESPACE__ . '\\process_form_submit' );
	add_action( 'admin_bar_menu', __NAMESPACE__ . '\\admin_bar_menu', 999 );
	add_action( 'admin_bar_menu', __NAMESPACE__ . '\\purge_all_admin_bar_menu' );
	add_action( 'admin_post_powered_cache_purge_all_cache', __NAMESPACE__ . '\\purge_all_cache_action' );
	add_action( 'admin_post_powered_cache_download_rewrite_settings', __NAMESPACE__ . '\\download_rewrite_config' );
	add_action( 'wp_ajax_powered_cache_check_alloptions', __NAMESPACE__ . '\\check_alloptions' );
	add_action( 'admin_post_deactivate_plugin', __NAMESPACE__ . '\\deactivate_plugin' );
	add_filter( 'plugin_action_links_' . plugin_basename( POWERED_CACHE_PLUGIN_FILE ), __NAMESPACE__ . '\\action_links' );
	add_filter( 'network_admin_plugin_action_links_' . plugin_basename( POWERED_CACHE_PLUGIN_FILE ), __NAMESPACE__ . '\\action_links' );
	add_filter( 'submenu_file', __NAMESPACE__ . '\\highlight_settings_section_submenu' );
}

/**
 * Adds admin menu item
 *
 * @since 1.0
 */
function admin_menu() {
	global $powered_cache_settings_page;

	$capability = 'manage_options';

	if ( POWERED_CACHE_IS_NETWORK ) {
		$capability = 'manage_network';
	}

	$powered_cache_settings_page = add_menu_page(
		esc_html__( 'Powered Cache Settings', 'powered-cache' ),
		esc_html__( 'Powered Cache', 'powered-cache' ),
		$capability,
		MENU_SLUG,
		__NAMESPACE__ . '\settings_page',
		'none'
	);

	$sections            = settings_submenu_sections();
	$default_section_key = key( $sections );
	$default_section     = current( $sections );

	add_submenu_page(
		MENU_SLUG,
		esc_html__( 'Powered Cache Settings', 'powered-cache' ),
		esc_html( $default_section['label'] ),
		$capability,
		MENU_SLUG
	);

	foreach ( $sections as $section_key => $section ) {
		if ( $default_section_key === $section_key ) {
			continue;
		}

		add_submenu_page(
			MENU_SLUG,
			/* translators: %s: settings section label. */
			sprintf( esc_html__( '%s Settings', 'powered-cache' ), esc_html( $section['label'] ) ),
			esc_html( $section['label'] ),
			$capability,
			MENU_SLUG . '&section=' . rawurlencode( $section_key ),
			__NAMESPACE__ . '\settings_page'
		);
	}
}

/**
 * Prints admin menu icon styles.
 *
 * WordPress can color pseudo-element icons for active, hover, and inactive
 * menu states. Rendering the SVG as a mask keeps the custom mark aligned with
 * native admin menu icon behavior.
 *
 * @return void
 */
function admin_menu_icon_styles() {
	?>
	<style id="powered-cache-admin-menu-icon">
		#adminmenu #toplevel_page_powered-cache .wp-menu-image::before {
			background-color: currentColor;
			content: "";
			display: inline-block;
			height: 20px;
			margin: 0;
			mask: url("<?php echo esc_attr( ICON_BASE64 ); ?>") center / 20px 20px no-repeat;
			width: 20px;
			-webkit-mask: url("<?php echo esc_attr( ICON_BASE64 ); ?>") center / 20px 20px no-repeat;
		}
	</style>
	<?php
}

/**
 * Return settings sections exposed as submenu deep links.
 *
 * @return array
 */
function settings_submenu_sections() {
	$sections = SettingsManifest::sections();

	uasort(
		$sections,
		function ( $first, $second ) {
			return $first['order'] - $second['order'];
		}
	);

	return $sections;
}

/**
 * Return the default settings section key.
 *
 * @return string
 */
function default_settings_section_key() {
	$sections = settings_submenu_sections();

	return (string) key( $sections );
}

/**
 * Highlight the submenu item matching the current settings section deep link.
 *
 * @param string $submenu_file Current submenu file.
 *
 * @return string
 */
function highlight_settings_section_submenu( $submenu_file ) {
	if ( ! isset( $_GET['page'], $_GET['section'] ) || MENU_SLUG !== $_GET['page'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		return $submenu_file;
	}

	$section_key = sanitize_key( wp_unslash( $_GET['section'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended

	if ( ! isset( settings_submenu_sections()[ $section_key ] ) ) {
		return $submenu_file;
	}

	if ( default_settings_section_key() === $section_key ) {
		return MENU_SLUG;
	}

	return MENU_SLUG . '&section=' . rawurlencode( $section_key );
}

/**
 * Main settings page of the plugin
 */
function settings_page() {
	include __DIR__ . '/partials/settings-app.php';
}

/**
 * Process settings form action
 *
 * @since 2.0
 */
function process_form_submit() {

	$capability = POWERED_CACHE_IS_NETWORK ? 'manage_network' : 'manage_options';

	if ( ! current_user_can( $capability ) ) {
		return;
	}

	$nonce = filter_input( INPUT_POST, 'powered_cache_settings_nonce', FILTER_SANITIZE_SPECIAL_CHARS );
	if ( wp_verify_nonce( $nonce, 'powered_cache_update_settings' ) ) {
		$settings_repository = SettingsRepository::factory( POWERED_CACHE_IS_NETWORK );
		$settings_service    = SettingsSaveService::factory( $settings_repository, POWERED_CACHE_IS_NETWORK );
		$action              = isset( $_POST['powered_cache_form_action'] ) ? sanitize_text_field( wp_unslash( $_POST['powered_cache_form_action'] ) ) : '';
		$supported_actions   = array(
			'reset_settings',
			'export_settings',
			'import_settings',
			'enable_dev_mode',
			'disable_dev_mode',
			'optimize_database',
			'regenerate_configuration',
			'save_settings_and_optimize',
			'save_settings_and_clear_cache',
			'save_settings',
		);

		if ( ! in_array( $action, $supported_actions, true ) ) {
			return;
		}

		$old_options         = $settings_repository->all();
		$options             = $old_options;
		$should_save_options = true;

		switch ( $action ) {
			case 'reset_settings':
				$settings_repository->delete();

				if ( 'off' !== $old_options['object_cache'] ) {
					wp_cache_flush();
				}

				$options = $settings_repository->all();

				break;
			case 'export_settings':
				$filename = sprintf( 'powered-cache-settings-%s-%s.json', gmdate( 'Y-m-d' ), uniqid() );
				$options  = SettingsRepository::export_payload( SettingsRepository::redact_sensitive( $settings_repository->all() ) );
				$options  = wp_json_encode( $options, JSON_PRETTY_PRINT );

				nocache_headers();
				// phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged
				@header( 'Content-Type: application/json' );
				@header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
				@header( 'Content-Transfer-Encoding: binary' );
				@header( 'Content-Length: ' . strlen( $options ) );
				@header( 'Connection: close' );
				// phpcs:enable WordPress.PHP.NoSilencedErrors.Discouraged
				echo $options; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
				exit;
			case 'import_settings':
				if ( $_FILES['import_file'] && ! empty( $_FILES['import_file']['tmp_name'] ) ) { // phpcs:ignore
					$import_data     = file_get_contents( $_FILES['import_file']['tmp_name'] ); // phpcs:ignore
					$import_settings = SettingsRepository::settings_from_payload( json_decode( $import_data, true ) );
					$options         = prepare_import_options( $settings_repository, $import_settings );
				}
				break;
			case 'enable_dev_mode':
				$options['dev_mode'] = true;
				break;
			case 'disable_dev_mode':
				$options['dev_mode'] = false;
				break;
			case 'optimize_database':
				db_optimize( $old_options );
				$should_save_options = false;
				break;
			case 'regenerate_configuration':
				// Repair config files without touching settings, e.g. after another cache plugin removed WP_CACHE.
				$problems            = Config::factory()->regenerate_configuration( $old_options, POWERED_CACHE_IS_NETWORK );
				$action              = empty( $problems ) ? 'regenerate_configuration' : 'regenerate_configuration_failed';
				$should_save_options = false;
				break;
			case 'save_settings_and_optimize':
				$options = maybe_process_sensitive_settings( sanitize_options( $_POST ) );
				db_optimize( $options );
				break;
			case 'save_settings_and_clear_cache':
				$options = maybe_process_sensitive_settings( sanitize_options( $_POST ) );
				purge_all_cache( $options );
				break;
			case 'save_settings':
			default:
				$options = maybe_process_sensitive_settings( sanitize_options( $_POST ) );
				break;
		}

		if ( $should_save_options ) {
			$options = $settings_service->save( $options, $old_options );
		}

		$redirect_url = wp_get_referer();

		if ( empty( $redirect_url ) && isset( $_SERVER['REQUEST_URI'] ) ) {
			$redirect_url = wp_unslash( $_SERVER['REQUEST_URI'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		}

		$redirect_url = add_query_arg(
			[
				'pc_action' => $action,
			],
			$redirect_url
		);

		wp_safe_redirect( esc_url_raw( $redirect_url ) );
		exit;
	}
}

/**
 * Prepare imported settings without dropping legacy or extension-owned keys.
 *
 * @param SettingsRepository $settings_repository Settings repository.
 * @param array              $import_settings Imported settings.
 *
 * @return array
 */
function prepare_import_options( SettingsRepository $settings_repository, array $import_settings ) {
	return $settings_repository->sanitize( array_merge( $settings_repository->all(), $import_settings ) );
}

/**
 * Sanitize options
 *
 * @param array $options Raw input, most likely $_POST request
 *
 * @return array|mixed Sanitized options
 */
function sanitize_options( $options ) {
	$sanitized_options = [];

	if ( isset( $options['object_cache'] ) && in_array( $options['object_cache'], get_available_object_caches(), true ) ) {
		$sanitized_options['object_cache'] = sanitize_text_field( $options['object_cache'] );
	} else {
		$sanitized_options['object_cache'] = 'off';
	}

	$sanitized_options['enable_page_cache']                = ! empty( $options['enable_page_cache'] );
	$sanitized_options['cache_mobile']                     = ! empty( $options['cache_mobile'] );
	$sanitized_options['cache_mobile_separate_file']       = ! empty( $options['cache_mobile_separate_file'] );
	$sanitized_options['loggedin_user_cache']              = ! empty( $options['loggedin_user_cache'] );
	$sanitized_options['gzip_compression']                 = ! empty( $options['gzip_compression'] );
	$sanitized_options['cache_timeout']                    = absint( $options['cache_timeout'] );
	$sanitized_options['auto_configure_htaccess']          = ! empty( $options['auto_configure_htaccess'] );
	$sanitized_options['rewrite_file_optimizer']           = ! empty( $options['rewrite_file_optimizer'] );
	$sanitized_options['rejected_user_agents']             = sanitize_textarea_field( $options['rejected_user_agents'] );
	$sanitized_options['rejected_cookies']                 = sanitize_textarea_field( $options['rejected_cookies'] );
	$sanitized_options['rejected_referrers']               = sanitize_textarea_field( $options['rejected_referrers'] );
	$sanitized_options['vary_cookies']                     = sanitize_textarea_field( $options['vary_cookies'] );
	$sanitized_options['rejected_uri']                     = sanitize_textarea_field( $options['rejected_uri'] );
	$sanitized_options['cache_query_strings']              = sanitize_textarea_field( $options['cache_query_strings'] );
	$sanitized_options['ignored_query_strings']            = sanitize_textarea_field( $options['ignored_query_strings'] );
	$sanitized_options['purge_additional_pages']           = sanitize_textarea_field( $options['purge_additional_pages'] );
	$sanitized_options['minify_html']                      = ! empty( $options['minify_html'] );
	$sanitized_options['minify_html_dom_optimization']     = ! empty( $options['minify_html_dom_optimization'] );
	$sanitized_options['combine_google_fonts']             = ! empty( $options['combine_google_fonts'] );
	$sanitized_options['swap_google_fonts_display']        = ! empty( $options['swap_google_fonts_display'] );
	$sanitized_options['use_bunny_fonts']                  = ! empty( $options['use_bunny_fonts'] );
	$sanitized_options['self_host_google_fonts']           = ! empty( $options['self_host_google_fonts'] );
	$sanitized_options['minify_css']                       = ! empty( $options['minify_css'] );
	$sanitized_options['combine_css']                      = ! empty( $options['combine_css'] );
	$sanitized_options['critical_css']                     = ! empty( $options['critical_css'] );
	$sanitized_options['critical_css_additional_files']    = sanitize_textarea_field( $options['critical_css_additional_files'] );
	$sanitized_options['critical_css_excluded_files']      = sanitize_textarea_field( $options['critical_css_excluded_files'] );
	$sanitized_options['excluded_css_files']               = sanitize_textarea_field( $options['excluded_css_files'] );
	$sanitized_options['remove_unused_css']                = ! empty( $options['remove_unused_css'] );
	$sanitized_options['ucss_safelist']                    = sanitize_textarea_field( $options['ucss_safelist'] );
	$sanitized_options['ucss_excluded_files']              = sanitize_textarea_field( $options['ucss_excluded_files'] );
	$sanitized_options['minify_js']                        = ! empty( $options['minify_js'] );
	$sanitized_options['combine_js']                       = ! empty( $options['combine_js'] );
	$sanitized_options['excluded_js_files']                = sanitize_textarea_field( $options['excluded_js_files'] );
	$sanitized_options['js_defer']                         = ! empty( $options['js_defer'] );
	$sanitized_options['js_defer_exclusions']              = sanitize_textarea_field( $options['js_defer_exclusions'] );
	$sanitized_options['js_delay']                         = ! empty( $options['js_delay'] );
	$sanitized_options['js_delay_exclusions']              = sanitize_textarea_field( $options['js_delay_exclusions'] );
	$sanitized_options['js_delay_timeout']                 = absint( $options['js_delay_timeout'] );
	$sanitized_options['enable_image_optimization']        = ! empty( $options['enable_image_optimization'] );
	$sanitized_options['image_optimizer_preferred_format'] = isset( $options['image_optimizer_preferred_format'] ) ? sanitize_text_field( wp_unslash( $options['image_optimizer_preferred_format'] ) ) : '';
	$sanitized_options['enable_lazy_load']                 = ! empty( $options['enable_lazy_load'] );
	$sanitized_options['lazy_load_post_content']           = ! empty( $options['lazy_load_post_content'] );
	$sanitized_options['lazy_load_images']                 = ! empty( $options['lazy_load_images'] );
	$sanitized_options['lazy_load_iframes']                = ! empty( $options['lazy_load_iframes'] );
	$sanitized_options['lazy_load_widgets']                = ! empty( $options['lazy_load_widgets'] );
	$sanitized_options['lazy_load_post_thumbnail']         = ! empty( $options['lazy_load_post_thumbnail'] );
	$sanitized_options['lazy_load_avatars']                = ! empty( $options['lazy_load_avatars'] );
	$sanitized_options['lazy_load_youtube']                = ! empty( $options['lazy_load_youtube'] );
	$sanitized_options['lazy_load_exclusions']             = sanitize_textarea_field( $options['lazy_load_exclusions'] );
	$sanitized_options['lazy_load_skip_first_nth_img']     = absint( $options['lazy_load_skip_first_nth_img'] );
	$sanitized_options['disable_wp_lazy_load']             = ! empty( $options['disable_wp_lazy_load'] );
	$sanitized_options['add_missing_image_dimensions']     = ! empty( $options['add_missing_image_dimensions'] );
	$sanitized_options['disable_wp_embeds']                = ! empty( $options['disable_wp_embeds'] );
	$sanitized_options['disable_emoji_scripts']            = ! empty( $options['disable_emoji_scripts'] );
	$sanitized_options['enable_cdn']                       = ! empty( $options['enable_cdn'] );

	// convert TTL in minute
	if ( $options['cache_timeout'] > 0 && isset( $options['cache_timeout_interval'] ) ) {
		switch ( $options['cache_timeout_interval'] ) {
			case 'DAY':
				$sanitized_options['cache_timeout'] = $options['cache_timeout'] * 1440;
				break;
			case 'HOUR':
				$sanitized_options['cache_timeout'] = $options['cache_timeout'] * 60;
				break;
			case 'MINUTE':
			default:
				$sanitized_options['cache_timeout'] = $options['cache_timeout'] * 1;
		}
	}

	$cdn_hostname = [];
	if ( isset( $options['cdn_hostname'] ) ) {
		foreach ( (array) $options['cdn_hostname'] as $hostname ) {
			$hostname = trim( $hostname );
			if ( filter_var( $hostname, FILTER_VALIDATE_URL ) ) {
				$cdn_hostname[] = wp_parse_url( $hostname, PHP_URL_HOST );
			} else {
				$cdn_hostname[] = $hostname;
			}
		}
	}

	$cdn_zone = [];

	if ( isset( $options['cdn_zone'] ) ) {
		foreach ( (array) $options['cdn_zone'] as $zone ) {
			$cdn_zone[] = sanitize_text_field( $zone );
		}
	}

	if ( empty( $cdn_hostname ) ) {
		$cdn_hostname = [ '' ];
	}

	if ( empty( $cdn_zone ) ) {
		$cdn_zone = [ '' ];
	}

	$sanitized_options['cdn_hostname']                   = $cdn_hostname;
	$sanitized_options['cdn_zone']                       = $cdn_zone;
	$sanitized_options['cdn_rejected_files']             = sanitize_textarea_field( $options['cdn_rejected_files'] );
	$sanitized_options['enable_cache_preload']           = ! empty( $options['enable_cache_preload'] );
	$sanitized_options['preload_homepage']               = ! empty( $options['preload_homepage'] );
	$sanitized_options['preload_public_posts']           = ! empty( $options['preload_public_posts'] );
	$sanitized_options['preload_public_tax']             = ! empty( $options['preload_public_tax'] );
	$sanitized_options['enable_sitemap_preload']         = ! empty( $options['enable_sitemap_preload'] );
	$sanitized_options['preload_request_interval']       = absint( $options['preload_request_interval'] );
	$sanitized_options['preload_sitemap']                = sanitize_textarea_field( $options['preload_sitemap'] );
	$sanitized_options['prefetch_dns']                   = sanitize_textarea_field( $options['prefetch_dns'] );
	$sanitized_options['preconnect_resource']            = sanitize_textarea_field( $options['preconnect_resource'] );
	$sanitized_options['preload_fonts']                  = sanitize_textarea_field( $options['preload_fonts'] );
	$sanitized_options['enable_lcp_optimization']        = ! empty( $options['enable_lcp_optimization'] );
	$sanitized_options['prefetch_links']                 = ! empty( $options['prefetch_links'] );
	$sanitized_options['db_cleanup_post_revisions']      = ! empty( $options['db_cleanup_post_revisions'] );
	$sanitized_options['db_cleanup_auto_drafts']         = ! empty( $options['db_cleanup_auto_drafts'] );
	$sanitized_options['db_cleanup_trashed_posts']       = ! empty( $options['db_cleanup_trashed_posts'] );
	$sanitized_options['db_cleanup_spam_comments']       = ! empty( $options['db_cleanup_spam_comments'] );
	$sanitized_options['db_cleanup_trashed_comments']    = ! empty( $options['db_cleanup_trashed_comments'] );
	$sanitized_options['db_cleanup_expired_transients']  = ! empty( $options['db_cleanup_expired_transients'] );
	$sanitized_options['db_cleanup_all_transients']      = ! empty( $options['db_cleanup_all_transients'] );
	$sanitized_options['db_cleanup_optimize_tables']     = ! empty( $options['db_cleanup_optimize_tables'] );
	$sanitized_options['enable_scheduled_db_cleanup']    = ! empty( $options['enable_scheduled_db_cleanup'] );
	$sanitized_options['scheduled_db_cleanup_frequency'] = sanitize_text_field( $options['scheduled_db_cleanup_frequency'] );
	$sanitized_options['enable_cloudflare']              = ! empty( $options['enable_cloudflare'] );
	$sanitized_options['cloudflare_email']               = sanitize_email( $options['cloudflare_email'] );
	$sanitized_options['cloudflare_api_key']             = sanitize_text_field( $options['cloudflare_api_key'] );
	$sanitized_options['cloudflare_api_token']           = sanitize_text_field( $options['cloudflare_api_token'] );
	$sanitized_options['cloudflare_zone']                = sanitize_text_field( $options['cloudflare_zone'] );
	$sanitized_options['enable_sucuri']                  = ! empty( $options['enable_sucuri'] );
	$sanitized_options['sucuri_api_key']                 = sanitize_text_field( $options['sucuri_api_key'] );
	$sanitized_options['sucuri_api_secret']              = sanitize_text_field( $options['sucuri_api_secret'] );
	$sanitized_options['enable_heartbeat']               = ! empty( $options['enable_heartbeat'] );
	$sanitized_options['heartbeat_dashboard_status']     = sanitize_text_field( $options['heartbeat_dashboard_status'] );
	$sanitized_options['heartbeat_editor_status']        = sanitize_text_field( $options['heartbeat_editor_status'] );
	$sanitized_options['heartbeat_frontend_status']      = sanitize_text_field( $options['heartbeat_frontend_status'] );
	$sanitized_options['heartbeat_dashboard_interval']   = absint( $options['heartbeat_dashboard_interval'] );
	$sanitized_options['heartbeat_editor_interval']      = absint( $options['heartbeat_editor_interval'] );
	$sanitized_options['heartbeat_frontend_interval']    = absint( $options['heartbeat_frontend_interval'] );
	$sanitized_options['enable_varnish']                 = ! empty( $options['enable_varnish'] );
	$sanitized_options['varnish_ip']                     = sanitize_text_field( $options['varnish_ip'] );
	$sanitized_options['cache_footprint']                = ! empty( $options['cache_footprint'] );
	$sanitized_options['async_cache_cleaning']           = ! empty( $options['async_cache_cleaning'] );
	$sanitized_options['dev_mode']                       = ! empty( $options['dev_mode'] );
	$sanitized_options['enable_google_tracking']         = ! empty( $options['enable_google_tracking'] );
	$sanitized_options['enable_fb_tracking']             = ! empty( $options['enable_fb_tracking'] );

	if ( isset( $options['critical_css_appended_content'] ) ) {
		$sanitized_options['critical_css_appended_content'] = sanitize_css( $options['critical_css_appended_content'] );
	}

	if ( isset( $options['critical_css_fallback'] ) ) {
		$sanitized_options['critical_css_fallback'] = sanitize_css( $options['critical_css_fallback'] );
	}

	/**
	 * Filters sanitized options.
	 *
	 * @hook   powered_cache_sanitized_options
	 *
	 * @param  {array} $sanitized_options Sanitized options.
	 * @param  {array} $options raw input.
	 *
	 * @return {array} New value.
	 *
	 * @since  2.0
	 */
	return apply_filters( 'powered_cache_sanitized_options', $sanitized_options, $options );
}

/**
 * Add base admin bar menu
 *
 * @param object $wp_admin_bar Admin bar object
 *
 * @since 2.0
 */
function admin_bar_menu( $wp_admin_bar ) {
	$href = admin_url( 'admin.php?page=powered-cache' );

	if ( POWERED_CACHE_IS_NETWORK && current_user_can( 'manage_network' ) ) {
		$href = network_admin_url( 'admin.php?page=powered-cache' );
	}

	if ( POWERED_CACHE_IS_NETWORK && ! current_user_can( 'manage_network' ) ) {
		$href = '#';
	}

	if ( current_user_can( 'manage_options' ) ) {
		$wp_admin_bar->add_menu(
			array(
				'id'    => MENU_SLUG,
				'title' => __( 'Powered Cache', 'powered-cache' ),
				'href'  => $href,
			)
		);
	}
}

/**
 * Maybe display feedback messages when certain action is taken
 *
 * @since 2.0
 */
function maybe_display_message() {
	// phpcs:disable WordPress.Security.NonceVerification.Recommended
	if ( ! isset( $_GET['pc_action'] ) ) {
		return;
	}

	/**
	 * Dont display multiple message when saving the options while having the query_params
	 */
	if ( ! empty( $_POST ) ) {
		return;
	}

	$success_messages = [
		'purge_image_optimizer_cache'   => esc_html__( 'Image optimizer cache purged successfully!', 'powered-cache' ),
		'flush_page_cache_network'      => esc_html__( 'Page cache deleted for all websites!', 'powered-cache' ),
		'flush_page_cache'              => esc_html__( 'Page cache deleted successfully!', 'powered-cache' ),
		'flush_object_cache'            => esc_html__( 'Object cache deleted successfully!', 'powered-cache' ),
		'flush_all_cache'               => esc_html__( 'All cached items flushed successfully!', 'powered-cache' ),
		'start_preload'                 => esc_html__( 'The cache preloading has been initialized!', 'powered-cache' ),
		'generate_critical'             => esc_html__( 'The Critical CSS generation process has been initialized!', 'powered-cache' ),
		'generate_critical_network'     => esc_html__( 'The Critical CSS generation process has been initialized for all sites! This might take a while, depending on the network size.', 'powered-cache' ),
		'generate_ucss'                 => esc_html__( 'The UCSS generation process has been initialized!', 'powered-cache' ),
		'generate_ucss_network'         => esc_html__( 'The UCSS generation process has been initialized for all sites! This might take a while, depending on the network size.', 'powered-cache' ),
		'flush_cf_cache'                => esc_html__( 'Cloudflare cache flushed, it can take up to 30 seconds to delete all cache from Cloudflare!', 'powered-cache' ),
		'reset_settings'                => esc_html__( 'Settings have been reset!', 'powered-cache' ),
		'import_settings'               => esc_html__( 'Settings have been imported!', 'powered-cache' ),
		'save_settings_and_optimize'    => esc_html__( 'Settings saved and database being optimized...', 'powered-cache' ),
		'save_settings_and_clear_cache' => esc_html__( 'Settings have been successfully saved, and all cache has been cleared.', 'powered-cache' ),
		'save_settings'                 => esc_html__( 'Settings saved.', 'powered-cache' ),
		'optimize_database'             => esc_html__( 'Database cleanup has started in the background.', 'powered-cache' ),
		'regenerate_configuration'      => esc_html__( 'Configuration files have been regenerated.', 'powered-cache' ),
	];

	if ( isset( $_GET['language'] ) ) {
		/* translators: %s: language code. */
		$success_messages['flush_lang_cache'] = sprintf( esc_html__( 'Page cache for %s language has been deleted!', 'powered-cache' ), esc_attr( urldecode_deep( $_GET['language'] ) ) ); // phpcs:ignore
	}

	$err_messages = [
		'purge_image_optimizer_cache_failed'      => esc_html__( 'Could not purge image optimizer cache. Please try again later and ensure your license key is activated!', 'powered-cache' ),
		'generic_permission_err'                  => esc_html__( 'You don\'t have permission to perform this action!', 'powered-cache' ),
		'flush_page_cache_network_err_permission' => esc_html__( 'You don\'t have permission to perform this action!', 'powered-cache' ),
		'flush_page_cache_err_permission'         => esc_html__( 'You don\'t have permission to perform this action!', 'powered-cache' ),
		'flush_object_cache_err_permission'       => esc_html__( 'You don\'t have permission to perform this action!', 'powered-cache' ),
		'flush_all_cache_err_permission'          => esc_html__( 'You don\'t have permission to perform this action!', 'powered-cache' ),
		'start_preload_err_permission'            => esc_html__( 'You don\'t have permission to perform this action!', 'powered-cache' ),
		'start_critical_err_permission'           => esc_html__( 'You don\'t have permission to perform this action!', 'powered-cache' ),
		'start_ucss_err_permission'               => esc_html__( 'You don\'t have permission to perform this action!', 'powered-cache' ),
		'start_critical_err_nonce'                => esc_html__( 'The Critical CSS action link has expired. Please try again.', 'powered-cache' ),
		'start_ucss_err_nonce'                    => esc_html__( 'The Used CSS action link has expired. Please try again.', 'powered-cache' ),
		'start_critical_err_license'              => esc_html__( 'Your license key does not seem valid. A valid license is required for the Critical CSS!', 'powered-cache' ),
		'start_ucss_err_license'                  => esc_html__( 'Your license key does not seem valid. A valid license is required for removing unused CSS!', 'powered-cache' ),
		'flush_cf_cache_failed'                   => esc_html__( 'Could not flush Cloudflare cache. Please make sure you entered the correct credentials and zone id!', 'powered-cache' ),
		'regenerate_configuration_failed'         => sprintf(
			/* translators: %s: WP_CACHE definition */
			esc_html__( 'Some configuration files could not be updated. Make sure wp-config.php and the wp-content directory are writable, or add %s to wp-config.php manually.', 'powered-cache' ),
			'<code>define( \'WP_CACHE\', true );</code>'
		),
	];

	// The settings app has no settings_errors() area, so use standard admin notices on every screen.
	if ( isset( $success_messages[ $_GET['pc_action'] ] ) ) {
		printf( '<div class="notice notice-success is-dismissible"><p>%s</p></div>', $success_messages[ $_GET['pc_action'] ] ); // phpcs:ignore
	}

	if ( isset( $err_messages[ $_GET['pc_action'] ] ) ) {
		printf( '<div class="notice notice-error is-dismissible"><p>%s</p></div>', $err_messages[ $_GET['pc_action'] ] ); // phpcs:ignore
	}
	// phpcs:enable WordPress.Security.NonceVerification.Recommended
}


/**
 * Adds `Purge All Cache` menu bar item
 *
 * @param object $wp_admin_bar Admin bar object
 *
 * @since 1.1
 */
function purge_all_admin_bar_menu( $wp_admin_bar ) {
	// Only available for the network admins on multisite.
	if ( is_multisite() && ! current_user_can( 'manage_network' ) ) {
		return;
	}

	$wp_admin_bar->add_menu(
		array(
			'id'     => 'all-cache-purge',
			'title'  => __( 'Purge All Cache', 'powered-cache' ),
			'href'   => wp_nonce_url( admin_url( 'admin-post.php?action=powered_cache_purge_all_cache' ), 'powered_cache_purge_all_cache' ),
			'parent' => 'powered-cache',
		)
	);
}

/**
 * Purges all cache related things
 *
 * @since 1.1
 */
function purge_all_cache_action() {

	$nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : '';
	if ( ! wp_verify_nonce( $nonce, 'powered_cache_purge_all_cache' ) ) {
		wp_nonce_ays( '' );
	}

	if ( is_multisite() && ! current_user_can( 'manage_network' ) ) {
		$redirect_url = add_query_arg( 'pc_action', 'flush_all_cache_err_permission', wp_get_referer() );
		wp_safe_redirect( esc_url_raw( $redirect_url ) );
		exit;
	}

	if ( ! current_user_can( 'manage_options' ) ) {
		$redirect_url = add_query_arg( 'pc_action', 'flush_all_cache_err_permission', wp_get_referer() );
		wp_safe_redirect( esc_url_raw( $redirect_url ) );
		exit;
	}

	purge_all_cache();

	$redirect_url = add_query_arg( 'pc_action', 'flush_all_cache', wp_get_referer() );

	wp_safe_redirect( esc_url_raw( $redirect_url ) );
	exit;
}


/**
 * Purge all cache
 *
 * @param array $settings plugin settings
 *
 * @return void
 */
function purge_all_cache( $settings = array() ) {
	$cache_purger = CachePurger::factory();
	if ( empty( $settings ) ) {
		$settings = \PoweredCache\Utils\get_settings();
	}

	if ( $settings['async_cache_cleaning'] ) {
		if ( function_exists( 'wp_cache_flush' ) ) {
			wp_cache_flush();
		}
		$cache_purger->push_to_queue( [ 'call' => 'powered_cache_flush' ] );
		$cache_purger->save()->dispatch();
	} else {
		powered_cache_flush();// cleans object cache + page cache dir
	}

	/**
	 * Fires after purging all cache
	 *
	 * @hook  powered_cache_purge_all_cache
	 * @since 1.1
	 */
	do_action( 'powered_cache_purge_all_cache' );

	if ( POWERED_CACHE_IS_NETWORK ) {
		delete_site_transient( PURGE_CACHE_PLUGIN_NOTICE_TRANSIENT );
	} else {
		delete_transient( PURGE_CACHE_PLUGIN_NOTICE_TRANSIENT );
	}
}


/**
 * Downloads proper configuration file
 *
 * @since 1.1
 */
function download_rewrite_config() {
	$nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : '';
	if ( ! wp_verify_nonce( $nonce, 'powered_cache_download_rewrite' ) ) {
		wp_nonce_ays( '' );
	}

	if ( ! can_control_all_settings() ) {
		$redirect_url = add_query_arg( 'pc_action', 'generic_permission_err', wp_get_referer() );
		wp_safe_redirect( esc_url_raw( $redirect_url ) );
		exit;
	}

	if ( ! empty( $_GET['server'] ) ) {
		$server = sanitize_text_field( wp_unslash( $_GET['server'] ) );
		Config::factory()->download_rewrite_rules( $server );
	}

	wp_safe_redirect( wp_get_referer() );
	die();
}

/**
 * Run DB optimization
 *
 * @param array $options plugin settings
 */
function db_optimize( $options ) {
	$powered_cache_db_optimizer = DatabaseOptimizer::factory();

	$supported_options = $powered_cache_db_optimizer->get_supported_options();

	if ( POWERED_CACHE_IS_NETWORK ) {
		$sites = get_sites();
		foreach ( $sites as $site ) {
			switch_to_blog( $site->blog_id );
			foreach ( $supported_options as $optimization_item ) {
				if ( $options[ $optimization_item ] ) {
					$powered_cache_db_optimizer->push_to_queue( $optimization_item );
				}
			}

			$powered_cache_db_optimizer->save()->dispatch();
			restore_current_blog();
		}
	} else {
		foreach ( $supported_options as $optimization_item ) {
			if ( $options[ $optimization_item ] ) {
				$powered_cache_db_optimizer->push_to_queue( $optimization_item );
			}
		}

		$powered_cache_db_optimizer->save()->dispatch();
	}

}

/**
 * Check alloptions size like performance lab does.
 * It's critical to know this before enabling memcached based persistent object cache backends.
 * When alloptions size is too big (1mb in this case), it will cause performance degradation instead of improvement.
 *
 * @return void
 * @since 3.4
 */
function check_alloptions() {
	$nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
	if ( ! wp_verify_nonce( $nonce, 'powered_cache_update_settings' ) || ! can_configure_object_cache() ) {
		wp_send_json_error( [ 'message' => esc_html__( 'Invalid request', 'powered-cache' ) ] );

		return;
	}

	$autoload_option_size  = \PoweredCache\Utils\autoloaded_options_size();
	$autoload_option_count = count( wp_load_alloptions() );

	if ( $autoload_option_size > ALLOPTIONS_CRITICAL_THRESHOLD ) {
		$status = 'critical';
	} elseif ( $autoload_option_size > ALLOPTIONS_WARNING_THRESHOLD && $autoload_option_size <= ALLOPTIONS_CRITICAL_THRESHOLD ) {
		$status = 'warning';
	} else {
		$status = 'good';
	}

	$message = '<p><b>' . esc_html__( 'Autoloaded options could affect performance:', 'powered-cache' ) . '</b> ' .
            sprintf(
                esc_html__(
		           /* translators: 1: Number of autoloaded options. 2: Size of autoloaded options. */
                    'Your site has %1$s autoloaded options (size: %2$s) in the options table, which could cause your site to be slow. You can reduce the number of autoloaded options by cleaning up your site\'s options table.',
                    'powered-cache'
                ),
                esc_html( number_format_i18n( $autoload_option_count ) ),
                esc_html( size_format( $autoload_option_size ) )
            ) . '</p>';

	wp_send_json_success(
        [
			'status'  => $status,
			'message' => $message,
		]
    );
}


/**
 * Deactivate incompatible plugins
 *
 * @since 1.0
 */
function deactivate_plugin() {
	$nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : '';
	if ( ! wp_verify_nonce( $nonce, 'deactivate_plugin' ) ) {
		wp_nonce_ays( '' );
	}

	if ( ! current_user_can( 'activate_plugins' ) ) {
		$redirect_url = add_query_arg( 'pc_action', 'generic_permission_err', wp_get_referer() );
		wp_safe_redirect( esc_url_raw( $redirect_url ) );
		exit;
	}

	if ( isset( $_GET['plugin'] ) ) {
		$plugin = sanitize_text_field( wp_unslash( $_GET['plugin'] ) );
		deactivate_plugins( $plugin );
	}

	wp_safe_redirect( wp_get_referer() );
	die();
}

/**
 * Adds settings link to plugin actions
 *
 * @param array $actions Plugin actions.
 *
 * @return array
 * @since  1.0
 */
function action_links( $actions ) {

	$settings_url      = POWERED_CACHE_IS_NETWORK ? network_admin_url( 'admin.php?page=powered-cache' ) : admin_url( 'admin.php?page=powered-cache' );
	$powered_cache_url = 'https://poweredcache.com/pricing/?utm_source=wp_admin&utm_medium=plugin&utm_campaign=plugin_action_links';

	$actions['powered_settings'] = sprintf( '<a href="%s">%s</a>', esc_url( $settings_url ), esc_html__( 'Settings', 'powered-cache' ) );

	if ( ! is_premium() ) {
		$actions['get_premium'] = sprintf( '<a href="%s" style="color: red;">%s</a>', esc_url( $powered_cache_url ), esc_html__( 'Get Premium', 'powered-cache' ) );
	}

	return array_reverse( $actions );
}

/**
 * Conditionally process Cloudflare API key and token settings based on the form input.
 *
 * @param array $options The form options submitted by the user.
 *
 * @return array Updated options with processed sensitive settings.
 */
function maybe_process_sensitive_settings( $options ) {
	$options = maybe_process_cloudflare_settings( $options );

	return maybe_process_sucuri_settings( $options );
}

/**
 * Process Cloudflare settings to handle masked values and encryption.
 *
 * @param array $options The form options submitted by the user.
 *
 * @return array Updated options with processed Cloudflare settings.
 */
function maybe_process_cloudflare_settings( $options ) {
	// Retrieve the previous Cloudflare API key and token values.
	$prev_cf_api_key   = \PoweredCache\Extensions\Cloudflare\Cloudflare::get_cf_api_key();
	$prev_cf_api_token = \PoweredCache\Extensions\Cloudflare\Cloudflare::get_cf_api_token();

	// Check if the submitted Cloudflare API key matches the masked version of the previous key.
	if ( isset( $options['cloudflare_api_key'] ) && mask_string( $prev_cf_api_key, UNMASK_CHARACTER_LENGTH ) === $options['cloudflare_api_key'] ) {
		$options['cloudflare_api_key'] = $prev_cf_api_key; // Use the unmasked value.
	}

	if ( isset( $options['cloudflare_api_token'] ) && mask_string( $prev_cf_api_token, UNMASK_CHARACTER_LENGTH ) === $options['cloudflare_api_token'] ) {
		$options['cloudflare_api_token'] = $prev_cf_api_token; // Use the unmasked value.
	}

	$encryption = new Encryption();
	// Encrypt sensitive data if it's not empty.
	if ( ! empty( $options['cloudflare_api_key'] ) ) {
		$options['cloudflare_api_key'] = $encryption->encrypt( $options['cloudflare_api_key'] );
	}

	if ( ! empty( $options['cloudflare_api_token'] ) ) {
		$options['cloudflare_api_token'] = $encryption->encrypt( $options['cloudflare_api_token'] );
	}

	// Override with empty values if constants are defined.
	if ( defined( 'POWERED_CACHE_CF_API_KEY' ) && POWERED_CACHE_CF_API_KEY ) {
		$options['cloudflare_api_key'] = '';
	}

	if ( defined( 'POWERED_CACHE_CF_API_TOKEN' ) && POWERED_CACHE_CF_API_TOKEN ) {
		$options['cloudflare_api_token'] = '';
	}

	return $options;
}

/**
 * Process Sucuri settings to handle masked values and encryption.
 *
 * @param array $options The form options submitted by the user.
 *
 * @return array Updated options with processed Sucuri settings.
 */
function maybe_process_sucuri_settings( $options ) {
	$prev_api_key    = get_decrypted_setting( 'sucuri_api_key' );
	$prev_api_secret = get_decrypted_setting( 'sucuri_api_secret' );

	if ( isset( $options['sucuri_api_key'] ) && mask_string( $prev_api_key, UNMASK_CHARACTER_LENGTH ) === $options['sucuri_api_key'] ) {
		$options['sucuri_api_key'] = $prev_api_key;
	}

	if ( isset( $options['sucuri_api_secret'] ) && mask_string( $prev_api_secret, UNMASK_CHARACTER_LENGTH ) === $options['sucuri_api_secret'] ) {
		$options['sucuri_api_secret'] = $prev_api_secret;
	}

	$encryption = new Encryption();

	if ( ! empty( $options['sucuri_api_key'] ) ) {
		$options['sucuri_api_key'] = $encryption->encrypt( $options['sucuri_api_key'] );
	}

	if ( ! empty( $options['sucuri_api_secret'] ) ) {
		$options['sucuri_api_secret'] = $encryption->encrypt( $options['sucuri_api_secret'] );
	}

	if ( defined( 'POWERED_CACHE_SUCURI_API_KEY' ) && POWERED_CACHE_SUCURI_API_KEY ) {
		$options['sucuri_api_key'] = '';
	}

	if ( defined( 'POWERED_CACHE_SUCURI_API_SECRET' ) && POWERED_CACHE_SUCURI_API_SECRET ) {
		$options['sucuri_api_secret'] = '';
	}

	return $options;
}