Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
92.50% covered (success)
92.50%
37 / 40
33.33% covered (danger)
33.33%
1 / 3
CRAP
n/a
0 / 0
Automattic\Jetpack\Jetpack_Mu_Wpcom\Jetpack_AI_Hub\configure
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
Automattic\Jetpack\Jetpack_Mu_Wpcom\Jetpack_AI_Hub\get_plan_info
88.24% covered (warning)
88.24%
15 / 17
0.00% covered (danger)
0.00%
0 / 1
6.06
Automattic\Jetpack\Jetpack_Mu_Wpcom\Jetpack_AI_Hub\register
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
5.02
1<?php
2/**
3 * WordPress.com Simple integration for the Jetpack AI Hub.
4 *
5 * @package automattic/jetpack-mu-wpcom
6 */
7
8namespace Automattic\Jetpack\Jetpack_Mu_Wpcom\Jetpack_AI_Hub;
9
10use Automattic\Jetpack\Jetpack_Mu_Wpcom\Expiry_Notices\Expiry_Data;
11
12/**
13 * Point the upstream Hub at WordPress.com's native site-scoped MCP API and
14 * supply the Simple answers for the host-specific configuration.
15 *
16 * @param array $config Upstream AI Hub host configuration.
17 * @return array WordPress.com Simple configuration.
18 */
19function configure( $config ) {
20    $blog_id = get_current_blog_id();
21
22    // On Simple the jetpack_mu_wpcom_load_jetpack_ai_hub filter is the gate:
23    // when the Hub is deliberately loaded here, every view shows. No second
24    // predicate — turning Simple on stays exactly one filter flip.
25    $config['showGatedViews'] = true;
26    // Host-opened, not internal-gated: no "A12s only" badge on the tabs.
27    $config['gatedViewsBadge'] = false;
28    // Simple users are WordPress.com users by construction, so the usage
29    // endpoint can always proxy as the current user.
30    $config['isUserConnected'] = true;
31    // The Overview plan card — get_plan_info() says why Simple answers it.
32    $config['planInfo']       = get_plan_info();
33    $config['mcpSettingsApi'] = array(
34        'path'   => '/wpcom/v2/sites/' . $blog_id . '/mcp-abilities',
35        'format' => 'wpcom',
36    );
37
38    return $config;
39}
40
41/**
42 * Name, renewal date, and auto-renew state of the site's WordPress.com plan,
43 * for the Hub's Overview plan card.
44 *
45 * Answered from the WordPress.com store — the site's plan purchase, named
46 * via the product list — because My Jetpack's purchase lookup signs a
47 * blog-token request Simple sites cannot make. All-or-nothing like upstream:
48 * a plan that cannot be named reports the empty shape.
49 *
50 * @return array{name: string, renews_on: string, auto_renew: bool}
51 */
52function get_plan_info() {
53    $info = array(
54        'name'       => '',
55        'renews_on'  => '',
56        'auto_renew' => true,
57    );
58
59    if ( ! function_exists( 'wpcom_get_site_purchases' ) || ! class_exists( '\Store_Product_List' ) ) {
60        return $info;
61    }
62
63    // The store returns active subscriptions only; the expiry-notices picker
64    // selects the latest-expiring plan row if several are active at once.
65    $plan = Expiry_Data::pick_primary_plan_purchase( wpcom_get_site_purchases() );
66    if ( ! is_object( $plan ) ) {
67        return $info;
68    }
69
70    $name = \Store_Product_List::get_from_cache()[ (int) ( $plan->product_id ?? 0 ) ]['product_name'] ?? '';
71    if ( ! is_string( $name ) || '' === $name ) {
72        return $info;
73    }
74
75    // Raw store name; the Hub page trims the brand prefixes in one place.
76    $info['name'] = $name;
77    // Always an ISO8601 string: the store normalizer rewrites empty and
78    // '0000-00-00' (never-expiring) expiries before they reach purchases.
79    $info['renews_on'] = (string) ( $plan->expiry_date ?? '' );
80    // Absent means unknown, not off — and Simple store rows historically carry
81    // the flag as user_allows_auto_renew, with auto_renew as its alias.
82    $info['auto_renew'] = (bool) ( $plan->user_allows_auto_renew ?? $plan->auto_renew ?? true );
83
84    return $info;
85}
86
87// Scheduled tasks are a WordPress.com-backed experience and should be visible
88// whenever the AI Hub integration is loaded on Simple sites.
89add_filter( 'jetpack_feature_flag_enabled_ai-hub-scheduled-tasks', '__return_true' );
90
91/**
92 * Register the upstream AI Hub page on WordPress.com Simple.
93 */
94function register() {
95    if ( ! defined( 'JETPACK__PLUGIN_DIR' ) ) {
96        return;
97    }
98
99    $page_path = JETPACK__PLUGIN_DIR . '_inc/lib/admin-pages/class-jetpack-ai-page.php';
100    if ( ! file_exists( $page_path ) ) {
101        return;
102    }
103
104    require_once $page_path;
105    // @phan-suppress-next-line PhanUndeclaredClassReference -- loaded from the sibling Jetpack plugin above and guarded at runtime.
106    if ( ! class_exists( '\Jetpack_AI_Page' ) || ! method_exists( '\Jetpack_AI_Page', 'add_actions' ) ) {
107        return;
108    }
109
110    add_filter( 'jetpack_ai_admin_config', __NAMESPACE__ . '\\configure' );
111
112    // @phan-suppress-next-line PhanUndeclaredClassMethod -- loaded from the sibling Jetpack plugin above and guarded at runtime.
113    $page = new \Jetpack_AI_Page();
114    // @phan-suppress-next-line PhanUndeclaredClassInCallable -- loaded from the sibling Jetpack plugin above and guarded at runtime.
115    add_action( 'admin_menu', array( $page, 'add_actions' ), 998 );
116}
117
118register();