'array', 'sanitize_callback' => 'a11ybridge_sanitize_option_array']); // AI settings register_setting('a11ybridge_ai_settings_group', 'a11ybridge_ai_settings', ['type' => 'array', 'sanitize_callback' => 'a11ybridge_sanitize_option_array']); register_setting('a11ybridge_ai_settings_group', 'a11ybridge_simplification_settings', ['type' => 'array', 'sanitize_callback' => 'a11ybridge_sanitize_simplification_settings']); // (WP.org build) Voice settings excluded from MVP. // User settings register_setting('a11ybridge_user_settings_group', 'a11ybridge_user_profile_settings', ['type' => 'array', 'sanitize_callback' => 'a11ybridge_sanitize_option_array']); register_setting('a11ybridge_user_settings_group', 'a11ybridge_user_privacy_settings', ['type' => 'array', 'sanitize_callback' => 'a11ybridge_sanitize_option_array']); register_setting('a11ybridge_user_settings_group', 'a11ybridge_user_storage_settings', ['type' => 'array', 'sanitize_callback' => 'a11ybridge_sanitize_option_array']); register_setting('a11ybridge_user_settings_group', 'a11ybridge_user_defaults_settings', ['type' => 'array', 'sanitize_callback' => 'a11ybridge_sanitize_option_array']); // Advanced settings register_setting('a11ybridge_advanced_settings_group', 'a11ybridge_advanced_settings', ['type' => 'array', 'sanitize_callback' => 'a11ybridge_sanitize_option_array']); register_setting('a11ybridge_advanced_settings_group', 'a11ybridge_scanner_settings', ['type' => 'array', 'sanitize_callback' => 'a11ybridge_sanitize_option_array']); // Note: voice settings are already registered in the voice group. // If you ever expose advanced voice settings separately, you can keep this line. register_setting('a11ybridge_advanced_settings_group', 'a11ybridge_voice_settings', ['type' => 'array', 'sanitize_callback' => 'a11ybridge_sanitize_option_array']); // Pro settings register_setting('a11ybridge_pro_settings_group', 'a11ybridge_pro_license_settings', ['type' => 'array', 'sanitize_callback' => 'a11ybridge_sanitize_option_array']); // Cloud / external service consent (opt-in) register_setting('a11ybridge_pro_settings_group', 'a11ybridge_cloud_settings', ['type' => 'array', 'sanitize_callback' => 'a11ybridge_sanitize_option_array']); } // AJAX: save license hash & meta locally add_action('wp_ajax_a11ybridge_save_license_hash', 'a11ybridge_ajax_save_license_hash'); add_action('wp_ajax_a11ybridge_clear_license', 'a11ybridge_ajax_clear_license'); add_action('wp_ajax_a11ybridge_save_cloud_settings', 'a11ybridge_ajax_save_cloud_settings'); /** * AJAX handler: store license hash and metadata locally. */ function a11ybridge_ajax_save_license_hash() { if (!current_user_can('manage_options')) { wp_send_json_error( [ 'msg' => 'forbidden', 'message' => __('You are not allowed to perform this action.', 'a11ybridge'), ], 403 ); } // Nonce (must match the nonce created in JS, e.g. `wp_create_nonce('a11ybridge_pro_nonce')`) check_ajax_referer('a11ybridge_pro_nonce', 'nonce'); $hash = isset($_POST['license_hash']) ? sanitize_text_field(wp_unslash($_POST['license_hash'])) : ''; $hint = isset($_POST['license_hint']) ? sanitize_text_field(wp_unslash($_POST['license_hint'])) : ''; $plan = isset($_POST['plan']) ? sanitize_text_field(wp_unslash($_POST['plan'])) : ''; $domain = isset($_POST['domain']) ? sanitize_text_field(wp_unslash($_POST['domain'])) : ''; $domains_json = isset($_POST['domains']) ? sanitize_textarea_field(wp_unslash($_POST['domains'])) : '[]'; $limit_raw = isset($_POST['limit']) ? sanitize_text_field(wp_unslash($_POST['limit'])) : ''; if ($hash === '') { wp_send_json_error( [ 'msg' => 'invalid_hash', 'message' => __('Invalid or empty license hash.', 'a11ybridge'), ], 400 ); } $opt = get_option('a11ybridge_pro_license_settings', []); if (!is_array($opt)) { $opt = []; } $opt['license_hash'] = $hash; $opt['license_hint'] = $hint; $opt['plan'] = $plan ?: 'free'; $opt['bound_domain'] = $domain; $opt['activated_at'] = current_time('mysql'); $decoded_domains = json_decode($domains_json, true); $opt['domains'] = is_array($decoded_domains) ? $decoded_domains : []; $opt['limit'] = $limit_raw !== '' ? (int) $limit_raw : null; update_option('a11ybridge_pro_license_settings', $opt, false); wp_send_json_success([ 'ok' => true, 'message' => __('License data saved successfully.', 'a11ybridge'), ]); } /** * AJAX handler: clear local license data. */ function a11ybridge_ajax_clear_license() { if (!current_user_can('manage_options')) { wp_send_json_error( [ 'msg' => 'forbidden', 'message' => __('You are not allowed to perform this action.', 'a11ybridge'), ], 403 ); } check_ajax_referer('a11ybridge_pro_nonce', 'nonce'); // Remove local license completely (including legacy keys/counters & caches) delete_option('a11ybridge_pro_license_settings'); // Legacy single-option storage (older installs) $legacy_license_related_options = [ 'a11yb_license_key_hash', 'a11yb_license_domain', 'a11yb_license_environment', 'a11yb_license_state', 'a11yb_license_status', 'a11yb_plan_level', 'a11yb_license_created_at', 'a11yb_license_created_at_ts', 'a11yb_license_updated_at_ts', 'a11yb_license_expires_at', 'a11yb_license_expires_at_ts', 'a11yb_usage_period', 'a11yb_reset_date', 'a11yb_reset_date_ts', 'a11yb_monthly_limit', 'a11yb_monthly_used', 'a11yb_ai_credits_remaining', 'a11yb_next_update_at', 'a11yb_next_update_at_ts', ]; foreach ($legacy_license_related_options as $k) { delete_option($k); // in case a persistent object cache is in play wp_cache_delete($k, 'options'); } // License status cache used by licensing.php delete_transient('a11ybridge_license_cache'); wp_cache_delete('a11ybridge_license_cache', 'transient'); wp_send_json_success([ 'ok' => true, 'message' => __('License data cleared.', 'a11ybridge'), ]); } /** * AJAX handler: save cloud consent settings (opt-in) immediately. * * WP.org compliance note: * - This stores settings locally in WordPress options. * - It does NOT contact external services by itself. */ function a11ybridge_ajax_save_cloud_settings() { if (!current_user_can('manage_options')) { wp_send_json_error( [ 'msg' => 'forbidden', 'message' => __('You are not allowed to perform this action.', 'a11ybridge'), ], 403 ); } // Nonce (matches window.A11YBRIDGE_ADMIN.noncePro) check_ajax_referer('a11ybridge_pro_nonce', 'nonce'); $enabled = isset($_POST['enabled']) ? (int) sanitize_text_field(wp_unslash($_POST['enabled'])) : 0; $telemetry = isset($_POST['telemetry']) ? (int) sanitize_text_field(wp_unslash($_POST['telemetry'])) : 0; $enabled = $enabled ? 1 : 0; // If cloud is disabled, telemetry must be disabled too. $telemetry = ($enabled && $telemetry) ? 1 : 0; $opt = get_option('a11ybridge_cloud_settings', []); if (!is_array($opt)) { $opt = []; } $opt['enabled'] = $enabled; $opt['telemetry'] = $telemetry; $opt['updated_at'] = current_time('mysql'); update_option('a11ybridge_cloud_settings', $opt, false); wp_send_json_success([ 'ok' => true, 'enabled' => $enabled, 'telemetry' => $telemetry, 'message' => __('Backend settings saved.', 'a11ybridge'), ]); } /** * Add admin menu entry for A11yBridge settings. */ function a11ybridge_plugin_admin_menu() { add_menu_page( __('A11yBridge settings', 'a11ybridge'), __('A11yBridge', 'a11ybridge'), 'manage_options', 'a11ybridge-settings', 'a11ybridge_plugin_render_admin_settings_page', 'dashicons-universal-access-alt', 100 ); } add_action('admin_menu', 'a11ybridge_plugin_admin_menu'); /** * Normalisiert Admin-Einstellungen fΓΌr das Frontend. * * A11YBRIDGE_ADMIN = { * [flag_id]: { * enabled: bool, * default: mixed|null * } * } */ require_once plugin_dir_path(__FILE__) . '../includes/frontend-admin-snapshot.php'; /** * Render main admin settings page. */ function a11ybridge_plugin_render_admin_settings_page() { ?>

%s', esc_attr($tooltip), esc_html($badge) ); if ($privacy !== '') { $html .= sprintf( '%s', esc_attr__('Privacy notice', 'a11ybridge'), esc_html($privacy) ); } if ($echo) { echo wp_kses_post($html); } return $html; }