get_required_capability() ) ) { wp_die( esc_html__( 'You do not have permission to manage AeroSpin CSS Loading Spinners settings.', 'aerospin-css-loading-spinners' ), esc_html__( 'AeroSpin CSS Loading Spinners Permission Error', 'aerospin-css-loading-spinners' ), array( 'response' => 403, ) ); } // Verify the nonce to prevent CSRF attacks. if ( isset( $_POST['acls_nonce'] ) ) { check_admin_referer( 'acls_save_settings', 'acls_nonce' ); } elseif ( isset( $_POST['AeroSpin_CSS_Loading_Spinners_nonce'] ) ) { check_admin_referer( 'AeroSpin_CSS_Loading_Spinners_save_settings', 'AeroSpin_CSS_Loading_Spinners_nonce' ); } elseif ( isset( $_POST['Pure_CSS_Spinners_nonce'] ) ) { check_admin_referer( 'Pure_CSS_Spinners_save_settings', 'Pure_CSS_Spinners_nonce' ); } else { check_admin_referer( 'cps_save_settings', 'cps_nonce' ); } // Retrieve and sanitize settings. $post = $this->get_unslashed_post_data(); $settings = $this->get_posted_settings(); $settings = $this->sanitize_settings( $settings ); // Safely update the option in the database. $options_key = defined( 'ACLS_OPTIONS_KEY' ) ? ACLS_OPTIONS_KEY : ( defined( 'Pure_CSS_Spinners_OPTIONS_KEY' ) ? Pure_CSS_Spinners_OPTIONS_KEY : 'pure_css_spinners_settings' ); update_option( $options_key, $settings, false ); // Purge transients and caches to ensure the frontend reflects changes. $this->clear_settings_cache(); // Store a success message using an escaped translation string. $this->set_settings_message( 'success', esc_html__( 'Settings saved successfully.', 'aerospin-css-loading-spinners' ) ); // Safely redirect back to the settings page. wp_safe_redirect( $this->get_redirect_url( $post ) ); exit; } /* |-------------------------------------------------------------------------- | // SECTION 3: POST Mapping |-------------------------------------------------------------------------- */ /** * Get unslashed POST data after nonce verification. * * @since 1.1.4 * * @return array */ private function get_unslashed_post_data() { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in save_settings() before this method is called. $post = isset( $_POST ) && is_array( $_POST ) ? wp_unslash( $_POST ) : array(); return is_array( $post ) ? $post : array(); } /** * Map expected POST fields into plugin setting keys. * * This method intentionally avoids deep sanitization. The returned data * is normalized by the centralized schema helper before saving. * * @since 1.1.4 * * @return array */ private function get_posted_settings() { $post = $this->get_unslashed_post_data(); $existing_settings = $this->get_existing_settings(); $get_val = function ( $acls_key, $aerospin_key, $modern_key, $legacy_key, $default_value ) use ( $post, $existing_settings ) { if ( isset( $post[ $acls_key ] ) ) { return $post[ $acls_key ]; } if ( isset( $post[ $aerospin_key ] ) ) { return $post[ $aerospin_key ]; } if ( isset( $post[ $modern_key ] ) ) { return $post[ $modern_key ]; } if ( isset( $post[ $legacy_key ] ) ) { return $post[ $legacy_key ]; } return $this->get_existing_setting_value( $existing_settings, str_replace( array( 'acls_', 'cps_' ), '', $legacy_key ), $default_value ); }; $is_enabled = isset( $post['acls_enabled'] ) || isset( $post['AeroSpin_CSS_Loading_Spinners_enabled'] ) || isset( $post['Pure_CSS_Spinners_enabled'] ) || isset( $post['cps_enabled'] ); // Map inputs to expected data structure, falling back to existing values if missing. return array( 'enabled' => $is_enabled ? 1 : 0, 'spinner_style' => $get_val( 'acls_spinner_style', 'AeroSpin_CSS_Loading_Spinners_spinner_style', 'Pure_CSS_Spinners_spinner_style', 'cps_spinner_style', 'ring' ), 'spinner_color' => $get_val( 'acls_spinner_color', 'AeroSpin_CSS_Loading_Spinners_spinner_color', 'Pure_CSS_Spinners_spinner_color', 'cps_spinner_color', '#4f46e5' ), 'spinner_secondary' => $get_val( 'acls_spinner_secondary', 'AeroSpin_CSS_Loading_Spinners_spinner_secondary', 'Pure_CSS_Spinners_spinner_secondary', 'cps_spinner_secondary', '#e0e7ff' ), 'spinner_size' => $get_val( 'acls_spinner_size', 'AeroSpin_CSS_Loading_Spinners_spinner_size', 'Pure_CSS_Spinners_spinner_size', 'cps_spinner_size', 60 ), 'overlay_color' => $get_val( 'acls_overlay_color', 'AeroSpin_CSS_Loading_Spinners_overlay_color', 'Pure_CSS_Spinners_overlay_color', 'cps_overlay_color', '#ffffff' ), 'overlay_opacity' => $get_val( 'acls_overlay_opacity', 'AeroSpin_CSS_Loading_Spinners_overlay_opacity', 'Pure_CSS_Spinners_overlay_opacity', 'cps_overlay_opacity', 85 ), 'overlay_blur' => $get_val( 'acls_overlay_blur', 'AeroSpin_CSS_Loading_Spinners_overlay_blur', 'Pure_CSS_Spinners_overlay_blur', 'cps_overlay_blur', 0 ), 'z_index' => $get_val( 'acls_z_index', 'AeroSpin_CSS_Loading_Spinners_z_index', 'Pure_CSS_Spinners_z_index', 'cps_z_index', 99999 ), 'fade_out_speed' => $get_val( 'acls_fade_out_speed', 'AeroSpin_CSS_Loading_Spinners_fade_out_speed', 'Pure_CSS_Spinners_fade_out_speed', 'cps_fade_out_speed', 400 ), 'show_on_external' => ( isset( $post['acls_show_on_external'] ) || isset( $post['AeroSpin_CSS_Loading_Spinners_show_on_external'] ) || isset( $post['Pure_CSS_Spinners_show_on_external'] ) || isset( $post['cps_show_on_external'] ) ) ? 1 : 0, 'show_on_admin_bar' => ( isset( $post['acls_show_on_admin_bar'] ) || isset( $post['AeroSpin_CSS_Loading_Spinners_show_on_admin_bar'] ) || isset( $post['Pure_CSS_Spinners_show_on_admin_bar'] ) || isset( $post['cps_show_on_admin_bar'] ) ) ? 1 : 0, 'show_on_forms' => ( isset( $post['acls_show_on_forms'] ) || isset( $post['AeroSpin_CSS_Loading_Spinners_show_on_forms'] ) || isset( $post['Pure_CSS_Spinners_show_on_forms'] ) || isset( $post['cps_show_on_forms'] ) ) ? 1 : 0, 'exclude_selectors' => $get_val( 'acls_exclude_selectors', 'AeroSpin_CSS_Loading_Spinners_exclude_selectors', 'Pure_CSS_Spinners_exclude_selectors', 'cps_exclude_selectors', '' ), 'exclude_pages' => $get_val( 'acls_exclude_pages', 'AeroSpin_CSS_Loading_Spinners_exclude_pages', 'Pure_CSS_Spinners_exclude_pages', 'cps_exclude_pages', '' ), 'min_display_time' => $get_val( 'acls_min_display_time', 'AeroSpin_CSS_Loading_Spinners_min_display_time', 'Pure_CSS_Spinners_min_display_time', 'cps_min_display_time', 0 ), 'custom_css' => $get_val( 'acls_custom_css', 'AeroSpin_CSS_Loading_Spinners_custom_css', 'Pure_CSS_Spinners_custom_css', 'cps_custom_css', '' ), ); } /** * Retrieve existing settings before overwriting the option. * * @since 1.1.4 * * @return array */ private function get_existing_settings() { if ( function_exists( 'aerospin_css_loading_spinners_get_settings' ) ) { $settings = aerospin_css_loading_spinners_get_settings(); return is_array( $settings ) ? $settings : array(); } elseif ( function_exists( 'Pure_CSS_Spinners_get_settings' ) ) { $settings = Pure_CSS_Spinners_get_settings(); return is_array( $settings ) ? $settings : array(); } if ( class_exists( 'AeroSpin_CSS_Loading_Spinners_Helpers' ) || class_exists( 'Pure_CSS_Spinners_Helpers' ) && is_callable( array( 'Pure_CSS_Spinners_Helpers', 'get_settings' ) ) ) { $settings = Pure_CSS_Spinners_Helpers::get_settings(); return is_array( $settings ) ? $settings : array(); } // Final fallback directly to database option. $options_key = defined( 'ACLS_OPTIONS_KEY' ) ? ACLS_OPTIONS_KEY : ( defined( 'Pure_CSS_Spinners_OPTIONS_KEY' ) ? Pure_CSS_Spinners_OPTIONS_KEY : 'pure_css_spinners_settings' ); $settings = get_option( $options_key, array() ); return is_array( $settings ) ? $settings : array(); } /** * Get an existing setting value with a fallback. * * @since 1.1.4 * * @param array $settings Existing settings. * @param string $key Setting key. * @param mixed $default_value Default fallback. * @return mixed */ private function get_existing_setting_value( $settings, $key, $default_value ) { if ( ! is_array( $settings ) ) { return $default_value; } $key = sanitize_key( $key ); return array_key_exists( $key, $settings ) ? $settings[ $key ] : $default_value; } /* |-------------------------------------------------------------------------- | // SECTION 4: Settings Normalization |-------------------------------------------------------------------------- */ /** * Sanitize settings through the centralized helper/schema layer. * * @since 1.1.4 * * @param array $settings Raw mapped settings. * @return array Sanitized settings. */ private function sanitize_settings( $settings ) { if ( function_exists( 'aerospin_css_loading_spinners_sanitize_settings' ) ) { return aerospin_css_loading_spinners_sanitize_settings( $settings ); } elseif ( function_exists( 'Pure_CSS_Spinners_sanitize_settings' ) ) { return Pure_CSS_Spinners_sanitize_settings( $settings ); } if ( class_exists( 'AeroSpin_CSS_Loading_Spinners_Helpers' ) || class_exists( 'Pure_CSS_Spinners_Helpers' ) && is_callable( array( 'Pure_CSS_Spinners_Helpers', 'sanitize_settings' ) ) ) { return Pure_CSS_Spinners_Helpers::sanitize_settings( $settings ); } return $this->fallback_sanitize_settings( $settings ); } /** * Fallback settings sanitization. * * This path should rarely run because acls-helpers.php loads before this * file. It exists as a defensive guard against partial bootstrap issues. * * @since 1.1.4 * * @param mixed $settings Raw settings. * @return array */ private function fallback_sanitize_settings( $settings ) { if ( ! is_array( $settings ) ) { return array(); } $sanitized = array(); foreach ( $settings as $key => $value ) { $key = sanitize_key( $key ); if ( '' === $key ) { continue; } // Prevent complex structures from passing through. if ( is_array( $value ) || is_object( $value ) ) { $sanitized[ $key ] = ''; continue; } $value = (string) $value; // Use textarea sanitization to preserve formatting on specific fields. if ( in_array( $key, array( 'exclude_selectors', 'exclude_pages', 'custom_css' ), true ) ) { $sanitized[ $key ] = sanitize_textarea_field( $value ); continue; } // Standard text field sanitization for everything else. $sanitized[ $key ] = sanitize_text_field( $value ); } return $sanitized; } /** * Clear settings helper cache after updating options. * * @since 1.1.4 * * @return void */ private function clear_settings_cache() { if ( function_exists( 'aerospin_css_loading_spinners_clear_settings_cache' ) ) { aerospin_css_loading_spinners_clear_settings_cache(); return; } elseif ( function_exists( 'Pure_CSS_Spinners_clear_settings_cache' ) ) { Pure_CSS_Spinners_clear_settings_cache(); return; } if ( class_exists( 'AeroSpin_CSS_Loading_Spinners_Helpers' ) || class_exists( 'Pure_CSS_Spinners_Helpers' ) && is_callable( array( 'Pure_CSS_Spinners_Helpers', 'clear_cache' ) ) ) { Pure_CSS_Spinners_Helpers::clear_cache(); return; } delete_transient( 'aerospin_css_loading_spinners_settings_cache' ); delete_transient( 'AeroSpin_CSS_Loading_Spinners_settings_cache' ); delete_transient( 'Pure_CSS_Spinners_settings_cache' ); delete_transient( 'cp_spinners_settings_cache' ); } /* |-------------------------------------------------------------------------- | // SECTION 5: Admin Message and Redirect Helpers |-------------------------------------------------------------------------- */ /** * Store a short-lived settings message for the redirected settings page. * * @since 1.1.4 * * @param string $type Notice type. * @param string $message Notice message. * @return void */ private function set_settings_message( $type, $message ) { $allowed_types = array( 'success', 'info', 'warning', 'error', ); $type = sanitize_key( $type ); if ( ! in_array( $type, $allowed_types, true ) ) { $type = 'info'; } $payload = array( 'type' => $type, 'message' => sanitize_text_field( $message ), ); // Store the message to survive the redirect. set_transient( 'AeroSpin_CSS_Loading_Spinners_settings_messages', $payload, 30 ); set_transient( 'Pure_CSS_Spinners_settings_messages', $payload, 30 ); } /** * Build the settings page redirect URL. * * @since 1.1.4 * * @param array|null $post Optional unslashed POST data. * @return string */ private function get_redirect_url( $post = null ) { $args = array( 'page' => $this->get_page_slug(), 'settings-updated' => 'true', ); if ( null === $post || ! is_array( $post ) ) { $post = $this->get_unslashed_post_data(); } $tab = ''; if ( ! empty( $post['acls_current_tab'] ) ) { $tab = sanitize_key( $post['acls_current_tab'] ); } elseif ( ! empty( $post['AeroSpin_CSS_Loading_Spinners_current_tab'] ) ) { $tab = sanitize_key( $post['AeroSpin_CSS_Loading_Spinners_current_tab'] ); } elseif ( ! empty( $post['Pure_CSS_Spinners_current_tab'] ) ) { $tab = sanitize_key( $post['Pure_CSS_Spinners_current_tab'] ); } elseif ( ! empty( $post['cps_current_tab'] ) ) { $tab = sanitize_key( $post['cps_current_tab'] ); } if ( '' !== $tab && in_array( $tab, array( 'settings', 'documentation', 'diagnostics' ), true ) ) { $args['tab'] = $tab; } return add_query_arg( $args, admin_url( 'admin.php' ) ); } /** * Get the required capability for saving settings. * * @since 1.1.4 * * @return string */ private function get_required_capability() { if ( defined( 'AEROSPIN_CSS_LOADING_SPINNERS_REQUIRED_CAPABILITY' ) ) { return AEROSPIN_CSS_LOADING_SPINNERS_REQUIRED_CAPABILITY; } elseif ( defined( 'Pure_CSS_Spinners_REQUIRED_CAPABILITY' ) ) { return Pure_CSS_Spinners_REQUIRED_CAPABILITY; } return 'manage_options'; } /** * Get the settings page slug. * * @since 1.1.4 * * @return string */ private function get_page_slug() { if ( defined( 'AEROSPIN_CSS_LOADING_SPINNERS_SETTINGS_PAGE_SLUG' ) ) { $page_slug = AEROSPIN_CSS_LOADING_SPINNERS_SETTINGS_PAGE_SLUG; } elseif ( defined( 'Pure_CSS_Spinners_SETTINGS_PAGE_SLUG' ) ) { $page_slug = Pure_CSS_Spinners_SETTINGS_PAGE_SLUG; } else { $page_slug = 'aerospin-css-loading-spinners'; } $page_slug = sanitize_key( $page_slug ); return '' !== $page_slug ? $page_slug : 'aerospin-css-loading-spinners'; } } } if ( ! class_exists( 'AeroSpin_CSS_Loading_Spinners_Settings_Handler' ) ) { class_alias( 'ACLS_Settings_Handler', 'AeroSpin_CSS_Loading_Spinners_Settings_Handler' ); } if ( ! class_exists( 'Pure_CSS_Spinners_Settings_Handler' ) ) { class_alias( 'ACLS_Settings_Handler', 'Pure_CSS_Spinners_Settings_Handler' ); } if ( ! class_exists( 'AreoSpin_Css_Loading_Spinners_Settings_Handler' ) ) { class_alias( 'ACLS_Settings_Handler', 'AreoSpin_Css_Loading_Spinners_Settings_Handler' ); } /* |-------------------------------------------------------------------------- | // SECTION 6: Initialize Settings Handler |-------------------------------------------------------------------------- */ if ( is_admin() ) { // Initialize the singleton instance to start listening for hooks. ACLS_Settings_Handler::get_instance(); }