Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 60
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
Jetpack_AI_Page
0.00% covered (danger)
0.00%
0 / 57
0.00% covered (danger)
0.00%
0 / 6
156
0.00% covered (danger)
0.00%
0 / 1
 get_page_hook
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 add_page_actions
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 additional_styles
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 page_admin_scripts
0.00% covered (danger)
0.00%
0 / 44
0.00% covered (danger)
0.00%
0 / 1
56
 render
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 page_render
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Jetpack AI admin page.
4 *
5 * Registers the "AI" submenu item under Jetpack and mounts the React-based
6 * MCP settings interface.
7 *
8 * @package automattic/jetpack
9 */
10
11use Automattic\Jetpack\Admin_UI\Admin_Menu;
12use Automattic\Jetpack\Connection\Manager as Connection_Manager;
13use Automattic\Jetpack\Redirect;
14use Automattic\Jetpack\Status;
15use Automattic\Jetpack\Status\Host;
16
17if ( ! defined( 'ABSPATH' ) ) {
18    exit( 0 );
19}
20
21require_once __DIR__ . '/class.jetpack-admin-page.php';
22
23/**
24 * Builds the Jetpack AI admin page and its sidebar menu entry.
25 */
26class Jetpack_AI_Page extends Jetpack_Admin_Page {
27
28    /**
29     * Hide the "AI" sidebar entry when Jetpack is not yet connected.
30     * Other Jetpack products follow the same convention.
31     *
32     * @var bool
33     */
34    protected $dont_show_if_not_active = true;
35
36    /**
37     * Register the "AI" submenu under the Jetpack top-level menu.
38     *
39     * @return string|false Hook returned by Admin_Menu::add_menu().
40     */
41    public function get_page_hook() {
42        return Admin_Menu::add_menu(
43            // "Jetpack AI" is a product name and should not be translated.
44            'Jetpack AI',
45            'AI',
46            'manage_options',
47            'jetpack-ai',
48            array( $this, 'render' ),
49            4
50        );
51    }
52
53    /**
54     * Attach page-specific actions.
55     *
56     * @param string $hook The page hook returned by get_page_hook().
57     */
58    public function add_page_actions( $hook ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
59        // Nothing extra needed beyond the common hooks in Jetpack_Admin_Page::add_actions().
60    }
61
62    /**
63     * No additional styles needed: AdminPage from @automattic/jetpack-components
64     * owns the full layout and does not need the wrap_ui admin.css / style.min.css
65     * bundle (which zeroes out #wpcontent padding and conflicts with AdminPage's
66     * margin-left compensation).
67     */
68    public function additional_styles() {}
69
70    /**
71     * Enqueue scripts and styles for the AI admin page.
72     */
73    public function page_admin_scripts() {
74        $script_path    = JETPACK__PLUGIN_DIR . '_inc/build/jetpack-ai-admin.asset.php';
75        $script_deps    = array( 'wp-element', 'wp-components', 'wp-i18n', 'wp-polyfill' );
76        $script_version = JETPACK__VERSION;
77
78        if ( file_exists( $script_path ) ) {
79            $asset_manifest = include $script_path;
80            $script_deps    = $asset_manifest['dependencies'];
81            $script_version = $asset_manifest['version'];
82        }
83
84        $blog_id     = Connection_Manager::get_site_id( true );
85        $site_suffix = ( new Status() )->get_site_suffix();
86        // Use the plain hostname for the Atomic activity log URL — get_site_suffix() can
87        // include '::' for subdirectory installs, which would break the URL. This matches
88        // the approach used by jetpack-mu-wpcom for the sidebar Activity Log link.
89        $site_host = wp_parse_url( home_url(), PHP_URL_HOST );
90        // On Atomic, jetpack-mu-wpcom replaces the cloud redirect with a direct WPCOM URL.
91        // Mirror that behaviour here so the activity log link resolves consistently.
92        $activity_log_site = ( is_string( $site_host ) && '' !== $site_host ) ? $site_host : $site_suffix;
93        $activity_log_url  = ( new Host() )->is_woa_site()
94            ? 'https://wordpress.com/activity-log/' . $activity_log_site
95            : Redirect::get_url( 'cloud-activity-log-wp-menu', array( 'site' => $blog_id ? $blog_id : $site_suffix ) );
96
97        wp_enqueue_script(
98            'jetpack-ai-admin',
99            plugins_url( '_inc/build/jetpack-ai-admin.js', JETPACK__PLUGIN_FILE ),
100            $script_deps,
101            $script_version,
102            true
103        );
104
105        wp_set_script_translations( 'jetpack-ai-admin', 'jetpack' );
106
107        wp_add_inline_script(
108            'jetpack-ai-admin',
109            'var jetpackAiSettings = ' . wp_json_encode(
110                array(
111                    'blogId'         => $blog_id ? (int) $blog_id : 0,
112                    'activityLogUrl' => $activity_log_url,
113                    'siteAdminUrl'   => admin_url(),
114                    'apiRoot'        => esc_url_raw( rest_url() ),
115                    'apiNonce'       => wp_create_nonce( 'wp_rest' ),
116                    'pluginUrl'      => plugins_url( '', JETPACK__PLUGIN_FILE ),
117                    'upgradeUrl'     => 'https://wordpress.com/plans/' . rawurlencode( wp_parse_url( home_url(), PHP_URL_HOST ) ?? '' ),
118                ),
119                JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP
120            ) . ';',
121            'before'
122        );
123
124        wp_enqueue_style(
125            'jetpack-ai-admin',
126            plugins_url( '_inc/build/jetpack-ai-admin.css', JETPACK__PLUGIN_FILE ),
127            array( 'wp-components' ),
128            $script_version
129        );
130    }
131
132    /**
133     * Override the base render() to skip wrap_ui entirely.
134     *
135     * Wrap_ui renders the Jetpack masthead header and static footer, which
136     * duplicate the header/footer that AdminPage (React) already provides.
137     * Calling page_render() directly lets AdminPage own the full layout.
138     */
139    public function render() {
140        $this->page_render();
141    }
142
143    /**
144     * Render the page container. The React app mounts into this div.
145     *
146     * AdminPage from @automattic/jetpack-components handles the full-page layout.
147     */
148    public function page_render() {
149        ?>
150        <div id="jetpack-ai-root"></div>
151        <?php
152    }
153}