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