%3$s', esc_attr( $id ), esc_attr__( 'More info', 'acceguru' ), esc_html( $text ) ); } /** @var string Hook suffix for the dashboard page. */ private string $dashboard_hook = ''; /** @var string Hook suffix for the settings page. */ private string $settings_hook = ''; /** @var string Hook suffix for the bulk alt-text page. */ private string $bulk_alt_hook = ''; /** @var string Hook suffix for the accessibility statement page. */ private string $statement_hook = ''; /** * Constructor - registers all hooks. */ public function __construct() { add_action( 'admin_menu', array( $this, 'register_menus' ) ); add_action( 'admin_init', array( $this, 'register_settings' ) ); add_action( 'admin_init', array( $this, 'maybe_create_statement_page' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_media_alt_assets' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_chrome' ) ); add_action( 'admin_init', array( $this, 'maybe_set_install_time' ) ); add_action( 'admin_init', array( $this, 'handle_review_notice_action' ) ); add_action( 'wp_dashboard_setup', array( $this, 'register_dashboard_widget' ) ); } /** * Register a dashboard widget on the WP Home screen with a brief * accessibility scan summary. * * @return void */ public function register_dashboard_widget(): void { if ( ! current_user_can( 'manage_options' ) ) { return; } wp_add_dashboard_widget( 'acceguru_dashboard_widget', esc_html__( 'AcceGuru - Accessibility Status', 'acceguru' ), array( $this, 'render_dashboard_widget' ) ); } /** * Render the dashboard widget content. * * @return void */ public function render_dashboard_widget(): void { $summary = get_option( 'acceguru_site_scan_summary', array() ); $pages_scanned = is_array( $summary ) ? (int) ( $summary['pages_scanned'] ?? 0 ) : 0; $dash_url = admin_url( 'admin.php?page=acceguru' ); if ( ! empty( $summary ) && is_array( $summary ) && ! Acceguru_Scanner::summary_is_current( $summary ) ) { echo '

' . esc_html__( 'Saved scan results use older rules or settings. Run a new full-site scan to refresh them.', 'acceguru' ) . '

'; echo '

' . esc_html__( 'Open AcceGuru Dashboard', 'acceguru' ) . '

'; return; } if ( $pages_scanned <= 0 ) { echo '

' . esc_html__( 'No site scan yet. Run your first full-site scan to see accessibility issues here.', 'acceguru' ) . '

'; echo '

' . esc_html__( 'Open AcceGuru Dashboard', 'acceguru' ) . '

'; return; } $data = $this->build_dashboard_data(); $totals = $data['totals'] ?? array(); $bpt = isset( $totals['by_post_type'] ) && is_array( $totals['by_post_type'] ) ? $totals['by_post_type'] : array(); $total_issues = (int) ( $totals['total'] ?? 0 ); $by_impact = $totals['by_impact'] ?? array(); // Same date, and the same full/cancelled rule, as the plugin dashboard // page. Every scan save writes 'scan_date', so the line always appears; // the old separate key was only ever written by a summary recompute. $last_scan_raw = isset( $summary['scan_date'] ) ? (string) $summary['scan_date'] : ''; $last_scan_ts = '' !== $last_scan_raw ? strtotime( $last_scan_raw ) : false; $last_scan = $last_scan_ts ? wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $last_scan_ts ) : $last_scan_raw; $scan_partial = ! empty( $summary['partial'] ); ?>

·

$row ) : $pt_obj = get_post_type_object( $pt ); $label = $pt_obj && ! empty( $pt_obj->labels->name ) ? $pt_obj->labels->name : $pt; ?>
()

time() ) { return false; } } $installed = (int) get_option( 'acceguru_install_time', 0 ); if ( ! $installed || ( time() - $installed ) < ( 5 * DAY_IN_SECONDS ) ) { return false; } // Require at least one site scan so we don't pester users on day 5 // who never tried the main feature. $summary = get_option( 'acceguru_site_scan_summary', array() ); if ( empty( $summary['pages_scanned'] ) ) { return false; } return true; } /** * Render the review-prompt card. Caller decides where to place it. * * @return void */ public static function render_review_notice(): void { if ( ! self::should_show_review_notice() ) { return; } $base_url = admin_url( 'admin.php?page=acceguru' ); $snooze_url = wp_nonce_url( add_query_arg( 'acceguru_review_action', 'snooze', $base_url ), 'acceguru_review_notice' ); $never_url = wp_nonce_url( add_query_arg( 'acceguru_review_action', 'dismiss', $base_url ), 'acceguru_review_notice' ); $review_url = 'https://wordpress.org/support/plugin/acceguru/reviews/'; ?>

.wp-has-submenu, #adminmenu #toplevel_page_acceguru > a.menu-top { background-color: #2FBE8B !important; color: #134B87 !important; font-weight: 700; transition: background-color .15s ease, color .15s ease; } #adminmenu #toplevel_page_acceguru > a.menu-top .wp-menu-name { color: #134B87 !important; } #adminmenu #toplevel_page_acceguru:hover > a.menu-top, #adminmenu #toplevel_page_acceguru.opensub > a.menu-top, #adminmenu #toplevel_page_acceguru.wp-has-current-submenu > a.menu-top, #adminmenu #toplevel_page_acceguru.current > a.menu-top { background-color: #134B87 !important; color: #2FBE8B !important; } #adminmenu #toplevel_page_acceguru:hover > a.menu-top .wp-menu-name, #adminmenu #toplevel_page_acceguru.opensub > a.menu-top .wp-menu-name, #adminmenu #toplevel_page_acceguru.wp-has-current-submenu > a.menu-top .wp-menu-name, #adminmenu #toplevel_page_acceguru.current > a.menu-top .wp-menu-name { color: #2FBE8B !important; } '; } // ------------------------------------------------------------------------- // Menus // ------------------------------------------------------------------------- /** * Registers top-level menu and submenus. */ public function register_menus(): void { $this->dashboard_hook = (string) add_menu_page( esc_html__( 'AcceGuru', 'acceguru' ), esc_html__( 'AcceGuru', 'acceguru' ), 'manage_options', 'acceguru', array( $this, 'render_dashboard_page' ), ACCEGURU_URL . 'assets/images/icon.png', 80 ); add_submenu_page( 'acceguru', esc_html__( 'Dashboard', 'acceguru' ), esc_html__( 'Dashboard', 'acceguru' ), 'manage_options', 'acceguru', array( $this, 'render_dashboard_page' ) ); $this->settings_hook = (string) add_submenu_page( 'acceguru', esc_html__( 'Settings', 'acceguru' ), esc_html__( 'Settings', 'acceguru' ), 'manage_options', 'acceguru-settings', array( $this, 'render_settings_page' ) ); // Bulk Alt Text and Statement now live as tabs of the Settings // screen. The render_*_page methods below remain so the // settings-page.php tab includes can call them. } // ------------------------------------------------------------------------- // Settings API // ------------------------------------------------------------------------- /** * Registers all settings sections and fields. */ public function register_settings(): void { // Per-tab page slug + option group. Separate groups are critical: // options.php zeroes any registered checkbox absent from $_POST, so a // shared group across tabs would wipe other tabs' checkboxes when the // user saves a single tab. $page = 'acceguru-settings-scanner'; $group = 'acceguru_settings_scanner'; // ----- Scanner Settings ----- add_settings_section( 'acceguru_scanner', esc_html__( 'Scanner Settings', 'acceguru' ), '__return_false', $page ); register_setting( $group, 'acceguru_wcag_level', array( 'sanitize_callback' => array( $this, 'sanitize_wcag_level' ) ) ); add_settings_field( 'acceguru_wcag_level', esc_html__( 'WCAG level to check', 'acceguru' ) . ' ' . self::help_tip( __( 'A, AA and AAA are WCAG conformance levels. AA is a common project target. AAA includes additional criteria and may not be achievable for all content. Choose the level appropriate for your site.', 'acceguru' ) ), array( $this, 'render_select' ), $page, 'acceguru_scanner', array( 'option' => 'acceguru_wcag_level', 'choices' => array( 'A' => 'A', 'AA' => 'AA', 'AAA' => 'AAA' ), ) ); register_setting( $group, 'acceguru_include_section_508', array( 'sanitize_callback' => 'absint', 'default' => 0 ) ); add_settings_field( 'acceguru_include_section_508', esc_html__( 'Include Section 508 rules', 'acceguru' ), array( $this, 'render_checkbox' ), $page, 'acceguru_scanner', array( 'option' => 'acceguru_include_section_508', 'description' => __( 'Adds axe-core rules tagged for Section 508. Enable this to include Section 508-tagged findings in scan results.', 'acceguru' ), ) ); register_setting( $group, 'acceguru_include_en_301_549', array( 'sanitize_callback' => 'absint', 'default' => 0 ) ); add_settings_field( 'acceguru_include_en_301_549', esc_html__( 'Include EN 301 549 rules', 'acceguru' ), array( $this, 'render_checkbox' ), $page, 'acceguru_scanner', array( 'option' => 'acceguru_include_en_301_549', 'description' => __( 'Adds axe-core rules tagged for EN 301 549. Enable this to include EN 301 549-tagged findings in scan results.', 'acceguru' ), ) ); register_setting( $group, 'acceguru_include_best_practice', array( 'sanitize_callback' => 'absint', 'default' => 1 ) ); register_setting( $group, 'acceguru_skip_noindex', array( 'sanitize_callback' => 'absint', 'default' => 0 ) ); register_setting( $group, 'acceguru_compat_wpml', array( 'sanitize_callback' => 'absint', 'default' => 1 ) ); add_settings_field( 'acceguru_include_best_practice', esc_html__( 'Include recommended checks', 'acceguru' ) . ' ' . self::help_tip( __( 'Adds recommended automated checks from axe-core and AcceGuru that are not formal WCAG failures. Recommended on.', 'acceguru' ) ), array( $this, 'render_checkbox' ), $page, 'acceguru_scanner', array( 'option' => 'acceguru_include_best_practice', 'default' => 1, ) ); // Post types eligible for full-site scan. Multi-checkbox of every // public post type the site has registered. register_setting( $group, 'acceguru_scan_post_types', array( 'sanitize_callback' => array( $this, 'sanitize_post_types' ), 'default' => array( 'post', 'page' ), ) ); add_settings_field( 'acceguru_scan_post_types', esc_html__( 'Post types to include in full-site scan', 'acceguru' ) . ' ' . self::help_tip( __( 'Choose which content types the site-scan walks. Defaults to Posts + Pages. On large sites with thousands of WooCommerce products you may want to leave Products off.', 'acceguru' ) ), array( $this, 'render_post_types' ), $page, 'acceguru_scanner', array( 'option' => 'acceguru_scan_post_types' ) ); register_setting( $group, 'acceguru_show_editor_sidebar', array( 'sanitize_callback' => 'absint', 'default' => 1 ) ); add_settings_field( 'acceguru_show_editor_sidebar', esc_html__( 'Show AcceGuru in Block Editor sidebar', 'acceguru' ), array( $this, 'render_checkbox' ), $page, 'acceguru_scanner', array( 'option' => 'acceguru_show_editor_sidebar', 'default' => 1, 'description' => __( 'Disable to hide the editor sidebar panel. Useful when the editor is too crowded with other plugins.', 'acceguru' ), ) ); register_setting( $group, 'acceguru_overlay_position', array( 'sanitize_callback' => array( $this, 'sanitize_overlay_position' ), 'default' => 'right' ) ); add_settings_field( 'acceguru_overlay_position', esc_html__( 'Front-end overlay position', 'acceguru' ) . ' ' . self::help_tip( __( 'Which side the AcceGuru panel docks to when you click the admin-bar button on the front end. Both options push the page content over to make room.', 'acceguru' ) ), array( $this, 'render_select' ), $page, 'acceguru_scanner', array( 'option' => 'acceguru_overlay_position', 'choices' => array( 'right' => __( 'Right (default)', 'acceguru' ), 'left' => __( 'Left', 'acceguru' ), ), ) ); register_setting( $group, 'acceguru_fab_position', array( 'sanitize_callback' => array( $this, 'sanitize_fab_position' ), 'default' => 'bottom-right' ) ); register_setting( $group, 'acceguru_skip_link_targets', array( 'sanitize_callback' => 'sanitize_text_field', 'default' => '' ) ); register_setting( $group, 'acceguru_scan_exclude_selectors', array( 'sanitize_callback' => 'sanitize_textarea_field', 'default' => '' ) ); add_settings_field( 'acceguru_skip_link_targets', esc_html__( 'Skip-link target selectors', 'acceguru' ) . ' ' . self::help_tip( __( 'Comma-separated CSS selectors the skip link will try first, before falling back to defaults (main, [role=main], #main, #content, #primary). Use when your theme\'s main content area has a non-standard ID/class.', 'acceguru' ) ), array( $this, 'render_text' ), $page, 'acceguru_scanner', array( 'option' => 'acceguru_skip_link_targets', 'placeholder' => '#site-main, .page-wrapper', ) ); // ----- Fixer Settings (own tab, four sub-sections) ----- $page = 'acceguru-settings-fixers'; $group = 'acceguru_settings_fixers'; add_settings_section( 'acceguru_fixer_structure', esc_html__( 'Page structure', 'acceguru' ), '__return_false', $page ); add_settings_section( 'acceguru_fixer_forms', esc_html__( 'Forms & search', 'acceguru' ), '__return_false', $page ); add_settings_section( 'acceguru_fixer_links', esc_html__( 'Links & buttons', 'acceguru' ), '__return_false', $page ); add_settings_section( 'acceguru_fixer_cleanup', esc_html__( 'Cleanup', 'acceguru' ), '__return_false', $page ); $fixer_fields = array( // Page structure. 'acceguru_fix_skip_links' => array( 'section' => 'acceguru_fixer_structure', 'label' => __( 'Enable skip links', 'acceguru' ), 'tip' => __( 'Adds a "Skip to main content" link as the first focusable element on every page. Helps keyboard users bypass the navigation. WCAG 2.4.1.', 'acceguru' ), ), 'acceguru_fix_focus_outlines' => array( 'section' => 'acceguru_fixer_structure', 'label' => __( 'Enable focus outlines', 'acceguru' ), 'tip' => __( 'Adds a visible focus indicator to every interactive element. Required so keyboard users can see what they have selected. WCAG 2.4.7.', 'acceguru' ), ), 'acceguru_fix_html_lang' => array( 'section' => 'acceguru_fixer_structure', 'label' => __( 'Add lang and dir attributes to ', 'acceguru' ), 'tip' => __( 'Adds based on the WordPress site language and text direction, so screen readers use the right pronunciation and right-to-left languages (Arabic, Hebrew) render correctly. WCAG 3.1.1.', 'acceguru' ), ), // Forms & search. 'acceguru_fix_search_form_label' => array( 'section' => 'acceguru_fixer_forms', 'label' => __( 'Add an accessible name to the search field', 'acceguru' ), 'tip' => __( 'Adds an aria-label to the WP search field when it has no accessible name of its own, so screen readers can announce it. Skipped when the field already has a name. WCAG 1.3.1 / 4.1.2.', 'acceguru' ), ), 'acceguru_fix_label_comment_form' => array( 'section' => 'acceguru_fixer_forms', 'label' => __( 'Add labels to the comment form', 'acceguru' ), 'tip' => __( 'Adds an accessible name to the WordPress comment fields (Name, Email, Website, Comment) when the theme leaves them without a label, so screen-reader users know what each field is for. WCAG 1.3.1 / 4.1.2.', 'acceguru' ), ), 'acceguru_fix_empty_search_results' => array( 'section' => 'acceguru_fixer_forms', 'label' => __( 'Show search results page on empty search', 'acceguru' ), 'tip' => __( 'When a search is submitted with an empty query, force WordPress to render the search results template so users see a meaningful "no results" response instead of being silently sent to the home page.', 'acceguru' ), ), // Links & buttons. 'acceguru_fix_label_new_window' => array( 'section' => 'acceguru_fixer_links', 'label' => __( 'Label links opening a new window', 'acceguru' ), 'tip' => __( 'Adds an "(opens in new tab)" screen-reader label and a small ↗ icon to every that does not already announce the new window. WCAG 3.2.5.', 'acceguru' ), ), 'acceguru_fix_label_document_links' => array( 'section' => 'acceguru_fixer_links', 'label' => __( 'Flag links to documents (PDF, Word...)', 'acceguru' ), 'tip' => __( 'Appends a short "(PDF)", "(Word)", "(Excel)" marker to links that point at non-HTML files - plus the file size for files on this site - so users know a download or different format follows. Skips links that already say so. WCAG 3.2.4 best practice.', 'acceguru' ), ), 'acceguru_fix_force_link_underline' => array( 'section' => 'acceguru_fixer_links', 'label' => __( 'Underline non-navigation links', 'acceguru' ), 'tip' => __( 'Underlines links inside content (skipping menus and button-styled links) so colour-blind and low-vision users can tell links from plain text. WCAG 1.4.1.', 'acceguru' ), ), 'acceguru_fix_autoplay_video_controls' => array( 'section' => 'acceguru_fixer_links', 'label' => __( 'Add controls to autoplay videos', 'acceguru' ), 'tip' => __( 'Forces controls="" onto every

$atts Shortcode attributes (unused). */ public static function render_statement_shortcode( $atts = array() ): string { unset( $atts ); return self::build_statement_html(); } /** * Finds a published page that already renders the statement shortcode. * * Used only when the stored page ID is missing, so a site that lost the ID * adopts its existing page instead of gaining a duplicate one. * * @return int Page ID, or 0 when there is none. */ private static function find_statement_page(): int { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- One-off lookup behind an admin button press; post content is not searchable through WP_Query. $page_id = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_type = 'page' AND post_status = 'publish' AND post_content LIKE %s ORDER BY ID ASC LIMIT 1", '%' . $wpdb->esc_like( '[acceguru_statement]' ) . '%' ) ); return null === $page_id ? 0 : (int) $page_id; } /** * Handles the "Create / update statement page" admin POST action. */ public function maybe_create_statement_page(): void { if ( ! isset( $_POST['acceguru_create_statement_page'] ) ) { return; } if ( ! current_user_can( 'manage_options' ) ) { return; } check_admin_referer( 'acceguru_statement_page' ); $existing_id = (int) get_option( 'acceguru_statement_page_id', 0 ); if ( $existing_id <= 0 || ! get_post_status( $existing_id ) ) { // No usable ID stored - adopt the page that already renders the // statement instead of publishing a second copy of it. $existing_id = self::find_statement_page(); } $post_data = array( 'post_title' => __( 'Accessibility Statement', 'acceguru' ), 'post_content' => '[acceguru_statement]', 'post_status' => 'publish', 'post_type' => 'page', ); if ( $existing_id > 0 && get_post_status( $existing_id ) ) { $post_data['ID'] = $existing_id; wp_update_post( $post_data ); $page_id = $existing_id; } else { $page_id = (int) wp_insert_post( $post_data ); } if ( $page_id > 0 ) { update_option( 'acceguru_statement_page_id', $page_id ); } wp_safe_redirect( add_query_arg( array( 'page' => 'acceguru-settings', 'tab' => 'statement', 'acceguru_saved' => '1', ), admin_url( 'admin.php' ) ) ); exit; } // ------------------------------------------------------------------------- // Asset enqueueing // ------------------------------------------------------------------------- /** * Enqueues CSS and JS only on plugin admin pages. * * @param string $hook_suffix Current page hook suffix. */ public function enqueue_assets( string $hook_suffix ): void { $plugin_hooks = array( $this->dashboard_hook, $this->settings_hook ); if ( ! in_array( $hook_suffix, $plugin_hooks, true ) ) { return; } wp_enqueue_style( 'acceguru-admin', ACCEGURU_URL . 'admin/css/acceguru-admin.css', array(), ACCEGURU_VERSION ); // New design system (Settings + Dashboard reference design). wp_enqueue_style( 'acceguru-design', ACCEGURU_URL . 'admin/css/acceguru-design.css', array( 'acceguru-admin' ), ACCEGURU_VERSION ); // DM Sans + JetBrains Mono for the new design surface, bundled with the // plugin (no external request - see assets/fonts/acceguru-fonts.css). wp_enqueue_style( 'acceguru-dmsans', ACCEGURU_URL . 'assets/fonts/acceguru-fonts.css', array(), ACCEGURU_VERSION ); wp_enqueue_script( 'acceguru-admin', ACCEGURU_URL . 'admin/js/acceguru-admin.js', array( 'wp-api-fetch', 'wp-i18n' ), ACCEGURU_VERSION, true ); wp_set_script_translations( 'acceguru-admin', 'acceguru', ACCEGURU_PATH . 'languages' ); wp_localize_script( 'acceguru-admin', 'acceguruAdmin', array( // Per-provider "is a key saved?" booleans, so the JS provider // switcher can enable/disable the Test button immediately // without a REST round-trip every time the dropdown changes. 'savedKeys' => array( 'gemini' => '' !== (string) get_option( 'acceguru_ai_api_key', '' ), 'mistral' => '' !== (string) get_option( 'acceguru_ai_api_key_mistral', '' ), 'openai' => '' !== (string) get_option( 'acceguru_ai_api_key_openai', '' ), 'anthropic' => '' !== (string) get_option( 'acceguru_ai_api_key_anthropic', '' ), ), ) ); // Tab-specific assets on the settings screen. if ( $hook_suffix === $this->settings_hook ) { $tab = isset( $_GET['tab'] ) ? sanitize_key( (string) $_GET['tab'] ) : 'scanner'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only tab check that only decides which assets to enqueue; nothing is written. if ( 'ai' === $tab && current_user_can( 'upload_files' ) ) { wp_enqueue_script( 'acceguru-bulk-alt', ACCEGURU_URL . 'admin/js/acceguru-bulk-alt.js', array( 'wp-api-fetch', 'wp-i18n' ), ACCEGURU_VERSION, true ); wp_set_script_translations( 'acceguru-bulk-alt', 'acceguru', ACCEGURU_PATH . 'languages' ); // Tell the JS whether the currently selected provider has a // key saved, so the Generate button stays disabled when there // is no chance the requests could succeed. Re-rendered on // every page load - the user just saves a key and the next // render flips the flag. $active_provider = (string) get_option( 'acceguru_ai_provider', 'gemini' ); if ( ! in_array( $active_provider, Acceguru_Alt_Text::PROVIDERS, true ) ) { $active_provider = 'gemini'; } $key_option = Acceguru_Alt_Text::api_key_option_name( $active_provider ); wp_localize_script( 'acceguru-bulk-alt', 'acceguruBulkAlt', array( 'hasApiKey' => '' !== (string) get_option( $key_option, '' ), 'provider' => $active_provider, ) ); } } if ( $hook_suffix === $this->dashboard_hook ) { wp_enqueue_script( 'acceguru-site-scan', ACCEGURU_URL . 'admin/js/acceguru-site-scan.js', array( 'wp-api-fetch', 'wp-i18n' ), ACCEGURU_VERSION, true ); wp_set_script_translations( 'acceguru-site-scan', 'acceguru', ACCEGURU_PATH . 'languages' ); // Full saved log for the "Download .txt" link. The on-screen log // collapses skipped pages into one line, but the export // must stay complete and grouped, so the download reads this full // copy rather than the collapsed DOM. $scan_log = Acceguru_Scanner::normalize_scan_log_entries( get_option( 'acceguru_last_scan_log', array() ) ); wp_localize_script( 'acceguru-site-scan', 'acceguruSiteScanLog', array_values( $scan_log ) ); // Last-scan counters so the on-screen "Scanned X - Y with issues - // Z failed - W clean" row and the "Totals:" line in the .txt // download survive the post-scan auto-reload. Always an array (may // be empty - JS treats missing/zero scanned as "do not render"). $scan_log_counters = get_option( 'acceguru_last_scan_log_counters', array() ); wp_localize_script( 'acceguru-site-scan', 'acceguruSiteScanLogCounters', is_array( $scan_log_counters ) ? $scan_log_counters : array() ); // WordPress timezone (offset in minutes east of UTC) so the // downloadable .txt log header shows the site's local time, not the // browser's. Plus the last scan's duration so the header can report // how long the scan took (the live JS timer resets on reload). $tz_offset = (int) round( wp_timezone()->getOffset( new \DateTime( 'now', new \DateTimeZone( 'UTC' ) ) ) / 60 ); $scan_summary = get_option( 'acceguru_site_scan_summary', array() ); $summary_current = Acceguru_Scanner::summary_is_current( $scan_summary ); // The site scan runs sequentially (one page at a time), so no // concurrency value is localized here. wp_localize_script( 'acceguru-site-scan', 'acceguruSiteScan', array_merge( Acceguru_Scanner::client_scan_context(), array( 'tzOffsetMin' => $tz_offset, 'durationMs' => is_array( $scan_summary ) && isset( $scan_summary['duration_ms'] ) ? (int) $scan_summary['duration_ms'] : 0, 'summaryCurrent' => $summary_current, 'summaryStale' => is_array( $scan_summary ) && ! $summary_current, 'savedScanProfileHash' => is_array( $scan_summary ) ? (string) ( $scan_summary['scan_profile_hash'] ?? '' ) : '', // Per-page deadline (ms) the site-scan controller waits before // giving up on a page. 'timeoutMs' => 30000, ) ) ); } } /** * Enqueues the Media Library "Generate with AI" button script on any * screen that can open the attachment details modal or edit an attachment. * * @param string $hook_suffix Current page hook suffix. */ public function enqueue_media_alt_assets( string $hook_suffix ): void { $screens = array( 'upload.php', 'post.php', 'post-new.php', 'site-editor.php', 'widgets.php' ); if ( ! in_array( $hook_suffix, $screens, true ) ) { return; } if ( ! current_user_can( 'upload_files' ) ) { return; } wp_enqueue_script( 'acceguru-media-alt', ACCEGURU_URL . 'admin/js/acceguru-media-alt.js', array( 'wp-api-fetch', 'wp-i18n' ), ACCEGURU_VERSION, true ); wp_set_script_translations( 'acceguru-media-alt', 'acceguru', ACCEGURU_PATH . 'languages' ); } }