PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.2-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.2-a.1
16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / jetpack_vendor / automattic / jetpack-agents-manager / src / class-agents-manager.php
jetpack / jetpack_vendor / automattic / jetpack-agents-manager / src Last commit date
js 2 weeks ago class-agents-manager.php 3 days ago class-open-state-store.php 3 days ago class-sidebar-open-preservation.php 2 weeks ago class-wp-rest-agents-manager-persisted-open-state.php 1 month ago class-wp-rest-jetpack-ai-jwt.php 2 months ago
class-agents-manager.php
1085 lines
1 <?php
2 /**
3 * Agents manager
4 *
5 * @package automattic/jetpack-agents-manager
6 */
7
8 namespace Automattic\Jetpack\Agents_Manager;
9
10 use Automattic\Jetpack\Connection\Manager as Connection_Manager;
11 use Automattic\Jetpack\Constants;
12
13 /**
14 * Class Agents_Manager
15 */
16 class Agents_Manager {
17 /**
18 * The package version of the Agents Manager package.
19 *
20 * @var string
21 */
22 const PACKAGE_VERSION = '0.9.5';
23
24 /**
25 * Help Center URL for disconnected variants.
26 *
27 * @var string
28 */
29 private const HELP_CENTER_URL = 'https://wordpress.com/help?help-center=home';
30
31 /**
32 * Class instance.
33 *
34 * @var Agents_Manager
35 */
36 private static $instance = null;
37
38 /**
39 * Agents_Manager constructor.
40 */
41 private function __construct() {
42 add_action( 'rest_api_init', array( $this, 'register_rest_api' ) );
43 add_filter( 'calypso_preferences_update', array( $this, 'calypso_preferences_update' ) );
44
45 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ), 101 );
46 add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ), 101 );
47 add_action( 'next_admin_init', array( $this, 'enqueue_scripts' ), 1001 );
48 add_filter( 'agents_manager_use_unified_experience', array( $this, 'should_use_unified_experience' ) );
49
50 Sidebar_Open_Preservation::init();
51 }
52
53 /**
54 * Check if the agents manager menu panel should be displayed.
55 *
56 * @return bool True if the menu panel should be displayed.
57 */
58 public function should_display_menu_panel() {
59 return self::is_unified_experience();
60 }
61
62 /**
63 * Get the SVG icon markup for a given icon name.
64 *
65 * @param string $icon_name The name of the icon to retrieve.
66 * @return string The SVG markup.
67 */
68 private function get_icon( $icon_name ) {
69 $icons = array(
70 'comment' => '<svg class="help-center-menu-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M18 4H6c-1.1 0-2 .9-2 2v12.9c0 .6.5 1.1 1.1 1.1.3 0 .5-.1.8-.3L8.5 17H18c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm.5 11c0 .3-.2.5-.5.5H7.9l-2.4 2.4V6c0-.3.2-.5.5-.5h12c.3 0 .5.2.5.5v9z" /></svg>',
71 'backup' => '<svg class="help-center-menu-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M5.5 12h1.75l-2.5 3-2.5-3H4a8 8 0 113.134 6.35l.907-1.194A6.5 6.5 0 105.5 12zm9.53 1.97l-2.28-2.28V8.5a.75.75 0 00-1.5 0V12a.747.747 0 00.218.529l1.282-.84-1.28.842 2.5 2.5a.75.75 0 101.06-1.061z" /></svg>',
72 'page' => '<svg class="help-center-menu-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M15.5 7.5h-7V9h7V7.5Zm-7 3.5h7v1.5h-7V11Zm7 3.5h-7V16h7v-1.5Z" /><path d="M17 4H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2ZM7 5.5h10a.5.5 0 0 1 .5.5v12a.5.5 0 0 1-.5.5H7a.5.5 0 0 1-.5-.5V6a.5.5 0 0 1 .5-.5Z" /></svg>',
73 'video' => '<svg class="help-center-menu-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M18.7 3H5.3C4 3 3 4 3 5.3v13.4C3 20 4 21 5.3 21h13.4c1.3 0 2.3-1 2.3-2.3V5.3C21 4 20 3 18.7 3zm.8 15.7c0 .4-.4.8-.8.8H5.3c-.4 0-.8-.4-.8-.8V5.3c0-.4.4-.8.8-.8h13.4c.4 0 .8.4.8.8v13.4zM10 15l5-3-5-3v6z" /></svg>',
74 'rss' => '<svg class="help-center-menu-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M5 10.2h-.8v1.5H5c1.9 0 3.8.8 5.1 2.1 1.4 1.4 2.1 3.2 2.1 5.1v.8h1.5V19c0-2.3-.9-4.5-2.6-6.2-1.6-1.6-3.8-2.6-6.1-2.6zm10.4-1.6C12.6 5.8 8.9 4.2 5 4.2h-.8v1.5H5c3.5 0 6.9 1.4 9.4 3.9s3.9 5.8 3.9 9.4v.8h1.5V19c0-3.9-1.6-7.6-4.4-10.4zM4 20h3v-3H4v3z" /></svg>',
75 );
76
77 return $icons[ $icon_name ] ?? '';
78 }
79
80 /**
81 * Add the Agents Manager Help "?" node (`agents-manager`) to the admin bar, replacing the
82 * legacy Help Center node (`help-center`).
83 *
84 * @param \WP_Admin_Bar $wp_admin_bar The WP_Admin_Bar instance.
85 * @param bool $use_disconnected Disconnected variants link straight to the Help Center instead of opening the dropdown.
86 */
87 public function add_help_menu( $wp_admin_bar, $use_disconnected ) {
88 $wp_admin_bar->remove_node( 'help-center' );
89
90 $menu_args = array(
91 'id' => 'agents-manager',
92 'title' => '<span title="' . esc_attr__( 'Help Center', 'jetpack-agents-manager' ) . '"><svg id="agents-manager-icon" class="ab-icon" width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
93 <path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2zm-1 16v-2h2v2h-2zm2-3v-1.141A3.991 3.991 0 0016 10a4 4 0 00-8 0h2c0-1.103.897-2 2-2s2 .897 2 2-.897 2-2 2a1 1 0 00-1 1v2h2z" />
94 </svg></span>',
95 'parent' => 'top-secondary',
96 );
97
98 if ( $use_disconnected ) {
99 $menu_args['href'] = self::HELP_CENTER_URL;
100 $menu_args['meta'] = array(
101 'target' => '_blank',
102 'rel' => 'noopener noreferrer',
103 );
104 } else {
105 $menu_args['meta'] = array(
106 'class' => 'menupop',
107 'target' => '_blank',
108 'rel' => 'noopener noreferrer',
109 );
110 }
111
112 $wp_admin_bar->add_menu( $menu_args );
113 }
114
115 /**
116 * Add the agents manager menu panel to the admin bar.
117 *
118 * @param \WP_Admin_Bar $wp_admin_bar The WP_Admin_Bar instance.
119 */
120 public function add_menu_panel( $wp_admin_bar ) {
121 // Add chat support group
122 $wp_admin_bar->add_group(
123 array(
124 'parent' => 'agents-manager',
125 'id' => 'agents-manager-menu-panel-chat',
126 'meta' => array(
127 'class' => 'ab-sub-secondary',
128 ),
129 )
130 );
131
132 // Add chat support menu item
133 $wp_admin_bar->add_node(
134 array(
135 'parent' => 'agents-manager-menu-panel-chat',
136 'id' => 'agents-manager-chat-support',
137 'title' => $this->get_icon( 'comment' ) . '<span>' . __( 'Chat support', 'jetpack-agents-manager' ) . '</span>',
138 )
139 );
140
141 // Add chat history menu item
142 $wp_admin_bar->add_node(
143 array(
144 'parent' => 'agents-manager-menu-panel-chat',
145 'id' => 'agents-manager-chat-history',
146 'title' => $this->get_icon( 'backup' ) . '<span>' . __( 'Chat history', 'jetpack-agents-manager' ) . '</span>',
147 )
148 );
149
150 // Add links group
151 $wp_admin_bar->add_group(
152 array(
153 'parent' => 'agents-manager',
154 'id' => 'agents-manager-menu-panel-links',
155 'meta' => array(
156 'class' => 'ab-sub-secondary',
157 ),
158 )
159 );
160
161 // Add support guides menu item
162 $wp_admin_bar->add_node(
163 array(
164 'parent' => 'agents-manager-menu-panel-links',
165 'id' => 'agents-manager-support-guides',
166 'title' => $this->get_icon( 'page' ) . '<span>' . __( 'Support guides', 'jetpack-agents-manager' ) . '</span>',
167 )
168 );
169
170 // Add courses menu item
171 $wp_admin_bar->add_node(
172 array(
173 'parent' => 'agents-manager-menu-panel-links',
174 'id' => 'agents-manager-courses',
175 'title' => $this->get_icon( 'video' ) . '<span>' . __( 'Courses', 'jetpack-agents-manager' ) . '</span>',
176 'href' => 'https://wordpress.com/support/courses/',
177 'meta' => array(
178 'target' => '_blank',
179 'rel' => 'noopener noreferrer',
180 ),
181 )
182 );
183
184 // Add product updates menu item
185 $wp_admin_bar->add_node(
186 array(
187 'parent' => 'agents-manager-menu-panel-links',
188 'id' => 'agents-manager-product-updates',
189 'title' => $this->get_icon( 'rss' ) . '<span>' . __( 'Product updates', 'jetpack-agents-manager' ) . '</span>',
190 'href' => 'https://wordpress.com/blog/category/product-features/',
191 'meta' => array(
192 'target' => '_blank',
193 'rel' => 'noopener noreferrer',
194 ),
195 )
196 );
197 }
198
199 /**
200 * Add the standalone AI chat button to the admin bar.
201 *
202 * @param \WP_Admin_Bar $wp_admin_bar The WP_Admin_Bar instance.
203 */
204 public function add_ai_chat_button( $wp_admin_bar ) {
205 $wp_admin_bar->add_menu(
206 array(
207 'id' => 'agents-manager-ai-chat',
208 'parent' => 'top-secondary',
209 'title' => '<span title="' . esc_attr__( 'Ask AI', 'jetpack-agents-manager' ) . '"><svg class="ab-icon" role="img" aria-label="' . esc_attr__( 'Ask AI', 'jetpack-agents-manager' ) . '" width="24" height="24" viewBox="-45 -45 490 490" xmlns="http://www.w3.org/2000/svg">
210 <path fill="currentColor" d="M391.528 188.061L309.455 159.75C276.997 148.597 251.403 123.003 240.25 90.5451L211.939 8.47185C208.079 -2.82395 191.921 -2.82395 188.061 8.47185L159.75 90.5451C148.597 123.003 123.003 148.597 90.5451 159.75L8.47185 188.061C-2.82395 191.921 -2.82395 208.079 8.47185 211.939L90.5451 240.25C123.003 251.403 148.597 276.997 159.75 309.455L188.061 391.528C191.921 402.824 208.079 402.824 211.939 391.528L240.25 309.455C251.403 276.997 276.997 251.403 309.455 240.25L391.528 211.939C402.824 208.079 402.824 191.921 391.528 188.061ZM295.728 206.077L254.692 220.232C238.391 225.809 225.666 238.677 220.089 254.835L205.934 295.871C203.932 301.591 195.925 301.591 193.923 295.871L179.768 254.835C174.191 238.534 161.323 225.809 145.165 220.232L104.129 206.077C98.4093 204.075 98.4093 196.068 104.129 194.066L145.165 179.911C161.466 174.334 174.191 161.466 179.768 145.308L193.923 104.272C195.925 98.5523 203.932 98.5523 205.934 104.272L220.089 145.308C225.666 161.609 238.534 174.334 254.692 179.911L295.728 194.066C301.448 196.068 301.448 204.075 295.728 206.077Z" />
211 </svg></span>',
212 'meta' => array(
213 'html' => '<div id="agents-manager-masterbar"></div>',
214 ),
215 )
216 );
217 }
218
219 /**
220 * Enqueue Agents Manager scripts and add inline script data.
221 */
222 public function enqueue_scripts() {
223 // Early return for P2 frontend - don't add admin bar or enqueue scripts.
224 $stylesheet = get_stylesheet();
225 $is_p2 = str_contains( $stylesheet, 'pub/p2' ) || function_exists( '\WPForTeams\is_wpforteams_site' ) && \WPForTeams\is_wpforteams_site( get_current_blog_id() );
226
227 if ( ! is_admin() && $is_p2 ) {
228 return;
229 }
230
231 // Determine which variant to load (null = don't load).
232 $variant = self::get_active_variant();
233 if ( null === $variant ) {
234 return;
235 }
236 $use_disconnected = str_contains( $variant, 'disconnected' );
237 $is_gutenberg = $this->is_block_editor();
238 $use_unified_experience = self::is_unified_experience();
239
240 // In Gutenberg, dequeue Help Center so we don't end up with two buttons — but only
241 // in the full unified experience, where Agents Manager takes over the Help Center.
242 // In block-editor-only mode (e.g. ?flags=unified-big-sky) Agents Manager replaces
243 // Big Sky's native UI and Help Center should remain available.
244 // Agents Manager fires at priority 101, after Help Center at 100, so HC is already enqueued.
245 if ( $is_gutenberg && $use_unified_experience ) {
246 wp_dequeue_script( 'help-center' );
247 wp_dequeue_style( 'help-center-style' );
248 }
249
250 // For non-Gutenberg, non-CIAB environments, add to the admin bar. Gutenberg uses JS when
251 // no admin bar is visible and the server-side branch below when one is available. CIAB
252 // hides the admin bar and uses its own Site Hub.
253 $is_ciab = $this->is_ciab_environment();
254 if ( ! $is_gutenberg && ! $is_ciab ) {
255 // Disconnected variants are Help-only. Full variants take over Help
256 // only when the unified experience is active.
257 if ( $use_disconnected || $use_unified_experience ) {
258 add_action(
259 'admin_bar_menu',
260 function ( $wp_admin_bar ) use ( $use_disconnected ) {
261 $this->add_help_menu( $wp_admin_bar, $use_disconnected );
262 },
263 // Add the agents manager icon after Help Center so it can replace that node.
264 100
265 );
266 }
267
268 // Initialize the Help menu panel only for the full unified experience.
269 if ( ! $use_disconnected && $use_unified_experience ) {
270 add_action( 'admin_bar_menu', array( $this, 'add_menu_panel' ), 100 );
271 }
272
273 // Standalone AI chat button, shown whenever the full Agents Manager app is enabled.
274 if ( ! $use_disconnected && self::is_enabled() ) {
275 add_action( 'admin_bar_menu', array( $this, 'add_ai_chat_button' ), 100 );
276 }
277 }
278
279 // When the block editor exposes the WordPress admin bar, add the entry points there.
280 // CIAB is excluded — it has its own Site Hub UI.
281 // Mirroring wp-admin: the Help "?" dropdown shows only in the full unified experience;
282 // the Ask AI button shows whenever Agents Manager is enabled in this editor.
283 if ( ! $is_ciab && ! $use_disconnected && self::is_admin_bar_in_editor() ) {
284 // Help "?" node + dropdown panel first, matching the wp-admin admin bar order.
285 if ( $use_unified_experience ) {
286 add_action(
287 'admin_bar_menu',
288 function ( $wp_admin_bar ) {
289 $this->add_help_menu( $wp_admin_bar, false );
290 },
291 100
292 );
293 add_action( 'admin_bar_menu', array( $this, 'add_menu_panel' ), 100 );
294 }
295
296 if ( self::is_enabled() ) {
297 add_action( 'admin_bar_menu', array( $this, 'add_ai_chat_button' ), 100 );
298 }
299 }
300
301 /**
302 * Filter to register agent provider modules for the Agents Manager.
303 *
304 * Plugins can hook into this filter to register script module IDs that export
305 * toolProvider and/or contextProvider. The Agents Manager JS will dynamically
306 * import these modules and merge their providers.
307 *
308 * @param array $providers Array of provider script module IDs.
309 */
310 $agent_providers = apply_filters( 'agents_manager_agent_providers', array() );
311
312 /**
313 * Filter the default agent ID for the Agents Manager.
314 *
315 * Allows host applications (e.g., CIAB, WooCommerce AI) to specify a custom
316 * workflow agent instead of the default orchestrator. The value is passed to
317 * the frontend as `agentsManagerData.agentId` and consumed by `useAgentConfig()`.
318 *
319 * @param string|null $agent_id The agent ID to use, or null for default behavior.
320 */
321 $agent_id = apply_filters( 'agents_manager_agent_id', null );
322
323 $this->enqueue_script( $variant );
324
325 $inline_data = array(
326 'agentProviders' => $agent_providers,
327 'useUnifiedExperience' => $use_unified_experience,
328 'isDevMode' => self::is_dev_mode(),
329 'isA11n' => self::is_tracking_automattician(),
330 'isWpcomPlatform' => ( new \Automattic\Jetpack\Status\Host() )->is_wpcom_platform(),
331 'sectionName' => apply_filters( 'agents_manager_section_name', $variant ),
332 'currentUser' => $this->get_current_user_data(),
333 'site' => $this->get_current_site(),
334 'helpCenterUrl' => self::HELP_CENTER_URL,
335 );
336
337 if ( $agent_id ) {
338 $inline_data['agentId'] = $agent_id;
339 }
340
341 /**
342 * Filter the data exposed to the Agents Manager frontend.
343 *
344 * @param array $inline_data Data encoded into `agentsManagerData`.
345 */
346 $filtered = apply_filters( 'jetpack_ai_sidebar_agents_manager_data', $inline_data );
347 $inline_data = is_array( $filtered ) ? $filtered : $inline_data;
348
349 wp_add_inline_script(
350 'agents-manager',
351 'const agentsManagerData = ' . wp_json_encode(
352 $inline_data,
353 JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP
354 ) . ';',
355 'before'
356 );
357 }
358
359 /**
360 * The script variant active for this request, or null if none.
361 *
362 * Single source of truth for "is the Agents Manager app loaded on this
363 * request?". Used both to enqueue the app and to gate the server-side
364 * sidebar pre-render, so the pre-rendered shell can never appear on a page
365 * where the app won't mount to reconcile it.
366 *
367 * @return string|null The variant name, or null if scripts should not be loaded.
368 */
369 public static function get_active_variant() {
370 if ( self::is_plugin_information_iframe() ) {
371 return null;
372 }
373
374 /**
375 * Filter the script variant the Agents Manager loads for this request.
376 *
377 * @since 0.1.0
378 *
379 * @param string|null $variant The resolved variant, or null to not load.
380 */
381 return apply_filters( 'agents_manager_variant', self::get_variant() );
382 }
383
384 /**
385 * Whether the current request renders the plugin information iframe.
386 *
387 * The parent plugin screen may load Agents Manager, but the iframe must not
388 * bootstrap a second copy of the app.
389 *
390 * @return bool
391 */
392 private static function is_plugin_information_iframe() {
393 global $current_screen;
394
395 if ( ! $current_screen || 'plugin-install' !== $current_screen->id ) {
396 return false;
397 }
398
399 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is a request context check, not a form submission.
400 return isset( $_GET['tab'] ) && 'plugin-information' === sanitize_text_field( wp_unslash( $_GET['tab'] ) );
401 }
402
403 /**
404 * Determine which script variant to load, or null if none should be loaded.
405 *
406 * Combines the gating logic (should we load at all?) with variant selection
407 * (which build to use?) into a single method so the two cannot get out of sync.
408 *
409 * @return string|null The variant name, or null if scripts should not be loaded.
410 */
411 private static function get_variant() {
412 // CIAB: Load either the connected or disconnected variants if enabled.
413 if ( self::is_ciab_environment() && self::is_enabled() ) {
414 return self::is_jetpack_disconnected() ? 'ciab-disconnected' : 'ciab';
415 }
416
417 // Frontend: load disconnected variant for eligible logged-in editors.
418 if ( ! is_admin() ) {
419 if ( self::is_loading_on_frontend() && self::is_enabled() ) {
420 return 'wp-admin-disconnected';
421 }
422 return null;
423 }
424
425 // Apply wp-admin exclusions (customizer, asset, and preview contexts).
426 if ( ! self::passes_admin_checks() ) {
427 return null;
428 }
429
430 if ( ! self::is_enabled() ) {
431 return null;
432 }
433
434 $disconnected = self::is_jetpack_disconnected();
435
436 if ( self::is_block_editor() ) {
437 return $disconnected ? 'gutenberg-disconnected' : 'gutenberg';
438 }
439
440 return $disconnected ? 'wp-admin-disconnected' : 'wp-admin';
441 }
442
443 /**
444 * Whether the unified experience — the Help Center takeover — is active.
445 *
446 * "Unified" here means Agents Manager takes over the Help Center, unifying Odie and
447 * Dolly (the orchestrator) into a single chat experience. This is distinct from
448 * block-editor-only enablement, which replaces Big Sky's native UI without taking
449 * over the Help Center.
450 *
451 * @return bool
452 */
453 public static function is_unified_experience() {
454 /**
455 * Filter to determine if the user should see the unified chat experience.
456 *
457 * When true, Help Center will render UnifiedAIAgent instead of traditional UI.
458 * The filter is hooked by should_use_unified_experience() in this class.
459 *
460 * @param bool $use_unified_experience Whether to use unified experience. Default false.
461 */
462 return (bool) apply_filters( 'agents_manager_use_unified_experience', false );
463 }
464
465 /**
466 * Returns true if the Agents Manager should be loaded in the current context.
467 *
468 * @return bool
469 */
470 public static function is_enabled() {
471 $enabled = false;
472
473 // CIAB: Agents Manager is the default AI experience — enabled unless explicitly
474 // disabled via filter (e.g. for debugging or gradual rollout).
475 if ( self::is_ciab_environment() ) {
476 /**
477 * Filter whether Agents Manager is enabled in CIAB (Next Admin) environments.
478 *
479 * @param bool $enabled Whether Agents Manager should load. Default true.
480 */
481 $enabled = (bool) apply_filters( 'agents_manager_enabled_in_ciab', true );
482 } elseif ( self::is_unified_experience() ) {
483 // Full unified experience: Agents Manager with support guides, Help Center takeover, etc.
484 $enabled = true;
485 } elseif ( self::is_block_editor() && apply_filters( 'agents_manager_enabled_in_block_editor', false ) ) {
486 // Block editor only: Agents Manager replaces Big Sky's native UI. Hooked by Big Sky.
487 $enabled = true;
488 }
489
490 /**
491 * Filters whether an integration requests the Agents Manager shell on this request.
492 *
493 * Providers should preserve an existing true value so multiple integrations can
494 * request the shared shell independently.
495 *
496 * @since 0.9.1
497 *
498 * @param bool $should_load Whether another integration already requested the shell.
499 */
500 $should_load = (bool) apply_filters( 'agents_manager_should_load', false );
501
502 return $enabled || $should_load;
503 }
504
505 /**
506 * Returns true if the current wp-admin context passes all exclusion checks.
507 *
508 * Excludes customizer previews, Gutenberg asset requests, and preview query
509 * param contexts.
510 *
511 * @return bool
512 */
513 private static function passes_admin_checks() {
514 // Don't load in customizer preview iframe.
515 if ( is_customize_preview() ) {
516 return false;
517 }
518
519 // Don't load during Gutenberg asset requests or preview contexts.
520 $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
521 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is a context check, not a form submission.
522 $is_preview = isset( $_GET['preview'] ) && 'true' === sanitize_text_field( wp_unslash( $_GET['preview'] ) );
523 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is a context check, not a form submission.
524 $is_preview_overlay = isset( $_GET['preview_overlay'] );
525 if ( str_contains( $request_uri, 'wp-content/plugins/gutenberg-core' ) || $is_preview || $is_preview_overlay ) {
526 return false;
527 }
528
529 return true;
530 }
531
532 /**
533 * Enqueue Agents Manager script based on context.
534 *
535 * @param string $variant The variant of the asset file to get.
536 */
537 private function enqueue_script( $variant ) {
538 $cache_key = 'agents-manager-asset-' . $variant . '.asset.json';
539 $asset_file = get_transient( $cache_key );
540
541 if ( ! $asset_file ) {
542 $asset_file = self::get_assets_json( 'widgets.wp.com/agents-manager/agents-manager-' . $variant . '.asset.json' );
543 if ( ! $asset_file ) {
544 return;
545 }
546 set_transient( $cache_key, $asset_file, HOUR_IN_SECONDS );
547 }
548
549 // When the request is dev mode, use a random cache buster as the version for easier debugging.
550 $version = self::is_dev_mode() ? wp_rand() : $asset_file['version'];
551
552 $script_dependencies = $asset_file['dependencies'] ?? array();
553
554 // Load translations for connected variants from widgets.wp.com.
555 // Disconnected variants have no translatable UI, so skip them (as Help
556 // Center does). English needs no translation file.
557 if ( ! str_contains( $variant, 'disconnected' ) ) {
558 $locale = self::determine_iso_639_locale();
559
560 if ( 'en' !== $locale ) {
561 wp_enqueue_script(
562 'agents-manager-translations',
563 'https://widgets.wp.com/agents-manager/languages/' . $locale . '-v1.js',
564 array( 'wp-i18n' ),
565 $version,
566 true
567 );
568
569 $script_dependencies[] = 'agents-manager-translations';
570 }
571 }
572
573 wp_enqueue_script(
574 'agents-manager',
575 'https://widgets.wp.com/agents-manager/agents-manager-' . $variant . '.min.js',
576 $script_dependencies,
577 $version,
578 /**
579 * Filter the strategy to use when enqueuing the script.
580 *
581 * @param array|bool $args The arguments to pass to wp_enqueue_script. Default is true.
582 * @param string $handle The handle of the script.
583 */
584 apply_filters( 'agents_manager_enqueue_script_strategy', true, 'agents-manager' )
585 );
586
587 if ( 'gutenberg-disconnected' !== $variant && 'ciab-disconnected' !== $variant ) {
588 wp_enqueue_style(
589 'agents-manager-style',
590 'https://widgets.wp.com/agents-manager/agents-manager-' . $variant . ( is_rtl() ? '.rtl.css' : '.css' ),
591 array(),
592 $version
593 );
594 }
595 }
596
597 /**
598 * Returns the ISO 639 conforming locale string for the current user.
599 *
600 * Normalizes the WordPress user locale to match the widgets.wp.com translation
601 * file naming at languages/{code}-v1.js. Preserves the region for the few locales
602 * where it is meaningful (pt-br, zh-tw, zh-cn); strips the region for all others;
603 * falls back to 'en' when the locale is empty.
604 *
605 * @return string The ISO 639 locale string, e.g. "en".
606 */
607 private static function determine_iso_639_locale() {
608 $language = get_user_locale();
609 $language = strtolower( $language );
610
611 if ( in_array( $language, array( 'pt_br', 'pt-br', 'zh_tw', 'zh-tw', 'zh_cn', 'zh-cn' ), true ) ) {
612 $language = str_replace( '_', '-', $language );
613 } else {
614 $language = preg_replace( '/([-_].*)$/i', '', $language );
615 }
616
617 if ( empty( $language ) ) {
618 return 'en';
619 }
620
621 return $language;
622 }
623
624 /**
625 * Get the asset via file-system on wpcom and via network on Atomic sites.
626 *
627 * @param string $filepath The URL to download the asset file from.
628 * @return array|null The asset file data or null on failure.
629 */
630 private static function get_assets_json( $filepath ) {
631 $accessible_directly = file_exists( ABSPATH . $filepath );
632
633 if ( $accessible_directly ) {
634 $file_contents = file_get_contents( ABSPATH . $filepath );
635
636 if ( false === $file_contents ) {
637 return null;
638 }
639
640 return json_decode( $file_contents, true );
641 }
642
643 $request = wp_remote_get( 'https://' . $filepath );
644
645 if ( is_wp_error( $request ) ) {
646 return null;
647 }
648
649 $response_code = wp_remote_retrieve_response_code( $request );
650 if ( 200 !== $response_code ) {
651 return null;
652 }
653
654 $content_type = wp_remote_retrieve_header( $request, 'content-type' );
655 if ( is_string( $content_type ) && false === strpos( $content_type, 'json' ) ) {
656 return null;
657 }
658
659 $body = wp_remote_retrieve_body( $request );
660 if ( '' === $body ) {
661 return null;
662 }
663
664 $decoded = json_decode( $body, true );
665 if ( json_last_error() !== JSON_ERROR_NONE ) {
666 return null;
667 }
668
669 return $decoded;
670 }
671
672 /**
673 * Update the calypso preferences.
674 *
675 * @param \stdClass $preferences The preferences.
676 *
677 * @return \stdClass The preferences.
678 */
679 public function calypso_preferences_update( $preferences ) {
680 // Check if agents_manager_router_history exists and is a valid array structure
681 if ( ! isset( $preferences->agents_manager_router_history ) ||
682 ! is_array( $preferences->agents_manager_router_history ) ) {
683 return $preferences;
684 }
685
686 $router_history = $preferences->agents_manager_router_history;
687
688 // Check if entries exist and is an array
689 if ( ! isset( $router_history['entries'] ) ||
690 ! is_array( $router_history['entries'] ) ) {
691 return $preferences;
692 }
693
694 $entries = $router_history['entries'];
695
696 // Limit entries to 50 to prevent spamming entries in the router history.
697 if ( count( $entries ) > 50 ) {
698 // Keep only the last 49 entries and add the root entry at the beginning.
699 $entries = array_slice( $entries, -49 );
700 // Keep the start at root so the back button always works.
701 array_unshift(
702 $entries,
703 array(
704 'pathname' => '/',
705 'search' => '',
706 'hash' => '',
707 'key' => 'default',
708 'state' => null,
709 )
710 );
711
712 // Update the preferences object directly
713 $preferences->agents_manager_router_history['entries'] = $entries;
714 $preferences->agents_manager_router_history['index'] = 49;
715 }
716
717 return $preferences;
718 }
719
720 /**
721 * Creates instance.
722 *
723 * @return Agents_Manager
724 */
725 public static function init() {
726 if ( did_action( 'jetpack_agents_manager_initialized' ) ) {
727 return self::get_instance();
728 }
729
730 self::$instance = new self();
731
732 /**
733 * Fires once the Agents Manager class has been instantiated.
734 *
735 * @since 0.5.0
736 */
737 do_action( 'jetpack_agents_manager_initialized' );
738
739 return self::$instance;
740 }
741
742 /**
743 * Returns the instance of the Agents Manager class.
744 *
745 * @return Agents_Manager
746 */
747 public static function get_instance() {
748 return self::$instance;
749 }
750
751 /**
752 * Returns whether the current request is coming from the A8C proxy.
753 *
754 * @return bool
755 */
756 private static function is_proxied() {
757 // On Simple sites, use the wpcom function if available.
758 if ( function_exists( 'wpcom_is_proxied_request' ) ) {
759 return wpcom_is_proxied_request();
760 }
761
762 // On WoA/Garden sites, check server variable or constant.
763 return isset( $_SERVER['A8C_PROXIED_REQUEST'] )
764 ? (bool) sanitize_text_field( wp_unslash( $_SERVER['A8C_PROXIED_REQUEST'] ) )
765 : Constants::is_true( 'A8C_PROXIED_REQUEST' );
766 }
767
768 /**
769 * Returns whether the current visitor should be marked as an Automattician in tracking.
770 *
771 * @return bool
772 */
773 private static function is_tracking_automattician() {
774 $is_automattician = function_exists( 'is_automattician' ) && (bool) is_automattician();
775
776 return $is_automattician || self::is_proxied() || Constants::is_true( 'AT_PROXIED_REQUEST' );
777 }
778
779 /**
780 * Enables "Development" features that should be accessible only for admins.
781 */
782 private static function is_dev_mode() {
783 // Known local environments.
784 $domain = wp_parse_url( get_site_url(), PHP_URL_HOST );
785 if (
786 $domain === 'localhost' ||
787 '.jurassic.tube' === stristr( $domain, '.jurassic.tube' ) ||
788 '.jurassic.ninja' === stristr( $domain, '.jurassic.ninja' )
789 ) {
790 return true;
791 }
792
793 // A8C development.
794 if ( self::is_proxied() ) {
795 return true;
796 }
797
798 if ( Constants::is_true( 'AT_PROXIED_REQUEST' ) && Constants::is_defined( 'ATOMIC_CLIENT_ID' ) ) {
799 switch ( Constants::get_constant( 'ATOMIC_CLIENT_ID' ) ) {
800 case 1:
801 case 2:
802 case 3: // Pressable
803 case 32:
804 case 118: // Commerce garden client (ciab)
805 return true;
806 }
807 }
808
809 return false;
810 }
811
812 /**
813 * Register the Agents Manager endpoints.
814 */
815 public function register_rest_api() {
816 ( new WP_REST_Agents_Manager_Persisted_Open_State() )->register_rest_route();
817 ( new WP_REST_Jetpack_AI_JWT() )->register_rest_route();
818 }
819
820 /**
821 * Determine if user should see unified experience.
822 *
823 * @param bool $use_unified_experience Whether to use unified experience.
824 * @return bool
825 */
826 public function should_use_unified_experience( $use_unified_experience = false ) {
827 // Early return for non-proxied/dev mode requests.
828 // This feature is currently only available to Automattic employees testing via proxy.
829 if ( ! self::is_dev_mode() ) {
830 return false;
831 }
832
833 $user_id = get_current_user_id();
834
835 if ( ! $user_id ) {
836 return false;
837 }
838
839 $is_simple_site = ( new \Automattic\Jetpack\Status\Host() )->is_wpcom_simple();
840 if ( $is_simple_site ) {
841 // On Simple sites, evaluate locally.
842 // Check Automattician and opt-in setting.
843 $is_automattician = function_exists( '\is_automattician' ) && \is_automattician( $user_id );
844 if ( $is_automattician && $this->has_unified_chat_opt_in_enabled( $user_id ) ) {
845 return true;
846 }
847 }
848
849 // On WoA and Garden sites, delegate to wpcom via the /agents-manager/state endpoint.
850 // This avoids duplicating rollout logic and handles cases where
851 // wpcom-specific functions (like get_user_attribute) aren't available.
852 if ( $this->fetch_unified_experience_preference() ) {
853 return true;
854 }
855
856 // Default to false, for now.
857 // In the future: users with a big sky site (similar to https://github.a8c.com/Automattic/wpcom/pull/196449/files), a big-sky free trial or a paid plan.
858 return $use_unified_experience;
859 }
860
861 /**
862 * Check if user has enabled unified chat opt-in in their Automattician options.
863 *
864 * This checks the unified_ai_chat calypso preference set via the wpcom profile settings.
865 * Only used on Simple sites where get_user_attribute is available.
866 *
867 * @param int $user_id User ID.
868 *
869 * @return bool
870 */
871 private function has_unified_chat_opt_in_enabled( $user_id ) {
872 if ( ! function_exists( '\get_user_attribute' ) ) {
873 return false;
874 }
875
876 $calypso_prefs = \get_user_attribute( $user_id, 'calypso_preferences' );
877 return ! empty( $calypso_prefs['unified_ai_chat'] );
878 }
879
880 /**
881 * Fetch unified experience preference from wpcom via Jetpack Connection.
882 *
883 * Used on Atomic sites to delegate the decision to wpcom, which has
884 * access to user attributes and can evaluate the rollout logic.
885 *
886 * Calls /agents-manager/state endpoint which is accessible via Jetpack user tokens.
887 *
888 * @return bool Whether user should see unified experience.
889 */
890 private function fetch_unified_experience_preference() {
891 $user_id = get_current_user_id();
892 if ( ! $user_id ) {
893 return false;
894 }
895
896 // Check transient cache first (per-user cache).
897 $cache_key = 'unified-experience-' . $user_id;
898 $cached_result = get_transient( $cache_key );
899 if ( false !== $cached_result ) {
900 return (bool) $cached_result;
901 }
902
903 // Check if user is connected before making API call.
904 if ( ! ( new Connection_Manager() )->is_user_connected( $user_id ) ) {
905 return false;
906 }
907
908 // Call dedicated agents-manager/state endpoint.
909 $wpcom_request = \Automattic\Jetpack\Connection\Client::wpcom_json_api_request_as_user(
910 '/agents-manager/state?key=unified_ai_chat',
911 '2',
912 array( 'method' => 'GET' )
913 );
914
915 if ( is_wp_error( $wpcom_request ) ) {
916 // Cache failures too to avoid hammering the API.
917 set_transient( $cache_key, 0, MINUTE_IN_SECONDS );
918 return false;
919 }
920
921 $response_code = wp_remote_retrieve_response_code( $wpcom_request );
922 if ( 200 !== $response_code ) {
923 set_transient( $cache_key, 0, MINUTE_IN_SECONDS );
924 return false;
925 }
926
927 $body = wp_remote_retrieve_body( $wpcom_request );
928 $decoded_body = json_decode( $body, true );
929
930 // The response is { "unified_ai_chat": true/false } when using key param.
931 $result = is_array( $decoded_body ) && ! empty( $decoded_body['unified_ai_chat'] );
932
933 // Cache for 1 minute.
934 set_transient( $cache_key, $result ? 1 : 0, MINUTE_IN_SECONDS );
935
936 return $result;
937 }
938
939 /**
940 * Returns true if the current request is on the frontend and the user can edit posts.
941 *
942 * Mirrors Help_Center::is_loading_on_frontend().
943 *
944 * @return bool True if loading on the frontend for an eligible user.
945 */
946 private static function is_loading_on_frontend() {
947 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is a context check, not a form submission.
948 if ( isset( $_GET['na_site_preview'] ) || isset( $_GET['preview_overlay'] ) ) {
949 return false;
950 }
951
952 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is a context check, not a form submission.
953 if ( isset( $_GET['preview'] ) && 'true' === sanitize_text_field( wp_unslash( $_GET['preview'] ) ) ) {
954 return false;
955 }
956
957 $can_edit_posts = current_user_can( 'edit_posts' ) && is_user_member_of_blog();
958 return ! is_admin() && ! self::is_block_editor() && $can_edit_posts;
959 }
960
961 /**
962 * Returns true if the current screen is the block editor.
963 *
964 * @return bool True if the current screen is the block editor.
965 */
966 private static function is_block_editor() {
967 if ( ! function_exists( 'get_current_screen' ) ) {
968 return false;
969 }
970
971 $current_screen = get_current_screen();
972 // The widgets screen has the block editor but no Gutenberg top bar.
973 return $current_screen && $current_screen->is_block_editor() && $current_screen->id !== 'widgets';
974 }
975
976 /**
977 * Returns true when the WordPress admin bar is available in the block editor.
978 *
979 * The frontend uses the visible admin bar as its signal to move editor entry points out of the
980 * Gutenberg toolbar. Use WordPress's matching server-side signal so a classic editor admin bar
981 * receives those entry points too, not only Gutenberg's experimental omnibar.
982 *
983 * @return bool
984 */
985 private static function is_admin_bar_in_editor() {
986 return self::is_block_editor() && is_admin_bar_showing();
987 }
988
989 /**
990 * Check if current environment is CIAB (Commerce in a Box) / Next Admin.
991 *
992 * Uses the same detection method as Help Center: checks if next_admin_init has fired.
993 *
994 * @return bool True if CIAB/Next Admin environment.
995 */
996 private static function is_ciab_environment() {
997 return (bool) did_action( 'next_admin_init' );
998 }
999
1000 /**
1001 * Returns true if the current user is NOT connected through Jetpack.
1002 *
1003 * Mirrors the logic from Help_Center::is_jetpack_disconnected().
1004 *
1005 * @return bool True if the site uses Jetpack but the current user is not connected.
1006 */
1007 private static function is_jetpack_disconnected() {
1008 $user_id = get_current_user_id();
1009 $blog_id = get_current_blog_id();
1010
1011 if ( defined( 'IS_ATOMIC' ) && IS_ATOMIC ) {
1012 return ! ( new Connection_Manager( 'jetpack' ) )->is_user_connected( $user_id );
1013 }
1014
1015 if ( true === apply_filters( 'is_jetpack_site', false, $blog_id ) ) {
1016 return ! ( new Connection_Manager( 'jetpack' ) )->is_user_connected( $user_id );
1017 }
1018
1019 return false;
1020 }
1021
1022 /**
1023 * Get current user data for the agents manager.
1024 *
1025 * Mirrors the user data structure from Help Center's helpCenterData.
1026 *
1027 * @return array|null User data array or null if not logged in.
1028 */
1029 private function get_current_user_data() {
1030 $user_id = get_current_user_id();
1031 if ( ! $user_id ) {
1032 return null;
1033 }
1034
1035 $user_data = get_userdata( $user_id );
1036 if ( ! $user_data ) {
1037 return null;
1038 }
1039
1040 $user_email = $user_data->user_email;
1041
1042 // Use wpcom_get_avatar_url on Simple sites, fall back to get_avatar_url elsewhere.
1043 if ( function_exists( 'wpcom_get_avatar_url' ) ) {
1044 $avatar_url = wpcom_get_avatar_url( $user_email, 64, '', true )[0];
1045 } else {
1046 $avatar_url = get_avatar_url( $user_id );
1047 }
1048
1049 return array(
1050 'ID' => $user_id,
1051 'username' => $user_data->user_login,
1052 'display_name' => $user_data->display_name,
1053 'avatar_URL' => $avatar_url,
1054 'email' => $user_email,
1055 );
1056 }
1057
1058 /**
1059 * Get current site data for the agents manager.
1060 *
1061 * Returns minimal site data needed by AgentsManager (ID and domain only).
1062 * Uses jetpack_options['id'] on Atomic sites for the wpcom blog ID.
1063 *
1064 * @return array Site data with ID and domain.
1065 */
1066 private function get_current_site() {
1067 /*
1068 * Atomic sites have the WP.com blog ID stored as a Jetpack option.
1069 * This code deliberately doesn't use `Jetpack_Options::get_option`
1070 * so it works even when Jetpack has not been loaded.
1071 */
1072 $jetpack_options = get_option( 'jetpack_options' );
1073 if ( is_array( $jetpack_options ) && isset( $jetpack_options['id'] ) ) {
1074 $site_id = (int) $jetpack_options['id'];
1075 } else {
1076 $site_id = get_current_blog_id();
1077 }
1078
1079 return array(
1080 'ID' => $site_id,
1081 'domain' => wp_parse_url( home_url(), PHP_URL_HOST ),
1082 );
1083 }
1084 }
1085