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

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