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.1 4.3.0 4.3.0-beta3 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 All 454 releases
← All changes | modules/ai/module.php +104 -589 4.3.13.22.2 View file →
@@ -1,14 +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\Plugin;
6 +use Elementor\Core\Experiments\Manager as Experiments_Manager;
9 7 use Elementor\Core\Utils\Collection;
10 8 use Elementor\Modules\Ai\Connect\Ai;
9 +use Elementor\Plugin;
11 10 use Elementor\User;
12 11 use Elementor\Utils;
13 12
14 13 if ( ! defined( 'ABSPATH' ) ) {
@@ -27,10 +26,11 @@
27 26 self::HISTORY_TYPE_CODE,
28 27 self::HISTORY_TYPE_IMAGE,
29 28 self::HISTORY_TYPE_BLOCK,
30 29 ];
31 - const MIN_PAGES_FOR_CREATE_WITH_AI_BANNER = 10;
32 30
31 + const LAYOUT_EXPERIMENT = 'ai-layout';
32 +
33 33 public function get_name() {
34 34 return 'ai';
35 35 }
36 36
@@ -36,21 +36,10 @@
36 36
37 37 public function __construct() {
38 38 parent::__construct();
39 39
40 - ( new SitePlannerConnect\Module() );
40 + $this->register_layout_experiment();
41 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 42 add_action( 'elementor/connect/apps/register', function ( ConnectModule $connect_module ) {
54 43 $connect_module->register_app( 'ai', Ai::get_class_name() );
55 44 } );
56 45
@@ -57,12 +46,10 @@
57 46 add_action( 'elementor/ajax/register_actions', function( $ajax ) {
58 47 $handlers = [
59 48 'ai_get_user_information' => [ $this, 'ajax_ai_get_user_information' ],
60 49 'ai_get_remote_config' => [ $this, 'ajax_ai_get_remote_config' ],
61 - 'ai_get_remote_frontend_config' => [ $this, 'ajax_ai_get_remote_frontend_config' ],
62 50 'ai_get_completion_text' => [ $this, 'ajax_ai_get_completion_text' ],
63 51 'ai_get_excerpt' => [ $this, 'ajax_ai_get_excerpt' ],
64 - 'ai_get_featured_image' => [ $this, 'ajax_ai_get_featured_image' ],
65 52 'ai_get_edit_text' => [ $this, 'ajax_ai_get_edit_text' ],
66 53 'ai_get_custom_code' => [ $this, 'ajax_ai_get_custom_code' ],
67 54 'ai_get_custom_css' => [ $this, 'ajax_ai_get_custom_css' ],
68 55 'ai_set_get_started' => [ $this, 'ajax_ai_set_get_started' ],
@@ -70,9 +57,8 @@
70 57 'ai_get_image_prompt_enhancer' => [ $this, 'ajax_ai_get_image_prompt_enhancer' ],
71 58 'ai_get_text_to_image' => [ $this, 'ajax_ai_get_text_to_image' ],
72 59 'ai_get_image_to_image' => [ $this, 'ajax_ai_get_image_to_image' ],
73 60 '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 61 'ai_get_image_to_image_outpainting' => [ $this, 'ajax_ai_get_image_to_image_outpainting' ],
76 62 'ai_get_image_to_image_upscale' => [ $this, 'ajax_ai_get_image_to_image_upscale' ],
77 63 'ai_get_image_to_image_remove_background' => [ $this, 'ajax_ai_get_image_to_image_remove_background' ],
78 64 'ai_get_image_to_image_replace_background' => [ $this, 'ajax_ai_get_image_to_image_replace_background' ],
@@ -81,11 +67,8 @@
81 67 'ai_get_layout_prompt_enhancer' => [ $this, 'ajax_ai_get_layout_prompt_enhancer' ],
82 68 'ai_get_history' => [ $this, 'ajax_ai_get_history' ],
83 69 'ai_delete_history_item' => [ $this, 'ajax_ai_delete_history_item' ],
84 70 '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 71 ];
89 72
90 73 foreach ( $handlers as $tag => $callback ) {
91 74 $ajax->register_ajax_action( $tag, $callback );
@@ -93,9 +76,12 @@
93 76 } );
94 77
95 78 add_action( 'elementor/editor/before_enqueue_scripts', function() {
96 79 $this->enqueue_main_script();
97 - $this->enqueue_layout_script();
80 +
81 + if ( $this->is_layout_active() ) {
82 + $this->enqueue_layout_script();
83 + }
98 84 } );
99 85
100 86 add_action( 'elementor/editor/after_enqueue_styles', function() {
101 87 wp_enqueue_style(
@@ -106,26 +92,20 @@
106 92 );
107 93 } );
108 94
109 95 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 - );
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 + }
116 104 } );
117 105
118 106 if ( is_admin() ) {
119 107 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 108 }
129 109
130 110 add_action( 'enqueue_block_editor_assets', function() {
131 111 wp_enqueue_script( 'elementor-ai-gutenberg',
@@ -133,27 +113,31 @@
133 113 [
134 114 'jquery',
135 115 'elementor-v2-ui',
136 116 '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',
146 117 ],
147 118 ELEMENTOR_VERSION, true );
148 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 +
149 136 wp_localize_script(
150 137 'elementor-ai-gutenberg',
151 138 'ElementorAiConfig',
152 - [
153 - 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
154 - 'connect_url' => $this->get_ai_connect_url(),
155 - ]
139 + $config
156 140 );
157 141
158 142 wp_set_script_translations( 'elementor-ai-gutenberg', 'elementor' );
159 143 });
@@ -158,274 +142,29 @@
158 142 wp_set_script_translations( 'elementor-ai-gutenberg', 'elementor' );
159 143 });
160 144
161 145 add_filter( 'elementor/document/save/data', function ( $data ) {
162 - return $this->remove_temporary_containers( $data );
163 - } );
164 -
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 - ]
250 - );
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;
146 + if ( $this->is_layout_active() ) {
147 + return $this->remove_temporary_containers( $data );
343 148 }
344 149
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();
150 + return $data;
151 + } );
365 152 }
366 153
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 - ],
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',
376 163 ],
377 - 'fields' => 'ids',
378 - 'numberposts' => 1,
379 164 ] );
380 -
381 - return ! empty( $attachments ) ? $attachments[0] : null;
382 165 }
383 166
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 167 public function enqueue_ai_media_library() {
429 168 wp_enqueue_script( 'elementor-ai-media-library',
430 169 $this->get_js_assets_url( 'ai-media-library' ),
431 170 [
@@ -437,15 +176,20 @@
437 176 ELEMENTOR_VERSION,
438 177 true
439 178 );
440 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 +
441 188 wp_localize_script(
442 189 'elementor-ai-media-library',
443 190 'ElementorAiConfig',
444 - [
445 - 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
446 - 'connect_url' => $this->get_ai_connect_url(),
447 - ]
191 + $config
448 192 );
449 193
450 194 wp_set_script_translations( 'elementor-ai-media-library', 'elementor' );
451 195 }
@@ -474,8 +218,13 @@
474 218 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
475 219 'connect_url' => $this->get_ai_connect_url(),
476 220 ];
477 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 +
478 227 wp_localize_script(
479 228 'elementor-ai',
480 229 'ElementorAiConfig',
481 230 $config
@@ -481,8 +230,31 @@
481 230 $config
482 231 );
483 232
484 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 + }
485 257 }
486 258
487 259 private function enqueue_layout_script() {
488 260 wp_enqueue_script(
@@ -505,10 +277,14 @@
505 277
506 278 wp_set_script_translations( 'elementor-ai-layout', 'elementor' );
507 279 }
508 280
281 + private function is_layout_active() {
282 + return Plugin::$instance->experiments->is_feature_active( self::LAYOUT_EXPERIMENT );
283 + }
284 +
509 285 private function remove_temporary_containers( $data ) {
510 - if ( empty( $data['elements'] ) || ! is_array( $data['elements'] ) ) {
286 + if ( empty( $data['elements'] ) ) {
511 287 return $data;
512 288 }
513 289
514 290 // If for some reason the document has been saved during an AI Layout session,
@@ -565,17 +341,8 @@
565 341 }
566 342
567 343 return $app->get_remote_config();
568 344 }
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 345 public function verify_upload_permissions( $data ) {
579 346 $referer = wp_get_referer();
580 347
581 348 if ( str_contains( $referer, 'wp-admin/upload.php' ) && current_user_can( 'upload_files' ) ) {
@@ -590,14 +357,10 @@
590 357 if ( ! $document ) {
591 358 throw new \Exception( 'Document not found' );
592 359 }
593 360
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' );
361 + if ( ! $document->is_editable_by_current_user() ) {
362 + throw new \Exception( 'Access denied' );
600 363 }
601 364 }
602 365
603 366 public function ajax_ai_get_image_prompt_enhancer( $data ) {
@@ -676,36 +439,9 @@
676 439 'usage' => $result['usage'],
677 440 ];
678 441 }
679 442
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 {
443 + private function get_ai_app() : Ai {
708 444 return Plugin::$instance->common->get_component( 'connect' )->get_app( 'ai' );
709 445 }
710 446
711 447 private function get_request_context( $data ) {
@@ -1018,42 +754,8 @@
1018 754 if ( empty( $data['payload']['settings'] ) ) {
1019 755 throw new \Exception( 'Missing prompt settings' );
1020 756 }
1021 757
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 758 if ( ! $app->is_connected() ) {
1057 759 throw new \Exception( 'not_connected' );
1058 760 }
1059 761
@@ -1063,9 +765,10 @@
1063 765
1064 766 $context = $this->get_request_context( $data );
1065 767 $request_ids = $this->get_request_ids( $data['payload'] );
1066 768
1067 - $result = $app->get_image_to_image_mask_cleanup( [
769 + $result = $app->get_image_to_image_mask( [
770 + 'prompt' => $data['payload']['prompt'],
1068 771 'attachment_id' => $data['payload']['image']['id'],
1069 772 'mask' => $data['payload']['mask'],
1070 773 ], $context, $request_ids );
1071 774
@@ -1076,9 +779,8 @@
1076 779 'response_id' => $result['responseId'],
1077 780 'usage' => $result['usage'],
1078 781 ];
1079 782 }
1080 -
1081 783 public function ajax_ai_get_image_to_image_outpainting( $data ) {
1082 784 $this->verify_upload_permissions( $data );
1083 785
1084 786 $app = $this->get_ai_app();
@@ -1109,10 +811,8 @@
1109 811 ];
1110 812 }
1111 813
1112 814 public function ajax_ai_upload_image( $data ) {
1113 - $this->verify_upload_permissions( $data );
1114 -
1115 815 if ( empty( $data['image'] ) ) {
1116 816 throw new \Exception( 'Missing image data' );
1117 817 }
1118 818
@@ -1124,14 +824,14 @@
1124 824
1125 825 $image_data = $this->upload_image( $image['image_url'], $data['prompt'], $data['editor_post_id'] );
1126 826
1127 827 if ( is_wp_error( $image_data ) ) {
1128 - throw new \Exception( esc_html( $image_data->get_error_message() ) );
828 + throw new \Exception( $image_data->get_error_message() );
1129 829 }
1130 830
1131 831 if ( ! empty( $image['use_gallery_image'] ) && ! empty( $image['id'] ) ) {
1132 - $app = $this->get_ai_app();
1133 - $app->set_used_gallery_image( $image['id'] );
832 + $app = $this->get_ai_app();
833 + $app->set_used_gallery_image( $image['id'] );
1134 834 }
1135 835
1136 836 return [
1137 837 'image' => array_merge( $image_data, $data ),
@@ -1160,9 +860,9 @@
1160 860 $message = $result->get_error_message();
1161 861
1162 862 if ( is_array( $message ) ) {
1163 863 $message = implode( ', ', $message );
1164 - throw new \Exception( esc_html( $message ) );
864 + throw new \Exception( $message );
1165 865 }
1166 866
1167 867 $this->throw_on_error( $result );
1168 868 }
@@ -1310,24 +1010,10 @@
1310 1010 if ( ! current_user_can( 'upload_files' ) ) {
1311 1011 throw new \Exception( 'Not Allowed to Upload images' );
1312 1012 }
1313 1013
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 1014 $attachment_id = media_sideload_image( $image_url, $parent_post_id, $image_title, 'id' );
1325 1015
1326 - if ( is_wp_error( $attachment_id ) ) {
1327 - return new \WP_Error( 'upload_error', $attachment_id->get_error_message() );
1328 - }
1329 -
1330 1016 if ( ! empty( $attachment_id['error'] ) ) {
1331 1017 return new \WP_Error( 'upload_error', $attachment_id['error'] );
1332 1018 }
1333 1019
@@ -1332,10 +1018,10 @@
1332 1018 }
1333 1019
1334 1020 return [
1335 1021 'id' => $attachment_id,
1336 - 'url' => esc_url( wp_get_attachment_image_url( $attachment_id, 'full' ) ),
1337 - 'alt' => esc_attr( $image_title ),
1022 + 'url' => wp_get_attachment_image_url( $attachment_id, 'full' ),
1023 + 'alt' => $image_title,
1338 1024 'source' => 'library',
1339 1025 ];
1340 1026 }
1341 1027
@@ -1359,9 +1045,9 @@
1359 1045
1360 1046 $result = $app->get_history_by_type( $type, $page, $limit, $context );
1361 1047
1362 1048 if ( is_wp_error( $result ) ) {
1363 - throw new \Exception( esc_html( $result->get_error_message() ) );
1049 + throw new \Exception( $result->get_error_message() );
1364 1050 }
1365 1051
1366 1052 return $result;
1367 1053 }
@@ -1381,9 +1067,9 @@
1381 1067
1382 1068 $result = $app->delete_history_item( $data['id'], $context );
1383 1069
1384 1070 if ( is_wp_error( $result ) ) {
1385 - throw new \Exception( esc_html( $result->get_error_message() ) );
1071 + throw new \Exception( $result->get_error_message() );
1386 1072 }
1387 1073
1388 1074 return [];
1389 1075 }
@@ -1403,83 +1089,14 @@
1403 1089
1404 1090 $result = $app->toggle_favorite_history_item( $data['id'], $context );
1405 1091
1406 1092 if ( is_wp_error( $result ) ) {
1407 - throw new \Exception( esc_html( $result->get_error_message() ) );
1093 + throw new \Exception( $result->get_error_message() );
1408 1094 }
1409 1095
1410 1096 return [];
1411 1097 }
1412 1098
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 1099 /**
1483 1100 * @param mixed $result
1484 1101 */
1485 1102 private function throw_on_error( $result ): void {
@@ -1484,112 +1101,10 @@
1484 1101 */
1485 1102 private function throw_on_error( $result ): void {
1486 1103 if ( is_wp_error( $result ) ) {
1487 1104 wp_send_json_error( [
1488 - 'message' => esc_html( $result->get_error_message() ),
1105 + 'message' => $result->get_error_message(),
1489 1106 'extra_data' => $result->get_error_data(),
1490 1107 ] );
1491 1108 }
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 1109 }
1595 1110 }