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