PluginProbe
Accessibility by AllAccessible / 2.1.1
Accessibility by AllAccessible v2.1.1
2.1.6 2.1.5 2.1.4 2.1.3 2.1.2 2.1.1 2.1.0 2.0.6 trunk 1.0 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.2 1.2.1 1.2.10 1.2.2 1.2.3 1.2.4 1.2.5 All 43 releases
allaccessible / allaccessible.php

allaccessible.php in Accessibility by AllAccessible 2.1.1, at allaccessible.php

417 lines 15.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 Plugin Name: AllAccessible
4 Plugin URI: https://www.allaccessible.org/platform/wordpress/
5 Description: Unlock true digital accessibility with AllAccessible - a comprehensive WordPress plugin driving your website towards WCAG/ADA compliance. Empower your users with a fully customizable accessibility widget, plus agentic AI remediation that auto-suggests fixes for your team to approve.
6 Version: 2.1.1
7 Requires at least: 5.5
8 Tested up to: 7.0
9 Requires PHP: 7.4
10 Author: AllAccessible Team
11 Author URI: https://www.allaccessible.org/
12 Text Domain: allaccessible
13 Domain Path: /languages
14 */
15
16 /**
17 * Copyright (C) 2024 AllAccessible.
18 * This file is part of AllAccessible.
19 *
20 * AllAccessible is free software: you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation, either version 2 of the License, or
23 * any later version.
24 *
25 * AllAccessible is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
29 *
30 * You should have received a copy of the GNU General Public License
31 * along with AllAccessible. If not, see <http://www.gnu.org/licenses/>.
32 *
33 * @package AllAccessible
34 * @author AllAccessible Team
35 * @copyright 2024 AllAccessible
36 * @license GPL-2.0+
37 */
38
39 if (!defined('ABSPATH')) {
40 die('You are not allowed to call this page directly.');
41 }
42
43 // Core Components
44 require_once plugin_dir_path(__FILE__) . 'inc/constants.php';
45 require_once plugin_dir_path(__FILE__) . 'inc/Debug.php';
46 require_once plugin_dir_path(__FILE__) . 'inc/SentryClient.php';
47 require_once plugin_dir_path(__FILE__) . 'inc/SentryBrowser.php';
48 AllAccessible_Sentry::init();
49 AllAccessible_SentryBrowser::register();
50 require_once plugin_dir_path(__FILE__) . 'inc/VersionManager.php';
51 require_once plugin_dir_path(__FILE__) . 'inc/UrlCanonicalizer.php';
52
53 // Widget & Frontend
54 require_once plugin_dir_path(__FILE__) . 'inc/WidgetLoader.php';
55
56 // Admin Interface
57 require_once plugin_dir_path(__FILE__) . 'inc/OnboardingWizard.php';
58 require_once plugin_dir_path(__FILE__) . 'inc/SettingsPage.php';
59 require_once plugin_dir_path(__FILE__) . 'inc/WidgetCustomizer.php';
60 require_once plugin_dir_path(__FILE__) . 'inc/UsageDashboard.php';
61 require_once plugin_dir_path(__FILE__) . 'inc/ConversionCTA.php';
62 require_once plugin_dir_path(__FILE__) . 'inc/FeatureComparison.php';
63 require_once plugin_dir_path(__FILE__) . 'inc/DashboardBanner.php';
64 require_once plugin_dir_path(__FILE__) . 'inc/DeactivationSurvey.php';
65 require_once plugin_dir_path(__FILE__) . 'inc/DashboardLayout.php';
66
67 // API Integration (Premium Features)
68 require_once plugin_dir_path(__FILE__) . 'inc/api/ApiClient.php';
69 require_once plugin_dir_path(__FILE__) . 'inc/TierGate.php';
70 require_once plugin_dir_path(__FILE__) . 'inc/ContextInjector.php';
71 require_once plugin_dir_path(__FILE__) . 'inc/AgenticFixes/Labels.php';
72 require_once plugin_dir_path(__FILE__) . 'inc/AgenticFixesDashboardWidget.php';
73 require_once plugin_dir_path(__FILE__) . 'inc/AgenticFixesPage.php';
74 require_once plugin_dir_path(__FILE__) . 'inc/ImageManagerPage.php';
75 require_once plugin_dir_path(__FILE__) . 'inc/SitemapDetector.php';
76 require_once plugin_dir_path(__FILE__) . 'inc/ScanTriggerPanel.php';
77 require_once plugin_dir_path(__FILE__) . 'inc/ConnectionStatusCard.php';
78 require_once plugin_dir_path(__FILE__) . 'inc/EditorMetaBox.php';
79 require_once plugin_dir_path(__FILE__) . 'inc/PostListColumn.php';
80 require_once plugin_dir_path(__FILE__) . 'inc/PostLinkBackfill.php';
81 require_once plugin_dir_path(__FILE__) . 'inc/AdminBar.php';
82 require_once plugin_dir_path(__FILE__) . 'inc/ReviewNudge.php';
83 AllAccessible_PostListColumn::register();
84 AllAccessible_PostLinkBackfill::register();
85 AllAccessible_AdminBar::register();
86 AllAccessible_ReviewNudge::register();
87
88
89 /**
90 * Load translations
91 */
92 function aacb_load_textdomain() {
93 load_plugin_textdomain('allaccessible', false, basename(dirname(__FILE__)) . '/languages/');
94 }
95 add_action('init', 'aacb_load_textdomain');
96
97 /**
98 * Plugin activation
99 */
100 function AllAccessible_Activation() {
101 $options = get_option('aacb_options');
102
103 if (!is_array($options) || !isset($options['aacb_installed']) || $options['aacb_installed'] != 1) {
104 $opt = array('aacb_installed' => 1);
105 update_option('aacb_options', $opt);
106 }
107
108 if (class_exists('AllAccessible_PostLinkBackfill')) {
109 AllAccessible_PostLinkBackfill::on_activate();
110 }
111
112 if (!get_option('aacb_accountID')) {
113 set_transient('aacb_activation_redirect', 1, 60);
114 }
115 }
116 register_activation_hook(__FILE__, 'AllAccessible_Activation');
117
118 /**
119 * One-shot redirect to the onboarding wizard right after activation.
120 */
121 function aacb_maybe_redirect_after_activation() {
122 if (!get_transient('aacb_activation_redirect')) return;
123 delete_transient('aacb_activation_redirect');
124
125 if (wp_doing_ajax() || !is_admin()) return;
126 if (isset($_GET['activate-multi'])) return;
127 if (!current_user_can('manage_options')) return;
128
129 wp_safe_redirect(admin_url('admin.php?page=allaccessible-wizard'));
130 exit;
131 }
132 add_action('admin_init', 'aacb_maybe_redirect_after_activation');
133
134 /**
135 * Plugin deactivation
136 */
137 function AllAccessible_Deactivation() {
138 // Clean up scheduled events
139 wp_clear_scheduled_hook('aacb_daily_analytics_calculation');
140 }
141 register_deactivation_hook(__FILE__, 'AllAccessible_Deactivation');
142
143 /**
144 * AJAX handler for saving account ID
145 * Used by wizard and legacy settings page
146 */
147 function AllAccessible_save_settings() {
148 // Verify capabilities
149 if (!current_user_can('manage_options')) {
150 wp_send_json_error('Unauthorized access');
151 return;
152 }
153
154 // Verify nonce (support both old and new nonce names)
155 $nonce = isset($_POST['_wpnonce']) ? sanitize_text_field($_POST['_wpnonce']) : '';
156 if (empty($nonce) || !wp_verify_nonce($nonce, 'allaccessible_save_settings')) {
157 wp_send_json_error('Invalid security token');
158 return;
159 }
160
161 // Save account ID if provided
162 if (isset($_POST['aacb_accountID'])) {
163 $account_id = sanitize_text_field($_POST['aacb_accountID']);
164 update_option('aacb_accountID', $account_id);
165
166 wp_send_json_success(array('message' => __('Account settings saved successfully', 'allaccessible')));
167 }
168
169 wp_send_json_error('No data to save');
170 }
171 add_action('wp_ajax_AllAccessible_save_settings', 'AllAccessible_save_settings');
172
173 /**
174 * AJAX handler to clear API cache
175 */
176 function aacb_clear_cache_ajax() {
177 check_ajax_referer('aacb_clear_cache', '_wpnonce');
178
179 if (!current_user_can('manage_options')) {
180 wp_send_json_error('Unauthorized');
181 }
182
183 $api_client = AllAccessible_ApiClient::get_instance();
184 $api_client->clear_cache();
185
186 wp_send_json_success();
187 }
188 add_action('wp_ajax_aacb_clear_cache', 'aacb_clear_cache_ajax');
189
190 /**
191 * AJAX handler to reset all plugin data
192 * Allows users to start fresh without deleting the plugin
193 *
194 * @since 2.0.3
195 */
196 function aacb_reset_plugin_data() {
197 check_ajax_referer('aacb_reset_plugin', '_wpnonce');
198
199 if (!current_user_can('manage_options')) {
200 wp_send_json_error(__('Unauthorized access', 'allaccessible'));
201 }
202
203 // Delete all plugin options
204 delete_option('aacb_options');
205 delete_option('aacb_installed');
206 delete_option('aacb_siteID');
207 delete_option('aacb_accountID');
208 delete_option('aacb_hide_premium_notice');
209 delete_option('aacb_version');
210 delete_option('aacb_wizard_completed');
211
212 // Clear all transients
213 delete_transient('aacb_site_options_cache');
214 delete_transient('aacb_validation_cache');
215
216 // Re-initialize with default options
217 $opt = array('aacb_installed' => 1);
218 update_option('aacb_options', $opt);
219
220 wp_send_json_success(array(
221 'message' => __('Plugin data has been reset successfully', 'allaccessible')
222 ));
223 }
224 add_action('wp_ajax_aacb_reset_plugin_data', 'aacb_reset_plugin_data');
225
226 /* =====================================================================
227 * Agentic Fixes — AJAX handlers
228 * ===================================================================== */
229
230 const AACB_MANIFEST_NONCE = 'aacb_manifest_action';
231
232 function aacb_assert_manifest_caller() {
233 check_ajax_referer(AACB_MANIFEST_NONCE, '_wpnonce');
234 if (!current_user_can('manage_options')) {
235 wp_send_json_error(__('Unauthorized', 'allaccessible'), 403);
236 }
237 }
238
239 function aacb_approve_manifest_ajax() {
240 aacb_assert_manifest_caller();
241 $manifest_id = isset($_POST['manifest_id']) ? (int) $_POST['manifest_id'] : 0;
242 $result = AllAccessible_ApiClient::get_instance()->approve_manifest($manifest_id);
243 if (is_wp_error($result)) {
244 wp_send_json_error($result->get_error_message(), 400);
245 }
246 wp_send_json_success($result);
247 }
248 add_action('wp_ajax_aacb_approve_manifest', 'aacb_approve_manifest_ajax');
249
250 function aacb_revert_manifest_ajax() {
251 aacb_assert_manifest_caller();
252 $manifest_id = isset($_POST['manifest_id']) ? (int) $_POST['manifest_id'] : 0;
253 $reason = isset($_POST['reason']) ? sanitize_text_field(wp_unslash($_POST['reason'])) : '';
254 $result = AllAccessible_ApiClient::get_instance()->revert_manifest($manifest_id, $reason);
255 if (is_wp_error($result)) {
256 wp_send_json_error($result->get_error_message(), 400);
257 }
258 wp_send_json_success($result);
259 }
260 add_action('wp_ajax_aacb_revert_manifest', 'aacb_revert_manifest_ajax');
261
262 function aacb_edit_fix_ajax() {
263 aacb_assert_manifest_caller();
264 $manifest_id = isset($_POST['manifest_id']) ? (int) $_POST['manifest_id'] : 0;
265 $fix_index = isset($_POST['fix_index']) ? (int) $_POST['fix_index'] : -1;
266 $value = isset($_POST['value']) ? wp_kses_post(wp_unslash($_POST['value'])) : '';
267 $result = AllAccessible_ApiClient::get_instance()->edit_fix($manifest_id, $fix_index, $value);
268
269 if (is_wp_error($result)) {
270 $payload = array(
271 'message' => $result->get_error_message(),
272 'wp_code' => $result->get_error_code(),
273 'server_data' => $result->get_error_data(),
274 'request' => array(
275 'manifest_id' => $manifest_id,
276 'fix_index' => $fix_index,
277 'value_len' => strlen((string) $value),
278 ),
279 );
280 wp_send_json_error($payload, 400);
281 }
282 wp_send_json_success($result);
283 }
284 add_action('wp_ajax_aacb_edit_fix', 'aacb_edit_fix_ajax');
285
286 /**
287 * Bulk approve.
288 */
289 function aacb_bulk_approve_manifests_ajax() {
290 aacb_assert_manifest_caller();
291 $site_id = isset($_POST['site_id']) ? (int) $_POST['site_id'] : 0;
292 $ids_raw = isset($_POST['manifest_ids']) ? (array) $_POST['manifest_ids'] : array();
293 $ids = array_values(array_filter(array_map('intval', $ids_raw), function($v) { return $v > 0; }));
294 $result = AllAccessible_ApiClient::get_instance()->bulk_approve_manifests($site_id, $ids);
295
296 AllAccessible_Debug::api('bulk_approve_manifests', array(
297 'site_id' => $site_id,
298 'manifest_ids' => $ids,
299 ), $result);
300
301 if (is_wp_error($result)) {
302 $payload = array(
303 'message' => $result->get_error_message(),
304 'wp_code' => $result->get_error_code(),
305 'server_data' => $result->get_error_data(),
306 'request' => array('site_id' => $site_id, 'manifest_ids' => $ids),
307 );
308 wp_send_json_error($payload, 400);
309 }
310 wp_send_json_success($result);
311 }
312 add_action('wp_ajax_aacb_bulk_approve_manifests', 'aacb_bulk_approve_manifests_ajax');
313
314 /* =====================================================================
315 * Scan trigger
316 * ===================================================================== */
317
318 const AACB_SCAN_NONCE = 'aacb_scan_action';
319
320 function aacb_assert_scan_caller() {
321 check_ajax_referer(AACB_SCAN_NONCE, '_wpnonce');
322 if (!current_user_can('manage_options')) {
323 wp_send_json_error(__('Unauthorized', 'allaccessible'), 403);
324 }
325 }
326
327 /**
328 * Return detected sitemap candidates.
329 */
330 function aacb_detect_sitemap_ajax() {
331 aacb_assert_scan_caller();
332 if (!class_exists('AllAccessible_SitemapDetector')) {
333 wp_send_json_error('Detector not loaded', 500);
334 }
335 $candidates = AllAccessible_SitemapDetector::all_candidates();
336 $primary = AllAccessible_SitemapDetector::detect(true);
337 wp_send_json_success(array(
338 'primary' => $primary,
339 'candidates' => $candidates,
340 ));
341 }
342 add_action('wp_ajax_aacb_detect_sitemap', 'aacb_detect_sitemap_ajax');
343
344 /**
345 * Start a scan.
346 */
347 function aacb_start_scan_ajax() {
348 aacb_assert_scan_caller();
349 $sitemap_url = isset($_POST['sitemap_url']) ? esc_url_raw(wp_unslash($_POST['sitemap_url'])) : '';
350 $viewport = isset($_POST['viewport']) ? sanitize_key($_POST['viewport']) : 'both';
351
352 $client = AllAccessible_ApiClient::get_instance();
353 $dispatched = $client->start_scan_workflow_async($sitemap_url, $viewport);
354
355 if (!$dispatched) {
356 $secret = $client->get_plugin_secret();
357 if (!empty($secret)) {
358 $dispatched = $client->start_scan_workflow_async($sitemap_url, $viewport);
359 }
360 }
361
362 if (!$dispatched) {
363 wp_send_json_error(__('Still finishing setup — wait a few seconds and try again.', 'allaccessible'), 503);
364 }
365
366 wp_send_json_success(array(
367 'queued' => true,
368 'message' => __('Scan queued. Results appear in 2-5 minutes.', 'allaccessible'),
369 'triggeredAt' => time(),
370 'sitemapUrl' => $sitemap_url,
371 ));
372 }
373 add_action('wp_ajax_aacb_start_scan', 'aacb_start_scan_ajax');
374
375 /**
376 * Poll scan progress.
377 */
378 function aacb_scan_status_ajax() {
379 aacb_assert_scan_caller();
380 $job_id = isset($_POST['job_id']) ? (int) $_POST['job_id'] : 0;
381 $result = AllAccessible_ApiClient::get_instance()->get_scan_status($job_id);
382 if (is_wp_error($result)) {
383 wp_send_json_error($result->get_error_message(), 400);
384 }
385 wp_send_json_success($result);
386 }
387 add_action('wp_ajax_aacb_scan_status', 'aacb_scan_status_ajax');
388
389 /**
390 * Background fetch of the plugin secret.
391 */
392 add_action('aacb_fetch_plugin_secret_event', function() {
393 if (class_exists('AllAccessible_ApiClient')) {
394 AllAccessible_ApiClient::get_instance()->fetch_plugin_secret();
395 }
396 });
397
398 /**
399 * Force-refresh.
400 */
401 function aacb_verify_connection_ajax() {
402 check_ajax_referer('aacb_verify_connection', '_wpnonce');
403 if (!current_user_can('manage_options')) {
404 wp_send_json_error(__('Unauthorized', 'allaccessible'), 403);
405 }
406 delete_transient('aacb_site_options_cache');
407 delete_transient('aacb_validation_cache');
408 delete_transient('aacb_cache_manifest_summary_v2');
409 $client = AllAccessible_ApiClient::get_instance();
410 $opts = $client->get_site_options(true);
411 if (is_wp_error($opts)) {
412 wp_send_json_error($opts->get_error_message(), 400);
413 }
414 wp_send_json_success(array('refreshed' => true));
415 }
416 add_action('wp_ajax_aacb_verify_connection', 'aacb_verify_connection_ajax');
417