PluginProbe
Elementor Website Builder – more than just a page builder / 3.21.5
Elementor Website Builder – more than just a page builder v3.21.5
4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 4.1.0-beta2 4.1.0-dev2 4.0.8 4.1.0-beta1 4.1.0-dev1 4.0.7 All 451 releases
elementor / modules / ai / connect / ai.php

ai.php in Elementor Website Builder – more than just a page builder 3.21.5, at modules/ai/connect/ai.php

661 lines 16.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Elementor\Modules\Ai\Connect;
3
4 use Elementor\Core\Common\Modules\Connect\Apps\Library;
5 use Elementor\Modules\Ai\Module;
6 use Elementor\Utils as ElementorUtils;
7 use Elementor\Plugin;
8
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit; // Exit if accessed directly
11 }
12
13 class Ai extends Library {
14 const API_URL = 'https://my.elementor.com/api/v2/ai/';
15
16 const STYLE_PRESET = 'style_preset';
17 const IMAGE_TYPE = 'image_type';
18 const IMAGE_STRENGTH = 'image_strength';
19 const ASPECT_RATIO = 'ratio';
20 const IMAGE_RESOLUTION = 'image_resolution';
21
22 const PROMPT = 'prompt';
23
24 public function get_title() {
25 return esc_html__( 'AI', 'elementor' );
26 }
27
28 protected function get_api_url() {
29 return static::API_URL . '/';
30 }
31
32 public function get_usage() {
33 return $this->ai_request(
34 'POST',
35 'status/check',
36 [
37 'api_version' => ELEMENTOR_VERSION,
38 'site_lang' => get_bloginfo( 'language' ),
39 ]
40 );
41 }
42
43 public function get_cached_usage() {
44 $cache_key = 'elementor_ai_usage';
45 $cache_time = 24 * HOUR_IN_SECONDS;
46 $usage = get_site_transient( $cache_key );
47
48 if ( ! $usage ) {
49 $usage = $this->get_usage();
50 set_site_transient( $cache_key, $usage, $cache_time );
51 }
52
53 return $usage;
54 }
55
56 public function get_remote_config() {
57 return $this->ai_request(
58 'GET',
59 'remote-config/config',
60 [
61 'api_version' => ELEMENTOR_VERSION,
62 'site_lang' => get_bloginfo( 'language' ),
63 ]
64 );
65 }
66
67 /**
68 * get_file_payload
69 * @param $filename
70 * @param $file_type
71 * @param $file_path
72 * @param $boundary
73 *
74 * @return string
75 */
76 private function get_file_payload( $filename, $file_type, $file_path, $boundary ) {
77 $name = $filename ?? basename( $file_path );
78 $mine_type = 'image' === $file_type ? image_type_to_mime_type( exif_imagetype( $file_path ) ) : $file_type;
79 $payload = '';
80 // Upload the file
81 $payload .= '--' . $boundary;
82 $payload .= "\r\n";
83 $payload .= 'Content-Disposition: form-data; name="' . esc_attr( $name ) . '"; filename="' . esc_attr( $name ) . '"' . "\r\n";
84 $payload .= 'Content-Type: ' . $mine_type . "\r\n";
85 $payload .= "\r\n";
86 $payload .= file_get_contents( $file_path );
87 $payload .= "\r\n";
88
89 return $payload;
90 }
91
92 private function get_upload_request_body( $body, $file, $boundary, $file_name = '' ) {
93 $payload = '';
94 // add all body fields as standard POST fields:
95 foreach ( $body as $name => $value ) {
96 $payload .= '--' . $boundary;
97 $payload .= "\r\n";
98 $payload .= 'Content-Disposition: form-data; name="' . esc_attr( $name ) . '"' . "\r\n\r\n";
99 $payload .= $value;
100 $payload .= "\r\n";
101 }
102
103 if ( is_array( $file ) ) {
104 foreach ( $file as $key => $file_data ) {
105 $payload .= $this->get_file_payload( $file_data['name'], $file_data['type'], $file_data['path'], $boundary );
106 }
107 } else {
108 $image_mime = image_type_to_mime_type( exif_imagetype( $file ) );
109 // @todo: add validation for supported image types
110
111 if ( empty( $file_name ) ) {
112 $file_name = basename( $file );
113 }
114 $payload .= $this->get_file_payload( $file_name, $image_mime, $file, $boundary );
115 }
116
117 $payload .= '--' . $boundary . '--';
118
119 return $payload;
120 }
121
122 private function ai_request( $method, $endpoint, $body, $file = false, $file_name = '', $format = 'default' ) {
123 $headers = [
124 'x-elementor-ai-version' => '2',
125 ];
126
127 if ( $file ) {
128 $boundary = wp_generate_password( 24, false );
129 $body = $this->get_upload_request_body( $body, $file, $boundary, $file_name );
130 // add content type header
131 $headers['Content-Type'] = 'multipart/form-data; boundary=' . $boundary;
132 } elseif ( 'json' === $format ) {
133 $headers['Content-Type'] = 'application/json';
134 $body = wp_json_encode( $body );
135 }
136
137 return $this->http_request(
138 $method,
139 $endpoint,
140 [
141 'timeout' => 100,
142 'headers' => $headers,
143 'body' => $body,
144
145 ],
146 [
147 'return_type' => static::HTTP_RETURN_TYPE_ARRAY,
148 'with_error_data' => true,
149 ]
150 );
151 }
152
153 public function set_get_started() {
154 return $this->ai_request(
155 'POST',
156 'status/get-started',
157 [
158 'api_version' => ELEMENTOR_VERSION,
159 'site_lang' => get_bloginfo( 'language' ),
160 ]
161 );
162 }
163
164 public function set_status_feedback( $response_id ) {
165 return $this->ai_request(
166 'POST',
167 'status/feedback/' . $response_id,
168 [
169 'api_version' => ELEMENTOR_VERSION,
170 'site_lang' => get_bloginfo( 'language' ),
171 ]
172 );
173 }
174
175 public function set_used_gallery_image( $image_id ) {
176 return $this->ai_request(
177 'POST',
178 'status/used-gallery-image/' . $image_id,
179 [
180 'api_version' => ELEMENTOR_VERSION,
181 'site_lang' => get_bloginfo( 'language' ),
182 ]
183 );
184 }
185
186 public function get_completion_text( $prompt, $context, $request_ids ) {
187 return $this->ai_request(
188 'POST',
189 'text/completion',
190 [
191 'prompt' => $prompt,
192 'context' => wp_json_encode( $context ),
193 'ids' => $request_ids,
194 'api_version' => ELEMENTOR_VERSION,
195 'site_lang' => get_bloginfo( 'language' ),
196 ]
197 );
198 }
199
200 /**
201 * get_image_prompt_enhanced
202 * @param $prompt
203 *
204 * @return mixed|\WP_Error
205 */
206 public function get_image_prompt_enhanced( $prompt, $context, $request_ids ) {
207 return $this->ai_request(
208 'POST',
209 'text/enhance-image-prompt',
210 [
211 'prompt' => $prompt,
212 'context' => wp_json_encode( $context ),
213 'ids' => $request_ids,
214 'api_version' => ELEMENTOR_VERSION,
215 'site_lang' => get_bloginfo( 'language' ),
216 ]
217 );
218 }
219
220 public function get_edit_text( $data, $context, $request_ids ) {
221 return $this->ai_request(
222 'POST',
223 'text/edit',
224 [
225 'input' => $data['payload']['input'],
226 'instruction' => $data['payload']['instruction'],
227 'context' => wp_json_encode( $context ),
228 'ids' => $request_ids,
229 'api_version' => ELEMENTOR_VERSION,
230 'site_lang' => get_bloginfo( 'language' ),
231 ]
232 );
233 }
234
235 public function get_custom_code( $data, $context, $request_ids ) {
236 return $this->ai_request(
237 'POST',
238 'text/custom-code',
239 [
240 'prompt' => $data['payload']['prompt'],
241 'language' => $data['payload']['language'],
242 'context' => wp_json_encode( $context ),
243 'ids' => $request_ids,
244 'api_version' => ELEMENTOR_VERSION,
245 'site_lang' => get_bloginfo( 'language' ),
246 ]
247 );
248 }
249
250 public function get_custom_css( $data, $context, $request_ids ) {
251 return $this->ai_request(
252 'POST',
253 'text/custom-css',
254 [
255 'prompt' => $data['payload']['prompt'],
256 'html_markup' => $data['payload']['html_markup'],
257 'element_id' => $data['payload']['element_id'],
258 'context' => wp_json_encode( $context ),
259 'ids' => $request_ids,
260 'api_version' => ELEMENTOR_VERSION,
261 'site_lang' => get_bloginfo( 'language' ),
262 ]
263 );
264 }
265
266 /**
267 * get_text_to_image
268 * @param $prompt
269 * @param $prompt_settings
270 *
271 * @return mixed|\WP_Error
272 */
273 public function get_text_to_image( $data, $context, $request_ids ) {
274 return $this->ai_request(
275 'POST',
276 'image/text-to-image',
277 [
278 self::PROMPT => $data['payload']['prompt'],
279 self::IMAGE_TYPE => $data['payload']['settings'][ self::IMAGE_TYPE ] . '/' . $data['payload']['settings'][ self::STYLE_PRESET ],
280 self::ASPECT_RATIO => $data['payload']['settings'][ self::ASPECT_RATIO ],
281 'context' => wp_json_encode( $context ),
282 'ids' => $request_ids,
283 'api_version' => ELEMENTOR_VERSION,
284 'site_lang' => get_bloginfo( 'language' ),
285 ]
286 );
287 }
288
289 /**
290 * get_image_to_image
291 * @param $image_data
292 *
293 * @return mixed|\WP_Error
294 * @throws \Exception
295 */
296 public function get_image_to_image( $image_data, $context, $request_ids ) {
297 $image_file = get_attached_file( $image_data['attachment_id'] );
298
299 if ( ! $image_file ) {
300 throw new \Exception( 'Image file not found' );
301 }
302
303 $result = $this->ai_request(
304 'POST',
305 'image/image-to-image',
306 [
307 self::PROMPT => $image_data[ self::PROMPT ],
308 self::IMAGE_TYPE => $image_data['promptSettings'][ self::IMAGE_TYPE ] . '/' . $image_data['promptSettings'][ self::STYLE_PRESET ],
309 self::IMAGE_STRENGTH => $image_data['promptSettings'][ self::IMAGE_STRENGTH ],
310 self::ASPECT_RATIO => $image_data['promptSettings'][ self::ASPECT_RATIO ],
311 'context' => wp_json_encode( $context ),
312 'ids' => $request_ids,
313 'api_version' => ELEMENTOR_VERSION,
314 'site_lang' => get_bloginfo( 'language' ),
315 ],
316 $image_file,
317 'image'
318 );
319
320 return $result;
321 }
322
323 /**
324 * get_image_to_image_upscale
325 * @param $image_data
326 *
327 * @return mixed|\WP_Error
328 * @throws \Exception
329 */
330 public function get_image_to_image_upscale( $image_data, $context, $request_ids ) {
331 $image_file = get_attached_file( $image_data['attachment_id'] );
332
333 if ( ! $image_file ) {
334 throw new \Exception( 'Image file not found' );
335 }
336
337 $result = $this->ai_request(
338 'POST',
339 'image/image-to-image/upscale',
340 [
341 self::IMAGE_RESOLUTION => $image_data['promptSettings']['upscale_to'],
342 'context' => wp_json_encode( $context ),
343 'ids' => $request_ids,
344 'api_version' => ELEMENTOR_VERSION,
345 'site_lang' => get_bloginfo( 'language' ),
346 ],
347 $image_file,
348 'image'
349 );
350
351 return $result;
352 }
353
354 /**
355 * get_image_to_image_remove_background
356 * @param $image_data
357 *
358 * @return mixed|\WP_Error
359 * @throws \Exception
360 */
361 public function get_image_to_image_remove_background( $image_data, $context, $request_ids ) {
362 $image_file = get_attached_file( $image_data['attachment_id'] );
363
364 if ( ! $image_file ) {
365 throw new \Exception( 'Image file not found' );
366 }
367
368 $result = $this->ai_request(
369 'POST',
370 'image/image-to-image/remove-background',
371 [
372 'context' => wp_json_encode( $context ),
373 'ids' => $request_ids,
374 'api_version' => ELEMENTOR_VERSION,
375 'site_lang' => get_bloginfo( 'language' ),
376 ],
377 $image_file,
378 'image'
379 );
380
381 return $result;
382 }
383
384 /**
385 * get_image_to_image_remove_text
386 * @param $image_data
387 *
388 * @return mixed|\WP_Error
389 * @throws \Exception
390 */
391 public function get_image_to_image_replace_background( $image_data, $context, $request_ids ) {
392 $image_file = get_attached_file( $image_data['attachment_id'] );
393
394 if ( ! $image_file ) {
395 throw new \Exception( 'Image file not found' );
396 }
397
398 $result = $this->ai_request(
399 'POST',
400 'image/image-to-image/replace-background',
401 [
402 self::PROMPT => $image_data[ self::PROMPT ],
403 'context' => wp_json_encode( $context ),
404 'ids' => $request_ids,
405 'api_version' => ELEMENTOR_VERSION,
406 'site_lang' => get_bloginfo( 'language' ),
407 ],
408 $image_file,
409 'image'
410 );
411
412 return $result;
413 }
414
415 /**
416 * store_temp_file
417 * used to store a temp file for the AI request and deletes it once the request is done
418 * @param $file_content
419 * @param $file_ext
420 *
421 * @return string
422 */
423 private function store_temp_file( $file_content, $file_ext = '' ) {
424 $temp_file = str_replace( '.tmp', '', wp_tempnam() . $file_ext );
425 file_put_contents( $temp_file, $file_content );
426 // make sure the temp file is deleted on shutdown
427 register_shutdown_function( function () use ( $temp_file ) {
428 if ( file_exists( $temp_file ) ) {
429 unlink( $temp_file );
430 }
431 } );
432 return $temp_file;
433 }
434
435 /**
436 * get_image_to_image_out_painting
437 * @param $image_data
438 *
439 * @return mixed|\WP_Error
440 * @throws \Exception
441 */
442 public function get_image_to_image_out_painting( $image_data, $context, $request_ids ) {
443 $img_content = str_replace( ' ', '+', $image_data['mask'] );
444 $img_content = substr( $img_content, strpos( $img_content, ',' ) + 1 );
445 $img_content = base64_decode( $img_content );
446 $mask_file = $this->store_temp_file( $img_content, '.png' );
447
448 if ( ! $mask_file ) {
449 throw new \Exception( 'Expended Image file not found' );
450 }
451
452 $result = $this->ai_request(
453 'POST',
454 'image/image-to-image/outpainting',
455 [
456 self::PROMPT => $image_data[ self::PROMPT ],
457 self::IMAGE_TYPE => '',
458 'context' => wp_json_encode( $context ),
459 'ids' => $request_ids,
460 'api_version' => ELEMENTOR_VERSION,
461 'site_lang' => get_bloginfo( 'language' ),
462 ],
463 [
464 [
465 'name' => 'image',
466 'type' => 'image',
467 'path' => $mask_file,
468 ],
469 ]
470 );
471
472 return $result;
473 }
474
475 /**
476 * get_image_to_image_mask
477 * @param $image_data
478 *
479 * @return mixed|\WP_Error
480 * @throws \Exception
481 */
482 public function get_image_to_image_mask( $image_data, $context, $request_ids ) {
483 $image_file = get_attached_file( $image_data['attachment_id'] );
484 $mask_file = $this->store_temp_file( $image_data['mask'], '.svg' );
485
486 if ( ! $image_file ) {
487 throw new \Exception( 'Image file not found' );
488 }
489
490 if ( ! $mask_file ) {
491 throw new \Exception( 'Mask file not found' );
492 }
493
494 $result = $this->ai_request(
495 'POST',
496 'image/image-to-image/inpainting',
497 [
498 self::PROMPT => $image_data[ self::PROMPT ],
499 self::IMAGE_TYPE => $image_data['promptSettings'][ self::IMAGE_TYPE ] . '/' . $image_data['promptSettings'][ self::STYLE_PRESET ],
500 self::IMAGE_STRENGTH => $image_data['promptSettings'][ self::IMAGE_STRENGTH ],
501 'context' => wp_json_encode( $context ),
502 'ids' => $request_ids,
503 'api_version' => ELEMENTOR_VERSION,
504 'site_lang' => get_bloginfo( 'language' ),
505 ],
506 [
507 [
508 'name' => 'image',
509 'type' => 'image',
510 'path' => $image_file,
511 ],
512 [
513 'name' => 'mask_image',
514 'type' => 'image/svg+xml',
515 'path' => $mask_file,
516 ],
517 ]
518 );
519
520 return $result;
521 }
522
523 public function generate_layout( $data, $context ) {
524 $endpoint = 'generate/layout';
525
526 $body = [
527 'prompt' => $data['prompt'],
528 'variationType' => (int) $data['variationType'],
529 'ids' => $data['ids'],
530 ];
531
532 if ( ! empty( $data['prevGeneratedIds'] ) ) {
533 $body['generatedBaseTemplatesIds'] = $data['prevGeneratedIds'];
534 }
535
536 if ( ! empty( $data['attachments'] ) ) {
537 $attachment = $data['attachments'][0];
538
539 switch ( $attachment['type'] ) {
540 case 'json':
541 $endpoint = 'generate/generate-json-variation';
542
543 $body['json'] = [
544 'type' => 'elementor',
545 'elements' => [ $attachment['content'] ],
546 'label' => $attachment['label'],
547 'source' => $attachment['source'],
548 ];
549 break;
550 case 'url':
551 $endpoint = 'generate/html-to-elementor';
552
553 $html = wp_json_encode( $attachment['content'] );
554
555 $body['html'] = $html;
556
557 break;
558 }
559 }
560
561 $context['currentContext'] = $data['currentContext'];
562 $context['features'] = [
563 'supportedFeatures' => [],
564 ];
565
566 if ( ElementorUtils::has_pro() ) {
567 $context['features']['subscriptions'] = [ 'Pro' ];
568 }
569
570 if ( Plugin::$instance->experiments->is_feature_active( 'container_grid' ) ) {
571 $context['features']['supportedFeatures'][] = 'Grid';
572 }
573
574 if ( Plugin::instance()->experiments->get_active_features()['nested-elements'] ) {
575 $context['features']['supportedFeatures'][] = 'NestedElements';
576 }
577
578 $metadata = [
579 'context' => $context,
580 'api_version' => ELEMENTOR_VERSION,
581 'site_lang' => get_bloginfo( 'language' ),
582 'config' => [
583 'generate' => [
584 'all' => true,
585 ],
586 ],
587 ];
588
589 $body = array_merge( $body, $metadata );
590
591 // Temp hack for platforms that filters the http_request_args, and it breaks JSON requests.
592 remove_all_filters( 'http_request_args' );
593
594 return $this->ai_request(
595 'POST',
596 $endpoint,
597 $body,
598 false,
599 '',
600 'json'
601 );
602 }
603
604 public function get_layout_prompt_enhanced( $prompt, $enhance_type, $context ) {
605 return $this->ai_request(
606 'POST',
607 'generate/enhance-prompt',
608 [
609 'prompt' => $prompt,
610 'enhance_type' => $enhance_type,
611 'context' => wp_json_encode( $context ),
612 'api_version' => ELEMENTOR_VERSION,
613 'site_lang' => get_bloginfo( 'language' ),
614 ]
615 );
616 }
617
618 public function get_history_by_type( $type, $page, $limit, $context = [] ) {
619 $endpoint = Module::HISTORY_TYPE_ALL === $type
620 ? 'history'
621 : add_query_arg( [
622 'page' => $page,
623 'limit' => $limit,
624 ], "history/{$type}" );
625
626 return $this->ai_request(
627 'POST',
628 $endpoint,
629 [
630 'context' => wp_json_encode( $context ),
631 'api_version' => ELEMENTOR_VERSION,
632 'site_lang' => get_bloginfo( 'language' ),
633 ]
634 );
635 }
636
637 public function delete_history_item( $id, $context = [] ) {
638 return $this->ai_request(
639 'DELETE', 'history/' . $id,
640 [
641 'context' => wp_json_encode( $context ),
642 'api_version' => ELEMENTOR_VERSION,
643 'site_lang' => get_bloginfo( 'language' ),
644 ]
645 );
646 }
647
648 public function toggle_favorite_history_item( $id, $context = [] ) {
649 return $this->ai_request(
650 'POST', sprintf( 'history/%s/favorite', $id ),
651 [
652 'context' => wp_json_encode( $context ),
653 'api_version' => ELEMENTOR_VERSION,
654 'site_lang' => get_bloginfo( 'language' ),
655 ]
656 );
657 }
658
659 protected function init() {}
660 }
661