PluginProbe
Elementor Website Builder – more than just a page builder / 3.25.8
Elementor Website Builder – more than just a page builder v3.25.8
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.25.8, at modules/ai/connect/ai.php

787 lines 19.0 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 public function get_remote_frontend_config( $data ) {
68 return $this->ai_request(
69 'POST',
70 'remote-config/frontend-config',
71 [
72 'client_name' => $data['payload']['client_name'],
73 'client_version' => $data['payload']['client_version'],
74 'client_session_id' => $data['payload']['client_session_id'],
75
76 'api_version' => ELEMENTOR_VERSION,
77 'site_lang' => get_bloginfo( 'language' ),
78 ],
79 false,
80 '',
81 'json'
82 );
83 }
84
85 /**
86 * get_file_payload
87 * @param $filename
88 * @param $file_type
89 * @param $file_path
90 * @param $boundary
91 *
92 * @return string
93 */
94 private function get_file_payload( $filename, $file_type, $file_path, $boundary ) {
95 $name = $filename ?? basename( $file_path );
96 $mine_type = 'image' === $file_type ? image_type_to_mime_type( exif_imagetype( $file_path ) ) : $file_type;
97 $payload = '';
98 // Upload the file
99 $payload .= '--' . $boundary;
100 $payload .= "\r\n";
101 $payload .= 'Content-Disposition: form-data; name="' . esc_attr( $name ) . '"; filename="' . esc_attr( $name ) . '"' . "\r\n";
102 $payload .= 'Content-Type: ' . $mine_type . "\r\n";
103 $payload .= "\r\n";
104 $payload .= file_get_contents( $file_path );
105 $payload .= "\r\n";
106
107 return $payload;
108 }
109
110 private function get_upload_request_body( $body, $file, $boundary, $file_name = '' ) {
111 $payload = '';
112 // add all body fields as standard POST fields:
113 foreach ( $body as $name => $value ) {
114 $payload .= '--' . $boundary;
115 $payload .= "\r\n";
116 $payload .= 'Content-Disposition: form-data; name="' . esc_attr( $name ) . '"' . "\r\n\r\n";
117 $payload .= $value;
118 $payload .= "\r\n";
119 }
120
121 if ( is_array( $file ) ) {
122 foreach ( $file as $key => $file_data ) {
123 $payload .= $this->get_file_payload( $file_data['name'], $file_data['type'], $file_data['path'], $boundary );
124 }
125 } else {
126 $image_mime = image_type_to_mime_type( exif_imagetype( $file ) );
127 // @todo: add validation for supported image types
128
129 if ( empty( $file_name ) ) {
130 $file_name = basename( $file );
131 }
132 $payload .= $this->get_file_payload( $file_name, $image_mime, $file, $boundary );
133 }
134
135 $payload .= '--' . $boundary . '--';
136
137 return $payload;
138 }
139
140 private function ai_request( $method, $endpoint, $body, $file = false, $file_name = '', $format = 'default' ) {
141 $headers = [
142 'x-elementor-ai-version' => '2',
143 ];
144
145 if ( $file ) {
146 $boundary = wp_generate_password( 24, false );
147 $body = $this->get_upload_request_body( $body, $file, $boundary, $file_name );
148 // add content type header
149 $headers['Content-Type'] = 'multipart/form-data; boundary=' . $boundary;
150 } elseif ( 'json' === $format ) {
151 $headers['Content-Type'] = 'application/json';
152 $body = wp_json_encode( $body );
153 }
154
155 return $this->http_request(
156 $method,
157 $endpoint,
158 [
159 'timeout' => 100,
160 'headers' => $headers,
161 'body' => $body,
162
163 ],
164 [
165 'return_type' => static::HTTP_RETURN_TYPE_ARRAY,
166 'with_error_data' => true,
167 ]
168 );
169 }
170
171 public function set_get_started() {
172 return $this->ai_request(
173 'POST',
174 'status/get-started',
175 [
176 'api_version' => ELEMENTOR_VERSION,
177 'site_lang' => get_bloginfo( 'language' ),
178 ]
179 );
180 }
181
182 public function set_status_feedback( $response_id ) {
183 return $this->ai_request(
184 'POST',
185 'status/feedback/' . $response_id,
186 [
187 'api_version' => ELEMENTOR_VERSION,
188 'site_lang' => get_bloginfo( 'language' ),
189 ]
190 );
191 }
192
193 public function set_used_gallery_image( $image_id ) {
194 return $this->ai_request(
195 'POST',
196 'status/used-gallery-image/' . $image_id,
197 [
198 'api_version' => ELEMENTOR_VERSION,
199 'site_lang' => get_bloginfo( 'language' ),
200 ]
201 );
202 }
203
204 public function get_completion_text( $prompt, $context, $request_ids ) {
205 return $this->ai_request(
206 'POST',
207 'text/completion',
208 [
209 'prompt' => $prompt,
210 'context' => wp_json_encode( $context ),
211 'ids' => $request_ids,
212 'api_version' => ELEMENTOR_VERSION,
213 'site_lang' => get_bloginfo( 'language' ),
214 ],
215 false,
216 '',
217 'json'
218 );
219 }
220
221 public function get_excerpt( $prompt, $context, $request_ids ) {
222 $excerpt_length = apply_filters( 'excerpt_length', 55 );
223 return $this->ai_request(
224 'POST',
225 'text/get-excerpt',
226 [
227 'content' => $prompt,
228 'maxLength' => $excerpt_length,
229 'context' => wp_json_encode( $context ),
230 'ids' => $request_ids,
231 'api_version' => ELEMENTOR_VERSION,
232 'site_lang' => get_bloginfo( 'language' ),
233 ],
234 false,
235 '',
236 'json'
237 );
238 }
239
240 /**
241 * get_image_prompt_enhanced
242 * @param $prompt
243 *
244 * @return mixed|\WP_Error
245 */
246 public function get_image_prompt_enhanced( $prompt, $context, $request_ids ) {
247 return $this->ai_request(
248 'POST',
249 'text/enhance-image-prompt',
250 [
251 'prompt' => $prompt,
252 'context' => wp_json_encode( $context ),
253 'ids' => $request_ids,
254 'api_version' => ELEMENTOR_VERSION,
255 'site_lang' => get_bloginfo( 'language' ),
256 ]
257 );
258 }
259
260 public function get_edit_text( $data, $context, $request_ids ) {
261 return $this->ai_request(
262 'POST',
263 'text/edit',
264 [
265 'input' => $data['payload']['input'],
266 'instruction' => $data['payload']['instruction'],
267 'context' => wp_json_encode( $context ),
268 'ids' => $request_ids,
269 'api_version' => ELEMENTOR_VERSION,
270 'site_lang' => get_bloginfo( 'language' ),
271 ],
272 false,
273 '',
274 'json'
275 );
276 }
277
278 public function get_custom_code( $data, $context, $request_ids ) {
279 return $this->ai_request(
280 'POST',
281 'text/custom-code',
282 [
283 'prompt' => $data['payload']['prompt'],
284 'language' => $data['payload']['language'],
285 'context' => wp_json_encode( $context ),
286 'ids' => $request_ids,
287 'api_version' => ELEMENTOR_VERSION,
288 'site_lang' => get_bloginfo( 'language' ),
289 ],
290 false,
291 '',
292 'json'
293 );
294 }
295
296 public function get_custom_css( $data, $context, $request_ids ) {
297 return $this->ai_request(
298 'POST',
299 'text/custom-css',
300 [
301 'prompt' => $data['payload']['prompt'],
302 'html_markup' => $data['payload']['html_markup'],
303 'element_id' => $data['payload']['element_id'],
304 'context' => wp_json_encode( $context ),
305 'ids' => $request_ids,
306 'api_version' => ELEMENTOR_VERSION,
307 'site_lang' => get_bloginfo( 'language' ),
308 ],
309 false,
310 '',
311 'json'
312 );
313 }
314
315 /**
316 * get_text_to_image
317 * @param $prompt
318 * @param $prompt_settings
319 *
320 * @return mixed|\WP_Error
321 */
322 public function get_text_to_image( $data, $context, $request_ids ) {
323 return $this->ai_request(
324 'POST',
325 'image/text-to-image',
326 [
327 self::PROMPT => $data['payload']['prompt'],
328 self::IMAGE_TYPE => $data['payload']['settings'][ self::IMAGE_TYPE ] . '/' . $data['payload']['settings'][ self::STYLE_PRESET ],
329 self::ASPECT_RATIO => $data['payload']['settings'][ self::ASPECT_RATIO ],
330 'context' => wp_json_encode( $context ),
331 'ids' => $request_ids,
332 'api_version' => ELEMENTOR_VERSION,
333 'site_lang' => get_bloginfo( 'language' ),
334 ],
335 false,
336 '',
337 'json'
338 );
339 }
340
341 /**
342 * get_featured_image
343 * @param $prompt
344 * @param $prompt_settings
345 *
346 * @return mixed|\WP_Error
347 */
348 public function get_featured_image( $data, $context, $request_ids ) {
349 return $this->ai_request(
350 'POST',
351 'image/text-to-image/featured-image',
352 [
353 self::PROMPT => $data['payload']['prompt'],
354 self::IMAGE_TYPE => $data['payload']['settings'][ self::IMAGE_TYPE ] . '/' . $data['payload']['settings'][ self::STYLE_PRESET ],
355 self::ASPECT_RATIO => $data['payload']['settings'][ self::ASPECT_RATIO ],
356 'context' => wp_json_encode( $context ),
357 'ids' => $request_ids,
358 'api_version' => ELEMENTOR_VERSION,
359 'site_lang' => get_bloginfo( 'language' ),
360 ],
361 false,
362 '',
363 'json'
364 );
365 }
366
367 /**
368 * get_image_to_image
369 * @param $image_data
370 *
371 * @return mixed|\WP_Error
372 * @throws \Exception
373 */
374 public function get_image_to_image( $image_data, $context, $request_ids ) {
375 $image_file = get_attached_file( $image_data['attachment_id'] );
376
377 if ( ! $image_file ) {
378 throw new \Exception( 'Image file not found' );
379 }
380
381 $result = $this->ai_request(
382 'POST',
383 'image/image-to-image',
384 [
385 self::PROMPT => $image_data[ self::PROMPT ],
386 self::IMAGE_TYPE => $image_data['promptSettings'][ self::IMAGE_TYPE ] . '/' . $image_data['promptSettings'][ self::STYLE_PRESET ],
387 self::IMAGE_STRENGTH => $image_data['promptSettings'][ self::IMAGE_STRENGTH ],
388 self::ASPECT_RATIO => $image_data['promptSettings'][ self::ASPECT_RATIO ],
389 'context' => wp_json_encode( $context ),
390 'ids' => $request_ids,
391 'api_version' => ELEMENTOR_VERSION,
392 'site_lang' => get_bloginfo( 'language' ),
393 ],
394 $image_file,
395 'image'
396 );
397
398 return $result;
399 }
400
401 /**
402 * get_image_to_image_upscale
403 * @param $image_data
404 *
405 * @return mixed|\WP_Error
406 * @throws \Exception
407 */
408 public function get_image_to_image_upscale( $image_data, $context, $request_ids ) {
409 $image_file = get_attached_file( $image_data['attachment_id'] );
410
411 if ( ! $image_file ) {
412 throw new \Exception( 'Image file not found' );
413 }
414
415 $result = $this->ai_request(
416 'POST',
417 'image/image-to-image/upscale',
418 [
419 self::IMAGE_RESOLUTION => $image_data['promptSettings']['upscale_to'],
420 'context' => wp_json_encode( $context ),
421 'ids' => $request_ids,
422 'api_version' => ELEMENTOR_VERSION,
423 'site_lang' => get_bloginfo( 'language' ),
424 ],
425 $image_file,
426 'image'
427 );
428
429 return $result;
430 }
431
432 /**
433 * get_image_to_image_remove_background
434 * @param $image_data
435 *
436 * @return mixed|\WP_Error
437 * @throws \Exception
438 */
439 public function get_image_to_image_remove_background( $image_data, $context, $request_ids ) {
440 $image_file = get_attached_file( $image_data['attachment_id'] );
441
442 if ( ! $image_file ) {
443 throw new \Exception( 'Image file not found' );
444 }
445
446 $result = $this->ai_request(
447 'POST',
448 'image/image-to-image/remove-background',
449 [
450 'context' => wp_json_encode( $context ),
451 'ids' => $request_ids,
452 'api_version' => ELEMENTOR_VERSION,
453 'site_lang' => get_bloginfo( 'language' ),
454 ],
455 $image_file,
456 'image'
457 );
458
459 return $result;
460 }
461
462 /**
463 * get_image_to_image_remove_text
464 * @param $image_data
465 *
466 * @return mixed|\WP_Error
467 * @throws \Exception
468 */
469 public function get_image_to_image_replace_background( $image_data, $context, $request_ids ) {
470 $image_file = get_attached_file( $image_data['attachment_id'] );
471
472 if ( ! $image_file ) {
473 throw new \Exception( 'Image file not found' );
474 }
475
476 $result = $this->ai_request(
477 'POST',
478 'image/image-to-image/replace-background',
479 [
480 self::PROMPT => $image_data[ self::PROMPT ],
481 'context' => wp_json_encode( $context ),
482 'ids' => $request_ids,
483 'api_version' => ELEMENTOR_VERSION,
484 'site_lang' => get_bloginfo( 'language' ),
485 ],
486 $image_file,
487 'image'
488 );
489
490 return $result;
491 }
492
493 /**
494 * store_temp_file
495 * used to store a temp file for the AI request and deletes it once the request is done
496 * @param $file_content
497 * @param $file_ext
498 *
499 * @return string
500 */
501 private function store_temp_file( $file_content, $file_ext = '' ) {
502 $temp_file = str_replace( '.tmp', '', wp_tempnam() . $file_ext );
503 file_put_contents( $temp_file, $file_content );
504 // make sure the temp file is deleted on shutdown
505 register_shutdown_function( function () use ( $temp_file ) {
506 if ( file_exists( $temp_file ) ) {
507 unlink( $temp_file );
508 }
509 } );
510 return $temp_file;
511 }
512
513 /**
514 * get_image_to_image_out_painting
515 * @param $image_data
516 *
517 * @return mixed|\WP_Error
518 * @throws \Exception
519 */
520 public function get_image_to_image_out_painting( $image_data, $context, $request_ids ) {
521 $img_content = str_replace( ' ', '+', $image_data['mask'] );
522 $img_content = substr( $img_content, strpos( $img_content, ',' ) + 1 );
523 $img_content = base64_decode( $img_content );
524 $mask_file = $this->store_temp_file( $img_content, '.png' );
525
526 if ( ! $mask_file ) {
527 throw new \Exception( 'Expended Image file not found' );
528 }
529
530 $result = $this->ai_request(
531 'POST',
532 'image/image-to-image/outpainting',
533 [
534 'context' => wp_json_encode( $context ),
535 'ids' => $request_ids,
536 'api_version' => ELEMENTOR_VERSION,
537 'site_lang' => get_bloginfo( 'language' ),
538 'size' => wp_json_encode( $image_data['size'] ),
539 'position' => wp_json_encode( $image_data['position'] ),
540 'image_base64' => $image_data['image_base64'],
541 $image_data['image'],
542 ],
543 [
544 [
545 'name' => 'image',
546 'type' => 'image',
547 'path' => $mask_file,
548 ],
549 ]
550 );
551
552 return $result;
553 }
554
555 /**
556 * get_image_to_image_mask
557 * @param $image_data
558 *
559 * @return mixed|\WP_Error
560 * @throws \Exception
561 */
562 public function get_image_to_image_mask( $image_data, $context, $request_ids ) {
563 $image_file = get_attached_file( $image_data['attachment_id'] );
564 $mask_file = $this->store_temp_file( $image_data['mask'], '.svg' );
565
566 if ( ! $image_file ) {
567 throw new \Exception( 'Image file not found' );
568 }
569
570 if ( ! $mask_file ) {
571 throw new \Exception( 'Mask file not found' );
572 }
573
574 $result = $this->ai_request(
575 'POST',
576 'image/image-to-image/inpainting',
577 [
578 self::PROMPT => $image_data[ self::PROMPT ],
579 'context' => wp_json_encode( $context ),
580 'ids' => $request_ids,
581 'api_version' => ELEMENTOR_VERSION,
582 'site_lang' => get_bloginfo( 'language' ),
583 'image_base64' => $image_data['image_base64'],
584 ],
585 [
586 [
587 'name' => 'image',
588 'type' => 'image',
589 'path' => $image_file,
590 ],
591 [
592 'name' => 'mask_image',
593 'type' => 'image/svg+xml',
594 'path' => $mask_file,
595 ],
596 ]
597 );
598
599 return $result;
600 }
601 public function get_image_to_image_mask_cleanup( $image_data, $context, $request_ids ) {
602 $image_file = get_attached_file( $image_data['attachment_id'] );
603 $mask_file = $this->store_temp_file( $image_data['mask'], '.svg' );
604
605 if ( ! $image_file ) {
606 throw new \Exception( 'Image file not found' );
607 }
608
609 if ( ! $mask_file ) {
610 throw new \Exception( 'Mask file not found' );
611 }
612
613 $result = $this->ai_request(
614 'POST',
615 'image/image-to-image/cleanup',
616 [
617 self::PROMPT => $image_data[ self::PROMPT ],
618 'context' => wp_json_encode( $context ),
619 'ids' => $request_ids,
620 'api_version' => ELEMENTOR_VERSION,
621 'site_lang' => get_bloginfo( 'language' ),
622 'image_base64' => $image_data['image_base64'],
623 ],
624 [
625 [
626 'name' => 'image',
627 'type' => 'image',
628 'path' => $image_file,
629 ],
630 [
631 'name' => 'mask_image',
632 'type' => 'image/svg+xml',
633 'path' => $mask_file,
634 ],
635 ]
636 );
637
638 return $result;
639 }
640
641 public function generate_layout( $data, $context ) {
642 $endpoint = 'generate/layout';
643
644 $body = [
645 'prompt' => $data['prompt'],
646 'variationType' => (int) $data['variationType'],
647 'ids' => $data['ids'],
648 ];
649
650 if ( ! empty( $data['prevGeneratedIds'] ) ) {
651 $body['generatedBaseTemplatesIds'] = $data['prevGeneratedIds'];
652 }
653
654 if ( ! empty( $data['attachments'] ) ) {
655 $attachment = $data['attachments'][0];
656
657 switch ( $attachment['type'] ) {
658 case 'json':
659 $endpoint = 'generate/generate-json-variation';
660
661 $body['json'] = [
662 'type' => 'elementor',
663 'elements' => [ $attachment['content'] ],
664 'label' => $attachment['label'],
665 'source' => $attachment['source'],
666 ];
667 break;
668 case 'url':
669 $endpoint = 'generate/html-to-elementor';
670
671 $html = wp_json_encode( $attachment['content'] );
672
673 $body['html'] = $html;
674 $body['htmlFetchedUrl'] = $attachment['label'];
675
676 break;
677 }
678 }
679
680 $context['currentContext'] = $data['currentContext'];
681 $context['features'] = [
682 'supportedFeatures' => [ 'Taxonomy' ],
683 ];
684
685 if ( ElementorUtils::has_pro() ) {
686 $context['features']['subscriptions'] = [ 'Pro' ];
687 }
688
689 if ( Plugin::instance()->experiments->get_active_features()['nested-elements'] ) {
690 $context['features']['supportedFeatures'][] = 'Nested';
691 }
692
693 if ( Plugin::instance()->experiments->get_active_features()['mega-menu'] ) {
694 $context['features']['supportedFeatures'][] = 'MegaMenu';
695 }
696
697 if ( class_exists( 'WC' ) ) {
698 $context['features']['supportedFeatures'][] = 'WooCommerce';
699 }
700
701 $metadata = [
702 'context' => $context,
703 'api_version' => ELEMENTOR_VERSION,
704 'site_lang' => get_bloginfo( 'language' ),
705 'config' => [
706 'generate' => [
707 'all' => true,
708 ],
709 ],
710 ];
711
712 $body = array_merge( $body, $metadata );
713
714 // Temp hack for platforms that filters the http_request_args, and it breaks JSON requests.
715 remove_all_filters( 'http_request_args' );
716
717 return $this->ai_request(
718 'POST',
719 $endpoint,
720 $body,
721 false,
722 '',
723 'json'
724 );
725 }
726
727 public function get_layout_prompt_enhanced( $prompt, $enhance_type, $context ) {
728 return $this->ai_request(
729 'POST',
730 'generate/enhance-prompt',
731 [
732 'prompt' => $prompt,
733 'enhance_type' => $enhance_type,
734 'context' => wp_json_encode( $context ),
735 'api_version' => ELEMENTOR_VERSION,
736 'site_lang' => get_bloginfo( 'language' ),
737 ],
738 false,
739 '',
740 'json'
741 );
742 }
743
744 public function get_history_by_type( $type, $page, $limit, $context = [] ) {
745 $endpoint = Module::HISTORY_TYPE_ALL === $type
746 ? 'history'
747 : add_query_arg( [
748 'page' => $page,
749 'limit' => $limit,
750 ], "history/{$type}" );
751
752 return $this->ai_request(
753 'POST',
754 $endpoint,
755 [
756 'context' => wp_json_encode( $context ),
757 'api_version' => ELEMENTOR_VERSION,
758 'site_lang' => get_bloginfo( 'language' ),
759 ]
760 );
761 }
762
763 public function delete_history_item( $id, $context = [] ) {
764 return $this->ai_request(
765 'DELETE', 'history/' . $id,
766 [
767 'context' => wp_json_encode( $context ),
768 'api_version' => ELEMENTOR_VERSION,
769 'site_lang' => get_bloginfo( 'language' ),
770 ]
771 );
772 }
773
774 public function toggle_favorite_history_item( $id, $context = [] ) {
775 return $this->ai_request(
776 'POST', sprintf( 'history/%s/favorite', $id ),
777 [
778 'context' => wp_json_encode( $context ),
779 'api_version' => ELEMENTOR_VERSION,
780 'site_lang' => get_bloginfo( 'language' ),
781 ]
782 );
783 }
784
785 protected function init() {}
786 }
787