PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 13.4.5
Jetpack – WP Security, Backup, Speed, & Growth v13.4.5
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 2 years ago class-jetpack-instagram-gallery-helper.php 2 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 2 years ago class-jetpack-top-posts-helper.php 2 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 2 years ago class.jetpack-password-checker.php 2 years ago class.jetpack-search-performance-logger.php 4 years ago class.media-extractor.php 2 years ago class.media-summary.php 2 years ago class.media.php 2 years ago components.php 3 years ago debugger.php 2 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 2 years ago
class-jetpack-ai-helper.php
430 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 $openai = new OpenAI( 'openai', array( 'post_id' => $post_id ) );
212 $moderation_result = $openai->moderate(
213 implode(
214 ' ',
215 array_map(
216 function ( $msg ) {
217 return $msg['role'] === 'user' ? $msg['content'] : '';
218 },
219 $data
220 )
221 )
222 );
223
224 if ( is_wp_error( $moderation_result ) ) {
225 return $moderation_result;
226 }
227
228 $max_tokens = 480; // Default
229 $result = $openai->request_chat_completion( $data, $max_tokens );
230
231 if ( is_wp_error( $result ) ) {
232 return $result;
233 }
234
235 $response = $result->choices[0]->message->content;
236
237 // 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.
238 set_transient( self::transient_name_for_completion(), $response, self::$text_completion_cooldown_seconds );
239 self::mark_post_as_ai_assisted( $post_id );
240 return $response;
241 }
242
243 $response = Client::wpcom_json_api_request_as_user(
244 sprintf( '/sites/%d/jetpack-ai/completions', $site_id ),
245 2,
246 array(
247 'method' => 'post',
248 'headers' => array( 'content-type' => 'application/json' ),
249 ),
250 wp_json_encode(
251 array(
252 'content' => $content,
253 )
254 ),
255 'wpcom'
256 );
257
258 if ( is_wp_error( $response ) ) {
259 return $response;
260 }
261
262 $data = json_decode( wp_remote_retrieve_body( $response ) );
263
264 if ( wp_remote_retrieve_response_code( $response ) >= 400 ) {
265 return new WP_Error( $data->code, $data->message, $data->data );
266 }
267
268 // Do not cache if it should be skipped.
269 if ( ! $skip_cache ) {
270 set_transient( self::transient_name_for_completion(), $data, self::$text_completion_cooldown_seconds );
271 }
272 self::mark_post_as_ai_assisted( $post_id );
273
274 return $data;
275 }
276
277 /**
278 * Get an array of image objects back from WordPress.com based off a prompt.
279 *
280 * @param string $prompt The prompt to generate images for.
281 * @param int $post_id Post ID for which the content is being generated.
282 * @return mixed
283 */
284 public static function get_dalle_generation( $prompt, $post_id ) {
285 $cache = get_transient( self::transient_name_for_image_generation( $prompt ) );
286 if ( $cache ) {
287 self::mark_post_as_ai_assisted( $post_id );
288 return $cache;
289 }
290
291 if ( ( new Status() )->is_offline_mode() ) {
292 return new WP_Error(
293 'dev_mode',
294 __( 'Jetpack AI is not available in offline mode.', 'jetpack' )
295 );
296 }
297
298 $site_id = Manager::get_site_id();
299 if ( is_wp_error( $site_id ) ) {
300 return $site_id;
301 }
302
303 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
304 if ( ! class_exists( 'OpenAI' ) ) {
305 \require_lib( 'openai' );
306 }
307
308 $result = ( new OpenAI( 'openai', array( 'post_id' => $post_id ) ) )->request_dalle_generation( $prompt );
309 if ( is_wp_error( $result ) ) {
310 return $result;
311 }
312 set_transient( self::transient_name_for_image_generation( $prompt ), $result, self::$image_generation_cache_timeout );
313 self::mark_post_as_ai_assisted( $post_id );
314 return $result;
315 }
316
317 $response = Client::wpcom_json_api_request_as_user(
318 sprintf( '/sites/%d/jetpack-ai/images/generations', $site_id ),
319 2,
320 array(
321 'method' => 'post',
322 'headers' => array( 'content-type' => 'application/json' ),
323 ),
324 wp_json_encode(
325 array(
326 'prompt' => $prompt,
327 )
328 ),
329 'wpcom'
330 );
331
332 if ( is_wp_error( $response ) ) {
333 return $response;
334 }
335
336 $data = json_decode( wp_remote_retrieve_body( $response ) );
337
338 if ( wp_remote_retrieve_response_code( $response ) >= 400 ) {
339 return new WP_Error( $data->code, $data->message, $data->data );
340 }
341 set_transient( self::transient_name_for_image_generation( $prompt ), $data, self::$image_generation_cache_timeout );
342 self::mark_post_as_ai_assisted( $post_id );
343
344 return $data;
345 }
346
347 /**
348 * Get an object with useful data about the requests made to the AI.
349 *
350 * @return mixed
351 */
352 public static function get_ai_assistance_feature() {
353 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
354 // On WPCOM, we can get the ID from the site.
355 $blog_id = get_current_blog_id();
356 $has_ai_assistant_feature = \wpcom_site_has_feature( 'ai-assistant', $blog_id );
357
358 if ( ! class_exists( 'WPCOM\Jetpack_AI\Usage\Helper' ) ) {
359 if ( is_readable( WP_CONTENT_DIR . '/lib/jetpack-ai/usage/helper.php' ) ) {
360 require_once WP_CONTENT_DIR . '/lib/jetpack-ai/usage/helper.php';
361 } else {
362 return new WP_Error(
363 'jetpack_ai_usage_helper_not_found',
364 __( 'WPCOM\Jetpack_AI\Usage\Helper class not found.', 'jetpack' )
365 );
366 }
367 }
368
369 // Determine the upgrade type
370 $upgrade_type = wpcom_is_vip( $blog_id ) ? 'vip' : 'default';
371
372 return array(
373 'has-feature' => $has_ai_assistant_feature,
374 'is-over-limit' => WPCOM\Jetpack_AI\Usage\Helper::is_over_limit( $blog_id ),
375 'requests-count' => WPCOM\Jetpack_AI\Usage\Helper::get_all_time_requests_count( $blog_id ),
376 'requests-limit' => WPCOM\Jetpack_AI\Usage\Helper::get_free_requests_limit( $blog_id ),
377 'usage-period' => WPCOM\Jetpack_AI\Usage\Helper::get_period_data( $blog_id ),
378 'site-require-upgrade' => WPCOM\Jetpack_AI\Usage\Helper::site_requires_upgrade( $blog_id ),
379 'upgrade-type' => $upgrade_type,
380 'current-tier' => WPCOM\Jetpack_AI\Usage\Helper::get_current_tier( $blog_id ),
381 'next-tier' => WPCOM\Jetpack_AI\Usage\Helper::get_next_tier( $blog_id ),
382 'tier-plans' => WPCOM\Jetpack_AI\Usage\Helper::get_tier_plans_list(),
383 'tier-plans-enabled' => WPCOM\Jetpack_AI\Usage\Helper::ai_tier_plans_enabled(),
384 'costs' => WPCOM\Jetpack_AI\Usage\Helper::get_costs(),
385 );
386 }
387
388 // Outside of WPCOM, we need to fetch the data from the site.
389 $blog_id = Jetpack_Options::get_option( 'id' );
390
391 // Try to pick the AI Assistant feature from cache.
392 $transient_name = self::transient_name_for_ai_assistance_feature( $blog_id );
393 $cache = get_transient( $transient_name );
394 if ( $cache ) {
395 return $cache;
396 }
397
398 $request_path = sprintf( '/sites/%d/jetpack-ai/ai-assistant-feature', $blog_id );
399
400 $wpcom_request = Client::wpcom_json_api_request_as_user(
401 $request_path,
402 'v2',
403 array(
404 'method' => 'GET',
405 'headers' => array(
406 'X-Forwarded-For' => ( new Visitor() )->get_ip( true ),
407 ),
408 ),
409 null,
410 'wpcom'
411 );
412
413 $response_code = wp_remote_retrieve_response_code( $wpcom_request );
414 if ( 200 === $response_code ) {
415 $ai_assistant_feature_data = json_decode( wp_remote_retrieve_body( $wpcom_request ), true );
416
417 // Cache the AI Assistant feature, for Jetpack sites.
418 set_transient( $transient_name, $ai_assistant_feature_data, self::$ai_assistant_feature_cache_timeout );
419
420 return $ai_assistant_feature_data;
421 } else {
422 return new WP_Error(
423 'failed_to_fetch_data',
424 esc_html__( 'Unable to fetch the requested data.', 'jetpack' ),
425 array( 'status' => $response_code )
426 );
427 }
428 }
429 }
430