Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
32.80% covered (danger)
32.80%
82 / 250
34.38% covered (danger)
34.38%
11 / 32
CRAP
0.00% covered (danger)
0.00%
0 / 1
Jetpack_Ai
32.66% covered (danger)
32.66%
81 / 248
34.38% covered (danger)
34.38%
11 / 32
2344.35
0.00% covered (danger)
0.00%
0 / 1
 get_plugin_slug
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_plugin_filename
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_name
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_title
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_tiers
33.33% covered (danger)
33.33%
2 / 6
0.00% covered (danger)
0.00%
0 / 1
3.19
 get_features_by_tier
3.39% covered (danger)
3.39%
2 / 59
0.00% covered (danger)
0.00%
0 / 1
18.43
 get_current_usage_tier
71.43% covered (warning)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
3.21
 get_next_usage_tier
28.57% covered (danger)
28.57%
2 / 7
0.00% covered (danger)
0.00%
0 / 1
14.11
 get_description
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_long_description_by_usage_tier
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
4.59
 get_long_description
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 get_features_by_usage_tier
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
6
 get_features
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 get_pricing_for_ui_by_usage_tier
15.38% covered (danger)
15.38%
4 / 26
0.00% covered (danger)
0.00%
0 / 1
58.07
 get_pricing_for_ui
22.22% covered (danger)
22.22%
8 / 36
0.00% covered (danger)
0.00%
0 / 1
11.53
 get_wpcom_product_slug
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_wpcom_monthly_product_slug
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_wpcom_bi_yearly_product_slug
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_paid_plan_product_slugs
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 is_upgradable
72.73% covered (warning)
72.73%
8 / 11
0.00% covered (danger)
0.00%
0 / 1
12.03
 get_post_checkout_url
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_post_activation_url
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_manage_url
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 is_plugin_installed
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 is_plugin_active
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 is_active
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 get_ai_assistant_feature
16.67% covered (danger)
16.67%
2 / 12
0.00% covered (danger)
0.00%
0 / 1
26.83
 are_tier_plans_enabled
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
4.59
 is_site_connected
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 extend_plugin_action_links
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 admin_enqueue_scripts
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
2
 add_ai_block
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
72
1<?php
2/**
3 * AI product
4 *
5 * @package my-jetpack
6 */
7
8namespace Automattic\Jetpack\My_Jetpack\Products;
9
10use Automattic\Jetpack\Connection\Manager as Connection_Manager;
11use Automattic\Jetpack\My_Jetpack\Initializer;
12use Automattic\Jetpack\My_Jetpack\Module_Product;
13use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
14use WP_Post;
15
16if ( ! defined( 'ABSPATH' ) ) {
17    exit( 0 );
18}
19
20/**
21 * Class responsible for handling the Jetpack AI product
22 */
23class Jetpack_Ai extends Module_Product {
24
25    const CURRENT_TIER_SLUG  = 'free';
26    const UPGRADED_TIER_SLUG = 'upgraded';
27
28    /**
29     * The product slug
30     *
31     * @var string
32     */
33    public static $slug = 'jetpack-ai';
34
35    /**
36     * The Jetpack module name associated with this product
37     *
38     * @var string
39     */
40    public static $module_name = 'ai';
41
42    /**
43     * The category of the product
44     *
45     * @var string
46     */
47    public static $category = 'create';
48
49    /**
50     * Whether this product has a free offering
51     *
52     * @var bool
53     */
54    public static $has_free_offering = true;
55
56    /**
57     * The feature slug that identifies the paid plan
58     *
59     * @var string
60     */
61    public static $feature_identifying_paid_plan = 'ai-assistant';
62
63    /**
64     * Get the plugin slug - ovewrite it and return Jetpack's
65     *
66     * @return ?string
67     */
68    public static function get_plugin_slug() {
69        return self::JETPACK_PLUGIN_SLUG;
70    }
71
72    /**
73     * Get the plugin filename - ovewrite it and return Jetpack's
74     *
75     * @return ?string
76     */
77    public static function get_plugin_filename() {
78        return self::JETPACK_PLUGIN_FILENAME;
79    }
80
81    /**
82     * Get the product name
83     *
84     * @return string
85     */
86    public static function get_name() {
87        return 'AI';
88    }
89
90    /**
91     * Get the product title
92     *
93     * @return string
94     */
95    public static function get_title() {
96        return 'Jetpack AI';
97    }
98
99    /**
100     * Get the product's available tiers
101     *
102     * @return string[] Slugs of the available tiers
103     */
104    public static function get_tiers() {
105        if ( ! self::are_tier_plans_enabled() ) {
106            return parent::get_tiers();
107        }
108
109        return array(
110            self::UPGRADED_TIER_SLUG,
111            self::CURRENT_TIER_SLUG,
112        );
113    }
114
115    /**
116     * Get the internationalized comparison of free vs upgraded features
117     *
118     * @return array[] Protect features comparison
119     */
120    public static function get_features_by_tier() {
121        if ( ! self::are_tier_plans_enabled() ) {
122            return parent::get_features_by_tier();
123        }
124
125        $current_tier        = self::get_current_usage_tier();
126        $current_description = 0 === $current_tier
127            ? __( 'Up to 20 requests', 'jetpack-my-jetpack' )
128            /* translators: number of requests */
129            : sprintf( __( 'Up to %d requests per month', 'jetpack-my-jetpack' ), $current_tier );
130        $next_tier        = self::get_next_usage_tier();
131        $next_description = $next_tier === null
132            ? __( 'Let\'s get in touch', 'jetpack-my-jetpack' )
133            /* translators: number of requests */
134            : sprintf( __( 'Up to %d requests per month', 'jetpack-my-jetpack' ), $next_tier );
135
136        return array(
137            array(
138                'name'  => __( 'Number of requests', 'jetpack-my-jetpack' ),
139                'info'  => array(
140                    'title'   => __( 'Requests', 'jetpack-my-jetpack' ),
141                    'content' => __( 'Increase your monthly request limit. Upgrade now and have the option to further increase your requests with additional upgrades.', 'jetpack-my-jetpack' ),
142                ),
143                'tiers' => array(
144                    self::CURRENT_TIER_SLUG  => array(
145                        'included'    => true,
146                        'description' => $current_description,
147                    ),
148                    self::UPGRADED_TIER_SLUG => array(
149                        'included'    => true,
150                        'description' => $next_description,
151                    ),
152                ),
153            ),
154            array(
155                'name'  => __( 'Generate and edit content', 'jetpack-my-jetpack' ),
156                'tiers' => array(
157                    self::CURRENT_TIER_SLUG  => array( 'included' => true ),
158                    self::UPGRADED_TIER_SLUG => array( 'included' => true ),
159                ),
160            ),
161            array(
162                'name'  => __( 'Build forms from prompts', 'jetpack-my-jetpack' ),
163                'tiers' => array(
164                    self::CURRENT_TIER_SLUG  => array( 'included' => true ),
165                    self::UPGRADED_TIER_SLUG => array( 'included' => true ),
166                ),
167            ),
168            array(
169                'name'  => __( 'Get feedback on posts', 'jetpack-my-jetpack' ),
170                'tiers' => array(
171                    self::CURRENT_TIER_SLUG  => array( 'included' => true ),
172                    self::UPGRADED_TIER_SLUG => array( 'included' => true ),
173                ),
174            ),
175            array(
176                'name'  => __( 'Generate featured images', 'jetpack-my-jetpack' ),
177                'tiers' => array(
178                    self::CURRENT_TIER_SLUG  => array( 'included' => true ),
179                    self::UPGRADED_TIER_SLUG => array( 'included' => true ),
180                ),
181            ),
182        );
183    }
184
185    /**
186     * Get the current usage tier
187     *
188     * @return int
189     */
190    public static function get_current_usage_tier() {
191        if ( ! self::is_site_connected() ) {
192            return 0;
193        }
194
195        $info = self::get_ai_assistant_feature();
196
197        // Bail early if it's not possible to fetch the feature data.
198        if ( is_wp_error( $info ) ) {
199            return 0;
200        }
201
202        $current_tier = $info['current-tier']['value'] ?? null;
203
204        return $current_tier;
205    }
206
207    /**
208     * Get the next usage tier
209     *
210     * @return int
211     */
212    public static function get_next_usage_tier() {
213        if ( ! self::is_site_connected() || ! self::has_paid_plan_for_product() ) {
214            // without site connection we can't know if tiers are enabled or not,
215            // hence we can't know if the next tier is 100 or 1 (unlimited).
216            return 100;
217        }
218
219        $info = self::get_ai_assistant_feature();
220
221        // Bail early if it's not possible to fetch the feature data or if it's included in a plan.
222        if ( is_wp_error( $info ) || empty( $info ) ) {
223            return null;
224        }
225
226        // Trust the next tier provided by the feature data.
227        $next_tier = $info['next-tier']['value'] ?? null;
228
229        return $next_tier;
230    }
231
232    /**
233     * Get the internationalized product description
234     *
235     * @return string
236     */
237    public static function get_description() {
238        return __( 'Turn your ideas into ready‑to‑publish content at lightspeed.', 'jetpack-my-jetpack' );
239    }
240
241    /**
242     * Get the internationalized usage tier long description by tier
243     *
244     * @param int|null $tier The usage tier.
245     * @return string
246     */
247    public static function get_long_description_by_usage_tier( $tier ) {
248        switch ( (int) $tier ) {
249            case 1:
250                return __( 'Jetpack AI Assistant brings the power of AI right into your WordPress editor, letting your content creation soar to new heights.', 'jetpack-my-jetpack' );
251            case 100:
252                return __( 'The most advanced AI technology Jetpack has to offer.', 'jetpack-my-jetpack' );
253            default:
254                return __( 'Upgrade and increase the amount of your available monthly requests to continue using the most advanced AI technology Jetpack has to offer.', 'jetpack-my-jetpack' );
255        }
256    }
257
258    /**
259     * Get the internationalized product long description
260     *
261     * @return string
262     */
263    public static function get_long_description() {
264        $next_tier = self::get_next_usage_tier();
265
266        return self::get_long_description_by_usage_tier( $next_tier );
267    }
268
269    /**
270     * Get the internationalized usage tier features by tier
271     *
272     * @param int $tier The usage tier.
273     * @return string
274     */
275    public static function get_features_by_usage_tier( $tier ) {
276        $is_tier_plan = $tier && intval( $tier ) > 1;
277
278        if ( $tier === 100 && ( ! self::is_site_connected() || ! self::has_paid_plan_for_product() ) ) {
279            // in these cases, get_next_usage_tier() will return 100
280            // 100 is fine as default when tiered plans are enabled, but not otherwise
281            $is_tier_plan = false;
282        }
283
284        $features = array(
285            __( 'High request capacity *', 'jetpack-my-jetpack' ),
286            __( 'Generate text, tables, lists, and forms', 'jetpack-my-jetpack' ),
287            __( 'Easily refine content to your liking', 'jetpack-my-jetpack' ),
288            __( 'Make your content easier to read', 'jetpack-my-jetpack' ),
289            __( 'Generate images with one-click', 'jetpack-my-jetpack' ),
290            __( 'Optimize your titles for better performance', 'jetpack-my-jetpack' ),
291            __( 'Priority support', 'jetpack-my-jetpack' ),
292        );
293
294        $tiered_features = array(
295            __( 'Prompt based content generation', 'jetpack-my-jetpack' ),
296            __( 'Generate text, tables, and lists', 'jetpack-my-jetpack' ),
297            __( 'Adaptive tone adjustment', 'jetpack-my-jetpack' ),
298            __( 'Superior spelling and grammar correction', 'jetpack-my-jetpack' ),
299            __( 'Title & summary generation', 'jetpack-my-jetpack' ),
300            __( 'Priority support', 'jetpack-my-jetpack' ),
301            /* translators: %d is the number of requests. */
302            sprintf( __( 'Up to %d requests per month', 'jetpack-my-jetpack' ), $tier ),
303        );
304
305        return $is_tier_plan ? $tiered_features : $features;
306    }
307
308    /**
309     * Get the internationalized features list
310     *
311     * @return array Jetpack AI features list
312     */
313    public static function get_features() {
314        $next_tier = self::get_next_usage_tier();
315
316        return self::get_features_by_usage_tier( $next_tier );
317    }
318
319    /**
320     * Get the product pricing details by tier
321     *
322     * @param int|null $tier The usage tier.
323     * @return array Pricing details
324     */
325    public static function get_pricing_for_ui_by_usage_tier( $tier ) {
326        if ( $tier === null ) {
327            return array();
328        }
329
330        $product = Wpcom_Products::get_product( static::get_wpcom_product_slug() );
331
332        if ( empty( $product ) ) {
333            return array();
334        }
335
336        $tier_plans_enabled = self::are_tier_plans_enabled();
337
338        /*
339         * when tiers are enabled and the price tier list is empty,
340         * we may need to renew the cache for the product data so
341         * we get the new price tier list.
342         *
343         * if the list is still empty after the fresh data, we will
344         * default to empty pricing (by returning an empty array).
345         */
346        if ( empty( $product->price_tier_list ) && $tier_plans_enabled ) {
347            $product = Wpcom_Products::get_product( static::get_wpcom_product_slug(), true );
348        }
349
350        // get the base pricing for the unlimited plan, for compatibility
351        $base_pricing = Wpcom_Products::get_product_pricing( static::get_wpcom_product_slug() );
352
353        $price_tier_list = $product->price_tier_list;
354        $yearly_prices   = array();
355
356        foreach ( $price_tier_list as $price_tier ) {
357            if ( isset( $price_tier->maximum_units ) && isset( $price_tier->maximum_price ) ) {
358                    // The prices are in cents
359                    $yearly_prices[ $price_tier->maximum_units ] = $price_tier->maximum_price / 100;
360            }
361        }
362
363        // add the base pricing to the list
364        $prices = array( 1 => $base_pricing );
365
366        foreach ( $yearly_prices as $units => $price ) {
367            $prices[ $units ] = array_merge(
368                $base_pricing,
369                array(
370                    'full_price'            => $price,
371                    'discount_price'        => $price,
372                    'is_introductory_offer' => false,
373                    'introductory_offer'    => null,
374                )
375            );
376        }
377
378        return $prices[ $tier ] ?? array();
379    }
380
381    /**
382     * Get the product pricing details
383     *
384     * @return array Pricing details
385     */
386    public static function get_pricing_for_ui() {
387        // no tiers
388        if ( ! self::are_tier_plans_enabled() ) {
389            return array_merge(
390                array(
391                    'available'          => true,
392                    'wpcom_product_slug' => static::get_wpcom_product_slug(),
393                ),
394                // hardcoding 1 as next tier if tiers are not enabled
395                self::get_pricing_for_ui_by_usage_tier( 1 )
396            );
397        }
398
399        $next_tier              = self::get_next_usage_tier();
400        $current_tier           = self::get_current_usage_tier();
401        $current_call_to_action = $current_tier === 0
402            ? __( 'Continue for free', 'jetpack-my-jetpack' )
403            : __( 'I\'m fine with my plan, thanks', 'jetpack-my-jetpack' );
404        $next_call_to_action    = $next_tier === null
405            ? __( 'Contact Us', 'jetpack-my-jetpack' )
406            : __( 'Upgrade', 'jetpack-my-jetpack' );
407
408        return array(
409            'tiers' => array(
410                self::CURRENT_TIER_SLUG  => array_merge(
411                    self::get_pricing_for_ui_by_usage_tier( $current_tier ),
412                    array(
413                        'available'      => true,
414                        'is_free'        => true,
415                        'call_to_action' => $current_call_to_action,
416                    )
417                ),
418                self::UPGRADED_TIER_SLUG => array_merge(
419                    self::get_pricing_for_ui_by_usage_tier( $next_tier ),
420                    array(
421                        'wpcom_product_slug' => static::get_wpcom_product_slug(),
422                        'quantity'           => $next_tier,
423                        'call_to_action'     => $next_call_to_action,
424                    )
425                ),
426            ),
427        );
428    }
429
430    /**
431     * Get the WPCOM product slug used to make the purchase
432     *
433     * @return string
434     */
435    public static function get_wpcom_product_slug() {
436        return 'jetpack_ai_yearly';
437    }
438
439    /**
440     * Get the WPCOM monthly product slug used to make the purchase
441     *
442     * @return string
443     */
444    public static function get_wpcom_monthly_product_slug() {
445        return 'jetpack_ai_monthly';
446    }
447
448    /**
449     * Get the WPCOM bi-yearly product slug used to make the purchase
450     *
451     * @return string
452     */
453    public static function get_wpcom_bi_yearly_product_slug() {
454        return 'jetpack_ai_bi_yearly';
455    }
456
457    /**
458     * Get the product-slugs of the paid plans for this product.
459     * (Do not include bundle plans, unless it's a bundle plan itself).
460     *
461     * @return array
462     */
463    public static function get_paid_plan_product_slugs() {
464        return array(
465            'jetpack_ai_yearly',
466            'jetpack_ai_monthly',
467            'jetpack_ai_bi_yearly',
468        );
469    }
470
471    /**
472     * Checks whether the product can be upgraded to a different product.
473     *
474     * @return boolean
475     */
476    public static function is_upgradable() {
477        $has_ai_feature     = static::does_site_have_feature( 'ai-assistant' );
478        $tier_plans_enabled = self::are_tier_plans_enabled();
479        $current_tier       = self::get_current_usage_tier();
480
481        if ( $has_ai_feature && ! $tier_plans_enabled && $current_tier >= 1 ) {
482            return false;
483        }
484
485        $next_tier = self::get_next_usage_tier();
486
487        // The check below is debatable, not having the feature should not flag as not upgradable.
488        // If user is free (tier = 0), not unlimited (tier = 1) and has a next tier, then it's upgradable.
489        if ( $current_tier !== null && $current_tier !== 1 && $next_tier ) {
490            return true;
491        }
492
493        // Mark as not upgradable if user is on unlimited tier or does not have any plan.
494        if ( ! $has_ai_feature || null === $current_tier || 1 === $current_tier ) {
495            return false;
496        }
497
498        return true;
499    }
500
501    /**
502     * Get the URL the user is taken after purchasing the product through the checkout
503     *
504     * @return ?string
505     */
506    public static function get_post_checkout_url() {
507        return self::get_manage_url();
508    }
509
510    /**
511     * Get the URL the user is taken after activating the product through the checkout
512     *
513     * @return ?string
514     */
515    public static function get_post_activation_url() {
516        return self::get_manage_url();
517    }
518
519    /**
520     * Get the URL where the user manages the product
521     *
522     * @return ?string
523     */
524    public static function get_manage_url() {
525        return admin_url( 'admin.php?page=my-jetpack#/jetpack-ai' );
526    }
527
528    /**
529     * Checks whether the plugin is installed
530     *
531     * @return boolean
532     */
533    public static function is_plugin_installed() {
534        return self::is_jetpack_plugin_installed();
535    }
536
537    /**
538     * Checks whether the plugin is active
539     *
540     * @return boolean
541     */
542    public static function is_plugin_active() {
543        return (bool) static::is_jetpack_plugin_active();
544    }
545
546    /**
547     * Checks whether the Product is active
548     *
549     * Overrides Module_Product::is_active() to also respect the jetpack_ai_enabled
550     * filter. The parent already checks that the Jetpack plugin and the 'ai' module
551     * are active; this override layers the host/master-off signal on top so the
552     * product card reflects it.
553     *
554     * @return boolean
555     */
556    public static function is_active() {
557        /**
558         * Filter to enable/disable Jetpack AI.
559         *
560         * @since 5.28.3
561         *
562         * @param boolean $enabled True if Jetpack AI should be enabled, false otherwise. Default true.
563         */
564        $is_enabled = apply_filters( 'jetpack_ai_enabled', true );
565
566        return $is_enabled && parent::is_active();
567    }
568
569    /**
570     * Get data about the AI Assistant feature
571     *
572     * @return array
573     */
574    public static function get_ai_assistant_feature() {
575        // Bail early if the plugin is not active.
576        if ( ! self::is_jetpack_plugin_installed() ) {
577            return array();
578        }
579
580        // Check if the global constant is defined.
581        if ( ! defined( 'JETPACK__PLUGIN_DIR' ) ) {
582            return array();
583        }
584
585        // Bail early if the site is not connected.
586        if ( ! self::is_site_connected() ) {
587            return array();
588        }
589
590        // Check if class exists. If not, try to require it once.
591        if ( ! class_exists( 'Jetpack_AI_Helper' ) ) {
592            $class_file_path = JETPACK__PLUGIN_DIR . '_inc/lib/class-jetpack-ai-helper.php';
593
594            // Check whether the file exists
595            if ( ! file_exists( $class_file_path ) ) {
596                return array();
597            }
598
599            require_once $class_file_path;
600        }
601
602        return \Jetpack_AI_Helper::get_ai_assistance_feature();
603    }
604
605    /**
606     * Get the AI Assistant tiered plans status
607     *
608     * @return boolean
609     */
610    public static function are_tier_plans_enabled() {
611        $info = self::get_ai_assistant_feature();
612        if ( is_wp_error( $info ) ) {
613            // this is another faulty default value, we'll assume disabled while
614            // production is enabled
615            return false;
616        }
617
618        if ( ! empty( $info ) && isset( $info['tier-plans-enabled'] ) ) {
619            return boolval( $info['tier-plans-enabled'] );
620        }
621        return false;
622    }
623
624    /**
625     * Checks whether the site is connected to WordPress.com.
626     *
627     * @return boolean
628     */
629    private static function is_site_connected() {
630        return ( new Connection_Manager() )->is_connected();
631    }
632
633    /**
634     * Get the URL where the user manages the product
635     *
636     * NOTE: this method is the only thing that resembles an initialization for the product.
637     *
638     * @return void
639     */
640    public static function extend_plugin_action_links() {
641        add_action( 'myjetpack_enqueue_scripts', array( static::class, 'admin_enqueue_scripts' ) );
642        add_filter( 'default_content', array( static::class, 'add_ai_block' ), 10, 2 );
643    }
644
645    /**
646     * Enqueue the AI Assistant script
647     *
648     * The script is just a global variable used for the nonce, needed for the create post link.
649     *
650     * @return void
651     */
652    public static function admin_enqueue_scripts() {
653        wp_register_script(
654            'my_jetpack_ai_app',
655            false,
656            array(),
657            Initializer::PACKAGE_VERSION,
658            array( 'in_footer' => true )
659        );
660        wp_localize_script(
661            'my_jetpack_ai_app',
662            'jetpackAi',
663            array(
664                'nonce' => wp_create_nonce( 'ai-assistant-content-nonce' ),
665            )
666        );
667        wp_enqueue_script( 'my_jetpack_ai_app' );
668    }
669
670    /**
671     * Add AI block to the post content
672     *
673     * Used only from the link on the product page, the filter will insert an AI Assistant block in the post content.
674     *
675     * @param string  $content The post content.
676     * @param WP_Post $post The post object.
677     * @return string
678     */
679    public static function add_ai_block( $content, $post ) {
680        if ( isset( $_GET['use_ai_block'] ) && isset( $_GET['_wpnonce'] )
681            && wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'ai-assistant-content-nonce' )
682            && ! empty( $post )
683            && ! is_wp_error( $post )
684            && current_user_can( 'edit_post', $post->ID )
685            && '' === $content
686        ) {
687            return '<!-- wp:jetpack/ai-assistant /-->';
688        }
689        return $content;
690    }
691}