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
132
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
42
 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        $activity_log_site = ( is_string( $site_host ) && '' !== $site_host ) ? $site_host : $site_suffix;
91        // On Atomic link to WPCOM activity log; on self-hosted link to the local wp-admin page.
92        $activity_log_url = ( new Host() )->is_woa_site()
93            ? 'https://wordpress.com/activity-log/' . $activity_log_site
94            : admin_url( 'admin.php?page=jetpack-activity-log' );
95
96        wp_enqueue_script(
97            'jetpack-ai-admin',
98            plugins_url( '_inc/build/jetpack-ai-admin.js', JETPACK__PLUGIN_FILE ),
99            $script_deps,
100            $script_version,
101            true
102        );
103
104        wp_set_script_translations( 'jetpack-ai-admin', 'jetpack' );
105
106        wp_add_inline_script(
107            'jetpack-ai-admin',
108            'var jetpackAiSettings = ' . wp_json_encode(
109                array(
110                    'blogId'         => $blog_id ? (int) $blog_id : 0,
111                    'activityLogUrl' => $activity_log_url,
112                    'siteAdminUrl'   => admin_url(),
113                    'apiRoot'        => esc_url_raw( rest_url() ),
114                    'apiNonce'       => wp_create_nonce( 'wp_rest' ),
115                    'pluginUrl'      => plugins_url( '', JETPACK__PLUGIN_FILE ),
116                    // Route through the Jetpack redirect service so the upgrade
117                    // destination for the MCP upsell can be retargeted without
118                    // shipping a code change.
119                    'upgradeUrl'     => Redirect::get_url( 'jetpack-ai-upgrade-url-for-jetpack-sites' ),
120                ),
121                JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP
122            ) . ';',
123            'before'
124        );
125
126        wp_enqueue_style(
127            'jetpack-ai-admin',
128            plugins_url( '_inc/build/jetpack-ai-admin.css', JETPACK__PLUGIN_FILE ),
129            array( 'wp-components' ),
130            $script_version
131        );
132    }
133
134    /**
135     * Override the base render() to skip wrap_ui entirely.
136     *
137     * Wrap_ui renders the Jetpack masthead header and static footer, which
138     * duplicate the header/footer that AdminPage (React) already provides.
139     * Calling page_render() directly lets AdminPage own the full layout.
140     */
141    public function render() {
142        $this->page_render();
143    }
144
145    /**
146     * Render the page container. The React app mounts into this div.
147     *
148     * AdminPage from @automattic/jetpack-components handles the full-page layout.
149     */
150    public function page_render() {
151        ?>
152        <div id="jetpack-ai-root"></div>
153        <?php
154    }
155}