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

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