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 / _inc / lib / class-jetpack-ai-helper.php
jetpack / _inc / lib Last commit date
admin-pages 3 days ago core-api 3 days ago debugger 1 month ago markdown 8 months ago class-jetpack-ai-helper.php 3 days ago class-jetpack-ai-settings.php 3 days ago class-jetpack-application-password-extras.php 5 months ago class-jetpack-blog-stats-helper.php 8 months ago class-jetpack-currencies.php 2 years ago class-jetpack-instagram-gallery-helper.php 2 months ago class-jetpack-mapbox-helper.php 3 years ago class-jetpack-newsletter-category-helper.php 8 months ago class-jetpack-podcast-feed-locator.php 3 days ago class-jetpack-podcast-helper.php 3 days ago class-jetpack-recommendations.php 2 months ago class-jetpack-spinner.php 3 months ago class-jetpack-top-posts-helper.php 3 days ago class.color.php 2 years ago class.core-rest-api-endpoints.php 3 days ago class.jetpack-automatic-install-skin.php 2 years ago class.jetpack-iframe-embed.php 3 years ago class.jetpack-password-checker.php 8 months ago class.jetpack-search-performance-logger.php 4 years ago class.media-extractor.php 3 months ago class.media-summary.php 1 month ago class.media.php 8 months ago components.php 1 month ago debugger.php 2 months ago icalendar-reader.php 2 months ago markdown.php 8 months ago plans.php 4 years ago plugins.php 4 years ago tonesque.php 8 months ago widgets.php 4 months ago
class-jetpack-ai-helper.php
530 lines
1 <?php
2 /**
3 * API helper for the AI blocks.
4 *
5 * @package automattic/jetpack
6 * @since 11.8
7 */
8
9 use Automattic\Jetpack\Connection\Client;
10 use Automattic\Jetpack\Connection\Manager;
11 use Automattic\Jetpack\Search\Plan as Search_Plan;
12 use Automattic\Jetpack\Status;
13 use Automattic\Jetpack\Status\Visitor;
14
15 // Required directly rather than relying on the plugin bootstrap: on
16 // WordPress.com Simple this helper is loaded outside load-jetpack.php
17 // (see the GUIDELINES_BANNER_DISMISSED_META_KEY note below).
18 require_once __DIR__ . '/class-jetpack-ai-settings.php';
19
20 /**
21 * Class Jetpack_AI_Helper
22 *
23 * @since 11.8
24 */
25 class Jetpack_AI_Helper {
26 /**
27 * User meta key storing whether the user dismissed the Content Guidelines
28 * AI empty-state banner. Written by the guidelines-banner-dismissed REST
29 * endpoint and read by the Content Guidelines admin-page preload.
30 *
31 * Lives here rather than on the endpoint class because on WordPress.com
32 * Simple the wpcom-endpoints classes are only loaded in REST requests,
33 * while the preload needs the key during admin page loads.
34 *
35 * @since 16.1
36 *
37 * @var string
38 */
39 const GUIDELINES_BANNER_DISMISSED_META_KEY = 'jetpack_content_guidelines_ai_banner_dismissed';
40
41 /**
42 * Allow new completion every X seconds. Will return cached result otherwise.
43 *
44 * @var int
45 */
46 public static $text_completion_cooldown_seconds = 15;
47
48 /**
49 * Cache images for a prompt for a month.
50 *
51 * @var int
52 */
53 public static $image_generation_cache_timeout = MONTH_IN_SECONDS;
54
55 /**
56 * Cache AI-assistant feature for 60 seconds.
57 *
58 * @var int
59 */
60 public static $ai_assistant_feature_cache_timeout = 60;
61
62 /**
63 * Cache AI-assistant errors for ten seconds.
64 *
65 * @var int
66 */
67 public static $ai_assistant_feature_error_cache_timeout = 10;
68
69 /**
70 * Stores the number of JetpackAI calls in case we want to mark AI-assisted posts some way.
71 *
72 * @var int
73 */
74 public static $post_meta_with_ai_generation_number = '_jetpack_ai_calls';
75
76 /**
77 * Storing the error to prevent repeated requests to WPCOM after failure.
78 *
79 * @var null|WP_Error
80 */
81 private static $ai_assistant_failed_request = null;
82
83 /**
84 * Checks if a given request is allowed to get AI data from WordPress.com.
85 *
86 * @param WP_REST_Request $request Full details about the request.
87 *
88 * @return true|WP_Error True if the request has access, WP_Error object otherwise.
89 */
90 public static function get_status_permission_check( $request ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
91
92 /*
93 * This may need to be updated
94 * to take into account the different ways we can make requests
95 * (from a WordPress.com site, from a Jetpack site).
96 */
97 if ( ! current_user_can( 'edit_posts' ) ) {
98 return new WP_Error(
99 'rest_forbidden',
100 __( 'Sorry, you are not allowed to access Jetpack AI help on this site.', 'jetpack' ),
101 array( 'status' => rest_authorization_required_code() )
102 );
103 }
104
105 return true;
106 }
107
108 /**
109 * Return true if these features should be active on the current site.
110 * Currently, it's limited to WPCOM Simple and Atomic.
111 */
112 public static function is_enabled() {
113 $default = false;
114
115 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
116 $default = true;
117 } elseif ( ( new Automattic\Jetpack\Status\Host() )->is_woa_site() ) {
118 $default = true;
119 }
120
121 // The jetpack_ai_enabled filter runs inside the helper; the host and
122 // master gates apply after the chain and cannot be filtered back on.
123 return Jetpack_AI_Settings::is_ai_enabled( $default );
124 }
125
126 /**
127 * Return true if the Content Guidelines AI surfaces should be active on the
128 * current site.
129 *
130 * Same platforms as is_enabled() (WPCOM Simple and Atomic), plus WordPress
131 * VIP sites. Kept separate from is_enabled() so widening Content Guidelines
132 * to VIP does not also open the general AI proxy endpoint there.
133 *
134 * @since 16.2
135 *
136 * @return bool
137 */
138 public static function is_enabled_for_content_guidelines() {
139 $default = false;
140
141 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
142 $default = true;
143 } else {
144 $host = new Automattic\Jetpack\Status\Host();
145 if ( $host->is_woa_site() || $host->is_vip_site() ) {
146 $default = true;
147 }
148 }
149
150 // The jetpack_ai_enabled filter runs inside the helper; the host and
151 // master gates apply after the chain and cannot be filtered back on.
152 return Jetpack_AI_Settings::is_ai_enabled( $default );
153 }
154
155 /**
156 * Whether the current user has dismissed the Content Guidelines AI
157 * empty-state banner.
158 *
159 * @since 16.1
160 *
161 * @return bool
162 */
163 public static function is_guidelines_banner_dismissed() {
164 return (bool) get_user_meta( get_current_user_id(), self::GUIDELINES_BANNER_DISMISSED_META_KEY, true );
165 }
166
167 /**
168 * Return true if the AI chat feature should be active on the current site.
169 *
170 * @todo IS_WPCOM (the endpoints need to be updated too).
171 *
172 * @return bool
173 */
174 public static function is_ai_chat_enabled() {
175 $default = false;
176
177 $connection = new Manager();
178 $plan = new Search_Plan();
179 if ( $connection->is_connected() && $plan->supports_search() ) {
180 $default = true;
181 }
182
183 /**
184 * Filter whether the AI chat feature is enabled in the Jetpack plugin.
185 *
186 * @since 12.6
187 *
188 * @param bool $default Is AI chat enabled? Defaults to false.
189 */
190 return apply_filters( 'jetpack_ai_chat_enabled', $default );
191 }
192
193 /**
194 * Get the name of the transient for image generation. Unique per prompt and allows for reuse of results for the same prompt across entire WPCOM.
195 * I expext "puppy" to always be from cache.
196 *
197 * @param string $prompt - Supplied prompt.
198 */
199 public static function transient_name_for_image_generation( $prompt ) {
200 return 'jetpack_openai_image_' . md5( $prompt );
201 }
202
203 /**
204 * Get the name of the transient for text completion. Unique per user, but not per text. Serves more as a cooldown.
205 */
206 public static function transient_name_for_completion() {
207 return 'jetpack_openai_completion_' . get_current_user_id(); // Cache for each user, so that other users dont get weird cached version from somebody else.
208 }
209
210 /**
211 * Get the name of the transient for AI assistance feature. Unique per user.
212 *
213 * @param int $blog_id - Blog ID to get the transient name for.
214 * @return string
215 */
216 public static function transient_name_for_ai_assistance_feature( $blog_id ) {
217 return 'jetpack_openai_ai_assistance_feature_' . $blog_id;
218 }
219
220 /**
221 * Mark the edited post as "touched" by AI stuff.
222 *
223 * @param int $post_id Post ID for which the content is being generated.
224 * @return void
225 */
226 private static function mark_post_as_ai_assisted( $post_id ) {
227 if ( ! $post_id ) {
228 return;
229 }
230 $previous = get_post_meta( $post_id, self::$post_meta_with_ai_generation_number, true );
231 if ( ! $previous ) {
232 $previous = 0;
233 } elseif ( ! is_numeric( $previous ) ) {
234 // Data corrupted, nothing to do.
235 return;
236 }
237 $new_value = intval( $previous ) + 1;
238 update_post_meta( $post_id, self::$post_meta_with_ai_generation_number, $new_value );
239 }
240
241 /**
242 * Get text back from WordPress.com based off a starting text.
243 *
244 * @param string $content The content provided to send to the AI.
245 * @param int $post_id Post ID for which the content is being generated.
246 * @param bool $skip_cache Skip cache and force a new request.
247 * @return mixed
248 */
249 public static function get_gpt_completion( $content, $post_id, $skip_cache = false ) {
250 $content = wp_strip_all_tags( $content );
251 $cache = get_transient( self::transient_name_for_completion() );
252 if ( $cache && ! $skip_cache ) {
253 return $cache;
254 }
255
256 if ( ( new Status() )->is_offline_mode() ) {
257 return new WP_Error(
258 'dev_mode',
259 __( 'Jetpack AI is not available in offline mode.', 'jetpack' )
260 );
261 }
262
263 $site_id = Manager::get_site_id();
264 if ( is_wp_error( $site_id ) ) {
265 return $site_id;
266 }
267
268 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
269 if ( ! class_exists( 'OpenAI' ) ) {
270 \require_lib( 'openai' );
271 }
272
273 // Set the content for chatGPT endpoint
274 $data = array(
275 array(
276 'role' => 'user',
277 'content' => $content,
278 ),
279 );
280
281 $openai = new OpenAI( 'openai', array( 'post_id' => $post_id ) );
282 $moderation_result = $openai->moderate(
283 implode(
284 ' ',
285 array_map(
286 function ( $msg ) {
287 return $msg['role'] === 'user' ? $msg['content'] : '';
288 },
289 $data
290 )
291 )
292 );
293
294 if ( is_wp_error( $moderation_result ) ) {
295 return $moderation_result;
296 }
297
298 $max_tokens = 480; // Default
299 $result = $openai->request_chat_completion( $data, $max_tokens );
300
301 if ( is_wp_error( $result ) ) {
302 return $result;
303 }
304
305 $response = $result->choices[0]->message->content;
306
307 // In case of Jetpack we are setting a transient on the WPCOM and not the remote site. I think the 'get_current_user_id' may default for the connection owner at this point but we'll deal with this later.
308 set_transient( self::transient_name_for_completion(), $response, self::$text_completion_cooldown_seconds );
309 self::mark_post_as_ai_assisted( $post_id );
310 return $response;
311 }
312
313 $response = Client::wpcom_json_api_request_as_user(
314 sprintf( '/sites/%d/jetpack-ai/completions', $site_id ),
315 2,
316 array(
317 'method' => 'post',
318 'headers' => array( 'content-type' => 'application/json' ),
319 ),
320 wp_json_encode(
321 array(
322 'content' => $content,
323 ),
324 JSON_UNESCAPED_SLASHES
325 ),
326 'wpcom'
327 );
328
329 if ( is_wp_error( $response ) ) {
330 return $response;
331 }
332
333 $data = json_decode( wp_remote_retrieve_body( $response ) );
334
335 if ( wp_remote_retrieve_response_code( $response ) >= 400 ) {
336 return new WP_Error( $data->code, $data->message, $data->data );
337 }
338
339 // Do not cache if it should be skipped.
340 if ( ! $skip_cache ) {
341 set_transient( self::transient_name_for_completion(), $data, self::$text_completion_cooldown_seconds );
342 }
343 self::mark_post_as_ai_assisted( $post_id );
344
345 return $data;
346 }
347
348 /**
349 * Get an array of image objects back from WordPress.com based off a prompt.
350 *
351 * @param string $prompt The prompt to generate images for.
352 * @param int $post_id Post ID for which the content is being generated.
353 * @return mixed
354 */
355 public static function get_dalle_generation( $prompt, $post_id ) {
356 $cache = get_transient( self::transient_name_for_image_generation( $prompt ) );
357 if ( $cache ) {
358 self::mark_post_as_ai_assisted( $post_id );
359 return $cache;
360 }
361
362 if ( ( new Status() )->is_offline_mode() ) {
363 return new WP_Error(
364 'dev_mode',
365 __( 'Jetpack AI is not available in offline mode.', 'jetpack' )
366 );
367 }
368
369 $site_id = Manager::get_site_id();
370 if ( is_wp_error( $site_id ) ) {
371 return $site_id;
372 }
373
374 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
375 if ( ! class_exists( 'OpenAI' ) ) {
376 \require_lib( 'openai' );
377 }
378
379 $result = ( new OpenAI( 'openai', array( 'post_id' => $post_id ) ) )->request_dalle_generation( $prompt );
380 if ( is_wp_error( $result ) ) {
381 return $result;
382 }
383 set_transient( self::transient_name_for_image_generation( $prompt ), $result, self::$image_generation_cache_timeout );
384 self::mark_post_as_ai_assisted( $post_id );
385 return $result;
386 }
387
388 $response = Client::wpcom_json_api_request_as_user(
389 sprintf( '/sites/%d/jetpack-ai/images/generations', $site_id ),
390 2,
391 array(
392 'method' => 'post',
393 'headers' => array( 'content-type' => 'application/json' ),
394 ),
395 wp_json_encode(
396 array(
397 'prompt' => $prompt,
398 ),
399 JSON_UNESCAPED_SLASHES
400 ),
401 'wpcom'
402 );
403
404 if ( is_wp_error( $response ) ) {
405 return $response;
406 }
407
408 $data = json_decode( wp_remote_retrieve_body( $response ) );
409
410 if ( wp_remote_retrieve_response_code( $response ) >= 400 ) {
411 return new WP_Error( $data->code, $data->message, $data->data );
412 }
413 set_transient( self::transient_name_for_image_generation( $prompt ), $data, self::$image_generation_cache_timeout );
414 self::mark_post_as_ai_assisted( $post_id );
415
416 return $data;
417 }
418
419 /**
420 * Get an object with useful data about the requests made to the AI.
421 *
422 * @return mixed
423 */
424 public static function get_ai_assistance_feature() {
425 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
426 // On WPCOM, we can get the ID from the site.
427 $blog_id = get_current_blog_id();
428 $has_ai_assistant_feature = \wpcom_site_has_feature( 'ai-assistant', $blog_id );
429
430 if ( ! class_exists( 'WPCOM\Jetpack_AI\Usage\Helper' ) ) {
431 if ( is_readable( WP_CONTENT_DIR . '/lib/jetpack-ai/usage/helper.php' ) ) {
432 require_once WP_CONTENT_DIR . '/lib/jetpack-ai/usage/helper.php';
433 } else {
434 return new WP_Error(
435 'jetpack_ai_usage_helper_not_found',
436 __( 'WPCOM\Jetpack_AI\Usage\Helper class not found.', 'jetpack' )
437 );
438 }
439 }
440
441 if ( ! class_exists( 'WPCOM\Jetpack_AI\Feature_Control' ) ) {
442 if ( is_readable( WP_CONTENT_DIR . '/lib/jetpack-ai/feature-control.php' ) ) {
443 require_once WP_CONTENT_DIR . '/lib/jetpack-ai/feature-control.php';
444 } else {
445 return new WP_Error(
446 'jetpack_ai_feature_control_not_found',
447 __( 'WPCOM\Jetpack_AI\Feature_Control class not found.', 'jetpack' )
448 );
449 }
450 }
451
452 // Determine the upgrade type
453 $upgrade_type = wpcom_is_vip( $blog_id ) ? 'vip' : 'default';
454
455 return array(
456 'has-feature' => $has_ai_assistant_feature,
457 'is-over-limit' => WPCOM\Jetpack_AI\Usage\Helper::is_over_limit( $blog_id ),
458 'requests-count' => WPCOM\Jetpack_AI\Usage\Helper::get_all_time_requests_count( $blog_id ),
459 'requests-limit' => WPCOM\Jetpack_AI\Usage\Helper::get_free_requests_limit( $blog_id ),
460 'usage-period' => WPCOM\Jetpack_AI\Usage\Helper::get_period_data( $blog_id ),
461 'site-require-upgrade' => WPCOM\Jetpack_AI\Usage\Helper::site_requires_upgrade( $blog_id ),
462 'upgrade-type' => $upgrade_type,
463 'upgrade-url' => WPCOM\Jetpack_AI\Usage\Helper::get_upgrade_url( $blog_id ),
464 'current-tier' => WPCOM\Jetpack_AI\Usage\Helper::get_current_tier( $blog_id ),
465 'next-tier' => WPCOM\Jetpack_AI\Usage\Helper::get_next_tier( $blog_id ),
466 'tier-plans' => WPCOM\Jetpack_AI\Usage\Helper::get_tier_plans_list(),
467 'tier-plans-enabled' => WPCOM\Jetpack_AI\Usage\Helper::ai_tier_plans_enabled(),
468 'costs' => WPCOM\Jetpack_AI\Usage\Helper::get_costs(),
469 'features-control' => WPCOM\Jetpack_AI\Feature_Control::get_features(),
470 );
471 }
472
473 // Outside of WPCOM, we need to fetch the data from the site.
474 $blog_id = Jetpack_Options::get_option( 'id' );
475
476 // Try to pick the AI Assistant feature from cache.
477 $transient_name = self::transient_name_for_ai_assistance_feature( $blog_id );
478 $cache = get_transient( $transient_name );
479 if ( $cache ) {
480 return $cache;
481 }
482
483 if ( null !== static::$ai_assistant_failed_request ) {
484 return static::$ai_assistant_failed_request;
485 }
486
487 $request_path = sprintf( '/sites/%d/jetpack-ai/ai-assistant-feature', $blog_id );
488
489 $wpcom_request = Client::wpcom_json_api_request_as_user(
490 $request_path,
491 'v2',
492 array(
493 'method' => 'GET',
494 'headers' => array(
495 'X-Forwarded-For' => ( new Visitor() )->get_ip( true ),
496 ),
497 'timeout' => 30,
498 ),
499 null,
500 'wpcom'
501 );
502
503 $response_code = wp_remote_retrieve_response_code( $wpcom_request );
504 if ( 200 === $response_code ) {
505 $ai_assistant_feature_data = json_decode( wp_remote_retrieve_body( $wpcom_request ), true );
506
507 // Cache the AI Assistant feature, for Jetpack sites.
508 set_transient( $transient_name, $ai_assistant_feature_data, self::$ai_assistant_feature_cache_timeout );
509
510 return $ai_assistant_feature_data;
511 } else {
512 $error = new WP_Error(
513 'failed_to_fetch_data',
514 esc_html__( 'Unable to fetch the requested data.', 'jetpack' ),
515 array(
516 'status' => $response_code,
517 'ts' => time(),
518 )
519 );
520
521 // Cache the AI Assistant feature error, for Jetpack sites, avoid API hammering.
522 set_transient( $transient_name, $error, self::$ai_assistant_feature_error_cache_timeout );
523
524 static::$ai_assistant_failed_request = $error;
525
526 return $error;
527 }
528 }
529 }
530