', esc_url( ACCESSMATE_URL . 'assets/accessmate-logomark-white.svg' ) ); $bar->add_node( [ 'id' => 'accessmate-scan', /* translators: Admin bar button label. */ 'title' => $icon . esc_html__( 'Scan Page', 'accessmate' ), 'href' => '#', 'meta' => [ 'class' => 'accessmate-adminbar-btn', /* translators: Admin bar button tooltip. */ 'title' => esc_attr__( 'Run AccessMate accessibility scan on this page', 'accessmate' ), ], ] ); } /** * Injects a small inline style for the admin bar node (frontend only). */ public static function enqueue_adminbar_style(): void { if ( ! current_user_can( 'manage_options' ) ) return; if ( ! is_admin_bar_showing() ) return; // Match the mobile "Scan Page" button to the visitor-facing widget's accent // colour instead of a fixed dark shade, so the two AccessMate touchpoints a // site owner sees together (widget FAB + scan button) look like one product. $widget_config = get_option( 'accessmate_widget_config', [] ); $accent = ( is_array( $widget_config ) && ! empty( $widget_config['color'] ) ) ? sanitize_hex_color( $widget_config['color'] ) : null; $accent = $accent ?: '#7c6aff'; // Both the scan button and the widget FAB independently default to the // bottom-right corner (see widget/widget.ts's POSITIONS map), so on mobile // they can land right on top of each other. When the widget is enabled and // also anchored bottom-right, sit the scan button on the same row as the // FAB (same bottom offset) and push it left by the FAB's width instead of // stacking it above — keeps both touchpoints on one line. $fab_sizes = [ 'small' => 40, 'medium' => 52, 'large' => 64 ]; $fab_size = $fab_sizes[ $widget_config['button_size'] ?? 'medium' ] ?? 52; $fab_offset_v = is_numeric( $widget_config['offset_vertical'] ?? null ) ? (int) $widget_config['offset_vertical'] : 15; $fab_offset_h = is_numeric( $widget_config['offset_horizontal'] ?? null ) ? (int) $widget_config['offset_horizontal'] : 15; $widget_shares_corner = ( ( $widget_config['enabled'] ?? true ) && ( $widget_config['position'] ?? 'bottom-right' ) === 'bottom-right' ); $scan_btn_bottom = $widget_shares_corner ? $fab_offset_v : 20; $scan_btn_right = $widget_shares_corner ? ( $fab_offset_h + $fab_size + 6 ) : 20; $css = '#wp-admin-bar-accessmate-scan > .ab-item { cursor: pointer; } #wp-admin-bar-accessmate-scan > .ab-item:hover { color: #3858e9 !important; } /* Mobile scan button — shown when WP hides the admin bar (<= 782px). The wrapper is a full-viewport fixed+overflow:hidden container so the button is always clipped to the visible viewport even when the page has horizontal overflow (which would otherwise push a plain position:fixed element off-screen). */ #accessmate-mobile-wrap { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; overflow: hidden; z-index: 99997; } #accessmate-mobile-btn { display: flex; position: absolute; bottom: ' . (int) $scan_btn_bottom . 'px; right: ' . (int) $scan_btn_right . 'px; pointer-events: auto; align-items: center; gap: 10px; background: ' . esc_attr( $accent ) . '; color: #fff; border: none; border-radius: 24px; padding: 10px 16px; font-size: 13px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; cursor: pointer; box-shadow: 0 2px 8px rgba(0,0,0,0.3); text-decoration: none; line-height: 1.4; } #accessmate-mobile-btn:hover { filter: brightness(90%); } @media screen and (max-width: 782px) { #accessmate-mobile-wrap { display: block; } }'; wp_add_inline_style( 'admin-bar', $css ); } /** * Outputs the overlay config object and loads overlay.js via wp_footer. * Only on frontend pages, only for admins, only when the bundle exists. */ public static function inject_overlay(): void { if ( ! current_user_can( 'manage_options' ) ) return; if ( is_admin() ) return; if ( ! file_exists( ACCESSMATE_PATH . 'assets/overlay.js' ) ) return; // Build the current page URL, stripping internal scan params. // // We use $_SERVER['REQUEST_URI'] (full path + query string) rather than // $wp->request (path only) so that plain-permalink URLs like ?page_id=2 are // preserved. $wp->request is empty for query-string-based URLs, which caused // every non-pretty-permalink page to be stored under the homepage key. // // We strip params with a regex on the raw string rather than remove_query_arg() // because remove_query_arg() re-encodes query values via http_build_query, // turning ?page_id=2/ into ?page_id=2%2F — a URL that no longer matches the // key produced by get_scannable_urls() (which keeps the literal slash). $raw_uri = wp_unslash( $_SERVER['REQUEST_URI'] ?? '' ); $raw_uri = preg_replace( '/([?&])(aai_scan|accessmate_scan|aai_ruleset)=[^]*/', '', $raw_uri ); $raw_uri = preg_replace( '/\?&/', '?', $raw_uri ); // orphaned ? when first param removed $raw_uri = rtrim( $raw_uri, '?&' ); // trailing ? or & with no params left $page_url = trailingslashit( home_url( $raw_uri ) ); /* PARKED: Pro upsell CTA in the overlay — hold until Pro edition public release. * Re-enable by uncommenting this block and the matching blocks in src/frontend-overlay.ts. $is_premium = AccessMate_Licensing::is_premium(); $upgrade_url = AccessMate_Licensing::upgrade_url(); */ $settings = AccessMate_Settings::get(); $config = [ 'nonce' => wp_create_nonce( 'wp_rest' ), 'restBase' => rest_url( 'accessmate/v1' ), 'pageUrl' => esc_url_raw( $page_url ), 'defaultRulesets' => $settings['default_rulesets'] ?? [ 'wcag22aa' ], 'hasProvider' => ( $settings['ai_provider'] ?? 'none' ) !== 'none', 'settingsUrl' => admin_url( 'admin.php?page=accessmate-settings' ), // 'isPremium' => $is_premium, // PARKED — see note above // 'upgradeUrl' => $upgrade_url, // PARKED — see note above ]; wp_register_script( 'accessmate-overlay', ACCESSMATE_URL . 'assets/overlay.js', [], ACCESSMATE_VERSION, true ); wp_add_inline_script( 'accessmate-overlay', 'window.accessmateOverlayConfig=' . wp_json_encode( $config ) . ';', 'before' ); wp_enqueue_script( 'accessmate-overlay' ); // Mobile floating button — wrapped in a full-viewport fixed+overflow:hidden // container so it stays within the visible viewport even on pages that have // horizontal overflow (which would otherwise push a plain position:fixed button // off-screen to the right, only reachable by horizontal scrolling). printf( '
', esc_attr__( 'Scan Page with AccessMate', 'accessmate' ), esc_url( ACCESSMATE_URL . 'assets/accessmate-logomark-white.svg' ), esc_html__( 'Scan Page', 'accessmate' ) ); } }