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