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

670 lines 16.5 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 'context' => wp_json_encode( $context ),
457 'ids' => $request_ids,
458 'api_version' => ELEMENTOR_VERSION,
459 'site_lang' => get_bloginfo( 'language' ),
460 'size' => wp_json_encode( $image_data['size'] ),
461 'position' => wp_json_encode( $image_data['position'] ),
462 'image_base64' => $image_data['image_base64'],
463 $image_data['image'],
464 ],
465 [
466 [
467 'name' => 'image',
468 'type' => 'image',
469 'path' => $mask_file,
470 ],
471 ]
472 );
473
474 return $result;
475 }
476
477 /**
478 * get_image_to_image_mask
479 * @param $image_data
480 *
481 * @return mixed|\WP_Error
482 * @throws \Exception
483 */
484 public function get_image_to_image_mask( $image_data, $context, $request_ids ) {
485 $image_file = get_attached_file( $image_data['attachment_id'] );
486 $mask_file = $this->store_temp_file( $image_data['mask'], '.svg' );
487
488 if ( ! $image_file ) {
489 throw new \Exception( 'Image file not found' );
490 }
491
492 if ( ! $mask_file ) {
493 throw new \Exception( 'Mask file not found' );
494 }
495
496 $result = $this->ai_request(
497 'POST',
498 'image/image-to-image/inpainting',
499 [
500 self::PROMPT => $image_data[ self::PROMPT ],
501 'context' => wp_json_encode( $context ),
502 'ids' => $request_ids,
503 'api_version' => ELEMENTOR_VERSION,
504 'site_lang' => get_bloginfo( 'language' ),
505 'image_base64' => $image_data['image_base64'],
506 ],
507 [
508 [
509 'name' => 'image',
510 'type' => 'image',
511 'path' => $image_file,
512 ],
513 [
514 'name' => 'mask_image',
515 'type' => 'image/svg+xml',
516 'path' => $mask_file,
517 ],
518 ]
519 );
520
521 return $result;
522 }
523
524 public function generate_layout( $data, $context ) {
525 $endpoint = 'generate/layout';
526
527 $body = [
528 'prompt' => $data['prompt'],
529 'variationType' => (int) $data['variationType'],
530 'ids' => $data['ids'],
531 ];
532
533 if ( ! empty( $data['prevGeneratedIds'] ) ) {
534 $body['generatedBaseTemplatesIds'] = $data['prevGeneratedIds'];
535 }
536
537 if ( ! empty( $data['attachments'] ) ) {
538 $attachment = $data['attachments'][0];
539
540 switch ( $attachment['type'] ) {
541 case 'json':
542 $endpoint = 'generate/generate-json-variation';
543
544 $body['json'] = [
545 'type' => 'elementor',
546 'elements' => [ $attachment['content'] ],
547 'label' => $attachment['label'],
548 'source' => $attachment['source'],
549 ];
550 break;
551 case 'url':
552 $endpoint = 'generate/html-to-elementor';
553
554 $html = wp_json_encode( $attachment['content'] );
555
556 $body['html'] = $html;
557
558 break;
559 }
560 }
561
562 $context['currentContext'] = $data['currentContext'];
563 $context['features'] = [
564 'supportedFeatures' => [],
565 ];
566
567 if ( ElementorUtils::has_pro() ) {
568 $context['features']['subscriptions'] = [ 'Pro' ];
569 }
570
571 if ( Plugin::$instance->experiments->is_feature_active( 'container_grid' ) ) {
572 $context['features']['supportedFeatures'][] = 'Grid';
573 }
574
575 if ( Plugin::instance()->experiments->get_active_features()['nested-elements'] ) {
576 $context['features']['supportedFeatures'][] = 'Nested';
577 }
578
579 if ( Plugin::instance()->experiments->get_active_features()['taxonomy-filter'] ) {
580 $context['features']['supportedFeatures'][] = 'Taxonomy';
581 }
582
583 if ( Plugin::instance()->experiments->get_active_features()['mega-menu'] ) {
584 $context['features']['supportedFeatures'][] = 'MegaMenu';
585 }
586
587 $metadata = [
588 'context' => $context,
589 'api_version' => ELEMENTOR_VERSION,
590 'site_lang' => get_bloginfo( 'language' ),
591 'config' => [
592 'generate' => [
593 'all' => true,
594 ],
595 ],
596 ];
597
598 $body = array_merge( $body, $metadata );
599
600 // Temp hack for platforms that filters the http_request_args, and it breaks JSON requests.
601 remove_all_filters( 'http_request_args' );
602
603 return $this->ai_request(
604 'POST',
605 $endpoint,
606 $body,
607 false,
608 '',
609 'json'
610 );
611 }
612
613 public function get_layout_prompt_enhanced( $prompt, $enhance_type, $context ) {
614 return $this->ai_request(
615 'POST',
616 'generate/enhance-prompt',
617 [
618 'prompt' => $prompt,
619 'enhance_type' => $enhance_type,
620 'context' => wp_json_encode( $context ),
621 'api_version' => ELEMENTOR_VERSION,
622 'site_lang' => get_bloginfo( 'language' ),
623 ]
624 );
625 }
626
627 public function get_history_by_type( $type, $page, $limit, $context = [] ) {
628 $endpoint = Module::HISTORY_TYPE_ALL === $type
629 ? 'history'
630 : add_query_arg( [
631 'page' => $page,
632 'limit' => $limit,
633 ], "history/{$type}" );
634
635 return $this->ai_request(
636 'POST',
637 $endpoint,
638 [
639 'context' => wp_json_encode( $context ),
640 'api_version' => ELEMENTOR_VERSION,
641 'site_lang' => get_bloginfo( 'language' ),
642 ]
643 );
644 }
645
646 public function delete_history_item( $id, $context = [] ) {
647 return $this->ai_request(
648 'DELETE', 'history/' . $id,
649 [
650 'context' => wp_json_encode( $context ),
651 'api_version' => ELEMENTOR_VERSION,
652 'site_lang' => get_bloginfo( 'language' ),
653 ]
654 );
655 }
656
657 public function toggle_favorite_history_item( $id, $context = [] ) {
658 return $this->ai_request(
659 'POST', sprintf( 'history/%s/favorite', $id ),
660 [
661 'context' => wp_json_encode( $context ),
662 'api_version' => ELEMENTOR_VERSION,
663 'site_lang' => get_bloginfo( 'language' ),
664 ]
665 );
666 }
667
668 protected function init() {}
669 }
670