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

650 lines 15.8 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 ]
148 );
149 }
150
151 public function set_get_started() {
152 return $this->ai_request(
153 'POST',
154 'status/get-started',
155 [
156 'api_version' => ELEMENTOR_VERSION,
157 'site_lang' => get_bloginfo( 'language' ),
158 ]
159 );
160 }
161
162 public function set_status_feedback( $response_id ) {
163 return $this->ai_request(
164 'POST',
165 'status/feedback/' . $response_id,
166 [
167 'api_version' => ELEMENTOR_VERSION,
168 'site_lang' => get_bloginfo( 'language' ),
169 ]
170 );
171 }
172
173 public function set_used_gallery_image( $image_id ) {
174 return $this->ai_request(
175 'POST',
176 'status/used-gallery-image/' . $image_id,
177 [
178 'api_version' => ELEMENTOR_VERSION,
179 'site_lang' => get_bloginfo( 'language' ),
180 ]
181 );
182 }
183
184 public function get_completion_text( $prompt, $context, $request_ids ) {
185 return $this->ai_request(
186 'POST',
187 'text/completion',
188 [
189 'prompt' => $prompt,
190 'context' => wp_json_encode( $context ),
191 'ids' => $request_ids,
192 'api_version' => ELEMENTOR_VERSION,
193 'site_lang' => get_bloginfo( 'language' ),
194 ]
195 );
196 }
197
198 /**
199 * get_image_prompt_enhanced
200 * @param $prompt
201 *
202 * @return mixed|\WP_Error
203 */
204 public function get_image_prompt_enhanced( $prompt, $context, $request_ids ) {
205 return $this->ai_request(
206 'POST',
207 'text/enhance-image-prompt',
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 );
216 }
217
218 public function get_edit_text( $data, $context, $request_ids ) {
219 return $this->ai_request(
220 'POST',
221 'text/edit',
222 [
223 'input' => $data['payload']['input'],
224 'instruction' => $data['payload']['instruction'],
225 'context' => wp_json_encode( $context ),
226 'ids' => $request_ids,
227 'api_version' => ELEMENTOR_VERSION,
228 'site_lang' => get_bloginfo( 'language' ),
229 ]
230 );
231 }
232
233 public function get_custom_code( $data, $context, $request_ids ) {
234 return $this->ai_request(
235 'POST',
236 'text/custom-code',
237 [
238 'prompt' => $data['payload']['prompt'],
239 'language' => $data['payload']['language'],
240 'context' => wp_json_encode( $context ),
241 'ids' => $request_ids,
242 'api_version' => ELEMENTOR_VERSION,
243 'site_lang' => get_bloginfo( 'language' ),
244 ]
245 );
246 }
247
248 public function get_custom_css( $data, $context, $request_ids ) {
249 return $this->ai_request(
250 'POST',
251 'text/custom-css',
252 [
253 'prompt' => $data['payload']['prompt'],
254 'html_markup' => $data['payload']['html_markup'],
255 'element_id' => $data['payload']['element_id'],
256 'context' => wp_json_encode( $context ),
257 'ids' => $request_ids,
258 'api_version' => ELEMENTOR_VERSION,
259 'site_lang' => get_bloginfo( 'language' ),
260 ]
261 );
262 }
263
264 /**
265 * get_text_to_image
266 * @param $prompt
267 * @param $prompt_settings
268 *
269 * @return mixed|\WP_Error
270 */
271 public function get_text_to_image( $data, $context, $request_ids ) {
272 return $this->ai_request(
273 'POST',
274 'image/text-to-image',
275 [
276 self::PROMPT => $data['payload']['prompt'],
277 self::IMAGE_TYPE => $data['payload']['promptSettings'][ self::IMAGE_TYPE ] . '/' . $data['payload']['promptSettings'][ self::STYLE_PRESET ],
278 self::ASPECT_RATIO => $data['payload']['promptSettings'][ self::ASPECT_RATIO ],
279 'context' => wp_json_encode( $context ),
280 'ids' => $request_ids,
281 'api_version' => ELEMENTOR_VERSION,
282 'site_lang' => get_bloginfo( 'language' ),
283 ]
284 );
285 }
286
287 /**
288 * get_image_to_image
289 * @param $image_data
290 *
291 * @return mixed|\WP_Error
292 * @throws \Exception
293 */
294 public function get_image_to_image( $image_data, $context, $request_ids ) {
295 $image_file = get_attached_file( $image_data['attachment_id'] );
296
297 if ( ! $image_file ) {
298 throw new \Exception( 'Image file not found' );
299 }
300
301 $result = $this->ai_request(
302 'POST',
303 'image/image-to-image',
304 [
305 self::PROMPT => $image_data[ self::PROMPT ],
306 self::IMAGE_TYPE => $image_data['promptSettings'][ self::IMAGE_TYPE ] . '/' . $image_data['promptSettings'][ self::STYLE_PRESET ],
307 self::IMAGE_STRENGTH => $image_data['promptSettings'][ self::IMAGE_STRENGTH ],
308 self::ASPECT_RATIO => $image_data['promptSettings'][ self::ASPECT_RATIO ],
309 'context' => wp_json_encode( $context ),
310 'ids' => $request_ids,
311 'api_version' => ELEMENTOR_VERSION,
312 'site_lang' => get_bloginfo( 'language' ),
313 ],
314 $image_file,
315 'image'
316 );
317
318 return $result;
319 }
320
321 /**
322 * get_image_to_image_upscale
323 * @param $image_data
324 *
325 * @return mixed|\WP_Error
326 * @throws \Exception
327 */
328 public function get_image_to_image_upscale( $image_data, $context, $request_ids ) {
329 $image_file = get_attached_file( $image_data['attachment_id'] );
330
331 if ( ! $image_file ) {
332 throw new \Exception( 'Image file not found' );
333 }
334
335 $result = $this->ai_request(
336 'POST',
337 'image/image-to-image/upscale',
338 [
339 self::IMAGE_RESOLUTION => $image_data['promptSettings']['upscale_to'],
340 'context' => wp_json_encode( $context ),
341 'ids' => $request_ids,
342 'api_version' => ELEMENTOR_VERSION,
343 'site_lang' => get_bloginfo( 'language' ),
344 ],
345 $image_file,
346 'image'
347 );
348
349 return $result;
350 }
351
352 /**
353 * get_image_to_image_remove_background
354 * @param $image_data
355 *
356 * @return mixed|\WP_Error
357 * @throws \Exception
358 */
359 public function get_image_to_image_remove_background( $image_data, $context, $request_ids ) {
360 $image_file = get_attached_file( $image_data['attachment_id'] );
361
362 if ( ! $image_file ) {
363 throw new \Exception( 'Image file not found' );
364 }
365
366 $result = $this->ai_request(
367 'POST',
368 'image/image-to-image/remove-background',
369 [
370 'context' => wp_json_encode( $context ),
371 'ids' => $request_ids,
372 'api_version' => ELEMENTOR_VERSION,
373 'site_lang' => get_bloginfo( 'language' ),
374 ],
375 $image_file,
376 'image'
377 );
378
379 return $result;
380 }
381
382 /**
383 * get_image_to_image_remove_text
384 * @param $image_data
385 *
386 * @return mixed|\WP_Error
387 * @throws \Exception
388 */
389 public function get_image_to_image_replace_background( $image_data, $context, $request_ids ) {
390 $image_file = get_attached_file( $image_data['attachment_id'] );
391
392 if ( ! $image_file ) {
393 throw new \Exception( 'Image file not found' );
394 }
395
396 $result = $this->ai_request(
397 'POST',
398 'image/image-to-image/replace-background',
399 [
400 self::PROMPT => $image_data[ self::PROMPT ],
401 'context' => wp_json_encode( $context ),
402 'ids' => $request_ids,
403 'api_version' => ELEMENTOR_VERSION,
404 'site_lang' => get_bloginfo( 'language' ),
405 ],
406 $image_file,
407 'image'
408 );
409
410 return $result;
411 }
412
413 /**
414 * store_temp_file
415 * used to store a temp file for the AI request and deletes it once the request is done
416 * @param $file_content
417 * @param $file_ext
418 *
419 * @return string
420 */
421 private function store_temp_file( $file_content, $file_ext = '' ) {
422 $temp_file = str_replace( '.tmp', '', wp_tempnam() . $file_ext );
423 file_put_contents( $temp_file, $file_content );
424 // make sure the temp file is deleted on shutdown
425 register_shutdown_function( function () use ( $temp_file ) {
426 if ( file_exists( $temp_file ) ) {
427 unlink( $temp_file );
428 }
429 } );
430 return $temp_file;
431 }
432
433 /**
434 * get_image_to_image_out_painting
435 * @param $image_data
436 *
437 * @return mixed|\WP_Error
438 * @throws \Exception
439 */
440 public function get_image_to_image_out_painting( $image_data, $context, $request_ids ) {
441 $img_content = str_replace( ' ', '+', $image_data['mask'] );
442 $img_content = substr( $img_content, strpos( $img_content, ',' ) + 1 );
443 $img_content = base64_decode( $img_content );
444 $mask_file = $this->store_temp_file( $img_content, '.png' );
445
446 if ( ! $mask_file ) {
447 throw new \Exception( 'Expended Image file not found' );
448 }
449
450 $result = $this->ai_request(
451 'POST',
452 'image/image-to-image/outpainting',
453 [
454 self::PROMPT => $image_data[ self::PROMPT ],
455 self::IMAGE_TYPE => '',
456 'context' => wp_json_encode( $context ),
457 'ids' => $request_ids,
458 'api_version' => ELEMENTOR_VERSION,
459 'site_lang' => get_bloginfo( 'language' ),
460 ],
461 [
462 [
463 'name' => 'image',
464 'type' => 'image',
465 'path' => $mask_file,
466 ],
467 ]
468 );
469
470 return $result;
471 }
472
473 /**
474 * get_image_to_image_mask
475 * @param $image_data
476 *
477 * @return mixed|\WP_Error
478 * @throws \Exception
479 */
480 public function get_image_to_image_mask( $image_data, $context, $request_ids ) {
481 $image_file = get_attached_file( $image_data['attachment_id'] );
482 $mask_file = $this->store_temp_file( $image_data['mask'], '.svg' );
483
484 if ( ! $image_file ) {
485 throw new \Exception( 'Image file not found' );
486 }
487
488 if ( ! $mask_file ) {
489 throw new \Exception( 'Mask file not found' );
490 }
491
492 $result = $this->ai_request(
493 'POST',
494 'image/image-to-image/inpainting',
495 [
496 self::PROMPT => $image_data[ self::PROMPT ],
497 self::IMAGE_TYPE => $image_data['promptSettings'][ self::IMAGE_TYPE ] . '/' . $image_data['promptSettings'][ self::STYLE_PRESET ],
498 self::IMAGE_STRENGTH => $image_data['promptSettings'][ self::IMAGE_STRENGTH ],
499 'context' => wp_json_encode( $context ),
500 'ids' => $request_ids,
501 'api_version' => ELEMENTOR_VERSION,
502 'site_lang' => get_bloginfo( 'language' ),
503 ],
504 [
505 [
506 'name' => 'image',
507 'type' => 'image',
508 'path' => $image_file,
509 ],
510 [
511 'name' => 'mask_image',
512 'type' => 'image/svg+xml',
513 'path' => $mask_file,
514 ],
515 ]
516 );
517
518 return $result;
519 }
520
521 public function generate_layout( $data, $context ) {
522 $endpoint = 'generate/layout';
523
524 $body = [
525 'prompt' => $data['prompt'],
526 'variationType' => (int) $data['variationType'],
527 'ids' => $data['ids'],
528 ];
529
530 if ( ! empty( $data['prevGeneratedIds'] ) ) {
531 $body['generatedBaseTemplatesIds'] = $data['prevGeneratedIds'];
532 }
533
534 if ( ! empty( $data['attachments'] ) ) {
535 $attachment = $data['attachments'][0];
536
537 switch ( $attachment['type'] ) {
538 case 'json':
539 $endpoint = 'generate/generate-json-variation';
540
541 $body['json'] = [
542 'type' => 'elementor',
543 'elements' => [ $attachment['content'] ],
544 'label' => $attachment['label'],
545 'source' => $attachment['source'],
546 ];
547 break;
548 case 'url':
549 $endpoint = 'generate/html-to-elementor';
550
551 $html = wp_json_encode( $attachment['content'] );
552
553 $body['html'] = $html;
554
555 break;
556 }
557 }
558
559 $context['currentContext'] = $data['currentContext'];
560
561 if ( ElementorUtils::has_pro() ) {
562 $context['features'] = [
563 'subscriptions' => [ 'Pro' ],
564 ];
565 }
566
567 $metadata = [
568 'context' => $context,
569 'api_version' => ELEMENTOR_VERSION,
570 'site_lang' => get_bloginfo( 'language' ),
571 'config' => [
572 'generate' => [
573 'all' => true,
574 ],
575 ],
576 ];
577
578 $body = array_merge( $body, $metadata );
579
580 // Temp hack for platforms that filters the http_request_args, and it breaks JSON requests.
581 remove_all_filters( 'http_request_args' );
582
583 return $this->ai_request(
584 'POST',
585 $endpoint,
586 $body,
587 false,
588 '',
589 'json'
590 );
591 }
592
593 public function get_layout_prompt_enhanced( $prompt, $enhance_type, $context ) {
594 return $this->ai_request(
595 'POST',
596 'generate/enhance-prompt',
597 [
598 'prompt' => $prompt,
599 'enhance_type' => $enhance_type,
600 'context' => wp_json_encode( $context ),
601 'api_version' => ELEMENTOR_VERSION,
602 'site_lang' => get_bloginfo( 'language' ),
603 ]
604 );
605 }
606
607 public function get_history_by_type( $type, $page, $limit, $context = [] ) {
608 $endpoint = Module::HISTORY_TYPE_ALL === $type
609 ? 'history'
610 : add_query_arg( [
611 'page' => $page,
612 'limit' => $limit,
613 ], "history/{$type}" );
614
615 return $this->ai_request(
616 'POST',
617 $endpoint,
618 [
619 'context' => wp_json_encode( $context ),
620 'api_version' => ELEMENTOR_VERSION,
621 'site_lang' => get_bloginfo( 'language' ),
622 ]
623 );
624 }
625
626 public function delete_history_item( $id, $context = [] ) {
627 return $this->ai_request(
628 'DELETE', 'history/' . $id,
629 [
630 'context' => wp_json_encode( $context ),
631 'api_version' => ELEMENTOR_VERSION,
632 'site_lang' => get_bloginfo( 'language' ),
633 ]
634 );
635 }
636
637 public function toggle_favorite_history_item( $id, $context = [] ) {
638 return $this->ai_request(
639 'POST', sprintf( 'history/%s/favorite', $id ),
640 [
641 'context' => wp_json_encode( $context ),
642 'api_version' => ELEMENTOR_VERSION,
643 'site_lang' => get_bloginfo( 'language' ),
644 ]
645 );
646 }
647
648 protected function init() {}
649 }
650