PluginProbe
Elementor Website Builder – more than just a page builder / 3.22.2
Elementor Website Builder – more than just a page builder v3.22.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
← All changes | modules/ai/module.php +104 -591 4.2.0-dev23.22.2 View file →
@@ -1,15 +1,13 @@
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;
8 -use Elementor\Modules\Ai\Feature_Intro\Product_Image_Unification_Intro;
9 -use Elementor\Plugin;
6 +use Elementor\Core\Experiments\Manager as Experiments_Manager;
10 7 use Elementor\Core\Utils\Collection;
11 8 use Elementor\Modules\Ai\Connect\Ai;
9 +use Elementor\Plugin;
12 10 use Elementor\User;
13 11 use Elementor\Utils;
14 12
15 13 if ( ! defined( 'ABSPATH' ) ) {
@@ -28,10 +26,11 @@
28 26 self::HISTORY_TYPE_CODE,
29 27 self::HISTORY_TYPE_IMAGE,
30 28 self::HISTORY_TYPE_BLOCK,
31 29 ];
32 - const MIN_PAGES_FOR_CREATE_WITH_AI_BANNER = 10;
33 30
31 + const LAYOUT_EXPERIMENT = 'ai-layout';
32 +
34 33 public function get_name() {
35 34 return 'ai';
36 35 }
37 36
@@ -37,21 +36,10 @@
37 36
38 37 public function __construct() {
39 38 parent::__construct();
40 39
41 - ( new SitePlannerConnect\Module() );
40 + $this->register_layout_experiment();
42 41
43 - if ( is_admin() ) {
44 - ( new Preferences() )->register();
45 - add_action( 'elementor/import-export/import-kit/runner/after-run', [ $this, 'handle_kit_install' ] );
46 - }
47 -
48 - if ( ! $this->is_ai_enabled() ) {
49 - return;
50 - }
51 -
52 - add_filter( 'elementor/core/admin/homescreen', [ $this, 'add_create_with_ai_banner_to_homescreen' ] );
53 -
54 42 add_action( 'elementor/connect/apps/register', function ( ConnectModule $connect_module ) {
55 43 $connect_module->register_app( 'ai', Ai::get_class_name() );
56 44 } );
57 45
@@ -58,12 +46,10 @@
58 46 add_action( 'elementor/ajax/register_actions', function( $ajax ) {
59 47 $handlers = [
60 48 'ai_get_user_information' => [ $this, 'ajax_ai_get_user_information' ],
61 49 'ai_get_remote_config' => [ $this, 'ajax_ai_get_remote_config' ],
62 - 'ai_get_remote_frontend_config' => [ $this, 'ajax_ai_get_remote_frontend_config' ],
63 50 'ai_get_completion_text' => [ $this, 'ajax_ai_get_completion_text' ],
64 51 'ai_get_excerpt' => [ $this, 'ajax_ai_get_excerpt' ],
65 - 'ai_get_featured_image' => [ $this, 'ajax_ai_get_featured_image' ],
66 52 'ai_get_edit_text' => [ $this, 'ajax_ai_get_edit_text' ],
67 53 'ai_get_custom_code' => [ $this, 'ajax_ai_get_custom_code' ],
68 54 'ai_get_custom_css' => [ $this, 'ajax_ai_get_custom_css' ],
69 55 'ai_set_get_started' => [ $this, 'ajax_ai_set_get_started' ],
@@ -71,9 +57,8 @@
71 57 'ai_get_image_prompt_enhancer' => [ $this, 'ajax_ai_get_image_prompt_enhancer' ],
72 58 'ai_get_text_to_image' => [ $this, 'ajax_ai_get_text_to_image' ],
73 59 'ai_get_image_to_image' => [ $this, 'ajax_ai_get_image_to_image' ],
74 60 'ai_get_image_to_image_mask' => [ $this, 'ajax_ai_get_image_to_image_mask' ],
75 - 'ai_get_image_to_image_mask_cleanup' => [ $this, 'ajax_ai_get_image_to_image_mask_cleanup' ],
76 61 'ai_get_image_to_image_outpainting' => [ $this, 'ajax_ai_get_image_to_image_outpainting' ],
77 62 'ai_get_image_to_image_upscale' => [ $this, 'ajax_ai_get_image_to_image_upscale' ],
78 63 'ai_get_image_to_image_remove_background' => [ $this, 'ajax_ai_get_image_to_image_remove_background' ],
79 64 'ai_get_image_to_image_replace_background' => [ $this, 'ajax_ai_get_image_to_image_replace_background' ],
@@ -82,11 +67,8 @@
82 67 'ai_get_layout_prompt_enhancer' => [ $this, 'ajax_ai_get_layout_prompt_enhancer' ],
83 68 'ai_get_history' => [ $this, 'ajax_ai_get_history' ],
84 69 'ai_delete_history_item' => [ $this, 'ajax_ai_delete_history_item' ],
85 70 'ai_toggle_favorite_history_item' => [ $this, 'ajax_ai_toggle_favorite_history_item' ],
86 - 'ai_get_product_image_unification' => [ $this, 'ajax_ai_get_product_image_unification' ],
87 - 'ai_get_animation' => [ $this, 'ajax_ai_get_animation' ],
88 - 'ai_get_image_to_image_isolate_objects' => [ $this, 'ajax_ai_get_product_image_unification' ],
89 71 ];
90 72
91 73 foreach ( $handlers as $tag => $callback ) {
92 74 $ajax->register_ajax_action( $tag, $callback );
@@ -94,9 +76,12 @@
94 76 } );
95 77
96 78 add_action( 'elementor/editor/before_enqueue_scripts', function() {
97 79 $this->enqueue_main_script();
98 - $this->enqueue_layout_script();
80 +
81 + if ( $this->is_layout_active() ) {
82 + $this->enqueue_layout_script();
83 + }
99 84 } );
100 85
101 86 add_action( 'elementor/editor/after_enqueue_styles', function() {
102 87 wp_enqueue_style(
@@ -107,27 +92,20 @@
107 92 );
108 93 } );
109 94
110 95 add_action( 'elementor/preview/enqueue_styles', function() {
111 - wp_enqueue_style(
112 - 'elementor-ai-layout-preview',
113 - $this->get_css_assets_url( 'modules/ai/layout-preview' ),
114 - [],
115 - ELEMENTOR_VERSION
116 - );
96 + if ( $this->is_layout_active() ) {
97 + wp_enqueue_style(
98 + 'elementor-ai-layout-preview',
99 + $this->get_css_assets_url( 'modules/ai/layout-preview' ),
100 + [],
101 + ELEMENTOR_VERSION
102 + );
103 + }
117 104 } );
118 105
119 106 if ( is_admin() ) {
120 107 add_action( 'wp_enqueue_media', [ $this, 'enqueue_ai_media_library' ] );
121 - add_action( 'admin_head', [ $this, 'enqueue_ai_media_library_upload_screen' ] );
122 -
123 - if ( current_user_can( 'edit_products' ) || current_user_can( 'publish_products' ) ) {
124 - add_action( 'admin_init', [ $this, 'enqueue_ai_products_page_scripts' ] );
125 - add_action( 'current_screen', [ $this, 'enqueue_ai_single_product_page_scripts' ] );
126 - add_action( 'wp_ajax_elementor-ai-get-product-images', [ $this, 'get_product_images_ajax' ] );
127 - add_action( 'wp_ajax_elementor-ai-set-product-images', [ $this, 'set_product_images_ajax' ] );
128 - Product_Image_Unification_Intro::add_hooks();
129 - }
130 108 }
131 109
132 110 add_action( 'enqueue_block_editor_assets', function() {
133 111 wp_enqueue_script( 'elementor-ai-gutenberg',
@@ -135,27 +113,31 @@
135 113 [
136 114 'jquery',
137 115 'elementor-v2-ui',
138 116 'elementor-v2-icons',
139 - 'wp-blocks',
140 - 'wp-element',
141 - 'wp-editor',
142 - 'wp-data',
143 - 'wp-components',
144 - 'wp-compose',
145 - 'wp-i18n',
146 - 'wp-hooks',
147 - 'elementor-ai-media-library',
148 117 ],
149 118 ELEMENTOR_VERSION, true );
150 119
120 + $session_id = 'elementor-editor-session-' . Utils::generate_random_string();
121 +
122 + $config = [
123 + 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
124 + 'connect_url' => $this->get_ai_connect_url(),
125 + 'client_session_id' => $session_id,
126 + ];
127 +
128 + if ( $this->get_ai_app()->is_connected() ) {
129 + $usage = $this->get_ai_app()->get_usage( 'gutenberg-loader', $session_id );
130 +
131 + if ( ! is_wp_error( $usage ) ) {
132 + $config['usage'] = $usage;
133 + }
134 + }
135 +
151 136 wp_localize_script(
152 137 'elementor-ai-gutenberg',
153 138 'ElementorAiConfig',
154 - [
155 - 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
156 - 'connect_url' => $this->get_ai_connect_url(),
157 - ]
139 + $config
158 140 );
159 141
160 142 wp_set_script_translations( 'elementor-ai-gutenberg', 'elementor' );
161 143 });
@@ -160,274 +142,29 @@
160 142 wp_set_script_translations( 'elementor-ai-gutenberg', 'elementor' );
161 143 });
162 144
163 145 add_filter( 'elementor/document/save/data', function ( $data ) {
164 - return $this->remove_temporary_containers( $data );
165 - } );
166 -
167 - add_action( 'elementor/element/common/section_effects/after_section_start', [ $this, 'register_ai_motion_effect_control' ], 10, 1 );
168 - add_action( 'elementor/element/container/section_effects/after_section_start', [ $this, 'register_ai_motion_effect_control' ], 10, 1 );
169 - add_action( 'elementor/element/common/_section_transform/after_section_end', [ $this, 'register_ai_hover_effect_control' ], 10, 1 );
170 - add_action( 'elementor/element/container/_section_transform/after_section_end', [ $this, 'register_ai_hover_effect_control' ], 10, 1 );
171 - }
172 -
173 - public function is_ai_enabled() {
174 - if ( ! Plugin::$instance->experiments->is_feature_active( 'container' ) ) {
175 - return false;
176 - }
177 -
178 - return Preferences::is_ai_enabled( get_current_user_id() );
179 - }
180 -
181 - public function handle_kit_install( $imported_data ) {
182 - if ( ! $this->is_ai_enabled() ) {
183 - return;
184 - }
185 -
186 - if ( ! isset( $imported_data['status'] ) || 'success' !== $imported_data['status'] ) {
187 - return;
188 - }
189 -
190 - if ( ! isset( $imported_data['runner'] ) || 'site-settings' !== $imported_data['runner'] ) {
191 - return;
192 - }
193 -
194 - if ( ! isset( $imported_data['configData']['lastImportedSession']['instance_data']['site_settings']['settings']['ai'] ) ) {
195 - return;
196 - }
197 -
198 - $is_connected = $this->get_ai_app()->is_connected() && User::get_introduction_meta( 'ai_get_started' );
199 -
200 - if ( ! $is_connected ) {
201 - return;
202 - }
203 -
204 - $last_imported_session = $imported_data['configData']['lastImportedSession'];
205 - $imported_ai_data = $last_imported_session['instance_data']['site_settings']['settings']['ai'];
206 -
207 - $this->get_ai_app()->send_event( [
208 - 'name' => 'kit_installed',
209 - 'data' => $imported_ai_data,
210 - 'client' => [
211 - 'name' => 'elementor',
212 - 'version' => ELEMENTOR_VERSION,
213 - 'session_id' => $last_imported_session['session_id'],
214 - ],
215 - ] );
216 - }
217 -
218 - public function register_ai_hover_effect_control( Element_Base $element ) {
219 - if ( ! $element->get_controls( 'ai_hover_animation' ) ) {
220 - $element->add_control(
221 - 'ai_hover_animation',
222 - [
223 - 'tabs_wrapper' => '_tabs_positioning',
224 - 'inner_tab' => '_tab_positioning_hover',
225 - 'label' => esc_html__( 'Animate With AI', 'elementor' ),
226 - 'type' => Controls_Manager::RAW_HTML,
227 - 'raw' => '
228 -<style>
229 - .elementor-control-ai_hover_animation .elementor-control-content {
230 - display: flex;
231 - flex-direction: row;
232 - justify-content: space-between;
233 - align-items: center;
234 - }
235 - .elementor-control-ai_hover_animation .elementor-control-raw-html {
236 - display: none;
237 - }
238 -</style>',
239 - 'render_type' => 'none',
240 - 'ai' => [
241 - 'active' => true,
242 - 'type' => 'hover_animation',
243 - ],
244 - ],
245 - [
246 - 'position' => [
247 - 'of' => '_transform_rotate_popover_hover',
248 - 'type' => 'control',
249 - 'at' => 'before',
250 - ],
251 - ]
252 - );
253 - }
254 - }
255 - public function register_ai_motion_effect_control( $element ) {
256 - if ( Utils::has_pro() && ! $element->get_controls( 'ai_animation' ) ) {
257 - $element->add_control(
258 - 'ai_animation',
259 - [
260 - 'label' => esc_html__( 'Animate With AI', 'elementor' ),
261 - 'type' => Controls_Manager::RAW_HTML,
262 - 'raw' => '
263 - <style>
264 - .elementor-control-ai_animation .elementor-control-content {
265 - display: flex;
266 - flex-direction: row;
267 - justify-content: space-between;
268 - align-items: center;
269 - }
270 - .elementor-control-ai_animation .elementor-control-raw-html {
271 - display: none;
272 - }
273 - </style>',
274 - 'render_type' => 'none',
275 - 'ai' => [
276 - 'active' => true,
277 - 'type' => 'animation',
278 - ],
279 - ]
280 - );
281 - }
282 - }
283 -
284 - private function get_current_screen() {
285 - $is_wc = class_exists( 'WooCommerce' ) && post_type_exists( 'product' );
286 - if ( ! $is_wc ) {
287 - return 'other';
288 - }
289 -
290 - $is_products_page = isset( $_GET['post_type'] ) && 'product' === $_GET['post_type'];
291 - if ( $is_products_page ) {
292 - return 'wc-products';
293 - }
294 -
295 - $screen = get_current_screen();
296 - $is_single_product_page = isset( $screen->post_type ) && ( 'product' === $screen->post_type && 'post' === $screen->base );
297 - if ( $is_single_product_page ) {
298 - return 'wc-single-product';
299 - }
300 -
301 - return 'other';
302 - }
303 -
304 - public function enqueue_ai_products_page_scripts() {
305 - if ( 'wc-products' !== $this->get_current_screen() ) {
306 - return;
307 - }
308 -
309 - $this->add_wc_scripts();
310 - }
311 -
312 - public function enqueue_ai_single_product_page_scripts() {
313 - if ( 'wc-single-product' !== $this->get_current_screen() ) {
314 - return;
315 - }
316 -
317 - $this->add_wc_scripts();
318 - }
319 -
320 - private function add_products_bulk_action( $bulk_actions ) {
321 - $bulk_actions['elementor-ai-unify-product-images'] = __( 'Unify with Elementor AI', 'elementor' );
322 - return $bulk_actions;
323 - }
324 -
325 - public function get_product_images_ajax() {
326 - check_ajax_referer( 'elementor-ai-unify-product-images_nonce', 'nonce' );
327 -
328 - $post_ids = isset( $_POST['post_ids'] ) ? array_map( 'intval', $_POST['post_ids'] ) : [];
329 - $is_galley_only = isset( $_POST['is_galley_only'] ) && sanitize_text_field( wp_unslash( $_POST['is_galley_only'] ) );
330 -
331 - $image_ids = [];
332 -
333 - foreach ( $post_ids as $post_id ) {
334 - if ( $is_galley_only ) {
335 - $product = wc_get_product( $post_id );
336 - $gallery_image_ids = $product->get_gallery_image_ids();
337 - foreach ( $gallery_image_ids as $image_id ) {
338 - $image_ids[] = [
339 - 'productId' => $post_id,
340 - 'id' => $image_id,
341 - 'image_url' => wp_get_attachment_url( $image_id ),
342 - ];
343 - }
344 - continue;
146 + if ( $this->is_layout_active() ) {
147 + return $this->remove_temporary_containers( $data );
345 148 }
346 149
347 - $image_id = get_post_thumbnail_id( $post_id );
348 -
349 - if ( ! $image_id ) {
350 - $product = wc_get_product( $post_id );
351 - $gallery_image_ids = $product->get_gallery_image_ids();
352 - if ( ! empty( $gallery_image_ids ) ) {
353 - $image_id = $gallery_image_ids[0];
354 - }
355 - }
356 -
357 - $image_ids[] = [
358 - 'productId' => $post_id,
359 - 'id' => $image_id ? $image_id : 'No Image',
360 - 'image_url' => $image_id ? wp_get_attachment_url( $image_id ) : 'No Image',
361 - ];
362 - }
363 -
364 - wp_send_json_success( [ 'product_images' => array_slice( $image_ids, 0, 10 ) ] );
365 -
366 - wp_die();
150 + return $data;
151 + } );
367 152 }
368 153
369 - private function get_attachment_id_by_url( $url ) {
370 - $attachments = get_posts( [
371 - 'post_type' => 'attachment',
372 - 'meta_query' => [
373 - [
374 - 'key' => '_wp_attached_file',
375 - 'value' => basename( $url ),
376 - 'compare' => 'LIKE',
377 - ],
154 + private function register_layout_experiment() {
155 + Plugin::$instance->experiments->add_feature( [
156 + 'name' => static::LAYOUT_EXPERIMENT,
157 + 'title' => esc_html__( 'Build with AI', 'elementor' ),
158 + 'description' => esc_html__( 'Tap into the potential of AI to easily create and customize containers to your specifications, right within Elementor. This feature comes packed with handy AI tools, including generation, variations, and URL references.', 'elementor' ),
159 + 'default' => Experiments_Manager::STATE_ACTIVE,
160 + 'release_status' => Experiments_Manager::RELEASE_STATUS_STABLE,
161 + 'dependencies' => [
162 + 'container',
378 163 ],
379 - 'fields' => 'ids',
380 - 'numberposts' => 1,
381 164 ] );
382 -
383 - return ! empty( $attachments ) ? $attachments[0] : null;
384 165 }
385 166
386 - public function set_product_images_ajax() {
387 - check_ajax_referer( 'elementor-ai-unify-product-images_nonce', 'nonce' );
388 -
389 - $product_id = isset( $_POST['productId'] ) ? sanitize_text_field( wp_unslash( $_POST['productId'] ) ) : '';
390 - $image_url = isset( $_POST['image_url'] ) ? sanitize_text_field( wp_unslash( $_POST['image_url'] ) ) : '';
391 - $image_to_add = isset( $_POST['image_to_add'] ) ? intval( wp_unslash( $_POST['image_to_add'] ) ) : null;
392 - $image_to_remove = isset( $_POST['image_to_remove'] ) ? intval( wp_unslash( $_POST['image_to_remove'] ) ) : null;
393 - $is_product_gallery = isset( $_POST['is_product_gallery'] ) && sanitize_text_field( wp_unslash( $_POST['is_product_gallery'] ) ) === 'true';
394 -
395 - if ( ! $product_id || ! $image_url ) {
396 - throw new \Exception( 'Product ID and Image URL are required' );
397 - }
398 -
399 - $product = wc_get_product( $product_id );
400 - if ( ! $product ) {
401 - throw new \Exception( 'Product not found' );
402 - }
403 -
404 - $attachment_id = $this->get_attachment_id_by_url( $image_url );
405 - if ( is_wp_error( $attachment_id ) ) {
406 - throw new \Exception( 'Image upload failed' );
407 - }
408 -
409 - if ( $is_product_gallery ) {
410 - $this->update_product_gallery( $product, $image_to_remove, $image_to_add );
411 - } else {
412 - $product->set_image_id( $attachment_id );
413 - $product->save();
414 - }
415 -
416 - wp_send_json_success( [
417 - 'message' => __( 'Image added successfully', 'elementor' ),
418 - ] );
419 - }
420 -
421 - public function enqueue_ai_media_library_upload_screen() {
422 - $screen = get_current_screen();
423 - if ( ! $screen || 'upload' !== $screen->id ) {
424 - return;
425 - }
426 -
427 - $this->enqueue_ai_media_library();
428 - }
429 -
430 167 public function enqueue_ai_media_library() {
431 168 wp_enqueue_script( 'elementor-ai-media-library',
432 169 $this->get_js_assets_url( 'ai-media-library' ),
433 170 [
@@ -439,15 +176,20 @@
439 176 ELEMENTOR_VERSION,
440 177 true
441 178 );
442 179
180 + $session_id = 'wp-media-library-session-' . Utils::generate_random_string();
181 +
182 + $config = [
183 + 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
184 + 'connect_url' => $this->get_ai_connect_url(),
185 + 'client_session_id' => $session_id,
186 + ];
187 +
443 188 wp_localize_script(
444 189 'elementor-ai-media-library',
445 190 'ElementorAiConfig',
446 - [
447 - 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
448 - 'connect_url' => $this->get_ai_connect_url(),
449 - ]
191 + $config
450 192 );
451 193
452 194 wp_set_script_translations( 'elementor-ai-media-library', 'elementor' );
453 195 }
@@ -476,8 +218,13 @@
476 218 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
477 219 'connect_url' => $this->get_ai_connect_url(),
478 220 ];
479 221
222 + if ( $this->get_ai_app()->is_connected() ) {
223 + // Use a cached version, don't call the API on every editor load.
224 + $config['usage'] = $this->get_ai_app()->get_cached_usage();
225 + }
226 +
480 227 wp_localize_script(
481 228 'elementor-ai',
482 229 'ElementorAiConfig',
483 230 $config
@@ -483,8 +230,31 @@
483 230 $config
484 231 );
485 232
486 233 wp_set_script_translations( 'elementor-ai', 'elementor' );
234 +
235 + if ( $this->get_ai_app()->is_connected() && ! empty( $config['is_get_started'] ) ) {
236 + $remote_config = Utils::get_cached_callback( [ $this->get_ai_app(), 'get_remote_config' ], 'ai_remote_config-' . get_current_user_id(), HOUR_IN_SECONDS );
237 +
238 + if ( ! is_wp_error( $remote_config ) && ! empty( $remote_config['config']['remoteIntegrationUrl'] ) ) {
239 + wp_enqueue_script(
240 + 'elementor-ai-integration',
241 + $remote_config['config']['remoteIntegrationUrl'],
242 + [
243 + 'elementor-ai',
244 + ],
245 + ELEMENTOR_VERSION,
246 + true
247 + );
248 + }
249 +
250 + add_filter( 'script_loader_tag', function( $tag, $handle ) {
251 + if ( 'elementor-ai-integration' === $handle ) {
252 + return str_replace( ' src', ' type="module" src', $tag );
253 + }
254 + return $tag;
255 + }, 10, 2 );
256 + }
487 257 }
488 258
489 259 private function enqueue_layout_script() {
490 260 wp_enqueue_script(
@@ -507,10 +277,14 @@
507 277
508 278 wp_set_script_translations( 'elementor-ai-layout', 'elementor' );
509 279 }
510 280
281 + private function is_layout_active() {
282 + return Plugin::$instance->experiments->is_feature_active( self::LAYOUT_EXPERIMENT );
283 + }
284 +
511 285 private function remove_temporary_containers( $data ) {
512 - if ( empty( $data['elements'] ) || ! is_array( $data['elements'] ) ) {
286 + if ( empty( $data['elements'] ) ) {
513 287 return $data;
514 288 }
515 289
516 290 // If for some reason the document has been saved during an AI Layout session,
@@ -567,17 +341,8 @@
567 341 }
568 342
569 343 return $app->get_remote_config();
570 344 }
571 -
572 - public function ajax_ai_get_remote_frontend_config( $data ) {
573 - $callback = function () use ( $data ) {
574 - return $this->get_ai_app()->get_remote_frontend_config( $data );
575 - };
576 -
577 - return Utils::get_cached_callback( $callback, 'ai_remote_frontend_config-' . get_current_user_id(), HOUR_IN_SECONDS );
578 - }
579 -
580 345 public function verify_upload_permissions( $data ) {
581 346 $referer = wp_get_referer();
582 347
583 348 if ( str_contains( $referer, 'wp-admin/upload.php' ) && current_user_can( 'upload_files' ) ) {
@@ -592,14 +357,10 @@
592 357 if ( ! $document ) {
593 358 throw new \Exception( 'Document not found' );
594 359 }
595 360
596 - if ( $document->is_built_with_elementor() ) {
597 - if ( ! $document->is_editable_by_current_user() ) {
598 - throw new \Exception( 'Access denied' );
599 - }
600 - } elseif ( ! current_user_can( 'edit_post', $editor_post_id ) ) {
601 - throw new \Exception( 'Access denied' );
361 + if ( ! $document->is_editable_by_current_user() ) {
362 + throw new \Exception( 'Access denied' );
602 363 }
603 364 }
604 365
605 366 public function ajax_ai_get_image_prompt_enhancer( $data ) {
@@ -678,36 +439,9 @@
678 439 'usage' => $result['usage'],
679 440 ];
680 441 }
681 442
682 - public function ajax_ai_get_featured_image( $data ): array {
683 - $this->verify_upload_permissions( $data );
684 -
685 - if ( empty( $data['payload']['prompt'] ) ) {
686 - throw new \Exception( 'Missing prompt' );
687 - }
688 -
689 - $app = $this->get_ai_app();
690 -
691 - if ( ! $app->is_connected() ) {
692 - throw new \Exception( 'not_connected' );
693 - }
694 -
695 - $context = $this->get_request_context( $data );
696 - $request_ids = $this->get_request_ids( $data['payload'] );
697 -
698 - $result = $app->get_featured_image( $data, $context, $request_ids );
699 -
700 - $this->throw_on_error( $result );
701 -
702 - return [
703 - 'images' => $result['images'],
704 - 'response_id' => $result['responseId'],
705 - 'usage' => $result['usage'],
706 - ];
707 - }
708 -
709 - private function get_ai_app(): Ai {
443 + private function get_ai_app() : Ai {
710 444 return Plugin::$instance->common->get_component( 'connect' )->get_app( 'ai' );
711 445 }
712 446
713 447 private function get_request_context( $data ) {
@@ -1020,42 +754,8 @@
1020 754 if ( empty( $data['payload']['settings'] ) ) {
1021 755 throw new \Exception( 'Missing prompt settings' );
1022 756 }
1023 757
1024 - if ( empty( $data['payload']['mask'] ) ) {
1025 - throw new \Exception( 'Missing Mask' );
1026 - }
1027 -
1028 - $context = $this->get_request_context( $data );
1029 - $request_ids = $this->get_request_ids( $data['payload'] );
1030 -
1031 - $result = $app->get_image_to_image_mask( [
1032 - 'prompt' => $data['payload']['prompt'],
1033 - 'attachment_id' => $data['payload']['image']['id'],
1034 - 'mask' => $data['payload']['mask'],
1035 - ], $context, $request_ids );
1036 -
1037 - $this->throw_on_error( $result );
1038 -
1039 - return [
1040 - 'images' => $result['images'],
1041 - 'response_id' => $result['responseId'],
1042 - 'usage' => $result['usage'],
1043 - ];
1044 - }
1045 - public function ajax_ai_get_image_to_image_mask_cleanup( $data ) {
1046 - $this->verify_upload_permissions( $data );
1047 -
1048 - $app = $this->get_ai_app();
1049 -
1050 - if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
1051 - throw new \Exception( 'Missing Image' );
1052 - }
1053 -
1054 - if ( empty( $data['payload']['settings'] ) ) {
1055 - throw new \Exception( 'Missing prompt settings' );
1056 - }
1057 -
1058 758 if ( ! $app->is_connected() ) {
1059 759 throw new \Exception( 'not_connected' );
1060 760 }
1061 761
@@ -1065,9 +765,10 @@
1065 765
1066 766 $context = $this->get_request_context( $data );
1067 767 $request_ids = $this->get_request_ids( $data['payload'] );
1068 768
1069 - $result = $app->get_image_to_image_mask_cleanup( [
769 + $result = $app->get_image_to_image_mask( [
770 + 'prompt' => $data['payload']['prompt'],
1070 771 'attachment_id' => $data['payload']['image']['id'],
1071 772 'mask' => $data['payload']['mask'],
1072 773 ], $context, $request_ids );
1073 774
@@ -1078,9 +779,8 @@
1078 779 'response_id' => $result['responseId'],
1079 780 'usage' => $result['usage'],
1080 781 ];
1081 782 }
1082 -
1083 783 public function ajax_ai_get_image_to_image_outpainting( $data ) {
1084 784 $this->verify_upload_permissions( $data );
1085 785
1086 786 $app = $this->get_ai_app();
@@ -1111,10 +811,8 @@
1111 811 ];
1112 812 }
1113 813
1114 814 public function ajax_ai_upload_image( $data ) {
1115 - $this->verify_upload_permissions( $data );
1116 -
1117 815 if ( empty( $data['image'] ) ) {
1118 816 throw new \Exception( 'Missing image data' );
1119 817 }
1120 818
@@ -1126,14 +824,14 @@
1126 824
1127 825 $image_data = $this->upload_image( $image['image_url'], $data['prompt'], $data['editor_post_id'] );
1128 826
1129 827 if ( is_wp_error( $image_data ) ) {
1130 - throw new \Exception( esc_html( $image_data->get_error_message() ) );
828 + throw new \Exception( $image_data->get_error_message() );
1131 829 }
1132 830
1133 831 if ( ! empty( $image['use_gallery_image'] ) && ! empty( $image['id'] ) ) {
1134 - $app = $this->get_ai_app();
1135 - $app->set_used_gallery_image( $image['id'] );
832 + $app = $this->get_ai_app();
833 + $app->set_used_gallery_image( $image['id'] );
1136 834 }
1137 835
1138 836 return [
1139 837 'image' => array_merge( $image_data, $data ),
@@ -1162,9 +860,9 @@
1162 860 $message = $result->get_error_message();
1163 861
1164 862 if ( is_array( $message ) ) {
1165 863 $message = implode( ', ', $message );
1166 - throw new \Exception( esc_html( $message ) );
864 + throw new \Exception( $message );
1167 865 }
1168 866
1169 867 $this->throw_on_error( $result );
1170 868 }
@@ -1312,24 +1010,10 @@
1312 1010 if ( ! current_user_can( 'upload_files' ) ) {
1313 1011 throw new \Exception( 'Not Allowed to Upload images' );
1314 1012 }
1315 1013
1316 - $uploads_manager = new \Elementor\Core\Files\Uploads_Manager();
1317 - if ( $uploads_manager::are_unfiltered_uploads_enabled() ) {
1318 - Plugin::$instance->uploads_manager->set_elementor_upload_state( true );
1319 - add_filter( 'wp_handle_sideload_prefilter', [ Plugin::$instance->uploads_manager, 'handle_elementor_upload' ] );
1320 - add_filter( 'image_sideload_extensions', function( $extensions ) {
1321 - $extensions[] = 'svg';
1322 - return $extensions;
1323 - });
1324 - }
1325 -
1326 1014 $attachment_id = media_sideload_image( $image_url, $parent_post_id, $image_title, 'id' );
1327 1015
1328 - if ( is_wp_error( $attachment_id ) ) {
1329 - return new \WP_Error( 'upload_error', $attachment_id->get_error_message() );
1330 - }
1331 -
1332 1016 if ( ! empty( $attachment_id['error'] ) ) {
1333 1017 return new \WP_Error( 'upload_error', $attachment_id['error'] );
1334 1018 }
1335 1019
@@ -1334,10 +1018,10 @@
1334 1018 }
1335 1019
1336 1020 return [
1337 1021 'id' => $attachment_id,
1338 - 'url' => esc_url( wp_get_attachment_image_url( $attachment_id, 'full' ) ),
1339 - 'alt' => esc_attr( $image_title ),
1022 + 'url' => wp_get_attachment_image_url( $attachment_id, 'full' ),
1023 + 'alt' => $image_title,
1340 1024 'source' => 'library',
1341 1025 ];
1342 1026 }
1343 1027
@@ -1361,9 +1045,9 @@
1361 1045
1362 1046 $result = $app->get_history_by_type( $type, $page, $limit, $context );
1363 1047
1364 1048 if ( is_wp_error( $result ) ) {
1365 - throw new \Exception( esc_html( $result->get_error_message() ) );
1049 + throw new \Exception( $result->get_error_message() );
1366 1050 }
1367 1051
1368 1052 return $result;
1369 1053 }
@@ -1383,9 +1067,9 @@
1383 1067
1384 1068 $result = $app->delete_history_item( $data['id'], $context );
1385 1069
1386 1070 if ( is_wp_error( $result ) ) {
1387 - throw new \Exception( esc_html( $result->get_error_message() ) );
1071 + throw new \Exception( $result->get_error_message() );
1388 1072 }
1389 1073
1390 1074 return [];
1391 1075 }
@@ -1405,83 +1089,14 @@
1405 1089
1406 1090 $result = $app->toggle_favorite_history_item( $data['id'], $context );
1407 1091
1408 1092 if ( is_wp_error( $result ) ) {
1409 - throw new \Exception( esc_html( $result->get_error_message() ) );
1093 + throw new \Exception( $result->get_error_message() );
1410 1094 }
1411 1095
1412 1096 return [];
1413 1097 }
1414 1098
1415 - public function ajax_ai_get_product_image_unification( $data ): array {
1416 - if ( ! empty( $data['payload']['postId'] ) ) {
1417 - $data['editor_post_id'] = $data['payload']['postId'];
1418 - }
1419 - $this->verify_upload_permissions( $data );
1420 -
1421 - $app = $this->get_ai_app();
1422 -
1423 - if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
1424 - throw new \Exception( 'Missing Image' );
1425 - }
1426 -
1427 - if ( empty( $data['payload']['settings'] ) ) {
1428 - throw new \Exception( 'Missing prompt settings' );
1429 - }
1430 -
1431 - if ( ! $app->is_connected() ) {
1432 - throw new \Exception( 'not_connected' );
1433 - }
1434 -
1435 - $context = $this->get_request_context( $data );
1436 - $request_ids = $this->get_request_ids( $data['payload'] );
1437 -
1438 - $result = $app->get_unify_product_images( [
1439 - 'promptSettings' => $data['payload']['settings'],
1440 - 'attachment_id' => $data['payload']['image']['id'],
1441 - 'featureIdentifier' => $data['payload']['featureIdentifier'] ?? '',
1442 - ], $context, $request_ids );
1443 -
1444 - $this->throw_on_error( $result );
1445 -
1446 - return [
1447 - 'images' => $result['images'],
1448 - 'response_id' => $result['responseId'],
1449 - 'usage' => $result['usage'],
1450 - ];
1451 - }
1452 -
1453 - public function ajax_ai_get_animation( $data ): array {
1454 - $this->verify_upload_permissions( $data );
1455 -
1456 - $app = $this->get_ai_app();
1457 -
1458 - if ( empty( $data['payload']['prompt'] ) ) {
1459 - throw new \Exception( 'Missing prompt' );
1460 - }
1461 -
1462 - if ( empty( $data['payload']['motionEffectType'] ) ) {
1463 - throw new \Exception( 'Missing animation type' );
1464 - }
1465 -
1466 - if ( ! $app->is_connected() ) {
1467 - throw new \Exception( 'not_connected' );
1468 - }
1469 -
1470 - $context = $this->get_request_context( $data );
1471 -
1472 - $request_ids = $this->get_request_ids( $data['payload'] );
1473 -
1474 - $result = $app->get_animation( $data, $context, $request_ids );
1475 - $this->throw_on_error( $result );
1476 -
1477 - return [
1478 - 'text' => $result['text'],
1479 - 'response_id' => $result['responseId'],
1480 - 'usage' => $result['usage'],
1481 - ];
1482 - }
1483 -
1484 1099 /**
1485 1100 * @param mixed $result
1486 1101 */
1487 1102 private function throw_on_error( $result ): void {
@@ -1486,112 +1101,10 @@
1486 1101 */
1487 1102 private function throw_on_error( $result ): void {
1488 1103 if ( is_wp_error( $result ) ) {
1489 1104 wp_send_json_error( [
1490 - 'message' => esc_html( $result->get_error_message() ),
1105 + 'message' => $result->get_error_message(),
1491 1106 'extra_data' => $result->get_error_data(),
1492 1107 ] );
1493 1108 }
1494 - }
1495 -
1496 - /**
1497 - * @return void
1498 - */
1499 - public function add_wc_scripts(): void {
1500 - wp_enqueue_script( 'elementor-ai-unify-product-images',
1501 - $this->get_js_assets_url( 'ai-unify-product-images' ),
1502 - [
1503 - 'jquery',
1504 - 'elementor-v2-ui',
1505 - 'elementor-v2-icons',
1506 - 'wp-components',
1507 - 'elementor-common',
1508 - ],
1509 - ELEMENTOR_VERSION,
1510 - true
1511 - );
1512 -
1513 - wp_localize_script(
1514 - 'elementor-ai-unify-product-images',
1515 - 'UnifyProductImagesConfig',
1516 - [
1517 - 'get_product_images_url' => admin_url( 'admin-ajax.php' ),
1518 - 'set_product_images_url' => admin_url( 'admin-ajax.php' ),
1519 - 'nonce' => wp_create_nonce( 'elementor-ai-unify-product-images_nonce' ),
1520 - 'placeholder' => ELEMENTOR_ASSETS_URL . 'images/app/ai/product-image-unification-example.gif?' . ELEMENTOR_VERSION,
1521 - 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
1522 - 'connect_url' => $this->get_ai_connect_url(),
1523 - ]
1524 - );
1525 -
1526 - add_filter( 'bulk_actions-edit-product', function ( $data ) {
1527 - return $this->add_products_bulk_action( $data );
1528 - });
1529 -
1530 - wp_set_script_translations( 'elementor-ai-unify-product-images', 'elementor' );
1531 - }
1532 -
1533 - /**
1534 - * @param $product
1535 - * @param int|null $image_to_remove
1536 - * @param int|null $image_to_add
1537 - * @return void
1538 - */
1539 - private function update_product_gallery( $product, ?int $image_to_remove, ?int $image_to_add ): void {
1540 - $gallery_image_ids = $product->get_gallery_image_ids();
1541 -
1542 - $index = array_search( $image_to_remove, $gallery_image_ids, true );
1543 - if ( false !== $index ) {
1544 - unset( $gallery_image_ids[ $index ] );
1545 - }
1546 -
1547 - if ( ! in_array( $image_to_add, $gallery_image_ids, true ) ) {
1548 - $gallery_image_ids[] = $image_to_add;
1549 - }
1550 -
1551 - $product->set_gallery_image_ids( $gallery_image_ids );
1552 - $product->save();
1553 - }
1554 -
1555 - private function should_display_create_with_ai_banner() {
1556 - $elementor_pages = new \WP_Query( [
1557 - 'post_type' => 'page',
1558 - 'post_status' => 'publish',
1559 - 'fields' => 'ids',
1560 - 'posts_per_page' => self::MIN_PAGES_FOR_CREATE_WITH_AI_BANNER + 1,
1561 - ] );
1562 -
1563 - if ( $elementor_pages->post_count > self::MIN_PAGES_FOR_CREATE_WITH_AI_BANNER ) {
1564 - return false;
1565 - }
1566 -
1567 - if ( Utils::is_custom_kit_applied() ) {
1568 - return false;
1569 - }
1570 -
1571 - return true;
1572 - }
1573 -
1574 - private function get_create_with_ai_banner_data() {
1575 - return [
1576 - 'title' => 'Create and launch your site faster with AI',
1577 - 'description' => 'Share your vision with our AI Chat and watch as it becomes a brief, sitemap, and wireframes in minutes:',
1578 - 'input_placeholder' => 'Start describing the site you want to create...',
1579 - 'button_title' => 'Create with AI',
1580 - 'button_cta_url' => 'http://planner.elementor.com/chat.html',
1581 - 'background_image' => ELEMENTOR_ASSETS_URL . 'images/app/ai/ai-site-creator-homepage-bg.svg',
1582 - 'utm_source' => 'editor-home',
1583 - 'utm_medium' => 'wp-dash',
1584 - 'utm_campaign' => 'generate-with-ai',
1585 - ];
1586 - }
1587 -
1588 - public function add_create_with_ai_banner_to_homescreen( $home_screen_data ) {
1589 - if ( $this->should_display_create_with_ai_banner() ) {
1590 - $home_screen_data['create_with_ai'] = $this->get_create_with_ai_banner_data();
1591 - } else {
1592 - $home_screen_data['create_with_ai'] = null;
1593 - }
1594 -
1595 - return $home_screen_data;
1596 1109 }
1597 1110 }