PluginProbe
Elementor Website Builder – more than just a page builder / 3.13.3
Elementor Website Builder – more than just a page builder v3.13.3
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
← All changes | modules/ai/module.php +58 -1400 4.3.0-beta13.13.3 View file →
@@ -1,35 +1,17 @@
1 1 <?php
2 2 namespace Elementor\Modules\Ai;
3 3
4 -use Elementor\Controls_Manager;
5 4 use Elementor\Core\Base\Module as BaseModule;
6 5 use Elementor\Core\Common\Modules\Connect\Module as ConnectModule;
7 -use Elementor\Element_Base;
6 +use Elementor\Modules\Ai\Connect\Ai;
8 7 use Elementor\Plugin;
9 -use Elementor\Core\Utils\Collection;
10 -use Elementor\Modules\Ai\Connect\Ai;
11 8 use Elementor\User;
12 -use Elementor\Utils;
13 9
14 10 if ( ! defined( 'ABSPATH' ) ) {
15 11 exit; // Exit if accessed directly.
16 12 }
17 -
18 13 class Module extends BaseModule {
19 - const HISTORY_TYPE_ALL = 'all';
20 - const HISTORY_TYPE_TEXT = 'text';
21 - const HISTORY_TYPE_CODE = 'code';
22 - const HISTORY_TYPE_IMAGE = 'images';
23 - const HISTORY_TYPE_BLOCK = 'blocks';
24 - const VALID_HISTORY_TYPES = [
25 - self::HISTORY_TYPE_ALL,
26 - self::HISTORY_TYPE_TEXT,
27 - self::HISTORY_TYPE_CODE,
28 - self::HISTORY_TYPE_IMAGE,
29 - self::HISTORY_TYPE_BLOCK,
30 - ];
31 - const MIN_PAGES_FOR_CREATE_WITH_AI_BANNER = 10;
32 14
33 15 public function get_name() {
34 16 return 'ai';
35 17 }
@@ -36,505 +18,56 @@
36 18
37 19 public function __construct() {
38 20 parent::__construct();
39 21
40 - ( new SitePlannerConnect\Module() );
41 -
42 - if ( is_admin() ) {
43 - ( new Preferences() )->register();
44 - add_action( 'elementor/import-export/import-kit/runner/after-run', [ $this, 'handle_kit_install' ] );
45 - }
46 -
47 - if ( ! $this->is_ai_enabled() ) {
48 - return;
49 - }
50 -
51 - add_filter( 'elementor/core/admin/homescreen', [ $this, 'add_create_with_ai_banner_to_homescreen' ] );
52 -
53 22 add_action( 'elementor/connect/apps/register', function ( ConnectModule $connect_module ) {
54 23 $connect_module->register_app( 'ai', Ai::get_class_name() );
55 24 } );
56 25
57 26 add_action( 'elementor/ajax/register_actions', function( $ajax ) {
58 - $handlers = [
59 - 'ai_get_user_information' => [ $this, 'ajax_ai_get_user_information' ],
60 - 'ai_get_remote_config' => [ $this, 'ajax_ai_get_remote_config' ],
61 - 'ai_get_remote_frontend_config' => [ $this, 'ajax_ai_get_remote_frontend_config' ],
62 - 'ai_get_completion_text' => [ $this, 'ajax_ai_get_completion_text' ],
63 - 'ai_get_excerpt' => [ $this, 'ajax_ai_get_excerpt' ],
64 - 'ai_get_featured_image' => [ $this, 'ajax_ai_get_featured_image' ],
65 - 'ai_get_edit_text' => [ $this, 'ajax_ai_get_edit_text' ],
66 - 'ai_get_custom_code' => [ $this, 'ajax_ai_get_custom_code' ],
67 - 'ai_get_custom_css' => [ $this, 'ajax_ai_get_custom_css' ],
68 - 'ai_set_get_started' => [ $this, 'ajax_ai_set_get_started' ],
69 - 'ai_set_status_feedback' => [ $this, 'ajax_ai_set_status_feedback' ],
70 - 'ai_get_image_prompt_enhancer' => [ $this, 'ajax_ai_get_image_prompt_enhancer' ],
71 - 'ai_get_text_to_image' => [ $this, 'ajax_ai_get_text_to_image' ],
72 - 'ai_get_image_to_image' => [ $this, 'ajax_ai_get_image_to_image' ],
73 - 'ai_get_image_to_image_mask' => [ $this, 'ajax_ai_get_image_to_image_mask' ],
74 - 'ai_get_image_to_image_mask_cleanup' => [ $this, 'ajax_ai_get_image_to_image_mask_cleanup' ],
75 - 'ai_get_image_to_image_outpainting' => [ $this, 'ajax_ai_get_image_to_image_outpainting' ],
76 - 'ai_get_image_to_image_upscale' => [ $this, 'ajax_ai_get_image_to_image_upscale' ],
77 - 'ai_get_image_to_image_remove_background' => [ $this, 'ajax_ai_get_image_to_image_remove_background' ],
78 - 'ai_get_image_to_image_replace_background' => [ $this, 'ajax_ai_get_image_to_image_replace_background' ],
79 - 'ai_upload_image' => [ $this, 'ajax_ai_upload_image' ],
80 - 'ai_generate_layout' => [ $this, 'ajax_ai_generate_layout' ],
81 - 'ai_get_layout_prompt_enhancer' => [ $this, 'ajax_ai_get_layout_prompt_enhancer' ],
82 - 'ai_get_history' => [ $this, 'ajax_ai_get_history' ],
83 - 'ai_delete_history_item' => [ $this, 'ajax_ai_delete_history_item' ],
84 - 'ai_toggle_favorite_history_item' => [ $this, 'ajax_ai_toggle_favorite_history_item' ],
85 - 'ai_get_product_image_unification' => [ $this, 'ajax_ai_get_product_image_unification' ],
86 - 'ai_get_animation' => [ $this, 'ajax_ai_get_animation' ],
87 - 'ai_get_image_to_image_isolate_objects' => [ $this, 'ajax_ai_get_product_image_unification' ],
88 - ];
89 -
90 - foreach ( $handlers as $tag => $callback ) {
91 - $ajax->register_ajax_action( $tag, $callback );
92 - }
27 + $ajax->register_ajax_action( 'ai_get_user_information', [ $this, 'ajax_ai_get_user_information' ] );
28 + $ajax->register_ajax_action( 'ai_get_completion_text', [ $this, 'ajax_ai_get_completion_text' ] );
29 + $ajax->register_ajax_action( 'ai_get_edit_text', [ $this, 'ajax_ai_get_edit_text' ] );
30 + $ajax->register_ajax_action( 'ai_get_custom_code', [ $this, 'ajax_ai_get_custom_code' ] );
31 + $ajax->register_ajax_action( 'ai_get_custom_css', [ $this, 'ajax_ai_get_custom_css' ] );
32 + $ajax->register_ajax_action( 'ai_set_get_started', [ $this, 'ajax_ai_set_get_started' ] );
33 + $ajax->register_ajax_action( 'ai_set_status_feedback', [ $this, 'ajax_ai_set_status_feedback' ] );
93 34 } );
94 35
95 36 add_action( 'elementor/editor/before_enqueue_scripts', function() {
96 - $this->enqueue_main_script();
97 - $this->enqueue_layout_script();
98 - } );
99 -
100 - add_action( 'elementor/editor/after_enqueue_styles', function() {
101 - wp_enqueue_style(
102 - 'elementor-ai-editor',
103 - $this->get_css_assets_url( 'modules/ai/editor' ),
104 - [],
105 - ELEMENTOR_VERSION
106 - );
107 - } );
108 -
109 - add_action( 'elementor/preview/enqueue_styles', function() {
110 - wp_enqueue_style(
111 - 'elementor-ai-layout-preview',
112 - $this->get_css_assets_url( 'modules/ai/layout-preview' ),
113 - [],
114 - ELEMENTOR_VERSION
115 - );
116 - } );
117 -
118 - if ( is_admin() ) {
119 - add_action( 'wp_enqueue_media', [ $this, 'enqueue_ai_media_library' ] );
120 - add_action( 'admin_head', [ $this, 'enqueue_ai_media_library_upload_screen' ] );
121 -
122 - if ( current_user_can( 'edit_products' ) || current_user_can( 'publish_products' ) ) {
123 - add_action( 'admin_init', [ $this, 'enqueue_ai_products_page_scripts' ] );
124 - add_action( 'current_screen', [ $this, 'enqueue_ai_single_product_page_scripts' ] );
125 - add_action( 'wp_ajax_elementor-ai-get-product-images', [ $this, 'get_product_images_ajax' ] );
126 - add_action( 'wp_ajax_elementor-ai-set-product-images', [ $this, 'set_product_images_ajax' ] );
127 - }
128 - }
129 -
130 - add_action( 'enqueue_block_editor_assets', function() {
131 - wp_enqueue_script( 'elementor-ai-gutenberg',
132 - $this->get_js_assets_url( 'ai-gutenberg' ),
37 + wp_enqueue_script(
38 + 'elementor-ai',
39 + $this->get_js_assets_url( 'ai' ),
133 40 [
134 - 'jquery',
135 - 'elementor-v2-ui',
136 - 'elementor-v2-icons',
137 - 'wp-blocks',
138 - 'wp-element',
139 - 'wp-editor',
140 - 'wp-data',
141 - 'wp-components',
142 - 'wp-compose',
143 - 'wp-i18n',
144 - 'wp-hooks',
145 - 'elementor-ai-media-library',
41 + 'elementor-common',
42 + 'elementor-editor-modules',
43 + 'elementor-editor-document',
44 + 'elementor-packages-ui',
45 + 'elementor-packages-icons',
146 46 ],
147 - ELEMENTOR_VERSION, true );
47 + ELEMENTOR_VERSION,
48 + true
49 + );
148 50
149 51 wp_localize_script(
150 - 'elementor-ai-gutenberg',
52 + 'elementor-ai',
151 53 'ElementorAiConfig',
152 54 [
153 55 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
154 - 'connect_url' => $this->get_ai_connect_url(),
155 56 ]
156 57 );
157 -
158 - wp_set_script_translations( 'elementor-ai-gutenberg', 'elementor' );
159 - });
160 -
161 - add_filter( 'elementor/document/save/data', function ( $data ) {
162 - return $this->remove_temporary_containers( $data );
163 58 } );
164 59
165 - add_action( 'elementor/element/common/section_effects/after_section_start', [ $this, 'register_ai_motion_effect_control' ], 10, 1 );
166 - add_action( 'elementor/element/container/section_effects/after_section_start', [ $this, 'register_ai_motion_effect_control' ], 10, 1 );
167 - add_action( 'elementor/element/common/_section_transform/after_section_end', [ $this, 'register_ai_hover_effect_control' ], 10, 1 );
168 - add_action( 'elementor/element/container/_section_transform/after_section_end', [ $this, 'register_ai_hover_effect_control' ], 10, 1 );
169 - }
170 -
171 - public function is_ai_enabled() {
172 - if ( ! Plugin::$instance->experiments->is_feature_active( 'container' ) ) {
173 - return false;
174 - }
175 -
176 - return Preferences::is_ai_enabled( get_current_user_id() );
177 - }
178 -
179 - public function handle_kit_install( $imported_data ) {
180 - if ( ! $this->is_ai_enabled() ) {
181 - return;
182 - }
183 -
184 - if ( ! isset( $imported_data['status'] ) || 'success' !== $imported_data['status'] ) {
185 - return;
186 - }
187 -
188 - if ( ! isset( $imported_data['runner'] ) || 'site-settings' !== $imported_data['runner'] ) {
189 - return;
190 - }
191 -
192 - if ( ! isset( $imported_data['configData']['lastImportedSession']['instance_data']['site_settings']['settings']['ai'] ) ) {
193 - return;
194 - }
195 -
196 - $is_connected = $this->get_ai_app()->is_connected() && User::get_introduction_meta( 'ai_get_started' );
197 -
198 - if ( ! $is_connected ) {
199 - return;
200 - }
201 -
202 - $last_imported_session = $imported_data['configData']['lastImportedSession'];
203 - $imported_ai_data = $last_imported_session['instance_data']['site_settings']['settings']['ai'];
204 -
205 - $this->get_ai_app()->send_event( [
206 - 'name' => 'kit_installed',
207 - 'data' => $imported_ai_data,
208 - 'client' => [
209 - 'name' => 'elementor',
210 - 'version' => ELEMENTOR_VERSION,
211 - 'session_id' => $last_imported_session['session_id'],
212 - ],
213 - ] );
214 - }
215 -
216 - public function register_ai_hover_effect_control( Element_Base $element ) {
217 - if ( ! $element->get_controls( 'ai_hover_animation' ) ) {
218 - $element->add_control(
219 - 'ai_hover_animation',
220 - [
221 - 'tabs_wrapper' => '_tabs_positioning',
222 - 'inner_tab' => '_tab_positioning_hover',
223 - 'label' => esc_html__( 'Animate With AI', 'elementor' ),
224 - 'type' => Controls_Manager::RAW_HTML,
225 - 'raw' => '
226 -<style>
227 - .elementor-control-ai_hover_animation .elementor-control-content {
228 - display: flex;
229 - flex-direction: row;
230 - justify-content: space-between;
231 - align-items: center;
232 - }
233 - .elementor-control-ai_hover_animation .elementor-control-raw-html {
234 - display: none;
235 - }
236 -</style>',
237 - 'render_type' => 'none',
238 - 'ai' => [
239 - 'active' => true,
240 - 'type' => 'hover_animation',
241 - ],
242 - ],
243 - [
244 - 'position' => [
245 - 'of' => '_transform_rotate_popover_hover',
246 - 'type' => 'control',
247 - 'at' => 'before',
248 - ],
249 - ]
60 + add_action( 'elementor/editor/after_enqueue_styles', function() {
61 + wp_enqueue_style(
62 + 'elementor-ai',
63 + $this->get_css_assets_url( 'modules/ai/editor' ),
64 + [],
65 + ELEMENTOR_VERSION
250 66 );
251 - }
252 - }
253 - public function register_ai_motion_effect_control( $element ) {
254 - if ( Utils::has_pro() && ! $element->get_controls( 'ai_animation' ) ) {
255 - $element->add_control(
256 - 'ai_animation',
257 - [
258 - 'label' => esc_html__( 'Animate With AI', 'elementor' ),
259 - 'type' => Controls_Manager::RAW_HTML,
260 - 'raw' => '
261 - <style>
262 - .elementor-control-ai_animation .elementor-control-content {
263 - display: flex;
264 - flex-direction: row;
265 - justify-content: space-between;
266 - align-items: center;
267 - }
268 - .elementor-control-ai_animation .elementor-control-raw-html {
269 - display: none;
270 - }
271 - </style>',
272 - 'render_type' => 'none',
273 - 'ai' => [
274 - 'active' => true,
275 - 'type' => 'animation',
276 - ],
277 - ]
278 - );
279 - }
280 - }
281 -
282 - private function get_current_screen() {
283 - $is_wc = class_exists( 'WooCommerce' ) && post_type_exists( 'product' );
284 - if ( ! $is_wc ) {
285 - return 'other';
286 - }
287 -
288 - $is_products_page = isset( $_GET['post_type'] ) && 'product' === $_GET['post_type'];
289 - if ( $is_products_page ) {
290 - return 'wc-products';
291 - }
292 -
293 - $screen = get_current_screen();
294 - $is_single_product_page = isset( $screen->post_type ) && ( 'product' === $screen->post_type && 'post' === $screen->base );
295 - if ( $is_single_product_page ) {
296 - return 'wc-single-product';
297 - }
298 -
299 - return 'other';
300 - }
301 -
302 - public function enqueue_ai_products_page_scripts() {
303 - if ( 'wc-products' !== $this->get_current_screen() ) {
304 - return;
305 - }
306 -
307 - $this->add_wc_scripts();
308 - }
309 -
310 - public function enqueue_ai_single_product_page_scripts() {
311 - if ( 'wc-single-product' !== $this->get_current_screen() ) {
312 - return;
313 - }
314 -
315 - $this->add_wc_scripts();
316 - }
317 -
318 - private function add_products_bulk_action( $bulk_actions ) {
319 - $bulk_actions['elementor-ai-unify-product-images'] = __( 'Unify with Elementor AI', 'elementor' );
320 - return $bulk_actions;
321 - }
322 -
323 - public function get_product_images_ajax() {
324 - check_ajax_referer( 'elementor-ai-unify-product-images_nonce', 'nonce' );
325 -
326 - $post_ids = isset( $_POST['post_ids'] ) ? array_map( 'intval', $_POST['post_ids'] ) : [];
327 - $is_galley_only = isset( $_POST['is_galley_only'] ) && sanitize_text_field( wp_unslash( $_POST['is_galley_only'] ) );
328 -
329 - $image_ids = [];
330 -
331 - foreach ( $post_ids as $post_id ) {
332 - if ( $is_galley_only ) {
333 - $product = wc_get_product( $post_id );
334 - $gallery_image_ids = $product->get_gallery_image_ids();
335 - foreach ( $gallery_image_ids as $image_id ) {
336 - $image_ids[] = [
337 - 'productId' => $post_id,
338 - 'id' => $image_id,
339 - 'image_url' => wp_get_attachment_url( $image_id ),
340 - ];
341 - }
342 - continue;
343 - }
344 -
345 - $image_id = get_post_thumbnail_id( $post_id );
346 -
347 - if ( ! $image_id ) {
348 - $product = wc_get_product( $post_id );
349 - $gallery_image_ids = $product->get_gallery_image_ids();
350 - if ( ! empty( $gallery_image_ids ) ) {
351 - $image_id = $gallery_image_ids[0];
352 - }
353 - }
354 -
355 - $image_ids[] = [
356 - 'productId' => $post_id,
357 - 'id' => $image_id ? $image_id : 'No Image',
358 - 'image_url' => $image_id ? wp_get_attachment_url( $image_id ) : 'No Image',
359 - ];
360 - }
361 -
362 - wp_send_json_success( [ 'product_images' => array_slice( $image_ids, 0, 10 ) ] );
363 -
364 - wp_die();
365 - }
366 -
367 - private function get_attachment_id_by_url( $url ) {
368 - $attachments = get_posts( [
369 - 'post_type' => 'attachment',
370 - 'meta_query' => [
371 - [
372 - 'key' => '_wp_attached_file',
373 - 'value' => basename( $url ),
374 - 'compare' => 'LIKE',
375 - ],
376 - ],
377 - 'fields' => 'ids',
378 - 'numberposts' => 1,
379 - ] );
380 -
381 - return ! empty( $attachments ) ? $attachments[0] : null;
382 - }
383 -
384 - public function set_product_images_ajax() {
385 - check_ajax_referer( 'elementor-ai-unify-product-images_nonce', 'nonce' );
386 -
387 - $product_id = isset( $_POST['productId'] ) ? sanitize_text_field( wp_unslash( $_POST['productId'] ) ) : '';
388 - $image_url = isset( $_POST['image_url'] ) ? sanitize_text_field( wp_unslash( $_POST['image_url'] ) ) : '';
389 - $image_to_add = isset( $_POST['image_to_add'] ) ? intval( wp_unslash( $_POST['image_to_add'] ) ) : null;
390 - $image_to_remove = isset( $_POST['image_to_remove'] ) ? intval( wp_unslash( $_POST['image_to_remove'] ) ) : null;
391 - $is_product_gallery = isset( $_POST['is_product_gallery'] ) && sanitize_text_field( wp_unslash( $_POST['is_product_gallery'] ) ) === 'true';
392 -
393 - if ( ! $product_id || ! $image_url ) {
394 - throw new \Exception( 'Product ID and Image URL are required' );
395 - }
396 -
397 - $product = wc_get_product( $product_id );
398 - if ( ! $product ) {
399 - throw new \Exception( 'Product not found' );
400 - }
401 -
402 - $attachment_id = $this->get_attachment_id_by_url( $image_url );
403 - if ( is_wp_error( $attachment_id ) ) {
404 - throw new \Exception( 'Image upload failed' );
405 - }
406 -
407 - if ( $is_product_gallery ) {
408 - $this->update_product_gallery( $product, $image_to_remove, $image_to_add );
409 - } else {
410 - $product->set_image_id( $attachment_id );
411 - $product->save();
412 - }
413 -
414 - wp_send_json_success( [
415 - 'message' => __( 'Image added successfully', 'elementor' ),
416 - ] );
417 - }
418 -
419 - public function enqueue_ai_media_library_upload_screen() {
420 - $screen = get_current_screen();
421 - if ( ! $screen || 'upload' !== $screen->id ) {
422 - return;
423 - }
424 -
425 - $this->enqueue_ai_media_library();
426 - }
427 -
428 - public function enqueue_ai_media_library() {
429 - wp_enqueue_script( 'elementor-ai-media-library',
430 - $this->get_js_assets_url( 'ai-media-library' ),
431 - [
432 - 'jquery',
433 - 'elementor-v2-ui',
434 - 'elementor-v2-icons',
435 - 'media-grid',
436 - ],
437 - ELEMENTOR_VERSION,
438 - true
439 - );
440 -
441 - wp_localize_script(
442 - 'elementor-ai-media-library',
443 - 'ElementorAiConfig',
444 - [
445 - 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
446 - 'connect_url' => $this->get_ai_connect_url(),
447 - ]
448 - );
449 -
450 - wp_set_script_translations( 'elementor-ai-media-library', 'elementor' );
451 - }
452 -
453 - private function enqueue_main_script() {
454 - wp_enqueue_script(
455 - 'elementor-ai',
456 - $this->get_js_assets_url( 'ai' ),
457 - [
458 - 'react',
459 - 'react-dom',
460 - 'backbone-marionette',
461 - 'elementor-web-cli',
462 - 'wp-date',
463 - 'elementor-common',
464 - 'elementor-editor-modules',
465 - 'elementor-editor-document',
466 - 'elementor-v2-ui',
467 - 'elementor-v2-icons',
468 - ],
469 - ELEMENTOR_VERSION,
470 - true
471 - );
472 -
473 - $config = [
474 - 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
475 - 'connect_url' => $this->get_ai_connect_url(),
476 - ];
477 -
478 - wp_localize_script(
479 - 'elementor-ai',
480 - 'ElementorAiConfig',
481 - $config
482 - );
483 -
484 - wp_set_script_translations( 'elementor-ai', 'elementor' );
485 - }
486 -
487 - private function enqueue_layout_script() {
488 - wp_enqueue_script(
489 - 'elementor-ai-layout',
490 - $this->get_js_assets_url( 'ai-layout' ),
491 - [
492 - 'react',
493 - 'react-dom',
494 - 'backbone-marionette',
495 - 'elementor-common',
496 - 'elementor-web-cli',
497 - 'elementor-editor-modules',
498 - 'elementor-ai',
499 - 'elementor-v2-ui',
500 - 'elementor-v2-icons',
501 - ],
502 - ELEMENTOR_VERSION,
503 - true
504 - );
505 -
506 - wp_set_script_translations( 'elementor-ai-layout', 'elementor' );
507 - }
508 -
509 - private function remove_temporary_containers( $data ) {
510 - if ( empty( $data['elements'] ) || ! is_array( $data['elements'] ) ) {
511 - return $data;
512 - }
513 -
514 - // If for some reason the document has been saved during an AI Layout session,
515 - // ensure that the temporary containers are removed from the data.
516 - $data['elements'] = array_filter( $data['elements'], function( $element ) {
517 - $is_preview_container = strpos( $element['id'], 'e-ai-preview-container' ) === 0;
518 - $is_screenshot_container = strpos( $element['id'], 'e-ai-screenshot-container' ) === 0;
519 -
520 - return ! $is_preview_container && ! $is_screenshot_container;
521 67 } );
522 -
523 - return $data;
524 68 }
525 69
526 - private function get_ai_connect_url() {
527 - $app = $this->get_ai_app();
528 -
529 - return $app->get_admin_url( 'authorize', [
530 - 'utm_source' => 'ai-popup',
531 - 'utm_campaign' => 'connect-account',
532 - 'utm_medium' => 'wp-dash',
533 - 'source' => 'generic',
534 - ] );
535 - }
536 -
537 70 public function ajax_ai_get_user_information( $data ) {
538 71 $app = $this->get_ai_app();
539 72
540 73 if ( ! $app->is_connected() ) {
@@ -539,9 +72,14 @@
539 72
540 73 if ( ! $app->is_connected() ) {
541 74 return [
542 75 'is_connected' => false,
543 - 'connect_url' => $this->get_ai_connect_url(),
76 + 'connect_url' => $app->get_admin_url( 'authorize', [
77 + 'utm_source' => 'ai-popup',
78 + 'utm_campaign' => 'connect-account',
79 + 'utm_medium' => 'wp-dash',
80 + 'source' => 'generic',
81 + ] ),
544 82 ];
545 83 }
546 84
547 85 $user_usage = wp_parse_args( $app->get_usage(), [
@@ -556,35 +94,8 @@
556 94 'usage' => $user_usage,
557 95 ];
558 96 }
559 97
560 - public function ajax_ai_get_remote_config() {
561 - $app = $this->get_ai_app();
562 -
563 - if ( ! $app->is_connected() ) {
564 - return [];
565 - }
566 -
567 - return $app->get_remote_config();
568 - }
569 -
570 - public function ajax_ai_get_remote_frontend_config( $data ) {
571 - $callback = function () use ( $data ) {
572 - return $this->get_ai_app()->get_remote_frontend_config( $data );
573 - };
574 -
575 - return Utils::get_cached_callback( $callback, 'ai_remote_frontend_config-' . get_current_user_id(), HOUR_IN_SECONDS );
576 - }
577 -
578 - public function verify_upload_permissions( $data ) {
579 - $referer = wp_get_referer();
580 -
581 - if ( str_contains( $referer, 'wp-admin/upload.php' ) && current_user_can( 'upload_files' ) ) {
582 - return;
583 - }
584 - $this->verify_permissions( $data['editor_post_id'] );
585 - }
586 -
587 98 private function verify_permissions( $editor_post_id ) {
588 99 $document = Plugin::$instance->documents->get( $editor_post_id );
589 100
590 101 if ( ! $document ) {
@@ -590,47 +101,19 @@
590 101 if ( ! $document ) {
591 102 throw new \Exception( 'Document not found' );
592 103 }
593 104
594 - if ( $document->is_built_with_elementor() ) {
595 - if ( ! $document->is_editable_by_current_user() ) {
596 - throw new \Exception( 'Access denied' );
597 - }
598 - } elseif ( ! current_user_can( 'edit_post', $editor_post_id ) ) {
599 - throw new \Exception( 'Access denied' );
105 + if ( ! $document->is_built_with_elementor() || ! $document->is_editable_by_current_user() ) {
106 + throw new \Exception( 'Access denied' );
600 107 }
601 108 }
602 109
603 - public function ajax_ai_get_image_prompt_enhancer( $data ) {
604 - $this->verify_upload_permissions( $data );
605 -
606 - $app = $this->get_ai_app();
607 -
608 - if ( empty( $data['prompt'] ) ) {
609 - throw new \Exception( 'Missing prompt' );
610 - }
611 -
612 - if ( ! $app->is_connected() ) {
613 - throw new \Exception( 'not_connected' );
614 - }
615 - $request_ids = $this->get_request_ids( $data['payload'] );
616 -
617 - $result = $app->get_image_prompt_enhanced( $data['prompt'], [], $request_ids );
618 - $this->throw_on_error( $result );
619 -
620 - return [
621 - 'text' => $result['text'],
622 - 'response_id' => $result['responseId'],
623 - 'usage' => $result['usage'],
624 - ];
625 - }
626 -
627 110 public function ajax_ai_get_completion_text( $data ) {
628 111 $this->verify_permissions( $data['editor_post_id'] );
629 112
630 113 $app = $this->get_ai_app();
631 114
632 - if ( empty( $data['payload']['prompt'] ) ) {
115 + if ( empty( $data['prompt'] ) ) {
633 116 throw new \Exception( 'Missing prompt' );
634 117 }
635 118
636 119 if ( ! $app->is_connected() ) {
@@ -636,41 +119,13 @@
636 119 if ( ! $app->is_connected() ) {
637 120 throw new \Exception( 'not_connected' );
638 121 }
639 122
640 - $context = $this->get_request_context( $data );
641 -
642 - $request_ids = $this->get_request_ids( $data['payload'] );
643 -
644 - $result = $app->get_completion_text( $data['payload']['prompt'], $context, $request_ids );
645 - $this->throw_on_error( $result );
646 -
647 - return [
648 - 'text' => $result['text'],
649 - 'response_id' => $result['responseId'],
650 - 'usage' => $result['usage'],
651 - ];
652 - }
653 -
654 -
655 - public function ajax_ai_get_excerpt( $data ): array {
656 - $app = $this->get_ai_app();
657 -
658 - if ( empty( $data['payload']['content'] ) ) {
659 - throw new \Exception( 'Missing content' );
123 + $result = $app->get_completion_text( $data['prompt'] );
124 + if ( is_wp_error( $result ) ) {
125 + throw new \Exception( $result->get_error_message() );
660 126 }
661 127
662 - if ( ! $app->is_connected() ) {
663 - throw new \Exception( 'Not connected' );
664 - }
665 -
666 - $context = $this->get_request_context( $data );
667 -
668 - $request_ids = $this->get_request_ids( $data['payload'] );
669 -
670 - $result = $app->get_excerpt( $data['payload']['content'], $context, $request_ids );
671 - $this->throw_on_error( $result );
672 -
673 128 return [
674 129 'text' => $result['text'],
675 130 'response_id' => $result['responseId'],
676 131 'usage' => $result['usage'],
@@ -676,65 +131,22 @@
676 131 'usage' => $result['usage'],
677 132 ];
678 133 }
679 134
680 - public function ajax_ai_get_featured_image( $data ): array {
681 - $this->verify_upload_permissions( $data );
682 -
683 - if ( empty( $data['payload']['prompt'] ) ) {
684 - throw new \Exception( 'Missing prompt' );
685 - }
686 -
687 - $app = $this->get_ai_app();
688 -
689 - if ( ! $app->is_connected() ) {
690 - throw new \Exception( 'not_connected' );
691 - }
692 -
693 - $context = $this->get_request_context( $data );
694 - $request_ids = $this->get_request_ids( $data['payload'] );
695 -
696 - $result = $app->get_featured_image( $data, $context, $request_ids );
697 -
698 - $this->throw_on_error( $result );
699 -
700 - return [
701 - 'images' => $result['images'],
702 - 'response_id' => $result['responseId'],
703 - 'usage' => $result['usage'],
704 - ];
705 - }
706 -
707 - private function get_ai_app(): Ai {
135 + private function get_ai_app() : Ai {
708 136 return Plugin::$instance->common->get_component( 'connect' )->get_app( 'ai' );
709 137 }
710 138
711 - private function get_request_context( $data ) {
712 - if ( empty( $data['context'] ) ) {
713 - return [];
714 - }
715 -
716 - return $data['context'];
717 - }
718 -
719 - private function get_request_ids( $data ) {
720 - if ( empty( $data['requestIds'] ) ) {
721 - return new \stdClass();
722 - }
723 -
724 - return $data['requestIds'];
725 - }
726 -
727 139 public function ajax_ai_get_edit_text( $data ) {
728 140 $this->verify_permissions( $data['editor_post_id'] );
729 141
730 142 $app = $this->get_ai_app();
731 143
732 - if ( empty( $data['payload']['input'] ) ) {
144 + if ( empty( $data['input'] ) ) {
733 145 throw new \Exception( 'Missing input' );
734 146 }
735 147
736 - if ( empty( $data['payload']['instruction'] ) ) {
148 + if ( empty( $data['instruction'] ) ) {
737 149 throw new \Exception( 'Missing instruction' );
738 150 }
739 151
740 152 if ( ! $app->is_connected() ) {
@@ -740,15 +152,13 @@
740 152 if ( ! $app->is_connected() ) {
741 153 throw new \Exception( 'not_connected' );
742 154 }
743 155
744 - $context = $this->get_request_context( $data );
156 + $result = $app->get_edit_text( $data['input'], $data['instruction'] );
157 + if ( is_wp_error( $result ) ) {
158 + throw new \Exception( $result->get_error_message() );
159 + }
745 160
746 - $request_ids = $this->get_request_ids( $data['payload'] );
747 -
748 - $result = $app->get_edit_text( $data, $context, $request_ids );
749 - $this->throw_on_error( $result );
750 -
751 161 return [
752 162 'text' => $result['text'],
753 163 'response_id' => $result['responseId'],
754 164 'usage' => $result['usage'],
@@ -757,13 +167,13 @@
757 167
758 168 public function ajax_ai_get_custom_code( $data ) {
759 169 $app = $this->get_ai_app();
760 170
761 - if ( empty( $data['payload']['prompt'] ) ) {
171 + if ( empty( $data['prompt'] ) ) {
762 172 throw new \Exception( 'Missing prompt' );
763 173 }
764 174
765 - if ( empty( $data['payload']['language'] ) ) {
175 + if ( empty( $data['language'] ) ) {
766 176 throw new \Exception( 'Missing language' );
767 177 }
768 178
769 179 if ( ! $app->is_connected() ) {
@@ -769,15 +179,13 @@
769 179 if ( ! $app->is_connected() ) {
770 180 throw new \Exception( 'not_connected' );
771 181 }
772 182
773 - $context = $this->get_request_context( $data );
183 + $result = $app->get_custom_code( $data['prompt'], $data['language'] );
184 + if ( is_wp_error( $result ) ) {
185 + throw new \Exception( $result->get_error_message() );
186 + }
774 187
775 - $request_ids = $this->get_request_ids( $data['payload'] );
776 -
777 - $result = $app->get_custom_code( $data, $context, $request_ids );
778 - $this->throw_on_error( $result );
779 -
780 188 return [
781 189 'text' => $result['text'],
782 190 'response_id' => $result['responseId'],
783 191 'usage' => $result['usage'],
@@ -788,17 +196,17 @@
788 196 $this->verify_permissions( $data['editor_post_id'] );
789 197
790 198 $app = $this->get_ai_app();
791 199
792 - if ( empty( $data['payload']['prompt'] ) ) {
200 + if ( empty( $data['prompt'] ) ) {
793 201 throw new \Exception( 'Missing prompt' );
794 202 }
795 203
796 - if ( empty( $data['payload']['html_markup'] ) ) {
204 + if ( empty( $data['html_markup'] ) ) {
797 205 $data['html_markup'] = '';
798 206 }
799 207
800 - if ( empty( $data['payload']['element_id'] ) ) {
208 + if ( empty( $data['element_id'] ) ) {
801 209 throw new \Exception( 'Missing element_id' );
802 210 }
803 211
804 212 if ( ! $app->is_connected() ) {
@@ -804,14 +212,13 @@
804 212 if ( ! $app->is_connected() ) {
805 213 throw new \Exception( 'not_connected' );
806 214 }
807 215
808 - $context = $this->get_request_context( $data );
809 - $request_ids = $this->get_request_ids( $data['payload'] );
216 + $result = $app->get_custom_css( $data['prompt'], $data['html_markup'], $data['element_id'] );
217 + if ( is_wp_error( $result ) ) {
218 + throw new \Exception( $result->get_error_message() );
219 + }
810 220
811 - $result = $app->get_custom_css( $data, $context, $request_ids );
812 - $this->throw_on_error( $result );
813 -
814 221 return [
815 222 'text' => $result['text'],
816 223 'response_id' => $result['responseId'],
817 224 'usage' => $result['usage'],
@@ -841,755 +248,6 @@
841 248
842 249 $app->set_status_feedback( $data['response_id'] );
843 250
844 251 return [];
845 - }
846 -
847 - public function ajax_ai_get_text_to_image( $data ) {
848 - $this->verify_upload_permissions( $data );
849 -
850 - if ( empty( $data['payload']['prompt'] ) ) {
851 - throw new \Exception( 'Missing prompt' );
852 - }
853 -
854 - $app = $this->get_ai_app();
855 -
856 - if ( ! $app->is_connected() ) {
857 - throw new \Exception( 'not_connected' );
858 - }
859 -
860 - $context = $this->get_request_context( $data );
861 - $request_ids = $this->get_request_ids( $data['payload'] );
862 -
863 - $result = $app->get_text_to_image( $data, $context, $request_ids );
864 -
865 - $this->throw_on_error( $result );
866 -
867 - return [
868 - 'images' => $result['images'],
869 - 'response_id' => $result['responseId'],
870 - 'usage' => $result['usage'],
871 - ];
872 - }
873 -
874 - public function ajax_ai_get_image_to_image( $data ) {
875 - $this->verify_upload_permissions( $data );
876 -
877 - $app = $this->get_ai_app();
878 -
879 - if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
880 - throw new \Exception( 'Missing Image' );
881 - }
882 -
883 - if ( empty( $data['payload']['settings'] ) ) {
884 - throw new \Exception( 'Missing prompt settings' );
885 - }
886 -
887 - if ( ! $app->is_connected() ) {
888 - throw new \Exception( 'not_connected' );
889 - }
890 -
891 - $context = $this->get_request_context( $data );
892 - $request_ids = $this->get_request_ids( $data['payload'] );
893 -
894 - $result = $app->get_image_to_image( [
895 - 'prompt' => $data['payload']['prompt'],
896 - 'promptSettings' => $data['payload']['settings'],
897 - 'attachment_id' => $data['payload']['image']['id'],
898 - ], $context, $request_ids );
899 -
900 - $this->throw_on_error( $result );
901 -
902 - return [
903 - 'images' => $result['images'],
904 - 'response_id' => $result['responseId'],
905 - 'usage' => $result['usage'],
906 - ];
907 - }
908 -
909 - public function ajax_ai_get_image_to_image_upscale( $data ) {
910 - $this->verify_upload_permissions( $data );
911 -
912 - $app = $this->get_ai_app();
913 -
914 - if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
915 - throw new \Exception( 'Missing Image' );
916 - }
917 -
918 - if ( empty( $data['payload']['promptSettings'] ) ) {
919 - throw new \Exception( 'Missing prompt settings' );
920 - }
921 -
922 - if ( ! $app->is_connected() ) {
923 - throw new \Exception( 'not_connected' );
924 - }
925 -
926 - $context = $this->get_request_context( $data );
927 - $request_ids = $this->get_request_ids( $data['payload'] );
928 -
929 - $result = $app->get_image_to_image_upscale( [
930 - 'promptSettings' => $data['payload']['promptSettings'],
931 - 'attachment_id' => $data['payload']['image']['id'],
932 - ], $context, $request_ids );
933 -
934 - $this->throw_on_error( $result );
935 -
936 - return [
937 - 'images' => $result['images'],
938 - 'response_id' => $result['responseId'],
939 - 'usage' => $result['usage'],
940 - ];
941 - }
942 -
943 - public function ajax_ai_get_image_to_image_replace_background( $data ) {
944 - $this->verify_upload_permissions( $data );
945 -
946 - $app = $this->get_ai_app();
947 -
948 - if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
949 - throw new \Exception( 'Missing Image' );
950 - }
951 -
952 - if ( empty( $data['payload']['prompt'] ) ) {
953 - throw new \Exception( 'Prompt Missing' );
954 - }
955 -
956 - if ( ! $app->is_connected() ) {
957 - throw new \Exception( 'not_connected' );
958 - }
959 -
960 - $context = $this->get_request_context( $data );
961 - $request_ids = $this->get_request_ids( $data['payload'] );
962 -
963 - $result = $app->get_image_to_image_replace_background( [
964 - 'attachment_id' => $data['payload']['image']['id'],
965 - 'prompt' => $data['payload']['prompt'],
966 - ], $context, $request_ids );
967 -
968 - $this->throw_on_error( $result );
969 -
970 - return [
971 - 'images' => $result['images'],
972 - 'response_id' => $result['responseId'],
973 - 'usage' => $result['usage'],
974 - ];
975 - }
976 -
977 - public function ajax_ai_get_image_to_image_remove_background( $data ) {
978 - $this->verify_upload_permissions( $data );
979 -
980 - $app = $this->get_ai_app();
981 -
982 - if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
983 - throw new \Exception( 'Missing Image' );
984 - }
985 -
986 - if ( ! $app->is_connected() ) {
987 - throw new \Exception( 'not_connected' );
988 - }
989 -
990 - $context = $this->get_request_context( $data );
991 - $request_ids = $this->get_request_ids( $data['payload'] );
992 - $result = $app->get_image_to_image_remove_background( [
993 - 'attachment_id' => $data['payload']['image']['id'],
994 - ], $context, $request_ids );
995 -
996 - $this->throw_on_error( $result );
997 -
998 - return [
999 - 'images' => $result['images'],
1000 - 'response_id' => $result['responseId'],
1001 - 'usage' => $result['usage'],
1002 - ];
1003 - }
1004 -
1005 - public function ajax_ai_get_image_to_image_mask( $data ) {
1006 - $this->verify_upload_permissions( $data );
1007 -
1008 - $app = $this->get_ai_app();
1009 -
1010 - if ( empty( $data['payload']['prompt'] ) ) {
1011 - throw new \Exception( 'Missing prompt' );
1012 - }
1013 -
1014 - if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
1015 - throw new \Exception( 'Missing Image' );
1016 - }
1017 -
1018 - if ( empty( $data['payload']['settings'] ) ) {
1019 - throw new \Exception( 'Missing prompt settings' );
1020 - }
1021 -
1022 - if ( empty( $data['payload']['mask'] ) ) {
1023 - throw new \Exception( 'Missing Mask' );
1024 - }
1025 -
1026 - $context = $this->get_request_context( $data );
1027 - $request_ids = $this->get_request_ids( $data['payload'] );
1028 -
1029 - $result = $app->get_image_to_image_mask( [
1030 - 'prompt' => $data['payload']['prompt'],
1031 - 'attachment_id' => $data['payload']['image']['id'],
1032 - 'mask' => $data['payload']['mask'],
1033 - ], $context, $request_ids );
1034 -
1035 - $this->throw_on_error( $result );
1036 -
1037 - return [
1038 - 'images' => $result['images'],
1039 - 'response_id' => $result['responseId'],
1040 - 'usage' => $result['usage'],
1041 - ];
1042 - }
1043 - public function ajax_ai_get_image_to_image_mask_cleanup( $data ) {
1044 - $this->verify_upload_permissions( $data );
1045 -
1046 - $app = $this->get_ai_app();
1047 -
1048 - if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
1049 - throw new \Exception( 'Missing Image' );
1050 - }
1051 -
1052 - if ( empty( $data['payload']['settings'] ) ) {
1053 - throw new \Exception( 'Missing prompt settings' );
1054 - }
1055 -
1056 - if ( ! $app->is_connected() ) {
1057 - throw new \Exception( 'not_connected' );
1058 - }
1059 -
1060 - if ( empty( $data['payload']['mask'] ) ) {
1061 - throw new \Exception( 'Missing Mask' );
1062 - }
1063 -
1064 - $context = $this->get_request_context( $data );
1065 - $request_ids = $this->get_request_ids( $data['payload'] );
1066 -
1067 - $result = $app->get_image_to_image_mask_cleanup( [
1068 - 'attachment_id' => $data['payload']['image']['id'],
1069 - 'mask' => $data['payload']['mask'],
1070 - ], $context, $request_ids );
1071 -
1072 - $this->throw_on_error( $result );
1073 -
1074 - return [
1075 - 'images' => $result['images'],
1076 - 'response_id' => $result['responseId'],
1077 - 'usage' => $result['usage'],
1078 - ];
1079 - }
1080 -
1081 - public function ajax_ai_get_image_to_image_outpainting( $data ) {
1082 - $this->verify_upload_permissions( $data );
1083 -
1084 - $app = $this->get_ai_app();
1085 -
1086 - if ( ! $app->is_connected() ) {
1087 - throw new \Exception( 'not_connected' );
1088 - }
1089 -
1090 - if ( empty( $data['payload']['mask'] ) ) {
1091 - throw new \Exception( 'Missing Expended Image' );
1092 - }
1093 -
1094 - $context = $this->get_request_context( $data );
1095 - $request_ids = $this->get_request_ids( $data['payload'] );
1096 - $result = $app->get_image_to_image_out_painting( [
1097 - 'size' => $data['payload']['size'],
1098 - 'position' => $data['payload']['position'],
1099 - 'mask' => $data['payload']['mask'],
1100 - 'image_base64' => $data['payload']['image_base64'],
1101 - ], $context, $request_ids );
1102 -
1103 - $this->throw_on_error( $result );
1104 -
1105 - return [
1106 - 'images' => $result['images'],
1107 - 'response_id' => $result['responseId'],
1108 - 'usage' => $result['usage'],
1109 - ];
1110 - }
1111 -
1112 - public function ajax_ai_upload_image( $data ) {
1113 - $this->verify_upload_permissions( $data );
1114 -
1115 - if ( empty( $data['image'] ) ) {
1116 - throw new \Exception( 'Missing image data' );
1117 - }
1118 -
1119 - $image = $data['image'];
1120 -
1121 - if ( empty( $image['image_url'] ) ) {
1122 - throw new \Exception( 'Missing image_url' );
1123 - }
1124 -
1125 - $image_data = $this->upload_image( $image['image_url'], $data['prompt'], $data['editor_post_id'] );
1126 -
1127 - if ( is_wp_error( $image_data ) ) {
1128 - throw new \Exception( esc_html( $image_data->get_error_message() ) );
1129 - }
1130 -
1131 - if ( ! empty( $image['use_gallery_image'] ) && ! empty( $image['id'] ) ) {
1132 - $app = $this->get_ai_app();
1133 - $app->set_used_gallery_image( $image['id'] );
1134 - }
1135 -
1136 - return [
1137 - 'image' => array_merge( $image_data, $data ),
1138 - ];
1139 - }
1140 -
1141 - public function ajax_ai_generate_layout( $data ) {
1142 - $this->verify_permissions( $data['editor_post_id'] );
1143 -
1144 - $app = $this->get_ai_app();
1145 -
1146 - if ( empty( $data['prompt'] ) && empty( $data['attachments'] ) ) {
1147 - throw new \Exception( 'Missing prompt / attachments' );
1148 - }
1149 -
1150 - if ( ! $app->is_connected() ) {
1151 - throw new \Exception( 'not_connected' );
1152 - }
1153 -
1154 - $result = $app->generate_layout(
1155 - $data,
1156 - $this->prepare_generate_layout_context( $data )
1157 - );
1158 -
1159 - if ( is_wp_error( $result ) ) {
1160 - $message = $result->get_error_message();
1161 -
1162 - if ( is_array( $message ) ) {
1163 - $message = implode( ', ', $message );
1164 - throw new \Exception( esc_html( $message ) );
1165 - }
1166 -
1167 - $this->throw_on_error( $result );
1168 - }
1169 -
1170 - $elements = $result['text']['elements'] ?? [];
1171 - $base_template_id = $result['baseTemplateId'] ?? null;
1172 - $template_type = $result['templateType'] ?? null;
1173 -
1174 - if ( empty( $elements ) || ! is_array( $elements ) ) {
1175 - throw new \Exception( 'unknown_error' );
1176 - }
1177 -
1178 - if ( 1 === count( $elements ) ) {
1179 - $template = $elements[0];
1180 - } else {
1181 - $template = [
1182 - 'elType' => 'container',
1183 - 'elements' => $elements,
1184 - 'settings' => [
1185 - 'content_width' => 'full',
1186 - 'flex_gap' => [
1187 - 'column' => '0',
1188 - 'row' => '0',
1189 - 'unit' => 'px',
1190 - ],
1191 - 'padding' => [
1192 - 'unit' => 'px',
1193 - 'top' => '0',
1194 - 'right' => '0',
1195 - 'bottom' => '0',
1196 - 'left' => '0',
1197 - 'isLinked' => true,
1198 - ],
1199 - ],
1200 - ];
1201 - }
1202 -
1203 - return [
1204 - 'all' => [],
1205 - 'text' => $template,
1206 - 'response_id' => $result['responseId'],
1207 - 'usage' => $result['usage'],
1208 - 'base_template_id' => $base_template_id,
1209 - 'template_type' => $template_type,
1210 - ];
1211 - }
1212 -
1213 - public function ajax_ai_get_layout_prompt_enhancer( $data ) {
1214 - $this->verify_permissions( $data['editor_post_id'] );
1215 -
1216 - $app = $this->get_ai_app();
1217 -
1218 - if ( empty( $data['prompt'] ) ) {
1219 - throw new \Exception( 'Missing prompt' );
1220 - }
1221 -
1222 - if ( ! $app->is_connected() ) {
1223 - throw new \Exception( 'not_connected' );
1224 - }
1225 -
1226 - $result = $app->get_layout_prompt_enhanced(
1227 - $data['prompt'],
1228 - $data['enhance_type'],
1229 - $this->prepare_generate_layout_context( $data )
1230 - );
1231 -
1232 - $this->throw_on_error( $result );
1233 -
1234 - return [
1235 - 'text' => $result['text'] ?? $data['prompt'],
1236 - 'response_id' => $result['responseId'] ?? '',
1237 - 'usage' => $result['usage'] ?? '',
1238 - ];
1239 - }
1240 -
1241 - private function prepare_generate_layout_context( $data ) {
1242 - $request_context = $this->get_request_context( $data );
1243 - $kit = Plugin::$instance->kits_manager->get_active_kit();
1244 -
1245 - if ( ! $kit ) {
1246 - return $request_context;
1247 - }
1248 -
1249 - $kits_data = Collection::make( $kit->get_data()['settings'] ?? [] );
1250 -
1251 - $colors = $kits_data
1252 - ->filter( function ( $_, $key ) {
1253 - return in_array( $key, [ 'system_colors', 'custom_colors' ], true );
1254 - } )
1255 - ->flatten()
1256 - ->filter( function ( $val ) {
1257 - return ! empty( $val['_id'] );
1258 - } )
1259 - ->map( function ( $val ) {
1260 - return [
1261 - 'id' => $val['_id'],
1262 - 'label' => $val['title'] ?? null,
1263 - 'value' => $val['color'] ?? null,
1264 - ];
1265 - } );
1266 -
1267 - $typography = $kits_data
1268 - ->filter( function ( $_, $key ) {
1269 - return in_array( $key, [ 'system_typography', 'custom_typography' ], true );
1270 - } )
1271 - ->flatten()
1272 - ->filter( function ( $val ) {
1273 - return ! empty( $val['_id'] );
1274 - } )
1275 - ->map( function ( $val ) {
1276 - $font_size = null;
1277 -
1278 - if ( isset(
1279 - $val['typography_font_size']['unit'],
1280 - $val['typography_font_size']['size']
1281 - ) ) {
1282 - $prop = $val['typography_font_size'];
1283 -
1284 - $font_size = 'custom' === $prop['unit']
1285 - ? $prop['size']
1286 - : $prop['size'] . $prop['unit'];
1287 - }
1288 -
1289 - return [
1290 - 'id' => $val['_id'],
1291 - 'label' => $val['title'] ?? null,
1292 - 'value' => [
1293 - 'family' => $val['typography_font_family'] ?? null,
1294 - 'weight' => $val['typography_font_weight'] ?? null,
1295 - 'style' => $val['typography_font_style'] ?? null,
1296 - 'size' => $font_size,
1297 - ],
1298 - ];
1299 - } );
1300 -
1301 - $request_context['globals'] = [
1302 - 'colors' => $colors->all(),
1303 - 'typography' => $typography->all(),
1304 - ];
1305 -
1306 - return $request_context;
1307 - }
1308 -
1309 - private function upload_image( $image_url, $image_title, $parent_post_id = 0 ) {
1310 - if ( ! current_user_can( 'upload_files' ) ) {
1311 - throw new \Exception( 'Not Allowed to Upload images' );
1312 - }
1313 -
1314 - $uploads_manager = new \Elementor\Core\Files\Uploads_Manager();
1315 - if ( $uploads_manager::are_unfiltered_uploads_enabled() ) {
1316 - Plugin::$instance->uploads_manager->set_elementor_upload_state( true );
1317 - add_filter( 'wp_handle_sideload_prefilter', [ Plugin::$instance->uploads_manager, 'handle_elementor_upload' ] );
1318 - add_filter( 'image_sideload_extensions', function( $extensions ) {
1319 - $extensions[] = 'svg';
1320 - return $extensions;
1321 - });
1322 - }
1323 -
1324 - $attachment_id = media_sideload_image( $image_url, $parent_post_id, $image_title, 'id' );
1325 -
1326 - if ( is_wp_error( $attachment_id ) ) {
1327 - return new \WP_Error( 'upload_error', $attachment_id->get_error_message() );
1328 - }
1329 -
1330 - if ( ! empty( $attachment_id['error'] ) ) {
1331 - return new \WP_Error( 'upload_error', $attachment_id['error'] );
1332 - }
1333 -
1334 - return [
1335 - 'id' => $attachment_id,
1336 - 'url' => esc_url( wp_get_attachment_image_url( $attachment_id, 'full' ) ),
1337 - 'alt' => esc_attr( $image_title ),
1338 - 'source' => 'library',
1339 - ];
1340 - }
1341 -
1342 - public function ajax_ai_get_history( $data ): array {
1343 - $type = $data['type'] ?? self::HISTORY_TYPE_ALL;
1344 -
1345 - if ( ! in_array( $type, self::VALID_HISTORY_TYPES, true ) ) {
1346 - throw new \Exception( 'Invalid history type' );
1347 - }
1348 -
1349 - $page = sanitize_text_field( $data['page'] ?? 1 );
1350 - $limit = sanitize_text_field( $data['limit'] ?? 10 );
1351 -
1352 - $app = $this->get_ai_app();
1353 -
1354 - if ( ! $app->is_connected() ) {
1355 - throw new \Exception( 'not_connected' );
1356 - }
1357 -
1358 - $context = $this->get_request_context( $data );
1359 -
1360 - $result = $app->get_history_by_type( $type, $page, $limit, $context );
1361 -
1362 - if ( is_wp_error( $result ) ) {
1363 - throw new \Exception( esc_html( $result->get_error_message() ) );
1364 - }
1365 -
1366 - return $result;
1367 - }
1368 -
1369 - public function ajax_ai_delete_history_item( $data ): array {
1370 - if ( empty( $data['id'] ) || ! wp_is_uuid( $data['id'] ) ) {
1371 - throw new \Exception( 'Missing id parameter' );
1372 - }
1373 -
1374 - $app = $this->get_ai_app();
1375 -
1376 - if ( ! $app->is_connected() ) {
1377 - throw new \Exception( 'not_connected' );
1378 - }
1379 -
1380 - $context = $this->get_request_context( $data );
1381 -
1382 - $result = $app->delete_history_item( $data['id'], $context );
1383 -
1384 - if ( is_wp_error( $result ) ) {
1385 - throw new \Exception( esc_html( $result->get_error_message() ) );
1386 - }
1387 -
1388 - return [];
1389 - }
1390 -
1391 - public function ajax_ai_toggle_favorite_history_item( $data ): array {
1392 - if ( empty( $data['id'] ) || ! wp_is_uuid( $data['id'] ) ) {
1393 - throw new \Exception( 'Missing id parameter' );
1394 - }
1395 -
1396 - $app = $this->get_ai_app();
1397 -
1398 - if ( ! $app->is_connected() ) {
1399 - throw new \Exception( 'not_connected' );
1400 - }
1401 -
1402 - $context = $this->get_request_context( $data );
1403 -
1404 - $result = $app->toggle_favorite_history_item( $data['id'], $context );
1405 -
1406 - if ( is_wp_error( $result ) ) {
1407 - throw new \Exception( esc_html( $result->get_error_message() ) );
1408 - }
1409 -
1410 - return [];
1411 - }
1412 -
1413 - public function ajax_ai_get_product_image_unification( $data ): array {
1414 - if ( ! empty( $data['payload']['postId'] ) ) {
1415 - $data['editor_post_id'] = $data['payload']['postId'];
1416 - }
1417 - $this->verify_upload_permissions( $data );
1418 -
1419 - $app = $this->get_ai_app();
1420 -
1421 - if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
1422 - throw new \Exception( 'Missing Image' );
1423 - }
1424 -
1425 - if ( empty( $data['payload']['settings'] ) ) {
1426 - throw new \Exception( 'Missing prompt settings' );
1427 - }
1428 -
1429 - if ( ! $app->is_connected() ) {
1430 - throw new \Exception( 'not_connected' );
1431 - }
1432 -
1433 - $context = $this->get_request_context( $data );
1434 - $request_ids = $this->get_request_ids( $data['payload'] );
1435 -
1436 - $result = $app->get_unify_product_images( [
1437 - 'promptSettings' => $data['payload']['settings'],
1438 - 'attachment_id' => $data['payload']['image']['id'],
1439 - 'featureIdentifier' => $data['payload']['featureIdentifier'] ?? '',
1440 - ], $context, $request_ids );
1441 -
1442 - $this->throw_on_error( $result );
1443 -
1444 - return [
1445 - 'images' => $result['images'],
1446 - 'response_id' => $result['responseId'],
1447 - 'usage' => $result['usage'],
1448 - ];
1449 - }
1450 -
1451 - public function ajax_ai_get_animation( $data ): array {
1452 - $this->verify_upload_permissions( $data );
1453 -
1454 - $app = $this->get_ai_app();
1455 -
1456 - if ( empty( $data['payload']['prompt'] ) ) {
1457 - throw new \Exception( 'Missing prompt' );
1458 - }
1459 -
1460 - if ( empty( $data['payload']['motionEffectType'] ) ) {
1461 - throw new \Exception( 'Missing animation type' );
1462 - }
1463 -
1464 - if ( ! $app->is_connected() ) {
1465 - throw new \Exception( 'not_connected' );
1466 - }
1467 -
1468 - $context = $this->get_request_context( $data );
1469 -
1470 - $request_ids = $this->get_request_ids( $data['payload'] );
1471 -
1472 - $result = $app->get_animation( $data, $context, $request_ids );
1473 - $this->throw_on_error( $result );
1474 -
1475 - return [
1476 - 'text' => $result['text'],
1477 - 'response_id' => $result['responseId'],
1478 - 'usage' => $result['usage'],
1479 - ];
1480 - }
1481 -
1482 - /**
1483 - * @param mixed $result
1484 - */
1485 - private function throw_on_error( $result ): void {
1486 - if ( is_wp_error( $result ) ) {
1487 - wp_send_json_error( [
1488 - 'message' => esc_html( $result->get_error_message() ),
1489 - 'extra_data' => $result->get_error_data(),
1490 - ] );
1491 - }
1492 - }
1493 -
1494 - /**
1495 - * @return void
1496 - */
1497 - public function add_wc_scripts(): void {
1498 - wp_enqueue_script( 'elementor-ai-unify-product-images',
1499 - $this->get_js_assets_url( 'ai-unify-product-images' ),
1500 - [
1501 - 'jquery',
1502 - 'elementor-v2-ui',
1503 - 'elementor-v2-icons',
1504 - 'wp-components',
1505 - 'elementor-common',
1506 - ],
1507 - ELEMENTOR_VERSION,
1508 - true
1509 - );
1510 -
1511 - wp_localize_script(
1512 - 'elementor-ai-unify-product-images',
1513 - 'UnifyProductImagesConfig',
1514 - [
1515 - 'get_product_images_url' => admin_url( 'admin-ajax.php' ),
1516 - 'set_product_images_url' => admin_url( 'admin-ajax.php' ),
1517 - 'nonce' => wp_create_nonce( 'elementor-ai-unify-product-images_nonce' ),
1518 - 'placeholder' => ELEMENTOR_ASSETS_URL . 'images/app/ai/product-image-unification-example.gif?' . ELEMENTOR_VERSION,
1519 - 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
1520 - 'connect_url' => $this->get_ai_connect_url(),
1521 - ]
1522 - );
1523 -
1524 - add_filter( 'bulk_actions-edit-product', function ( $data ) {
1525 - return $this->add_products_bulk_action( $data );
1526 - });
1527 -
1528 - wp_set_script_translations( 'elementor-ai-unify-product-images', 'elementor' );
1529 - }
1530 -
1531 - /**
1532 - * @param $product
1533 - * @param int|null $image_to_remove
1534 - * @param int|null $image_to_add
1535 - * @return void
1536 - */
1537 - private function update_product_gallery( $product, ?int $image_to_remove, ?int $image_to_add ): void {
1538 - $gallery_image_ids = $product->get_gallery_image_ids();
1539 -
1540 - $index = array_search( $image_to_remove, $gallery_image_ids, true );
1541 - if ( false !== $index ) {
1542 - unset( $gallery_image_ids[ $index ] );
1543 - }
1544 -
1545 - if ( ! in_array( $image_to_add, $gallery_image_ids, true ) ) {
1546 - $gallery_image_ids[] = $image_to_add;
1547 - }
1548 -
1549 - $product->set_gallery_image_ids( $gallery_image_ids );
1550 - $product->save();
1551 - }
1552 -
1553 - private function should_display_create_with_ai_banner() {
1554 - $elementor_pages = new \WP_Query( [
1555 - 'post_type' => 'page',
1556 - 'post_status' => 'publish',
1557 - 'fields' => 'ids',
1558 - 'posts_per_page' => self::MIN_PAGES_FOR_CREATE_WITH_AI_BANNER + 1,
1559 - ] );
1560 -
1561 - if ( $elementor_pages->post_count > self::MIN_PAGES_FOR_CREATE_WITH_AI_BANNER ) {
1562 - return false;
1563 - }
1564 -
1565 - if ( Utils::is_custom_kit_applied() ) {
1566 - return false;
1567 - }
1568 -
1569 - return true;
1570 - }
1571 -
1572 - private function get_create_with_ai_banner_data() {
1573 - return [
1574 - 'title' => 'Create and launch your site faster with AI',
1575 - 'description' => 'Share your vision with our AI Chat and watch as it becomes a brief, sitemap, and wireframes in minutes:',
1576 - 'input_placeholder' => 'Start describing the site you want to create...',
1577 - 'button_title' => 'Create with AI',
1578 - 'button_cta_url' => 'http://planner.elementor.com/chat.html',
1579 - 'background_image' => ELEMENTOR_ASSETS_URL . 'images/app/ai/ai-site-creator-homepage-bg.svg',
1580 - 'utm_source' => 'editor-home',
1581 - 'utm_medium' => 'wp-dash',
1582 - 'utm_campaign' => 'generate-with-ai',
1583 - ];
1584 - }
1585 -
1586 - public function add_create_with_ai_banner_to_homescreen( $home_screen_data ) {
1587 - if ( $this->should_display_create_with_ai_banner() ) {
1588 - $home_screen_data['create_with_ai'] = $this->get_create_with_ai_banner_data();
1589 - } else {
1590 - $home_screen_data['create_with_ai'] = null;
1591 - }
1592 -
1593 - return $home_screen_data;
1594 252 }
1595 253 }