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