Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
18.55% |
23 / 124 |
|
20.00% |
1 / 5 |
CRAP | n/a |
0 / 0 |
|
| wpcom_should_show_ai_assistant_banner | |
29.41% |
5 / 17 |
|
0.00% |
0 / 1 |
53.56 | |||
| wpcom_ai_assistant_banner_tracks_props | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
12 | |||
| wpcom_render_ai_assistant_banner | |
0.00% |
0 / 76 |
|
0.00% |
0 / 1 |
2 | |||
| wpcom_maybe_add_ai_assistant_banner | |
41.67% |
5 / 12 |
|
0.00% |
0 / 1 |
7.18 | |||
| wpcom_dismiss_ai_assistant_banner | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
| 1 | <?php |
| 2 | /** |
| 3 | * AI Assistant Banner for the WordPress.com dashboard. |
| 4 | * |
| 5 | * Displays a banner prompting Business/Commerce admins to enable the AI assistant. |
| 6 | * Works on both Simple and Atomic sites via cross-platform utilities. |
| 7 | * |
| 8 | * @package automattic/jetpack-mu-wpcom |
| 9 | */ |
| 10 | |
| 11 | use Automattic\Jetpack\Jetpack_Mu_Wpcom\Expiry_Notices\Expiry_Data; |
| 12 | use Automattic\Jetpack\Status\Host; |
| 13 | |
| 14 | // The audience helpers live in the AI Launchpad feature, loaded by a different |
| 15 | // loader method; require them directly so the props can never silently degrade. |
| 16 | require_once __DIR__ . '/../ai-launchpad/helpers.php'; |
| 17 | |
| 18 | /** |
| 19 | * Determines whether the AI assistant banner should be shown to the current user. |
| 20 | * |
| 21 | * @return bool |
| 22 | */ |
| 23 | function wpcom_should_show_ai_assistant_banner() { |
| 24 | if ( ! current_user_can( 'manage_options' ) ) { |
| 25 | return false; |
| 26 | } |
| 27 | |
| 28 | // Check dismissal early — most users will have dismissed over time. |
| 29 | if ( get_user_meta( get_current_user_id(), 'wpcom_ai_assistant_banner_dismissed', true ) ) { |
| 30 | return false; |
| 31 | } |
| 32 | |
| 33 | if ( ! function_exists( 'wpcom_site_has_feature' ) || ! wpcom_site_has_feature( WPCOM_Features::BIG_SKY ) ) { |
| 34 | return false; |
| 35 | } |
| 36 | |
| 37 | // Don't show on Big Sky sites. |
| 38 | if ( wpcom_has_blog_sticker( 'big-sky-enabled', get_wpcom_blog_id() ) ) { |
| 39 | return false; |
| 40 | } |
| 41 | |
| 42 | // Don't show if AI assistant is already enabled. |
| 43 | if ( class_exists( 'Big_Sky' ) && get_option( 'big_sky_enable', '1' ) ) { |
| 44 | return false; |
| 45 | } |
| 46 | |
| 47 | if ( function_exists( 'get_user_attribute' ) ) { |
| 48 | $user_id = get_current_user_id(); |
| 49 | |
| 50 | // Don't show if user has opted out of AI features. |
| 51 | if ( '1' === get_user_attribute( $user_id, 'ai_features_opted_out' ) ) { |
| 52 | return false; |
| 53 | } |
| 54 | |
| 55 | // Don't show if user has unsubscribed from AI Tips emails. |
| 56 | if ( '1' === get_user_attribute( $user_id, 'notifications_wpcom_email_ai_tips' ) ) { |
| 57 | return false; |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | return true; |
| 62 | } |
| 63 | |
| 64 | /** |
| 65 | * Tracks props shared by the banner's three events, per the AI events standard. |
| 66 | * |
| 67 | * @param string $screen_id The current admin screen id. |
| 68 | * @return array<string,string> |
| 69 | */ |
| 70 | function wpcom_ai_assistant_banner_tracks_props( $screen_id ) { |
| 71 | $purchases = function_exists( 'wpcom_expiry_get_purchases' ) ? wpcom_expiry_get_purchases() : array(); |
| 72 | // Real plan bundles only: the expiry picker's slug fallback would also match |
| 73 | // add-ons such as Professional Email Premium ("premium" substring). |
| 74 | $bundles = wp_list_filter( (array) $purchases, array( 'product_type' => 'bundle' ) ); |
| 75 | $plan = class_exists( Expiry_Data::class ) ? Expiry_Data::pick_primary_plan_purchase( $bundles ) : null; |
| 76 | $has_slug = $plan && isset( $plan->product_slug ) && is_string( $plan->product_slug ) && '' !== $plan->product_slug; |
| 77 | |
| 78 | return array( |
| 79 | 'channel' => 'web', |
| 80 | 'surface' => 'dashboard', |
| 81 | // Deliberately the WP screen id, not $pagenow — AI Launchpad's hardcoded |
| 82 | // 'admin.php' uses the other vocabulary for the same standard prop. |
| 83 | 'screen' => (string) $screen_id, |
| 84 | // Two buckets, mirroring AI Launchpad: this package ships only on Simple and |
| 85 | // Atomic (wpcomsh, mu-wpcom-plugin). Add a self-hosted branch if the banner |
| 86 | // ever moves into the Jetpack plugin. |
| 87 | 'site_type' => ( new Host() )->is_wpcom_simple() ? 'simple' : 'atomic', |
| 88 | // The AI events standard wants the string "none" over an empty value. |
| 89 | 'product_slug' => $has_slug ? $plan->product_slug : 'none', |
| 90 | 'is_test' => function_exists( 'wpcom_ai_launchpad_is_test' ) && wpcom_ai_launchpad_is_test() ? 'true' : 'false', |
| 91 | 'is_a11n' => function_exists( 'wpcom_ai_launchpad_is_a11n' ) && wpcom_ai_launchpad_is_a11n() ? 'true' : 'false', |
| 92 | ); |
| 93 | } |
| 94 | |
| 95 | /** |
| 96 | * Renders the AI assistant banner markup. |
| 97 | */ |
| 98 | function wpcom_render_ai_assistant_banner() { |
| 99 | $site_slug = wpcom_get_site_slug(); |
| 100 | $cta_url = wpcom_get_calypso_origin() . '/sites/' . $site_slug . '/settings/ai-tools'; |
| 101 | $nonce = wp_create_nonce( 'dismiss_ai_assistant_banner' ); |
| 102 | ?> |
| 103 | <div id="wpcom-ai-assistant-banner" class="notice is-dismissible" style="border-left-color: #3858E9;" data-nonce="<?php echo esc_attr( $nonce ); ?>"> |
| 104 | <div class="wpcom-ai-assistant-banner__layout"> |
| 105 | <svg width="24" height="24" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg"> |
| 106 | <path d="M391.528 188.061L309.455 159.75C276.997 148.597 251.403 123.003 240.25 90.5451L211.939 8.47185C208.079 -2.82395 191.921 -2.82395 188.061 8.47185L159.75 90.5451C148.597 123.003 123.003 148.597 90.5451 159.75L8.47185 188.061C-2.82395 191.921 -2.82395 208.079 8.47185 211.939L90.5451 240.25C123.003 251.403 148.597 276.997 159.75 309.455L188.061 391.528C191.921 402.824 208.079 402.824 211.939 391.528L240.25 309.455C251.403 276.997 276.997 251.403 309.455 240.25L391.528 211.939C402.824 208.079 402.824 191.921 391.528 188.061ZM295.728 206.077L254.692 220.232C238.391 225.809 225.666 238.677 220.089 254.835L205.934 295.871C203.932 301.591 195.925 301.591 193.923 295.871L179.768 254.835C174.191 238.534 161.323 225.809 145.165 220.232L104.129 206.077C98.4093 204.075 98.4093 196.068 104.129 194.066L145.165 179.911C161.466 174.334 174.191 161.466 179.768 145.308L193.923 104.272C195.925 98.5523 203.932 98.5523 205.934 104.272L220.089 145.308C225.666 161.609 238.534 174.334 254.692 179.911L295.728 194.066C301.448 196.068 301.448 204.075 295.728 206.077Z" fill="#3858E9"/> |
| 107 | </svg> |
| 108 | <div class="wpcom-ai-assistant-banner__body"> |
| 109 | <div class="wpcom-ai-assistant-banner__text"> |
| 110 | <strong><?php esc_html_e( 'Bring AI to your site.', 'jetpack-mu-wpcom' ); ?></strong><br> |
| 111 | <?php esc_html_e( 'Opt-in to get site-aware assistance where you write and design.', 'jetpack-mu-wpcom' ); ?> |
| 112 | </div> |
| 113 | <a href="<?php echo esc_url( $cta_url ); ?>" class="button-secondary"> |
| 114 | <?php esc_html_e( 'Enable AI assistant', 'jetpack-mu-wpcom' ); ?> |
| 115 | </a> |
| 116 | </div> |
| 117 | </div> |
| 118 | </div> |
| 119 | <style> |
| 120 | #wpcom-ai-assistant-banner .wpcom-ai-assistant-banner__layout { |
| 121 | display: flex; |
| 122 | align-items: center; |
| 123 | gap: 16px; |
| 124 | padding: 4px 0; |
| 125 | } |
| 126 | #wpcom-ai-assistant-banner .wpcom-ai-assistant-banner__layout > svg { |
| 127 | flex-shrink: 0; |
| 128 | } |
| 129 | #wpcom-ai-assistant-banner .wpcom-ai-assistant-banner__body { |
| 130 | display: flex; |
| 131 | align-items: center; |
| 132 | gap: 16px; |
| 133 | flex: 1; |
| 134 | } |
| 135 | #wpcom-ai-assistant-banner .wpcom-ai-assistant-banner__text { |
| 136 | flex: 1; |
| 137 | } |
| 138 | #wpcom-ai-assistant-banner .button-secondary { |
| 139 | white-space: nowrap; |
| 140 | } |
| 141 | #wpcom-ai-assistant-banner .notice-dismiss { |
| 142 | top: 50%; |
| 143 | transform: translateY(-50%); |
| 144 | } |
| 145 | #wpcom-ai-assistant-banner .notice-dismiss:before { |
| 146 | content: "\2715"; |
| 147 | font-family: inherit; |
| 148 | color: #787c82; |
| 149 | background: none; |
| 150 | } |
| 151 | @media ( max-width: 782px ) { |
| 152 | #wpcom-ai-assistant-banner .wpcom-ai-assistant-banner__layout { |
| 153 | align-items: flex-start; |
| 154 | gap: 8px; |
| 155 | } |
| 156 | #wpcom-ai-assistant-banner .wpcom-ai-assistant-banner__layout > svg { |
| 157 | width: 18px; |
| 158 | height: 18px; |
| 159 | margin-top: 4px; |
| 160 | } |
| 161 | /* Stack the copy and button, keeping both aligned to the text column rather than under the icon. */ |
| 162 | #wpcom-ai-assistant-banner .wpcom-ai-assistant-banner__body { |
| 163 | flex-direction: column; |
| 164 | align-items: flex-start; |
| 165 | gap: 12px; |
| 166 | } |
| 167 | #wpcom-ai-assistant-banner .wpcom-ai-assistant-banner__text { |
| 168 | font-size: 13px; |
| 169 | line-height: 1.5; |
| 170 | } |
| 171 | #wpcom-ai-assistant-banner .wpcom-ai-assistant-banner__text strong { |
| 172 | font-size: 14px; |
| 173 | } |
| 174 | /* Keep the dismiss control top-right like every other notice, lined up with the title. */ |
| 175 | #wpcom-ai-assistant-banner .notice-dismiss { |
| 176 | top: 8px; |
| 177 | transform: none; |
| 178 | } |
| 179 | } |
| 180 | </style> |
| 181 | <?php |
| 182 | } |
| 183 | |
| 184 | /** |
| 185 | * Conditionally adds the AI assistant banner on the dashboard screen. |
| 186 | */ |
| 187 | function wpcom_maybe_add_ai_assistant_banner() { |
| 188 | $screen = get_current_screen(); |
| 189 | if ( ! $screen || 'dashboard' !== $screen->id ) { |
| 190 | return; |
| 191 | } |
| 192 | |
| 193 | if ( ! wpcom_should_show_ai_assistant_banner() ) { |
| 194 | return; |
| 195 | } |
| 196 | |
| 197 | add_action( 'admin_notices', 'wpcom_render_ai_assistant_banner', 1 ); |
| 198 | |
| 199 | $asset_handle = jetpack_mu_wpcom_enqueue_assets( 'ai-assistant-banner', array( 'js' ) ); |
| 200 | wp_localize_script( |
| 201 | $asset_handle, |
| 202 | 'wpcomAiAssistantBanner', |
| 203 | array( 'trackProps' => wpcom_ai_assistant_banner_tracks_props( $screen->id ) ) |
| 204 | ); |
| 205 | } |
| 206 | add_action( 'current_screen', 'wpcom_maybe_add_ai_assistant_banner' ); |
| 207 | |
| 208 | /** |
| 209 | * AJAX handler to dismiss the AI assistant banner. |
| 210 | */ |
| 211 | function wpcom_dismiss_ai_assistant_banner() { |
| 212 | check_ajax_referer( 'dismiss_ai_assistant_banner', 'nonce' ); |
| 213 | update_user_meta( get_current_user_id(), 'wpcom_ai_assistant_banner_dismissed', '1' ); |
| 214 | wp_send_json_success( null, 200, JSON_UNESCAPED_SLASHES ); |
| 215 | } |
| 216 | add_action( 'wp_ajax_dismiss_ai_assistant_banner', 'wpcom_dismiss_ai_assistant_banner' ); |