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