Changeset 3462162
- Timestamp:
- 02/16/2026 02:46:47 AM (5 days ago)
- Location:
- a11ybridge/trunk
- Files:
-
- 6 edited
-
a11ybridge-plugin.php (modified) (8 diffs)
-
admin/settings-pro.php (modified) (8 diffs)
-
admin/settings.php (modified) (3 diffs)
-
frontend/assets/js/text-simplification.js (modified) (1 diff)
-
includes/class-a11ybridge-license.php (modified) (4 diffs)
-
readme.txt (modified) (7 diffs)
Legend:
- Unmodified
- Added
- Removed
-
a11ybridge/trunk/a11ybridge-plugin.php
r3461477 r3462162 4 4 * Plugin URI: https://a11ybridge.de 5 5 * Description: Accessibility toolbar for WordPress: font size, contrast, focus mode, color-blind filters, keyboard navigation, text-to-speech, and optional AI text simplification. 6 * Version: 1.0. 466 * Version: 1.0.50 7 7 * Requires at least: 6.0 8 8 * Tested up to: 6.9 … … 52 52 // 1) Plugin release version (already defined by you from plugin header) 53 53 if (!defined('A11YBRIDGE_VERSION')) { 54 define('A11YBRIDGE_VERSION', '1.0. 46'); // fallback, should never be used54 define('A11YBRIDGE_VERSION', '1.0.50'); // fallback, should never be used 55 55 } 56 56 … … 107 107 } 108 108 109 /** 110 * Cloud / external service consent (opt-in). 111 * 112 * WP.org policy: the plugin must not phone home or register domains automatically. 113 * Cloud requests are only performed after explicit admin opt-in. 114 * (Entering/activating a license is treated as an explicit admin action, but does not automatically enable cloud processing for visitors.) 115 */ 116 if (!function_exists('a11ybridge_get_cloud_settings')) { 117 function a11ybridge_get_cloud_settings(): array { 118 $opt = get_option('a11ybridge_cloud_settings', []); 119 return is_array($opt) ? $opt : []; 120 } 121 } 122 123 if (!function_exists('a11ybridge_cloud_enabled')) { 124 function a11ybridge_cloud_enabled(): bool { 125 $opt = a11ybridge_get_cloud_settings(); 126 return !empty($opt['enabled']); 127 } 128 } 129 130 if (!function_exists('a11ybridge_any_ai_feature_enabled')) { 131 function a11ybridge_any_ai_feature_enabled(): bool { 132 $ai = get_option('a11ybridge_ai_settings', []); 133 $simpl = get_option('a11ybridge_simplification_settings', []); 134 if (!is_array($ai)) { $ai = []; } 135 if (!is_array($simpl)) { $simpl = []; } 136 137 // WP.org build: MVP cloud features. 138 return (!empty($ai['enable_ai_image_description']) || !empty($simpl['enable_text_simplification'])); 139 } 140 } 141 142 if (!function_exists('a11ybridge_text_simplification_admin_enabled')) { 143 function a11ybridge_text_simplification_admin_enabled(): bool { 144 $simpl = get_option('a11ybridge_simplification_settings', []); 145 if (!is_array($simpl)) { $simpl = []; } 146 return !empty($simpl['enable_text_simplification']); 147 } 148 } 149 150 if (!function_exists('a11ybridge_text_simplification_allowed')) { 151 function a11ybridge_text_simplification_allowed(): bool { 152 // Requirement: admin must explicitly enable cloud services AND the feature. 153 return a11ybridge_cloud_enabled() && a11ybridge_text_simplification_admin_enabled(); 154 } 155 } 156 157 if (!function_exists('a11ybridge_has_paid_license')) { 158 function a11ybridge_has_paid_license(): bool { 159 $pro = get_option('a11ybridge_pro_license_settings', []); 160 if (!is_array($pro)) { $pro = []; } 161 162 $license_hash = !empty($pro['license_hash']) ? (string) $pro['license_hash'] : ''; 163 if ($license_hash === '') { 164 $license_hash = (string) get_option('a11yb_license_key_hash', ''); 165 } 166 167 $plan = !empty($pro['plan']) ? strtolower(trim((string) $pro['plan'])) : ''; 168 return ($license_hash !== '' && $plan !== 'free'); 169 } 170 } 171 172 if (!function_exists('a11ybridge_cloud_opted_in')) { 173 function a11ybridge_cloud_opted_in(): bool { 174 // Explicit opt-in only (WP.org compliance): do not treat feature toggles as consent. 175 return a11ybridge_cloud_enabled(); 176 } 177 } 178 109 179 register_activation_hook(__FILE__, function () { 110 180 // Ensure install_id exists early. … … 123 193 124 194 $content = '<h2>A11yBridge</h2>'; 125 $content .= '<p>A11yBridge is an accessibility plugin. Some optional features may send content to A11yBridge servers (https://a11ybridge.de) for processing (for example: AI text simplification), and return the result to the site visitor.</p>';195 $content .= '<p>A11yBridge is an accessibility plugin. Some optional cloud features may send content to A11yBridge servers (https://a11ybridge.de) for processing (for example: AI text simplification), and return the result to the site visitor. Cloud features are disabled by default and are only used after the site owner enables them.</p>'; 126 196 $content .= '<p>When a cloud feature is used, the selected text and the chosen settings (e.g. language and simplification level) are transmitted for processing.</p>'; 127 197 $content .= '<p>A11yBridge does not use analytics cookies. The plugin may store accessibility preferences locally (e.g. browser storage) and may set a session cookie for guest users if needed to persist settings between requests.</p>'; … … 535 605 'nonce' => wp_create_nonce('a11ybridge_settings_nonce'), 536 606 'isUserLoggedIn' => is_user_logged_in(), 607 'cloudEnabled' => (int) a11ybridge_cloud_enabled(), 608 'adminTextSimplificationEnabled' => (int) a11ybridge_text_simplification_admin_enabled(), 537 609 'simplifyProxyUrl' => esc_url_raw( rest_url('a11ybridge/v1/ai/simplify') ), 538 610 'aiTokenUrl' => esc_url_raw( rest_url('a11ybridge/v1/ai/token') ), … … 685 757 } 686 758 759 // If cloud services are not enabled, do not issue tokens (no AI without explicit opt-in). 760 if (function_exists('a11ybridge_text_simplification_allowed') && !a11ybridge_text_simplification_allowed()) { 761 return new WP_REST_Response([ 'ok' => false, 'error' => 'cloud_or_feature_disabled' ], 403); 762 } 763 687 764 $token = a11ybridge_ai_proxy_issue_token(); 688 765 … … 694 771 695 772 function a11ybridge_rest_ai_simplify_permission( WP_REST_Request $req ) { 773 // Admin must explicitly enable cloud services AND the feature (double opt-in). 774 if (function_exists('a11ybridge_text_simplification_allowed') && !a11ybridge_text_simplification_allowed()) { 775 return new WP_Error('a11ybridge_cloud_disabled', 'Cloud services are disabled. Enable Cloud connection in the plugin settings first.', [ 'status' => 403 ]); 776 } 777 778 $simpl = get_option('a11ybridge_simplification_settings', []); 779 if (!is_array($simpl) || empty($simpl['enable_text_simplification'])) { 780 return new WP_Error('a11ybridge_feature_disabled', 'AI text simplification is disabled by the site administrator', [ 'status' => 403 ]); 781 } 696 782 $token = sanitize_text_field( (string) $req->get_header( 'x-a11yb-token' ) ); 697 783 if ( $token === '' ) { … … 957 1043 'nonce' => wp_create_nonce('a11ybridge_settings_nonce'), 958 1044 'isUserLoggedIn' => is_user_logged_in(), 1045 'cloudEnabled' => (int) a11ybridge_cloud_enabled(), 1046 'adminTextSimplificationEnabled' => (int) a11ybridge_text_simplification_admin_enabled(), 959 1047 'simplifyProxyUrl' => esc_url_raw( rest_url('a11ybridge/v1/ai/simplify') ), 960 1048 'aiTokenUrl' => esc_url_raw( rest_url('a11ybridge/v1/ai/token') ), -
a11ybridge/trunk/admin/settings-pro.php
r3461470 r3462162 26 26 27 27 function a11ybridge_show_pro_settings() { 28 if (class_exists('A11YB_License_API')) { 29 // Ignore errors, UI will fall back to defaults if needed 30 A11YB_License_API::refresh_status_and_options([]); 31 } 32 28 33 29 // 1) Load local license options 34 30 $opt = get_option('a11ybridge_pro_license_settings', []); … … 36 32 $opt = []; 37 33 } 34 35 // Cloud consent (opt-in) 36 $cloud = get_option('a11ybridge_cloud_settings', []); 37 if (!is_array($cloud)) { 38 $cloud = []; 39 } 40 $cloud_enabled = !empty($cloud['enabled']); 41 $telemetry_optin = !empty($cloud['telemetry']); 38 42 39 43 // Current domain … … 48 52 $bound_domain = $opt['bound_domain'] ?? ''; 49 53 54 // Fetch remote quota/status only after explicit opt-in (or if a paid license is present). 55 if (class_exists('A11YB_License_API')) { 56 try { 57 if (function_exists('a11ybridge_cloud_opted_in') ? a11ybridge_cloud_opted_in() : false) { 58 A11YB_License_API::refresh_status_and_options(['screen' => 'admin_pro_tab']); 59 } 60 } catch (\Throwable $e) { 61 // ignore 62 } 63 } 64 50 65 // "active" = hash + some bound domain according to backend 51 66 $has_hash = !empty($license_hash) && !empty($bound_domain); … … 67 82 // Global MVP counters (replace these option keys later with real sources) 68 83 $plan_level = $plan; // free|paid|unknown 69 70 if (!empty($license_hash)) {71 try {72 \A11YB_License_API::refresh_status_and_options(['screen' => 'admin_pro_tab']);73 } catch (\Throwable $e) {}74 }75 84 76 85 $reset_ts = (int) get_option('a11yb_next_update_at_ts', 0); … … 139 148 140 149 <!-- ========================= 150 Cloud connection (opt-in) 151 ========================= --> 152 <div class="a11y-settings-section" style="border-left: 4px solid #2271b1;"> 153 <h2><?php esc_html_e('Cloud connection (optional)', 'a11ybridge'); ?></h2> 154 <p class="description"> 155 <?php esc_html_e('A11yBridge can optionally connect to A11yBridge cloud services for AI features and quota/licensing. For WP.org compliance, no external requests are made until you enable cloud services (or activate a license).', 'a11ybridge'); ?> 156 </p> 157 158 <label style="display:block; margin: 8px 0;"> 159 <input type="hidden" name="a11ybridge_cloud_settings[enabled]" value="0" /> 160 <input type="checkbox" id="a11ybridge-cloud-enabled" name="a11ybridge_cloud_settings[enabled]" value="1" <?php checked($cloud_enabled); ?> /> 161 <?php esc_html_e('Enable cloud services for this site', 'a11ybridge'); ?> 162 </label> 163 164 <label style="display:block; margin: 8px 0;"> 165 <input type="hidden" name="a11ybridge_cloud_settings[telemetry]" value="0" /> 166 <input type="checkbox" id="a11ybridge-cloud-telemetry" name="a11ybridge_cloud_settings[telemetry]" value="1" <?php checked($telemetry_optin); ?> /> 167 <?php esc_html_e('Share anonymous diagnostics (optional)', 'a11ybridge'); ?> 168 </label> 169 170 <div id="a11ybridge-cloud-save-status" class="description" style="display:none; margin-top: 8px;"></div> 171 172 <?php if (!$cloud_enabled && empty($license_hash)): ?> 173 <p class="description" style="margin-top: 8px; color:#8a2424;"> 174 <?php esc_html_e('Cloud services are currently disabled. AI features that require external processing will not be available until you enable cloud services.', 'a11ybridge'); ?> 175 </p> 176 <?php endif; ?> 177 </div> 178 179 <?php 180 // Auto-save cloud consent toggles (no submit button on this tab). 181 ob_start(); 182 ?> 183 (function(){ 184 const enabledCb = document.getElementById('a11ybridge-cloud-enabled'); 185 const telemetryCb = document.getElementById('a11ybridge-cloud-telemetry'); 186 const statusEl = document.getElementById('a11ybridge-cloud-save-status'); 187 if (!enabledCb || !telemetryCb || !statusEl) return; 188 189 const ajaxUrl = (window.A11YBRIDGE_ADMIN && window.A11YBRIDGE_ADMIN.ajaxUrl) ? window.A11YBRIDGE_ADMIN.ajaxUrl : ''; 190 const nonce = (window.A11YBRIDGE_ADMIN && window.A11YBRIDGE_ADMIN.noncePro) ? window.A11YBRIDGE_ADMIN.noncePro : ''; 191 if (!ajaxUrl || !nonce) return; 192 193 function setStatus(msg, isOk){ 194 if (!statusEl) return; 195 statusEl.style.display = 'block'; 196 statusEl.style.color = isOk ? '#0f5132' : '#8a2424'; 197 statusEl.textContent = msg; 198 clearTimeout(setStatus._t); 199 setStatus._t = setTimeout(()=>{ statusEl.style.display='none'; }, 2500); 200 } 201 202 function syncTelemetryEnabledState(){ 203 const cloudEnabled = !!enabledCb.checked; 204 telemetryCb.disabled = !cloudEnabled; 205 if (!cloudEnabled && telemetryCb.checked){ 206 telemetryCb.checked = false; 207 } 208 } 209 210 async function save(){ 211 syncTelemetryEnabledState(); 212 213 const enabled = enabledCb.checked ? 1 : 0; 214 const telemetry = (!telemetryCb.disabled && telemetryCb.checked) ? 1 : 0; 215 const body = new URLSearchParams(); 216 body.set('action', 'a11ybridge_save_cloud_settings'); 217 body.set('nonce', nonce); 218 body.set('enabled', String(enabled)); 219 body.set('telemetry', String(telemetry)); 220 221 try{ 222 const res = await fetch(ajaxUrl, { 223 method: 'POST', 224 headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}, 225 body: body.toString(), 226 credentials: 'same-origin' 227 }); 228 const data = await res.json().catch(()=>null); 229 if (!res.ok || !data || !data.success){ 230 const msg = (data && data.data && (data.data.message || data.data.msg)) ? (data.data.message || data.data.msg) : 'Save failed.'; 231 setStatus(msg, false); 232 return; 233 } 234 // normalize server response 235 if (typeof data.data?.enabled !== 'undefined'){ 236 enabledCb.checked = !!data.data.enabled; 237 } 238 if (typeof data.data?.telemetry !== 'undefined'){ 239 telemetryCb.checked = !!data.data.telemetry; 240 } 241 syncTelemetryEnabledState(); 242 setStatus('Saved.', true); 243 } catch(e){ 244 setStatus('Save failed (network).', false); 245 } 246 } 247 248 enabledCb.addEventListener('change', save); 249 telemetryCb.addEventListener('change', save); 250 syncTelemetryEnabledState(); 251 })(); 252 <?php 253 $__js = ob_get_clean(); 254 wp_add_inline_script('a11ybridge-admin-inline', $__js, 'after'); 255 ?> 256 257 <!-- ========================= 141 258 Section 1: Overall statistics 142 259 ========================= --> … … 213 330 </p> 214 331 332 <?php if ($show_account_links && ($cloud_enabled || !empty($license_hash))): ?> 215 333 <p class="description" style="margin: 12px 0;"> 216 334 <?php esc_html_e('Need more AI quota? Manage your subscription in your account dashboard.', 'a11ybridge'); ?> … … 219 337 </a> 220 338 </p> 339 <?php endif; ?> 221 340 </div> 222 341 … … 589 708 // If your endpoint needs a nonce etc., add it here 590 709 }, 591 body: JSON.stringify({ meta: { } })710 body: JSON.stringify({ meta: { user_action: true } }) 592 711 }); 593 712 -
a11ybridge/trunk/admin/settings.php
r3461470 r3462162 46 46 // Pro settings 47 47 register_setting('a11ybridge_pro_settings_group', 'a11ybridge_pro_license_settings', ['type' => 'array', 'sanitize_callback' => 'a11ybridge_sanitize_option_array']); 48 // Cloud / external service consent (opt-in) 49 register_setting('a11ybridge_pro_settings_group', 'a11ybridge_cloud_settings', ['type' => 'array', 'sanitize_callback' => 'a11ybridge_sanitize_option_array']); 48 50 } 49 51 … … 51 53 add_action('wp_ajax_a11ybridge_save_license_hash', 'a11ybridge_ajax_save_license_hash'); 52 54 add_action('wp_ajax_a11ybridge_clear_license', 'a11ybridge_ajax_clear_license'); 53 55 add_action('wp_ajax_a11ybridge_save_cloud_settings', 'a11ybridge_ajax_save_cloud_settings'); 54 56 /** 55 57 * AJAX handler: store license hash and metadata locally. … … 169 171 170 172 /** 173 * AJAX handler: save cloud consent settings (opt-in) immediately. 174 * 175 * WP.org compliance note: 176 * - This stores settings locally in WordPress options. 177 * - It does NOT contact external services by itself. 178 */ 179 function a11ybridge_ajax_save_cloud_settings() { 180 if (!current_user_can('manage_options')) { 181 wp_send_json_error( 182 [ 183 'msg' => 'forbidden', 184 'message' => __('You are not allowed to perform this action.', 'a11ybridge'), 185 ], 186 403 187 ); 188 } 189 // Nonce (matches window.A11YBRIDGE_ADMIN.noncePro) 190 check_ajax_referer('a11ybridge_pro_nonce', 'nonce'); 191 192 $enabled = isset($_POST['enabled']) ? (int) sanitize_text_field(wp_unslash($_POST['enabled'])) : 0; 193 $telemetry = isset($_POST['telemetry']) ? (int) sanitize_text_field(wp_unslash($_POST['telemetry'])) : 0; 194 $enabled = $enabled ? 1 : 0; 195 // If cloud is disabled, telemetry must be disabled too. 196 $telemetry = ($enabled && $telemetry) ? 1 : 0; 197 198 $opt = get_option('a11ybridge_cloud_settings', []); 199 if (!is_array($opt)) { 200 $opt = []; 201 } 202 203 $opt['enabled'] = $enabled; 204 $opt['telemetry'] = $telemetry; 205 $opt['updated_at'] = current_time('mysql'); 206 207 update_option('a11ybridge_cloud_settings', $opt, false); 208 209 wp_send_json_success([ 210 'ok' => true, 211 'enabled' => $enabled, 212 'telemetry' => $telemetry, 213 'message' => __('Cloud settings saved.', 'a11ybridge'), 214 ]); 215 } 216 217 /** 171 218 * Add admin menu entry for A11yBridge settings. 172 219 */ -
a11ybridge/trunk/frontend/assets/js/text-simplification.js
r3461470 r3462162 22 22 23 23 async init() { 24 // Hard gate: do not expose UI or make requests unless admin enabled cloud + feature. 25 const cfg = window.a11ybridgePlugin || {}; 26 if (!cfg.cloudEnabled || !cfg.adminTextSimplificationEnabled) { 27 globalThis.A11yBridgeLogger.debug('🛑 Text Simplification disabled (cloud or admin feature toggle off).'); 28 return; 29 } 30 24 31 await this.loadSavedSettings(); 25 32 this.setupTextSelection(); -
a11ybridge/trunk/includes/class-a11ybridge-license.php
r3461470 r3462162 65 65 'license_hash' => $key_hash, // optional 66 66 'domain' => $domain, 67 'site_url' => $site_url,68 67 'wp_version' => get_bloginfo('version'), 69 68 'plugin_ver' => A11YBRIDGE_VERSION, 70 'admin_email' => get_option('admin_email'),71 69 'meta' => self::sanitize_meta($meta), 72 70 ]; … … 256 254 } 257 255 256 // WP.org compliance: do not contact external servers unless the site owner opted in 257 // (e.g. explicitly enabling cloud services in the Pro tab). 258 $cloud_ok = (function_exists('a11ybridge_cloud_opted_in') && a11ybridge_cloud_opted_in()); 259 $user_action = !empty($meta['user_action']); 260 if (!$cloud_ok && !$user_action) { 261 // Keep local counters only (no remote call). 262 $fallback_limit = (int) get_option('a11yb_monthly_limit', 100); 263 if ($fallback_limit <= 0) { $fallback_limit = 100; } 264 $local_used = max(0, (int) get_option('a11yb_monthly_used', 0)); 265 $local_remaining = max(0, $fallback_limit - $local_used); 266 267 update_option('a11yb_plan_level', 'free', false); 268 update_option('a11yb_monthly_limit', $fallback_limit, false); 269 update_option('a11yb_ai_credits_remaining', $local_remaining, false); 270 271 return [ 272 'plan' => 'free', 273 'monthly_limit' => $fallback_limit, 274 'monthly_used' => $local_used, 275 'remaining' => $local_remaining, 276 'status' => 'local_opt_out', 277 ]; 278 } 279 258 280 $payload = [ 259 281 'installation_id' => $install_id, 260 282 'domain' => $domain, 261 'site_url' => $site_url,262 283 'meta' => self::sanitize_meta($meta), 263 284 ]; … … 266 287 $resp = self::remote_json('/free/status', $payload, [ 267 288 'X-AB-Installation-Id' => $install_id, 268 'X-AB-Origin' => home_url('/'),269 289 ]); 270 290 } else { … … 272 292 'license_hash' => $license_hash, 273 293 'domain' => $domain, 274 'site_url' => $site_url,275 294 'meta' => self::sanitize_meta($meta), 276 295 ]; -
a11ybridge/trunk/readme.txt
r3461477 r3462162 2 2 Contributors: berlinlion 3 3 Donate link: https://a11ybridge.de/ 4 Tags: accessibility, wcag, contrast, text-to-speech, keyboard 4 Tags: accessibility, wcag, contrast, text-to-speech, keyboard, a11y, screen reader, web-accessibility 5 5 Requires at least: 6.0 6 Tested up to: 6.9 6 Tested up to: 6.9.1 7 7 Requires PHP: 7.4 8 Stable tag: 1.0. 468 Stable tag: 1.0.50 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html 11 11 12 Accessibility toolbar : font size, contrast, focus mode, keyboard support, text-to-speech, andoptional AI text simplification.12 Accessibility toolbar for WordPress: font size, contrast, focus mode, color filters, keyboard navigation, text-to-speech, plus optional AI text simplification. 13 13 14 14 … … 32 32 == AI features (optional) == 33 33 34 AI text simplification is processed on external servers. Any usage limits are enforced by the external service and depend on the service subscription associated with the installation.34 AI text simplification is processed on external servers. Cloud features are disabled by default and are only used after the site owner enables them (or activates a license). Any usage limits are enforced by the external service and depend on the service subscription associated with the installation. 35 35 All non-AI accessibility tools (toolbar, contrast, focus mode, keyboard support, text-to-speech) work without any AI connection. 36 36 37 AI text simplification is processed on external servers. Any usage limits are enforced by the external service and depend on the service plan associated with the installation.38 37 The plugin itself does not lock or restrict built-in/local functionality based on a license key, trial period, or local usage counters. 39 38 If the service quota is exceeded, the service returns an error response and the plugin displays a notice. … … 41 40 == External Services / Data Transmission == 42 41 43 This plugin may connect to A11yBridge backend services to provide optional AI text simplification and service operations (e.g., quota management, abuse prevention).42 This plugin can optionally connect to A11yBridge backend services to provide AI text simplification and related service operations (e.g., quota management, license activation, abuse prevention). For WP.org compliance, it does NOT contact external servers by default. External requests happen only after explicit admin opt-in (cloud enabled / AI feature enabled) or after the admin activates a license key. 44 43 45 44 Service endpoint(s): … … 49 48 - /license/activate (activate/bind license to this site) 50 49 - /license/domains/delete (remove domain binding from license) 50 - /free/status (free-tier quota status, only after admin opt-in) 51 51 - /chat/simplify (AI text simplification, only when triggered by the user) 52 52 … … 70 70 * Selected text submitted by the user for AI text simplification (only when the user triggers simplification) 71 71 * Pseudonymous installation ID 72 * Domain/host 72 * Domain/host (only after admin opt-in or license activation) 73 73 * License key hash (pseudonymous, if a license is used) 74 74 * Service-side usage/quota accounting … … 131 131 == Screenshots == 132 132 133 1. Frontend Toolbar opened (mobil)134 2. Admin Dashboard Overview (Basic Features)135 3. Admin AI Settings (inkl. Consent)136 4. Font /Text-Size Settings Dialog137 5. Color Adjustments (High Contrast)138 6. Focus Mode Dialog139 7. Keyboard Navigation Dialog140 8. Color Filters Dialog141 8. Text-to-Speech Dialog 142 10. Simplify Result Modal133 1. Frontend toolbar (mobile) 134 2. Admin dashboard overview (basic features) 135 3. Admin AI settings (including consent) 136 4. Font and text size controls 137 5. High contrast mode 138 6. Focus mode 139 7. Keyboard navigation and focus indicators 140 8. Color filters and adjustments 141 9. Text-to-speech settings 142 10. AI text simplification result 143 143 144 144 == Changelog == 145 145 146 = 1.0. 46=146 = 1.0.50 = 147 147 * WP.org compliance fixes: removed trialware indicators, removed legacy frontend templates with direct script/style tags. 148 148 * Fixed undefined function error in a11ybridge-plugin.php (legacy footer hook removed). … … 155 155 == Upgrade Notice == 156 156 157 = 1.0. 46=157 = 1.0.50 = 158 158 Recommended update for WP.org submission compliance and stability fixes. 159 159
Note: See TracChangeset
for help on using the changeset viewer.