PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 12.8.3
Jetpack – WP Security, Backup, Speed, & Growth v12.8.3
12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 14.5.1 14.6.1 14.7.1 14.8.1 14.9.2 15.0.3 15.1.2 15.2.1 15.3.2 15.4.1 15.5.1 15.6.1 15.7.2 15.8.1 15.9.2 16.0.2 16.1.3 16.2-a.5 16.2-a.3 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 2 years ago core-api 2 years ago debugger 2 years ago markdown 2 years ago class-jetpack-ai-helper.php 2 years ago class-jetpack-currencies.php 5 years ago class-jetpack-google-drive-helper.php 3 years ago class-jetpack-instagram-gallery-helper.php 3 years ago class-jetpack-mapbox-helper.php 3 years ago class-jetpack-podcast-feed-locator.php 2 years ago class-jetpack-podcast-helper.php 2 years ago class-jetpack-recommendations.php 3 years ago class-jetpack-tweetstorm-helper.php 2 years ago class-jetpack-wizard.php 5 years ago class.color.php 2 years ago class.core-rest-api-endpoints.php 2 years ago class.jetpack-automatic-install-skin.php 2 years ago class.jetpack-iframe-embed.php 3 years ago class.jetpack-keyring-service-helper.php 3 years ago class.jetpack-password-checker.php 3 years ago class.jetpack-search-performance-logger.php 4 years ago class.media-extractor.php 3 years ago class.media-summary.php 3 years ago class.media.php 2 years ago components.php 3 years ago debugger.php 4 years ago functions.wp-notify.php 3 years ago icalendar-reader.php 2 years ago markdown.php 3 years ago plans.php 4 years ago plugins.php 4 years ago tonesque.php 2 years ago widgets.php 4 years ago
class-jetpack-ai-helper.php
463 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 * Allow new completion every X seconds. Will return cached result otherwise.
23 *
24 * @var int
25 */
26 public static $text_completion_cooldown_seconds = 15;
27
28 /**
29 * Cache images for a prompt for a month.
30 *
31 * @var int
32 */
33 public static $image_generation_cache_timeout = MONTH_IN_SECONDS;
34
35 /**
36 * Cache AI-assistant feature for ten seconds.
37 *
38 * @var int
39 */
40 public static $ai_assistant_feature_cache_timeout = 10;
41
42 /**
43 * Stores the number of JetpackAI calls in case we want to mark AI-assisted posts some way.
44 *
45 * @var int
46 */
47 public static $post_meta_with_ai_generation_number = '_jetpack_ai_calls';
48
49 /**
50 * Checks if a given request is allowed to get AI data from WordPress.com.
51 *
52 * @param WP_REST_Request $request Full details about the request.
53 *
54 * @return true|WP_Error True if the request has access, WP_Error object otherwise.
55 */
56 public static function get_status_permission_check( $request ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
57
58 /*
59 * This may need to be updated
60 * to take into account the different ways we can make requests
61 * (from a WordPress.com site, from a Jetpack site).
62 */
63 if ( ! current_user_can( 'edit_posts' ) ) {
64 return new WP_Error(
65 'rest_forbidden',
66 __( 'Sorry, you are not allowed to access Jetpack AI help on this site.', 'jetpack' ),
67 array( 'status' => rest_authorization_required_code() )
68 );
69 }
70
71 return true;
72 }
73
74 /**
75 * Return true if these features should be active on the current site.
76 * Currently, it's limited to WPCOM Simple and Atomic.
77 */
78 public static function is_enabled() {
79 $default = false;
80
81 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
82 $default = true;
83 } elseif ( ( new Automattic\Jetpack\Status\Host() )->is_woa_site() ) {
84 $default = true;
85 }
86
87 /**
88 * Filter whether the AI features are enabled in the Jetpack plugin.
89 *
90 * @since 11.8
91 *
92 * @param bool $default Are AI features enabled? Defaults to false.
93 */
94 return apply_filters( 'jetpack_ai_enabled', $default );
95 }
96
97 /**
98 * Return true if the AI chat feature should be active on the current site.
99 *
100 * @todo IS_WPCOM (the endpoints need to be updated too).
101 *
102 * @return bool
103 */
104 public static function is_ai_chat_enabled() {
105 $default = false;
106
107 $connection = new Manager();
108 $plan = new Search_Plan();
109 if ( $connection->is_connected() && $plan->supports_search() ) {
110 $default = true;
111 }
112
113 /**
114 * Filter whether the AI chat feature is enabled in the Jetpack plugin.
115 *
116 * @since 12.6
117 *
118 * @param bool $default Is AI chat enabled? Defaults to false.
119 */
120 return apply_filters( 'jetpack_ai_chat_enabled', $default );
121 }
122
123 /**
124 * 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.
125 * I expext "puppy" to always be from cache.
126 *
127 * @param string $prompt - Supplied prompt.
128 */
129 public static function transient_name_for_image_generation( $prompt ) {
130 return 'jetpack_openai_image_' . md5( $prompt );
131 }
132
133 /**
134 * Get the name of the transient for text completion. Unique per user, but not per text. Serves more as a cooldown.
135 */
136 public static function transient_name_for_completion() {
137 return 'jetpack_openai_completion_' . get_current_user_id(); // Cache for each user, so that other users dont get weird cached version from somebody else.
138 }
139
140 /**
141 * Get the name of the transient for AI assistance feature. Unique per user.
142 *
143 * @param int $blog_id - Blog ID to get the transient name for.
144 * @return string
145 */
146 public static function transient_name_for_ai_assistance_feature( $blog_id ) {
147 return 'jetpack_openai_ai_assistance_feature_' . $blog_id;
148 }
149
150 /**
151 * Mark the edited post as "touched" by AI stuff.
152 *
153 * @param int $post_id Post ID for which the content is being generated.
154 * @return void
155 */
156 private static function mark_post_as_ai_assisted( $post_id ) {
157 if ( ! $post_id ) {
158 return;
159 }
160 $previous = get_post_meta( $post_id, self::$post_meta_with_ai_generation_number, true );
161 if ( ! $previous ) {
162 $previous = 0;
163 } elseif ( ! is_numeric( $previous ) ) {
164 // Data corrupted, nothing to do.
165 return;
166 }
167 $new_value = intval( $previous ) + 1;
168 update_post_meta( $post_id, self::$post_meta_with_ai_generation_number, $new_value );
169 }
170
171 /**
172 * Get text back from WordPress.com based off a starting text.
173 *
174 * @param string $content The content provided to send to the AI.
175 * @param int $post_id Post ID for which the content is being generated.
176 * @param bool $skip_cache Skip cache and force a new request.
177 * @return mixed
178 */
179 public static function get_gpt_completion( $content, $post_id, $skip_cache = false ) {
180 $content = wp_strip_all_tags( $content );
181 $cache = get_transient( self::transient_name_for_completion() );
182 if ( $cache && ! $skip_cache ) {
183 return $cache;
184 }
185
186 if ( ( new Status() )->is_offline_mode() ) {
187 return new WP_Error(
188 'dev_mode',
189 __( 'Jetpack AI is not available in offline mode.', 'jetpack' )
190 );
191 }
192
193 $site_id = Manager::get_site_id();
194 if ( is_wp_error( $site_id ) ) {
195 return $site_id;
196 }
197
198 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
199 if ( ! class_exists( 'OpenAI' ) ) {
200 \require_lib( 'openai' );
201 }
202
203 // Set the content for chatGPT endpoint
204 $data = array(
205 array(
206 'role' => 'user',
207 'content' => $content,
208 ),
209 );
210
211 $result = ( new OpenAI( 'openai', array( 'post_id' => $post_id ) ) )->request_chat_completion( $data );
212
213 if ( is_wp_error( $result ) ) {
214 return $result;
215 }
216
217 $response = $result->choices[0]->message->content;
218
219 // 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.
220 set_transient( self::transient_name_for_completion(), $response, self::$text_completion_cooldown_seconds );
221 self::mark_post_as_ai_assisted( $post_id );
222 return $response;
223 }
224
225 $response = Client::wpcom_json_api_request_as_user(
226 sprintf( '/sites/%d/jetpack-ai/completions', $site_id ),
227 2,
228 array(
229 'method' => 'post',
230 'headers' => array( 'content-type' => 'application/json' ),
231 ),
232 wp_json_encode(
233 array(
234 'content' => $content,
235 )
236 ),
237 'wpcom'
238 );
239
240 if ( is_wp_error( $response ) ) {
241 return $response;
242 }
243
244 $data = json_decode( wp_remote_retrieve_body( $response ) );
245
246 if ( wp_remote_retrieve_response_code( $response ) >= 400 ) {
247 return new WP_Error( $data->code, $data->message, $data->data );
248 }
249
250 // Do not cache if it should be skipped.
251 if ( ! $skip_cache ) {
252 set_transient( self::transient_name_for_completion(), $data, self::$text_completion_cooldown_seconds );
253 }
254 self::mark_post_as_ai_assisted( $post_id );
255
256 return $data;
257 }
258
259 /**
260 * Get an array of image objects back from WordPress.com based off a prompt.
261 *
262 * @param string $prompt The prompt to generate images for.
263 * @param int $post_id Post ID for which the content is being generated.
264 * @return mixed
265 */
266 public static function get_dalle_generation( $prompt, $post_id ) {
267 $cache = get_transient( self::transient_name_for_image_generation( $prompt ) );
268 if ( $cache ) {
269 self::mark_post_as_ai_assisted( $post_id );
270 return $cache;
271 }
272
273 if ( ( new Status() )->is_offline_mode() ) {
274 return new WP_Error(
275 'dev_mode',
276 __( 'Jetpack AI is not available in offline mode.', 'jetpack' )
277 );
278 }
279
280 $site_id = Manager::get_site_id();
281 if ( is_wp_error( $site_id ) ) {
282 return $site_id;
283 }
284
285 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
286 if ( ! class_exists( 'OpenAI' ) ) {
287 \require_lib( 'openai' );
288 }
289
290 $result = ( new OpenAI( 'openai', array( 'post_id' => $post_id ) ) )->request_dalle_generation( $prompt );
291 if ( is_wp_error( $result ) ) {
292 return $result;
293 }
294 set_transient( self::transient_name_for_image_generation( $prompt ), $result, self::$image_generation_cache_timeout );
295 self::mark_post_as_ai_assisted( $post_id );
296 return $result;
297 }
298
299 $response = Client::wpcom_json_api_request_as_user(
300 sprintf( '/sites/%d/jetpack-ai/images/generations', $site_id ),
301 2,
302 array(
303 'method' => 'post',
304 'headers' => array( 'content-type' => 'application/json' ),
305 ),
306 wp_json_encode(
307 array(
308 'prompt' => $prompt,
309 )
310 ),
311 'wpcom'
312 );
313
314 if ( is_wp_error( $response ) ) {
315 return $response;
316 }
317
318 $data = json_decode( wp_remote_retrieve_body( $response ) );
319
320 if ( wp_remote_retrieve_response_code( $response ) >= 400 ) {
321 return new WP_Error( $data->code, $data->message, $data->data );
322 }
323 set_transient( self::transient_name_for_image_generation( $prompt ), $data, self::$image_generation_cache_timeout );
324 self::mark_post_as_ai_assisted( $post_id );
325
326 return $data;
327 }
328
329 /**
330 * Get an object with useful data about the requests made to the AI.
331 *
332 * @return mixed
333 */
334 public static function get_ai_assistance_feature() {
335 $blog_id = Jetpack_Options::get_option( 'id' );
336
337 // Try to pick the AI Assistant feature from cache.
338 $transient_name = self::transient_name_for_ai_assistance_feature( $blog_id );
339 $cache = get_transient( $transient_name );
340 if ( $cache ) {
341 return $cache;
342 }
343
344 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
345 $has_ai_assistant_feature = \wpcom_site_has_feature( 'ai-assistant' );
346 if ( ! class_exists( 'OpenAI' ) ) {
347 \require_lib( 'openai' );
348 }
349
350 if ( ! class_exists( 'OpenAI_Limit_Usage' ) ) {
351 if ( is_readable( WP_PLUGIN_DIR . '/openai/openai-limit-usage.php' ) ) {
352 require_once WP_PLUGIN_DIR . '/openai/openai-limit-usage.php';
353 } else {
354 return new WP_Error(
355 'openai_limit_usage_not_found',
356 __( 'OpenAI_Limit_Usage class not found.', 'jetpack' )
357 );
358 }
359 }
360
361 if ( ! class_exists( 'OpenAI_Request_Count' ) ) {
362 if ( is_readable( WP_PLUGIN_DIR . '/openai/openai-request-count.php' ) ) {
363 require_once WP_PLUGIN_DIR . '/openai/openai-request-count.php';
364 } else {
365 return new WP_Error(
366 'openai_request_count_not_found',
367 __( 'OpenAI_Request_Count class not found.', 'jetpack' )
368 );
369 }
370 }
371
372 if ( ! class_exists( 'WPCOM\Jetpack_AI\Usage\Helper' ) ) {
373 if ( is_readable( WP_CONTENT_DIR . '/lib/jetpack-ai/usage/helper.php' ) ) {
374 require_once WP_CONTENT_DIR . '/lib/jetpack-ai/usage/helper.php';
375 } else {
376 return new WP_Error(
377 'jetpack_ai_usage_helper_not_found',
378 __( 'WPCOM\Jetpack_AI\Usage\Helper class not found.', 'jetpack' )
379 );
380 }
381 }
382
383 $blog_id = get_current_blog_id();
384 $is_over_limit = \OpenAI_Limit_Usage::is_blog_over_request_limit( $blog_id );
385 $requests_limit = \OpenAI_Limit_Usage::get_free_requests_limit( $blog_id );
386 $requests_count = \OpenAI_Request_Count::get_count( $blog_id );
387
388 /*
389 * Usage since the last plan purchase day
390 */
391 $usage_period_start = null;
392 $usage_next_period_start = null;
393 $usage_period_requests_count = 0;
394
395 /*
396 * Get current tier value, a number representing
397 * the current tier of the site.
398 *
399 * - 0 represents a site with the free plan.
400 * - 1 represents a site with the current, unlimited plan.
401 * - 100, 200, 500 represents a site with the new plans,
402 * with the respective number of allowed requests.
403 */
404 $current_tier_value = $has_ai_assistant_feature ? 1 : 0;
405
406 // Check if the site requires an upgrade.
407 $require_upgrade = $is_over_limit && ! $has_ai_assistant_feature;
408
409 // Determine the upgrade type
410 $upgrade_type = wpcom_is_vip( $blog_id ) ? 'vip' : 'default';
411
412 return array(
413 'has-feature' => $has_ai_assistant_feature,
414 'is-over-limit' => $is_over_limit,
415 'requests-count' => $requests_count,
416 'requests-limit' => $requests_limit,
417 'usage-period' => array(
418 'current-start' => $usage_period_start,
419 'next-start' => $usage_next_period_start,
420 'requests-count' => $usage_period_requests_count,
421 ),
422 'site-require-upgrade' => $require_upgrade,
423 'upgrade-type' => $upgrade_type,
424 'current-tier' => array(
425 'value' => $current_tier_value,
426 ),
427 'tier-plans' => WPCOM\Jetpack_AI\Usage\Helper::get_tier_plans_list(),
428 );
429 }
430
431 $request_path = sprintf( '/sites/%d/jetpack-ai/ai-assistant-feature', $blog_id );
432
433 $wpcom_request = Client::wpcom_json_api_request_as_user(
434 $request_path,
435 'v2',
436 array(
437 'method' => 'GET',
438 'headers' => array(
439 'X-Forwarded-For' => ( new Visitor() )->get_ip( true ),
440 ),
441 ),
442 null,
443 'wpcom'
444 );
445
446 $response_code = wp_remote_retrieve_response_code( $wpcom_request );
447 if ( 200 === $response_code ) {
448 $ai_assistant_feature_data = json_decode( wp_remote_retrieve_body( $wpcom_request ), true );
449
450 // Cache the AI Assistant feature, for Jetpack sites.
451 set_transient( $transient_name, $ai_assistant_feature_data, self::$ai_assistant_feature_cache_timeout );
452
453 return $ai_assistant_feature_data;
454 } else {
455 return new WP_Error(
456 'failed_to_fetch_data',
457 esc_html__( 'Unable to fetch the requested data.', 'jetpack' ),
458 array( 'status' => $response_code )
459 );
460 }
461 }
462 }
463