PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 12.7.3
Jetpack – WP Security, Backup, Speed, & Growth v12.7.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 3 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 3 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
395 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 * Stores the number of JetpackAI calls in case we want to mark AI-assisted posts some way.
37 *
38 * @var int
39 */
40 public static $post_meta_with_ai_generation_number = '_jetpack_ai_calls';
41
42 /**
43 * Checks if a given request is allowed to get AI data from WordPress.com.
44 *
45 * @param WP_REST_Request $request Full details about the request.
46 *
47 * @return true|WP_Error True if the request has access, WP_Error object otherwise.
48 */
49 public static function get_status_permission_check( $request ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
50
51 /*
52 * This may need to be updated
53 * to take into account the different ways we can make requests
54 * (from a WordPress.com site, from a Jetpack site).
55 */
56 if ( ! current_user_can( 'edit_posts' ) ) {
57 return new WP_Error(
58 'rest_forbidden',
59 __( 'Sorry, you are not allowed to access Jetpack AI help on this site.', 'jetpack' ),
60 array( 'status' => rest_authorization_required_code() )
61 );
62 }
63
64 return true;
65 }
66
67 /**
68 * Return true if these features should be active on the current site.
69 * Currently, it's limited to WPCOM Simple and Atomic.
70 */
71 public static function is_enabled() {
72 $default = false;
73
74 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
75 $default = true;
76 } elseif ( ( new Automattic\Jetpack\Status\Host() )->is_woa_site() ) {
77 $default = true;
78 }
79
80 /**
81 * Filter whether the AI features are enabled in the Jetpack plugin.
82 *
83 * @since 11.8
84 *
85 * @param bool $default Are AI features enabled? Defaults to false.
86 */
87 return apply_filters( 'jetpack_ai_enabled', $default );
88 }
89
90 /**
91 * Return true if the AI chat feature should be active on the current site.
92 *
93 * @todo IS_WPCOM (the endpoints need to be updated too).
94 *
95 * @return bool
96 */
97 public static function is_ai_chat_enabled() {
98 $default = false;
99
100 $connection = new Manager();
101 $plan = new Search_Plan();
102 if ( $connection->is_connected() && $plan->supports_search() ) {
103 $default = true;
104 }
105
106 /**
107 * Filter whether the AI chat feature is enabled in the Jetpack plugin.
108 *
109 * @since 12.6
110 *
111 * @param bool $default Is AI chat enabled? Defaults to false.
112 */
113 return apply_filters( 'jetpack_ai_chat_enabled', $default );
114 }
115
116 /**
117 * 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.
118 * I expext "puppy" to always be from cache.
119 *
120 * @param string $prompt - Supplied prompt.
121 */
122 public static function transient_name_for_image_generation( $prompt ) {
123 return 'jetpack_openai_image_' . md5( $prompt );
124 }
125
126 /**
127 * Get the name of the transient for text completion. Unique per user, but not per text. Serves more as a cooldown.
128 */
129 public static function transient_name_for_completion() {
130 return 'jetpack_openai_completion_' . get_current_user_id(); // Cache for each user, so that other users dont get weird cached version from somebody else.
131 }
132
133 /**
134 * Mark the edited post as "touched" by AI stuff.
135 *
136 * @param int $post_id Post ID for which the content is being generated.
137 * @return void
138 */
139 private static function mark_post_as_ai_assisted( $post_id ) {
140 if ( ! $post_id ) {
141 return;
142 }
143 $previous = get_post_meta( $post_id, self::$post_meta_with_ai_generation_number, true );
144 if ( ! $previous ) {
145 $previous = 0;
146 } elseif ( ! is_numeric( $previous ) ) {
147 // Data corrupted, nothing to do.
148 return;
149 }
150 $new_value = intval( $previous ) + 1;
151 update_post_meta( $post_id, self::$post_meta_with_ai_generation_number, $new_value );
152 }
153
154 /**
155 * Get text back from WordPress.com based off a starting text.
156 *
157 * @param string $content The content provided to send to the AI.
158 * @param int $post_id Post ID for which the content is being generated.
159 * @param bool $skip_cache Skip cache and force a new request.
160 * @return mixed
161 */
162 public static function get_gpt_completion( $content, $post_id, $skip_cache = false ) {
163 $content = wp_strip_all_tags( $content );
164 $cache = get_transient( self::transient_name_for_completion() );
165 if ( $cache && ! $skip_cache ) {
166 return $cache;
167 }
168
169 if ( ( new Status() )->is_offline_mode() ) {
170 return new WP_Error(
171 'dev_mode',
172 __( 'Jetpack AI is not available in offline mode.', 'jetpack' )
173 );
174 }
175
176 $site_id = Manager::get_site_id();
177 if ( is_wp_error( $site_id ) ) {
178 return $site_id;
179 }
180
181 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
182 if ( ! class_exists( 'OpenAI' ) ) {
183 \require_lib( 'openai' );
184 }
185
186 // Set the content for chatGPT endpoint
187 $data = array(
188 array(
189 'role' => 'user',
190 'content' => $content,
191 ),
192 );
193
194 $result = ( new OpenAI( 'openai', array( 'post_id' => $post_id ) ) )->request_chat_completion( $data );
195
196 if ( is_wp_error( $result ) ) {
197 return $result;
198 }
199
200 $response = $result->choices[0]->message->content;
201
202 // 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.
203 set_transient( self::transient_name_for_completion(), $response, self::$text_completion_cooldown_seconds );
204 self::mark_post_as_ai_assisted( $post_id );
205 return $response;
206 }
207
208 $response = Client::wpcom_json_api_request_as_user(
209 sprintf( '/sites/%d/jetpack-ai/completions', $site_id ),
210 2,
211 array(
212 'method' => 'post',
213 'headers' => array( 'content-type' => 'application/json' ),
214 ),
215 wp_json_encode(
216 array(
217 'content' => $content,
218 )
219 ),
220 'wpcom'
221 );
222
223 if ( is_wp_error( $response ) ) {
224 return $response;
225 }
226
227 $data = json_decode( wp_remote_retrieve_body( $response ) );
228
229 if ( wp_remote_retrieve_response_code( $response ) >= 400 ) {
230 return new WP_Error( $data->code, $data->message, $data->data );
231 }
232
233 // Do not cache if it should be skipped.
234 if ( ! $skip_cache ) {
235 set_transient( self::transient_name_for_completion(), $data, self::$text_completion_cooldown_seconds );
236 }
237 self::mark_post_as_ai_assisted( $post_id );
238
239 return $data;
240 }
241
242 /**
243 * Get an array of image objects back from WordPress.com based off a prompt.
244 *
245 * @param string $prompt The prompt to generate images for.
246 * @param int $post_id Post ID for which the content is being generated.
247 * @return mixed
248 */
249 public static function get_dalle_generation( $prompt, $post_id ) {
250 $cache = get_transient( self::transient_name_for_image_generation( $prompt ) );
251 if ( $cache ) {
252 self::mark_post_as_ai_assisted( $post_id );
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 $result = ( new OpenAI( 'openai', array( 'post_id' => $post_id ) ) )->request_dalle_generation( $prompt );
274 if ( is_wp_error( $result ) ) {
275 return $result;
276 }
277 set_transient( self::transient_name_for_image_generation( $prompt ), $result, self::$image_generation_cache_timeout );
278 self::mark_post_as_ai_assisted( $post_id );
279 return $result;
280 }
281
282 $response = Client::wpcom_json_api_request_as_user(
283 sprintf( '/sites/%d/jetpack-ai/images/generations', $site_id ),
284 2,
285 array(
286 'method' => 'post',
287 'headers' => array( 'content-type' => 'application/json' ),
288 ),
289 wp_json_encode(
290 array(
291 'prompt' => $prompt,
292 )
293 ),
294 'wpcom'
295 );
296
297 if ( is_wp_error( $response ) ) {
298 return $response;
299 }
300
301 $data = json_decode( wp_remote_retrieve_body( $response ) );
302
303 if ( wp_remote_retrieve_response_code( $response ) >= 400 ) {
304 return new WP_Error( $data->code, $data->message, $data->data );
305 }
306 set_transient( self::transient_name_for_image_generation( $prompt ), $data, self::$image_generation_cache_timeout );
307 self::mark_post_as_ai_assisted( $post_id );
308
309 return $data;
310 }
311
312 /**
313 * Get an object with useful data about the requests made to the AI.
314 *
315 * @return mixed
316 */
317 public static function get_ai_assistance_feature() {
318 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
319 $has_ai_assistant_feature = \wpcom_site_has_feature( 'ai-assistant' );
320 if ( ! class_exists( 'OpenAI' ) ) {
321 \require_lib( 'openai' );
322 }
323
324 if ( ! class_exists( 'OpenAI_Limit_Usage' ) ) {
325 if ( is_readable( WP_PLUGIN_DIR . '/openai/openai-limit-usage.php' ) ) {
326 require_once WP_PLUGIN_DIR . '/openai/openai-limit-usage.php';
327 } else {
328 return new WP_Error(
329 'openai_limit_usage_not_found',
330 __( 'OpenAI_Limit_Usage class not found.', 'jetpack' )
331 );
332 }
333 }
334
335 if ( ! class_exists( 'OpenAI_Request_Count' ) ) {
336 if ( is_readable( WP_PLUGIN_DIR . '/openai/openai-request-count.php' ) ) {
337 require_once WP_PLUGIN_DIR . '/openai/openai-request-count.php';
338 } else {
339 return new WP_Error(
340 'openai_request_count_not_found',
341 __( 'OpenAI_Request_Count class not found.', 'jetpack' )
342 );
343 }
344 }
345
346 $blog_id = get_current_blog_id();
347 $is_over_limit = \OpenAI_Limit_Usage::is_blog_over_request_limit( $blog_id );
348 $requests_limit = \OpenAI_Limit_Usage::get_free_requests_limit( $blog_id );
349 $requests_count = \OpenAI_Request_Count::get_count( $blog_id );
350
351 // Check if the site requires an upgrade.
352 $require_upgrade = $is_over_limit && ! $has_ai_assistant_feature;
353
354 // Determine the upgrade type
355 $upgrade_type = wpcom_is_vip( $blog_id ) ? 'vip' : 'default';
356
357 return array(
358 'has-feature' => $has_ai_assistant_feature,
359 'is-over-limit' => $is_over_limit,
360 'requests-count' => $requests_count,
361 'requests-limit' => $requests_limit,
362 'site-require-upgrade' => $require_upgrade,
363 'upgrade-type' => $upgrade_type,
364 );
365 }
366
367 $blog_id = Jetpack_Options::get_option( 'id' );
368 $request_path = sprintf( '/sites/%d/jetpack-ai/ai-assistant-feature', $blog_id );
369
370 $wpcom_request = Client::wpcom_json_api_request_as_user(
371 $request_path,
372 'v2',
373 array(
374 'method' => 'GET',
375 'headers' => array(
376 'X-Forwarded-For' => ( new Visitor() )->get_ip( true ),
377 ),
378 ),
379 null,
380 'wpcom'
381 );
382
383 $response_code = wp_remote_retrieve_response_code( $wpcom_request );
384 if ( 200 === $response_code ) {
385 return json_decode( wp_remote_retrieve_body( $wpcom_request ), true );
386 } else {
387 return new WP_Error(
388 'failed_to_fetch_data',
389 esc_html__( 'Unable to fetch the requested data.', 'jetpack' ),
390 array( 'status' => $response_code )
391 );
392 }
393 }
394 }
395