'array', 'sanitize_callback' => 'a11ybridge_sanitize_option_array')); } // Render directly if (is_admin()) { $a11ybridge_page_raw = filter_input(INPUT_GET, 'page', FILTER_UNSAFE_RAW); $a11ybridge_page = is_string($a11ybridge_page_raw) ? sanitize_key(wp_unslash($a11ybridge_page_raw)) : ''; if ($a11ybridge_page === 'a11ybridge-settings') { a11ybridge_show_pro_settings(); } } function a11ybridge_show_pro_settings() { // 1) Load local license options $opt = get_option('a11ybridge_pro_license_settings', []); if (!is_array($opt)) { $opt = []; } // Cloud consent (opt-in) $cloud = get_option('a11ybridge_cloud_settings', []); if (!is_array($cloud)) { $cloud = []; } $cloud_enabled = !empty($cloud['enabled']); $telemetry_optin = !empty($cloud['telemetry']); // Current domain $site_url = site_url(); $domain = wp_parse_url($site_url, PHP_URL_HOST) ?: $site_url; $license_hash = $opt['license_hash'] ?? ''; $license_hint = $opt['license_hint'] ?? ''; $plan_long = $opt['plan'] ?? 'free'; $license_api = new A11YB_License_API(); $plan = $license_api->shorten_plan($plan_long) ?? 'free'; $bound_domain = $opt['bound_domain'] ?? ''; // Fetch remote quota/status only after explicit opt-in (or if a paid license is present). if (class_exists('A11YB_License_API')) { try { if (function_exists('a11ybridge_cloud_opted_in') ? a11ybridge_cloud_opted_in() : false) { A11YB_License_API::refresh_status_and_options(['screen' => 'admin_pro_tab']); } } catch (\Throwable $e) { // ignore } } // "active" = hash + some bound domain according to backend $has_hash = !empty($license_hash) && !empty($bound_domain); /** * ------------------------------------------------------------ * Context / Variables * ------------------------------------------------------------ */ // $site_url = site_url(); // $domain = wp_parse_url($site_url, PHP_URL_HOST) ?: $site_url; $wp_version = get_bloginfo('version'); $admin_email = get_option('admin_email'); $nonce = wp_create_nonce('wp_rest'); // for X-WP-Nonce header (WP REST) // Global MVP counters (replace these option keys later with real sources) $plan_level = $plan; // free|paid|unknown $reset_ts = (int) get_option('a11yb_next_update_at_ts', 0); if (!$reset_ts) { $reset_ts = (int) get_option('a11yb_reset_date_ts', 0); } // Canonical: limit by plan, remaining from backend mirror option // Plan limits are provided by the license backend and stored locally as options. $monthly_limit = (int) get_option('a11yb_monthly_limit', 100); if ($monthly_limit <= 0) { $monthly_limit = 100; } $remaining_opt = get_option('a11yb_ai_credits_remaining', null); $remaining = ($remaining_opt !== null && $remaining_opt !== '') ? max(0, (int) $remaining_opt) : null; // If remaining is missing, fall back to stored used if ($remaining === null) { $monthly_used = max(0, (int) a11yb_get_option('a11yb_monthly_used', 0)); $remaining = max(0, $monthly_limit - $monthly_used); } else { // top-up safety if ($remaining > $monthly_limit) { $monthly_limit = $remaining; $monthly_used = 0; } else { $monthly_used = max(0, $monthly_limit - $remaining); } } $progress = $monthly_limit > 0 ? round(($monthly_used / $monthly_limit) * 100, 1) : 0.0; // Reset date formatted $reset_str = $reset_ts ? esc_html(wp_date(get_option('date_format') . ' ' . get_option('time_format'), $reset_ts)) : ''; // Endpoints (can be overridden via filter or data-* attributes) if (function_exists('a11ybridge_backend_rest_url')) { $endpoint_activate = a11ybridge_backend_rest_url('/license/activate'); $endpoint_status = a11ybridge_backend_rest_url('/license/status'); } else { $endpoint_activate = 'https://a11ybridge.de/wp-json/a11ybridge/v1/license/activate'; $endpoint_status = 'https://a11ybridge.de/wp-json/a11ybridge/v1/license/status'; } // Labels $plan_label = a11yb_plan_label($plan_level); // WP.org note: keep the admin UI focused on managing the optional AI service. // External account/billing links can be enabled by site owners via a filter. $show_account_links = (bool) apply_filters('a11ybridge_show_account_links', false); ?>

(function(){ const enabledCb = document.getElementById('a11ybridge-cloud-enabled'); const telemetryCb = document.getElementById('a11ybridge-cloud-telemetry'); const statusEl = document.getElementById('a11ybridge-cloud-save-status'); if (!enabledCb || !telemetryCb || !statusEl) return; const ajaxUrl = (window.A11YBRIDGE_ADMIN && window.A11YBRIDGE_ADMIN.ajaxUrl) ? window.A11YBRIDGE_ADMIN.ajaxUrl : ''; const nonce = (window.A11YBRIDGE_ADMIN && window.A11YBRIDGE_ADMIN.noncePro) ? window.A11YBRIDGE_ADMIN.noncePro : ''; if (!ajaxUrl || !nonce) return; function setStatus(msg, isOk){ if (!statusEl) return; statusEl.style.display = 'block'; statusEl.style.color = isOk ? '#0f5132' : '#8a2424'; statusEl.textContent = msg; clearTimeout(setStatus._t); setStatus._t = setTimeout(()=>{ statusEl.style.display='none'; }, 2500); } function syncTelemetryEnabledState(){ const cloudEnabled = !!enabledCb.checked; telemetryCb.disabled = !cloudEnabled; if (!cloudEnabled && telemetryCb.checked){ telemetryCb.checked = false; } } async function save(){ syncTelemetryEnabledState(); const enabled = enabledCb.checked ? 1 : 0; const telemetry = (!telemetryCb.disabled && telemetryCb.checked) ? 1 : 0; const body = new URLSearchParams(); body.set('action', 'a11ybridge_save_cloud_settings'); body.set('nonce', nonce); body.set('enabled', String(enabled)); body.set('telemetry', String(telemetry)); try{ const res = await fetch(ajaxUrl, { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}, body: body.toString(), credentials: 'same-origin' }); const data = await res.json().catch(()=>null); if (!res.ok || !data || !data.success){ const msg = (data && data.data && (data.data.message || data.data.msg)) ? (data.data.message || data.data.msg) : 'Save failed.'; setStatus(msg, false); return; } // normalize server response if (typeof data.data?.enabled !== 'undefined'){ enabledCb.checked = !!data.data.enabled; } if (typeof data.data?.telemetry !== 'undefined'){ telemetryCb.checked = !!data.data.telemetry; } syncTelemetryEnabledState(); setStatus('Saved.', true); } catch(e){ setStatus('Save failed (network).', false); } } enabledCb.addEventListener('change', save); telemetryCb.addEventListener('change', save); syncTelemetryEnabledState(); })();

 · 

0); $in_future = ($has_date && $expires_ts > $now_ts); $expires_str = $has_date ? wp_date(get_option('date_format') . ' ' . get_option('time_format'), $expires_ts) : ''; $is_active = ($lic_status === 'active'); $is_cancel = in_array($lic_status, ['cancelled','canceled'], true); $mode = 'unknown'; if ($has_date) { if ($is_cancel && $in_future) $mode = 'cancelled_valid'; elseif ($is_cancel && !$in_future) $mode = 'expired'; elseif ($is_active && $in_future) $mode = 'active'; elseif ($is_active && !$in_future) $mode = 'unpaid'; } // Human output $status_line = ''; switch ($mode) { case 'cancelled_valid': /* translators: %s: date */ $status_line = sprintf(__('Cancelled, valid until %s', 'a11ybridge'), $expires_str); break; case 'active': /* translators: %s: date */ $status_line = sprintf(__('Active, updates on %s', 'a11ybridge'), $expires_str); break; case 'expired': /* translators: %s: date */ $status_line = sprintf(__('Expired on %s', 'a11ybridge'), $expires_str); break; case 'unpaid': /* translators: %s: date */ $status_line = sprintf(__('Unpaid since %s', 'a11ybridge'), $expires_str); break; default: // fallback if no expires date if ($is_active) $status_line = __('Active', 'a11ybridge'); elseif ($is_cancel) $status_line = __('Cancelled', 'a11ybridge'); else $status_line = __('Unknown', 'a11ybridge'); break; } ?>

einmalig an a11ybridge.de übertragen; lokal speichern wir ausschließlich den Hash.' esc_html_e( 'Please enter your license key to connect optional AI services. The key is sent to a11ybridge.de exactly once in plain text; locally only the hash is stored.', 'a11ybridge' ); ?>

(function(){ var btn = document.getElementById('ab-unbind-domain'); if (!btn) return; const backendDelUrl = "https://a11ybridge.de/wp-json/a11ybridge/v1/license/domains/delete"; var licenseHash = btn.getAttribute('data-license-hash') || ''; var domain = btn.getAttribute('data-domain') || ''; // Ajax endpoint + nonce for WP var ajaxUrl = (window.A11YBRIDGE_ADMIN && window.A11YBRIDGE_ADMIN.ajaxUrl) ? window.A11YBRIDGE_ADMIN.ajaxUrl : ''; var nonce = (window.A11YBRIDGE_ADMIN && window.A11YBRIDGE_ADMIN.noncePro) ? window.A11YBRIDGE_ADMIN.noncePro : ''; globalThis.A11yBridgeLogger.debug("licenseHash:", licenseHash, "domain:", domain); btn.addEventListener('click', async () => { if (!licenseHash) { // DE: 'Kein Lizenz-Hash verfügbar – bitte Lizenz neu aktivieren.' alert('No license hash available – please re-activate the license.'); return; } if (!domain) { // DE: 'Keine Domain zum Entbinden vorhanden.' alert('No domain available to unbind.'); return; } // DE: 'Domain ${domain} von dieser Lizenz trennen?' if (!confirm(`Unbind domain ${domain} from this license?`)) return; btn.disabled = true; // DE: '… entferne' btn.textContent = '… removing'; try { // 1) Delete domain in backend var resp = await fetch(backendDelUrl, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ license_hash: licenseHash, domain }) }); var data = await resp.json(); if (!resp.ok || !data?.ok) { // DE: 'Entfernen fehlgeschlagen: ' alert('Unbinding failed: ' + (data?.error || resp.status)); btn.disabled = false; // DE: 'Domain von Lizenz trennen' btn.textContent = 'Unbind domain from license'; return; } // 2) Delete local license in WordPress try { var clearResp = await fetch(ajaxUrl, { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: new URLSearchParams({ action: 'a11ybridge_clear_license', nonce: nonce }) }); var clearJson = await clearResp.json(); if (!clearJson?.success) { globalThis.A11yBridgeLogger.error('a11ybridge_clear_license failed:', clearJson); } } catch (e) { globalThis.A11yBridgeLogger.error('Error calling a11ybridge_clear_license:', e); } // DE: 'Domain entfernt.' alert('Domain removed.'); // location.reload(); location.href = window.location.href + '&t=' + Date.now(); } catch (e) { // DE: 'Netzwerkfehler beim Entfernen der Domain.' alert('Network error while removing domain.'); btn.disabled = false; // DE: 'Domain von Lizenz trennen' btn.textContent = 'Unbind domain from license'; } }); })();
.a11yb-card { margin-top: 18px; } .a11yb-stats-grid { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 12px; margin: 10px 0 14px; } .a11yb-stat { background:#fff; border:1px solid #e5e5e5; border-radius:6px; padding:10px 12px; } .a11yb-stat-label { font-size:12px; color:#666; margin-bottom:4px; } .a11yb-stat-value { font-size:16px; font-weight:600; } .a11yb-progress { background:#f1f1f1; border-radius:999px; height:10px; overflow:hidden; margin-top:6px; } .a11yb-progress-bar { background:#2271b1; height:10px; } .a11yb-progress-meta { margin-top:8px; color:#333; } .a11yb-inline-msg { margin-left:8px; } document.addEventListener('DOMContentLoaded', function() { var refreshBtn = document.getElementById('refresh-license-btn'); var statusDiv = document.getElementById('license-status'); if (!refreshBtn || !statusDiv) { return; } const restUrl = ""; refreshBtn.addEventListener('click', async function() { const btn = this; btn.disabled = true; // DE: '🔄 Prüfe Lizenz...' btn.textContent = '🔄 Checking license...'; statusDiv.className = 'a11y-status'; statusDiv.style.display = 'block'; // DE: 'Kontaktiere A11yBridge Server...' statusDiv.textContent = 'Contacting A11yBridge server...'; try { const resp = await fetch(restUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' // If your endpoint needs a nonce etc., add it here }, body: JSON.stringify({ meta: { user_action: true } }) }); const data = await resp.json().catch(() => ({})); if (!resp.ok) { statusDiv.className = 'a11y-status error'; // DE: 'Fehler bei der Lizenzprüfung: ' statusDiv.textContent = 'Error while checking license: ' + (data && data.message ? data.message : resp.status); } else { statusDiv.className = 'a11y-status success'; // DE: '✅ Lizenz-Status erfolgreich aktualisiert.' statusDiv.textContent = data && data.message ? data.message : '✅ License status successfully updated.'; // Reload page so that new values appear in UI setTimeout(function() { location.href = window.location.href + '&t=' + Date.now(); }, 1200); } } catch (e) { globalThis.A11yBridgeLogger.error('Error calling license status:', e); statusDiv.className = 'a11y-status error'; // DE: 'Netzwerkfehler bei der Lizenzprüfung.' statusDiv.textContent = 'Network error while checking license.'; } finally { btn.disabled = false; // DE: '🔄 Lizenz-Status prüfen' btn.textContent = '🔄 Check license status'; } }); }); (function(){ var ajaxUrl = (window.A11YBRIDGE_ADMIN && window.A11YBRIDGE_ADMIN.ajaxUrl) ? window.A11YBRIDGE_ADMIN.ajaxUrl : ''; var nonce = (window.A11YBRIDGE_ADMIN && window.A11YBRIDGE_ADMIN.noncePro) ? window.A11YBRIDGE_ADMIN.noncePro : ''; var siteUrl = ""; var origin = window.location.origin; const backendActivateUrl = "https://a11ybridge.de/wp-json/a11ybridge/v1/license/activate"; async function activateOrRebind(licenseKey) { // 1) Send to a11ybridge.de (once in plain text) const resp = await fetch(backendActivateUrl, { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ license_key: licenseKey || null, domain: origin, site_url: siteUrl, plugin: 'a11ybridge', plugin_version: (window.A11YBRIDGE_CONFIG && window.A11YBRIDGE_CONFIG.version) || '' }) }); const data = await resp.json(); globalThis.A11yBridgeLogger.debug('A11yBridge activate response:', resp.status, data); if (!resp.ok || !data?.ok) { var msg = data?.error || ('Activation failed: ' + resp.status); // DE: 'Aktivierung fehlgeschlagen: ' alert('Activation failed: ' + msg); return; } // Response contains license_hash + license_hint + plan + bound_domain // 2) Save locally (hash etc. only) globalThis.A11yBridgeLogger.debug('Saving license data locally:', data); globalThis.A11yBridgeLogger.debug('Sending to ajaxUrl:', ajaxUrl); const save = await fetch(ajaxUrl, { method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body: new URLSearchParams({ action: 'a11ybridge_save_license_hash', nonce: nonce, license_hash: data.license_hash || data.license_key_hash, license_hint: data.license_hint || '', plan: data.plan || '', domain: data.bound_domain || '', domains: JSON.stringify(data.domains || []), limit: data.limit ?? '' }) }); var saved = await save.json(); if (!saved?.success) { var msg = saved?.data?.msg || saved?.data || 'unknown error'; globalThis.A11yBridgeLogger.error('a11ybridge_pro_save_license_hash response:', saved); // DE: 'Lokales Speichern fehlgeschlagen: ' alert('Saving license locally failed: ' + msg); return; } // DE: 'Lizenz aktiviert.' alert('License activated.'); // location.reload(); location.href = window.location.href + '&t=' + Date.now(); } var btn = document.getElementById('ab-activate'); if (btn) { btn.addEventListener('click', async () => { var input = document.getElementById('ab-license-input'); var key = (input && input.value || '').trim(); if (!key || key.length < 16) { // DE: 'Bitte gültigen Lizenzschlüssel eingeben.' alert('Please enter a valid license key.'); return; } btn.disabled = true; // DE: '… aktiviere' btn.textContent = '… activating'; try { await activateOrRebind(key); } finally { btn.disabled = false; // DE: 'Lizenz aktivieren' btn.textContent = 'Activate license'; } }); } var rebind = document.getElementById('ab-rebind'); if (rebind) { rebind.addEventListener('click', async () => { // DE: 'Diese Domain wird mit deiner Lizenz neu gebunden. Fortfahren?' if (!confirm('This domain will be re-bound to your license. Continue?')) return; rebind.disabled = true; // DE: '… binde neu' rebind.textContent = '… re-binding'; try { await activateOrRebind(null); } finally { rebind.disabled = false; // DE: 'Domain neu binden' rebind.textContent = 'Re-bind domain'; } }); } })();