'; } /** * Allowed-HTML map for wp_kses() when echoing the plugin's own pre-built admin * markup (section icons, help tooltips, the FAQ rail, * the by-impact table cells, the post-type quick links). Every tag and * attribute these helpers emit is listed here, so wrapping their output in * wp_kses() keeps the markup intact while making the escaping explicit at the * point of output - no phpcs:ignore needed. * * @return array> */ function acceguru_admin_allowed_html(): array { $common = array( 'class' => true, 'id' => true, 'style' => true, 'title' => true, 'role' => true, 'aria-hidden' => true, 'aria-label' => true, 'aria-describedby' => true, 'aria-expanded' => true, ); return array( 'a' => $common + array( 'href' => true, 'target' => true, 'rel' => true ), 'img' => $common + array( 'src' => true, 'alt' => true, 'width' => true, 'height' => true ), 'span' => $common, 'div' => $common + array( 'data-acceguru-feature' => true ), 'p' => $common, 'h2' => $common, 'h3' => $common, 'ul' => $common, 'li' => $common, 'button' => $common + array( 'type' => true ), 'details' => $common + array( 'open' => true ), 'summary' => $common, 'aside' => $common, ); } /** * Returns the FAQ box HTML for the right-side rail. Plain-language Q&A * about the most common scan / fixer questions. Native
/ * accordion - no JS, no dependencies. * * @return string Escaped HTML, safe to echo. */ function acceguru_faq_box(): string { $faq = array( array( 'q' => __( 'Why do some pages fail during a scan?', 'acceguru' ), 'a' => __( 'If your shared hosting is weak or overloaded, scanning a single page can take longer than 30 seconds. When that happens, the scan moves on to the next page. Click "Retry failed" or run the scan again.', 'acceguru' ), ), array( 'q' => __( 'Do I need to keep the tab open during a full-site scan?', 'acceguru' ), 'a' => __( 'Yes. The scan runs in your browser tab. Closing the tab, switching away, or clicking to another page stops it. Keep the tab open and in front until it finishes. Switching to a different browser tab also slows it down.', 'acceguru' ), ), array( 'q' => __( 'Why can\'t every problem be fixed automatically?', 'acceguru' ), 'a' => __( 'Some problems need a human to decide. A plugin can fix structural markup, but it cannot judge whether your alt text truly describes the picture, or whether a heading makes sense in context. Structural fixes are automated; judgement calls are up to you.', 'acceguru' ), ), array( 'q' => __( 'Why does another tool show a different number of issues?', 'acceguru' ), 'a' => __( 'Different tools check for different things and count differently. AcceGuru looks at your page the way a visitor sees it - after our automatic fixers have done their work - and counts one problem per rule. Other tools may check the raw page, or count every element separately. The numbers will not match, and that is normal.', 'acceguru' ), ), array( 'q' => __( 'Why does "Issues Found" not drop after I turn on fixers?', 'acceguru' ), 'a' => __( 'The fixers do not rewrite your saved content - they fix the page on the fly each time it loads. The scanner sees pages the way visitors do, so it already sees the fixed version. The number you see is what is LEFT after the fixers ran.', 'acceguru' ), ), array( 'q' => __( 'Does scanning send my data anywhere?', 'acceguru' ), 'a' => __( 'No. Accessibility scanning runs entirely in your browser, and scan results are stored only in your site database. Optional AI features may contact the selected AI provider or a remote attachment/CDN URL, as described in the AI providers and Privacy sections of the plugin readme.', 'acceguru' ), ), array( 'q' => __( 'Why do I see problems from my theme or other plugins?', 'acceguru' ), 'a' => __( 'The scan checks the whole page as it is displayed - including parts your theme and other plugins add, not just your own post content. Some of those our fixers handle automatically. For the rest you can hide the rule with "Ignore this rule", or fix it in the theme itself.', 'acceguru' ), ), ); $items = ''; foreach ( $faq as $row ) { $items .= sprintf( '
%1$s
%2$s
', esc_html( $row['q'] ), esc_html( $row['a'] ) ); } return sprintf( '

%1$s

%2$s' . '%4$s' . '
', esc_html__( 'FAQ', 'acceguru' ), $items, esc_url( 'https://docs.acce.guru' ), esc_html__( 'View All Documentation', 'acceguru' ) ); } /** * Renders the right-side rail (Dashboard + Settings): the FAQ box wrapped * in a sticky container so it stays visible during scroll on wide screens. * * @return string Escaped HTML, safe to echo. */ function acceguru_rail(): string { $inner = acceguru_faq_box(); return sprintf( '', esc_attr__( 'AcceGuru sidebar', 'acceguru' ), $inner ); } /** * Bootstraps the plugin on plugins_loaded. */ function acceguru_init(): void { // Translations: WP 4.6+ auto-loads .mo files for plugins hosted on // wordpress.org as soon as the textdomain matches the slug, so we no // longer call load_plugin_textdomain() ourselves. // On version change, clear any cached dashboard aggregate so the new // build's stale-scan filter takes effect immediately instead of waiting // for the transient to expire. $stored_version = get_option( 'acceguru_installed_version', '' ); $stored_version = is_scalar( $stored_version ) ? (string) $stored_version : ''; if ( $stored_version !== ACCEGURU_VERSION ) { delete_transient( 'acceguru_dashboard_data' ); update_option( 'acceguru_installed_version', ACCEGURU_VERSION ); } new Acceguru_Scanner(); new Acceguru_Alt_Text(); // Accessibility statement shortcode - needed in both admin (preview) and front-end. add_shortcode( 'acceguru_statement', array( 'Acceguru_Admin', 'render_statement_shortcode' ) ); if ( is_admin() ) { new Acceguru_Admin(); new Acceguru_Admin_Columns(); } else { // Front-end only: apply automated fixes conditionally per settings. new Acceguru_Fixer(); } } add_action( 'plugins_loaded', 'acceguru_init' ); /** * Adds Documentation and Rate plugin links to the plugins-list row meta. * * @param array $links Existing meta links. * @param string $plugin_file Plugin file path relative to plugins dir. * @return array */ function acceguru_plugin_row_meta( array $links, string $plugin_file ): array { if ( plugin_basename( __FILE__ ) !== $plugin_file ) { return $links; } $extra = array( '' . esc_html__( 'Documentation', 'acceguru' ) . '', '' . esc_html__( 'Support', 'acceguru' ) . '', '' . esc_html__( 'Rate plugin', 'acceguru' ) . '', ); return array_merge( $links, $extra ); } add_filter( 'plugin_row_meta', 'acceguru_plugin_row_meta', 10, 2 ); /** * Adds a "Settings" link before "Deactivate" on the Plugins screen row. * * @param array $links Existing action links. * @return array */ function acceguru_plugin_action_links( array $links ): array { $settings_url = admin_url( 'admin.php?page=acceguru-settings' ); $settings_link = '' . esc_html__( 'Settings', 'acceguru' ) . ''; array_unshift( $links, $settings_link ); return $links; } add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), 'acceguru_plugin_action_links' ); /** * Runs on plugin activation: sets default options in wp_options. */ function acceguru_activate(): void { $defaults = array( 'wcag_level' => 'AA', 'include_best_practice' => 1, 'fix_skip_links' => 1, 'fix_focus_outlines' => 1, 'fix_html_lang' => 1, // Low-impact fixes - safe to enable out of the box. They add // screen-reader labels, strip harmful attributes, or make small // non-design changes (an empty-search-results message, dropping // redundant title tooltips); they do not restyle the normal page. 'fix_search_form_label' => 1, 'fix_label_comment_form' => 1, 'fix_empty_search_results' => 1, 'fix_remove_positive_tabindex' => 1, 'fix_remove_redundant_title' => 1, 'fix_viewport_scalable' => 1, // Visible fixes - deliberately OFF by default because they change // the site's appearance (new-window links get a visible "(opens in // new tab)" marker with an arrow, underlined links, player controls // over background videos, visible "(PDF)" labels). Owners opt in. 'fix_label_new_window' => 0, 'fix_force_link_underline' => 0, 'fix_autoplay_video_controls' => 0, 'fix_label_document_links' => 0, 'ai_provider' => 'openai', 'ai_model_openai' => 'gpt-4o-mini', 'ai_max_alt_length' => 125, 'ai_auto_alt' => 0, ); foreach ( $defaults as $key => $value ) { if ( false === get_option( 'acceguru_' . $key ) ) { add_option( 'acceguru_' . $key, $value ); } } } register_activation_hook( __FILE__, 'acceguru_activate' ); /** * Runs on plugin deactivation: clears all plugin transients. */ function acceguru_deactivate(): void { global $wpdb; // One-shot bulk transient cleanup on deactivation; nothing to cache here. $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- One-shot bulk transient cleanup on deactivation; core has no delete-by-prefix API and caching is moot here. $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s", $wpdb->esc_like( '_transient_acceguru_' ) . '%', $wpdb->esc_like( '_transient_timeout_acceguru_' ) . '%' ) ); } register_deactivation_hook( __FILE__, 'acceguru_deactivate' );