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

707 lines 17.1 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 false,
198 '',
199 'json'
200 );
201 }
202
203 public function get_excerpt( $prompt, $context, $request_ids ) {
204 $excerpt_length = apply_filters( 'excerpt_length', 55 );
205 return $this->ai_request(
206 'POST',
207 'text/get-excerpt',
208 [
209 'content' => $prompt,
210 'maxLength' => $excerpt_length,
211 'context' => wp_json_encode( $context ),
212 'ids' => $request_ids,
213 'api_version' => ELEMENTOR_VERSION,
214 'site_lang' => get_bloginfo( 'language' ),
215 ],
216 false,
217 '',
218 'json'
219 );
220 }
221
222 /**
223 * get_image_prompt_enhanced
224 * @param $prompt
225 *
226 * @return mixed|\WP_Error
227 */
228 public function get_image_prompt_enhanced( $prompt, $context, $request_ids ) {
229 return $this->ai_request(
230 'POST',
231 'text/enhance-image-prompt',
232 [
233 'prompt' => $prompt,
234 'context' => wp_json_encode( $context ),
235 'ids' => $request_ids,
236 'api_version' => ELEMENTOR_VERSION,
237 'site_lang' => get_bloginfo( 'language' ),
238 ]
239 );
240 }
241
242 public function get_edit_text( $data, $context, $request_ids ) {
243 return $this->ai_request(
244 'POST',
245 'text/edit',
246 [
247 'input' => $data['payload']['input'],
248 'instruction' => $data['payload']['instruction'],
249 'context' => wp_json_encode( $context ),
250 'ids' => $request_ids,
251 'api_version' => ELEMENTOR_VERSION,
252 'site_lang' => get_bloginfo( 'language' ),
253 ],
254 false,
255 '',
256 'json'
257 );
258 }
259
260 public function get_custom_code( $data, $context, $request_ids ) {
261 return $this->ai_request(
262 'POST',
263 'text/custom-code',
264 [
265 'prompt' => $data['payload']['prompt'],
266 'language' => $data['payload']['language'],
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_css( $data, $context, $request_ids ) {
279 return $this->ai_request(
280 'POST',
281 'text/custom-css',
282 [
283 'prompt' => $data['payload']['prompt'],
284 'html_markup' => $data['payload']['html_markup'],
285 'element_id' => $data['payload']['element_id'],
286 'context' => wp_json_encode( $context ),
287 'ids' => $request_ids,
288 'api_version' => ELEMENTOR_VERSION,
289 'site_lang' => get_bloginfo( 'language' ),
290 ],
291 false,
292 '',
293 'json'
294 );
295 }
296
297 /**
298 * get_text_to_image
299 * @param $prompt
300 * @param $prompt_settings
301 *
302 * @return mixed|\WP_Error
303 */
304 public function get_text_to_image( $data, $context, $request_ids ) {
305 return $this->ai_request(
306 'POST',
307 'image/text-to-image',
308 [
309 self::PROMPT => $data['payload']['prompt'],
310 self::IMAGE_TYPE => $data['payload']['settings'][ self::IMAGE_TYPE ] . '/' . $data['payload']['settings'][ self::STYLE_PRESET ],
311 self::ASPECT_RATIO => $data['payload']['settings'][ self::ASPECT_RATIO ],
312 'context' => wp_json_encode( $context ),
313 'ids' => $request_ids,
314 'api_version' => ELEMENTOR_VERSION,
315 'site_lang' => get_bloginfo( 'language' ),
316 ],
317 false,
318 '',
319 'json'
320 );
321 }
322
323 /**
324 * get_image_to_image
325 * @param $image_data
326 *
327 * @return mixed|\WP_Error
328 * @throws \Exception
329 */
330 public function get_image_to_image( $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',
340 [
341 self::PROMPT => $image_data[ self::PROMPT ],
342 self::IMAGE_TYPE => $image_data['promptSettings'][ self::IMAGE_TYPE ] . '/' . $image_data['promptSettings'][ self::STYLE_PRESET ],
343 self::IMAGE_STRENGTH => $image_data['promptSettings'][ self::IMAGE_STRENGTH ],
344 self::ASPECT_RATIO => $image_data['promptSettings'][ self::ASPECT_RATIO ],
345 'context' => wp_json_encode( $context ),
346 'ids' => $request_ids,
347 'api_version' => ELEMENTOR_VERSION,
348 'site_lang' => get_bloginfo( 'language' ),
349 ],
350 $image_file,
351 'image'
352 );
353
354 return $result;
355 }
356
357 /**
358 * get_image_to_image_upscale
359 * @param $image_data
360 *
361 * @return mixed|\WP_Error
362 * @throws \Exception
363 */
364 public function get_image_to_image_upscale( $image_data, $context, $request_ids ) {
365 $image_file = get_attached_file( $image_data['attachment_id'] );
366
367 if ( ! $image_file ) {
368 throw new \Exception( 'Image file not found' );
369 }
370
371 $result = $this->ai_request(
372 'POST',
373 'image/image-to-image/upscale',
374 [
375 self::IMAGE_RESOLUTION => $image_data['promptSettings']['upscale_to'],
376 'context' => wp_json_encode( $context ),
377 'ids' => $request_ids,
378 'api_version' => ELEMENTOR_VERSION,
379 'site_lang' => get_bloginfo( 'language' ),
380 ],
381 $image_file,
382 'image'
383 );
384
385 return $result;
386 }
387
388 /**
389 * get_image_to_image_remove_background
390 * @param $image_data
391 *
392 * @return mixed|\WP_Error
393 * @throws \Exception
394 */
395 public function get_image_to_image_remove_background( $image_data, $context, $request_ids ) {
396 $image_file = get_attached_file( $image_data['attachment_id'] );
397
398 if ( ! $image_file ) {
399 throw new \Exception( 'Image file not found' );
400 }
401
402 $result = $this->ai_request(
403 'POST',
404 'image/image-to-image/remove-background',
405 [
406 'context' => wp_json_encode( $context ),
407 'ids' => $request_ids,
408 'api_version' => ELEMENTOR_VERSION,
409 'site_lang' => get_bloginfo( 'language' ),
410 ],
411 $image_file,
412 'image'
413 );
414
415 return $result;
416 }
417
418 /**
419 * get_image_to_image_remove_text
420 * @param $image_data
421 *
422 * @return mixed|\WP_Error
423 * @throws \Exception
424 */
425 public function get_image_to_image_replace_background( $image_data, $context, $request_ids ) {
426 $image_file = get_attached_file( $image_data['attachment_id'] );
427
428 if ( ! $image_file ) {
429 throw new \Exception( 'Image file not found' );
430 }
431
432 $result = $this->ai_request(
433 'POST',
434 'image/image-to-image/replace-background',
435 [
436 self::PROMPT => $image_data[ self::PROMPT ],
437 'context' => wp_json_encode( $context ),
438 'ids' => $request_ids,
439 'api_version' => ELEMENTOR_VERSION,
440 'site_lang' => get_bloginfo( 'language' ),
441 ],
442 $image_file,
443 'image'
444 );
445
446 return $result;
447 }
448
449 /**
450 * store_temp_file
451 * used to store a temp file for the AI request and deletes it once the request is done
452 * @param $file_content
453 * @param $file_ext
454 *
455 * @return string
456 */
457 private function store_temp_file( $file_content, $file_ext = '' ) {
458 $temp_file = str_replace( '.tmp', '', wp_tempnam() . $file_ext );
459 file_put_contents( $temp_file, $file_content );
460 // make sure the temp file is deleted on shutdown
461 register_shutdown_function( function () use ( $temp_file ) {
462 if ( file_exists( $temp_file ) ) {
463 unlink( $temp_file );
464 }
465 } );
466 return $temp_file;
467 }
468
469 /**
470 * get_image_to_image_out_painting
471 * @param $image_data
472 *
473 * @return mixed|\WP_Error
474 * @throws \Exception
475 */
476 public function get_image_to_image_out_painting( $image_data, $context, $request_ids ) {
477 $img_content = str_replace( ' ', '+', $image_data['mask'] );
478 $img_content = substr( $img_content, strpos( $img_content, ',' ) + 1 );
479 $img_content = base64_decode( $img_content );
480 $mask_file = $this->store_temp_file( $img_content, '.png' );
481
482 if ( ! $mask_file ) {
483 throw new \Exception( 'Expended Image file not found' );
484 }
485
486 $result = $this->ai_request(
487 'POST',
488 'image/image-to-image/outpainting',
489 [
490 'context' => wp_json_encode( $context ),
491 'ids' => $request_ids,
492 'api_version' => ELEMENTOR_VERSION,
493 'site_lang' => get_bloginfo( 'language' ),
494 'size' => wp_json_encode( $image_data['size'] ),
495 'position' => wp_json_encode( $image_data['position'] ),
496 'image_base64' => $image_data['image_base64'],
497 $image_data['image'],
498 ],
499 [
500 [
501 'name' => 'image',
502 'type' => 'image',
503 'path' => $mask_file,
504 ],
505 ]
506 );
507
508 return $result;
509 }
510
511 /**
512 * get_image_to_image_mask
513 * @param $image_data
514 *
515 * @return mixed|\WP_Error
516 * @throws \Exception
517 */
518 public function get_image_to_image_mask( $image_data, $context, $request_ids ) {
519 $image_file = get_attached_file( $image_data['attachment_id'] );
520 $mask_file = $this->store_temp_file( $image_data['mask'], '.svg' );
521
522 if ( ! $image_file ) {
523 throw new \Exception( 'Image file not found' );
524 }
525
526 if ( ! $mask_file ) {
527 throw new \Exception( 'Mask file not found' );
528 }
529
530 $result = $this->ai_request(
531 'POST',
532 'image/image-to-image/inpainting',
533 [
534 self::PROMPT => $image_data[ self::PROMPT ],
535 'context' => wp_json_encode( $context ),
536 'ids' => $request_ids,
537 'api_version' => ELEMENTOR_VERSION,
538 'site_lang' => get_bloginfo( 'language' ),
539 'image_base64' => $image_data['image_base64'],
540 ],
541 [
542 [
543 'name' => 'image',
544 'type' => 'image',
545 'path' => $image_file,
546 ],
547 [
548 'name' => 'mask_image',
549 'type' => 'image/svg+xml',
550 'path' => $mask_file,
551 ],
552 ]
553 );
554
555 return $result;
556 }
557
558 public function generate_layout( $data, $context ) {
559 $endpoint = 'generate/layout';
560
561 $body = [
562 'prompt' => $data['prompt'],
563 'variationType' => (int) $data['variationType'],
564 'ids' => $data['ids'],
565 ];
566
567 if ( ! empty( $data['prevGeneratedIds'] ) ) {
568 $body['generatedBaseTemplatesIds'] = $data['prevGeneratedIds'];
569 }
570
571 if ( ! empty( $data['attachments'] ) ) {
572 $attachment = $data['attachments'][0];
573
574 switch ( $attachment['type'] ) {
575 case 'json':
576 $endpoint = 'generate/generate-json-variation';
577
578 $body['json'] = [
579 'type' => 'elementor',
580 'elements' => [ $attachment['content'] ],
581 'label' => $attachment['label'],
582 'source' => $attachment['source'],
583 ];
584 break;
585 case 'url':
586 $endpoint = 'generate/html-to-elementor';
587
588 $html = wp_json_encode( $attachment['content'] );
589
590 $body['html'] = $html;
591
592 break;
593 }
594 }
595
596 $context['currentContext'] = $data['currentContext'];
597 $context['features'] = [
598 'supportedFeatures' => [],
599 ];
600
601 if ( ElementorUtils::has_pro() ) {
602 $context['features']['subscriptions'] = [ 'Pro' ];
603 }
604
605 if ( Plugin::$instance->experiments->is_feature_active( 'container_grid' ) ) {
606 $context['features']['supportedFeatures'][] = 'Grid';
607 }
608
609 if ( Plugin::instance()->experiments->get_active_features()['nested-elements'] ) {
610 $context['features']['supportedFeatures'][] = 'Nested';
611 }
612
613 if ( Plugin::instance()->experiments->get_active_features()['taxonomy-filter'] ) {
614 $context['features']['supportedFeatures'][] = 'Taxonomy';
615 }
616
617 if ( Plugin::instance()->experiments->get_active_features()['mega-menu'] ) {
618 $context['features']['supportedFeatures'][] = 'MegaMenu';
619 }
620
621 $metadata = [
622 'context' => $context,
623 'api_version' => ELEMENTOR_VERSION,
624 'site_lang' => get_bloginfo( 'language' ),
625 'config' => [
626 'generate' => [
627 'all' => true,
628 ],
629 ],
630 ];
631
632 $body = array_merge( $body, $metadata );
633
634 // Temp hack for platforms that filters the http_request_args, and it breaks JSON requests.
635 remove_all_filters( 'http_request_args' );
636
637 return $this->ai_request(
638 'POST',
639 $endpoint,
640 $body,
641 false,
642 '',
643 'json'
644 );
645 }
646
647 public function get_layout_prompt_enhanced( $prompt, $enhance_type, $context ) {
648 return $this->ai_request(
649 'POST',
650 'generate/enhance-prompt',
651 [
652 'prompt' => $prompt,
653 'enhance_type' => $enhance_type,
654 'context' => wp_json_encode( $context ),
655 'api_version' => ELEMENTOR_VERSION,
656 'site_lang' => get_bloginfo( 'language' ),
657 ],
658 false,
659 '',
660 'json'
661 );
662 }
663
664 public function get_history_by_type( $type, $page, $limit, $context = [] ) {
665 $endpoint = Module::HISTORY_TYPE_ALL === $type
666 ? 'history'
667 : add_query_arg( [
668 'page' => $page,
669 'limit' => $limit,
670 ], "history/{$type}" );
671
672 return $this->ai_request(
673 'POST',
674 $endpoint,
675 [
676 'context' => wp_json_encode( $context ),
677 'api_version' => ELEMENTOR_VERSION,
678 'site_lang' => get_bloginfo( 'language' ),
679 ]
680 );
681 }
682
683 public function delete_history_item( $id, $context = [] ) {
684 return $this->ai_request(
685 'DELETE', 'history/' . $id,
686 [
687 'context' => wp_json_encode( $context ),
688 'api_version' => ELEMENTOR_VERSION,
689 'site_lang' => get_bloginfo( 'language' ),
690 ]
691 );
692 }
693
694 public function toggle_favorite_history_item( $id, $context = [] ) {
695 return $this->ai_request(
696 'POST', sprintf( 'history/%s/favorite', $id ),
697 [
698 'context' => wp_json_encode( $context ),
699 'api_version' => ELEMENTOR_VERSION,
700 'site_lang' => get_bloginfo( 'language' ),
701 ]
702 );
703 }
704
705 protected function init() {}
706 }
707