true, 'auto_fix_enabled' => false, 'widget_enabled' => true, 'frontend_widget_enabled' => true, 'widget_position' => 'bottom-right', 'widget_style' => 'modern', 'compliance_level' => 'wcag_2_1_aa', 'scan_schedule' => 'daily', 'max_files_per_scan' => $is_pro ? self::PRO_SCAN_LIMIT : self::FREE_SCAN_LIMIT, 'max_sites' => $is_pro ? (function_exists('ability_is_enterprise') && ability_is_enterprise() ? 10 : 3) : 1, 'exclude_paths' => [], 'report_email' => get_option('admin_email'), 'show_branding' => !$is_pro, 'compliance_reports' => false, 'api_access' => false, 'license_key' => '', 'debug_mode' => false, 'api_endpoint' => '', 'experimental_features' => false, ]; } /** * Sanitize a partial settings payload. Unknown keys are ignored so the * generic AJAX endpoint cannot create arbitrary plugin settings. */ public static function sanitize_settings(array $settings): array { $defaults = self::get_defaults(); $allowed = array_fill_keys(array_keys($defaults), true); $clean = []; $boolean_keys = [ 'scan_enabled', 'auto_fix_enabled', 'widget_enabled', 'frontend_widget_enabled', 'show_branding', 'compliance_reports', 'api_access', 'debug_mode', 'experimental_features', ]; $integer_keys = ['max_files_per_scan', 'max_sites']; foreach ($settings as $key => $value) { $safe_key = sanitize_key((string) $key); if (!isset($allowed[$safe_key])) { continue; } if (in_array($safe_key, $boolean_keys, true)) { $clean[$safe_key] = empty($value) ? 0 : 1; continue; } if (in_array($safe_key, $integer_keys, true)) { $clean[$safe_key] = absint($value); continue; } if ('report_email' === $safe_key) { $clean[$safe_key] = sanitize_email((string) $value); continue; } if ('api_endpoint' === $safe_key) { $clean[$safe_key] = esc_url_raw((string) $value); continue; } if ('exclude_paths' === $safe_key) { $raw_paths = is_array($value) ? $value : preg_split('/\r\n|\r|\n/', (string) $value); $clean[$safe_key] = array_values(array_filter(array_map( static function ($path) { return sanitize_text_field((string) $path); }, is_array($raw_paths) ? $raw_paths : [] ))); continue; } $enum_values = [ 'scan_schedule' => ['manual', 'daily', 'weekly'], 'widget_position' => ['bottom-right', 'bottom-left', 'top-right', 'top-left'], 'widget_style' => ['modern', 'classic'], 'compliance_level' => ['wcag_2_1_a', 'wcag_2_1_aa', 'wcag_2_1_aaa', 'ada', 'section508'], ]; if (isset($enum_values[$safe_key])) { $candidate = sanitize_key((string) $value); $clean[$safe_key] = in_array($candidate, $enum_values[$safe_key], true) ? $candidate : $defaults[$safe_key]; continue; } $clean[$safe_key] = sanitize_text_field((string) $value); } return $clean; } /** * Enforce edition-owned limits at the server/runtime boundary. UI state is * never trusted as the entitlement source. */ private static function enforce_tier_limits(array $settings): array { $is_pro = function_exists('ability_is_pro') ? ability_is_pro() : false; if (!$is_pro) { $requested = isset($settings['max_files_per_scan']) ? absint($settings['max_files_per_scan']) : self::FREE_SCAN_LIMIT; $settings['max_files_per_scan'] = max(1, min(self::FREE_SCAN_LIMIT, $requested ?: self::FREE_SCAN_LIMIT)); $settings['max_sites'] = 1; $settings['auto_fix_enabled'] = false; $settings['compliance_reports'] = false; $settings['api_access'] = false; $settings['show_branding'] = true; } else { $requested = isset($settings['max_files_per_scan']) ? absint($settings['max_files_per_scan']) : self::PRO_SCAN_LIMIT; $settings['max_files_per_scan'] = max(1, min(self::PRO_SCAN_LIMIT, $requested ?: self::PRO_SCAN_LIMIT)); } return $settings; } /** * Get settings. */ public static function get_settings(): array { $defaults = self::get_defaults(); $settings = get_option('ability_settings', []); if (!is_array($settings)) { $settings = []; } return self::enforce_tier_limits(wp_parse_args($settings, $defaults)); } /** * Update settings from a partial payload while preserving unrelated saved * values and enforcing the active edition at the server boundary. */ public static function update_settings(array $settings): bool { $defaults = self::get_defaults(); $current = get_option('ability_settings', []); if (!is_array($current)) { $current = []; } $clean = self::sanitize_settings($settings); $merged = array_replace($defaults, $current, $clean); $merged = self::enforce_tier_limits($merged); return update_option('ability_settings', $merged); } }