PluginProbe
Elementor Website Builder – more than just a page builder / 3.23.0-dev3
Elementor Website Builder – more than just a page builder v3.23.0-dev3
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 +111 -565 4.2.43.23.0-dev3 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,9 +46,8 @@
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 52 'ai_get_featured_image' => [ $this, 'ajax_ai_get_featured_image' ],
66 53 'ai_get_edit_text' => [ $this, 'ajax_ai_get_edit_text' ],
@@ -71,9 +58,8 @@
71 58 'ai_get_image_prompt_enhancer' => [ $this, 'ajax_ai_get_image_prompt_enhancer' ],
72 59 'ai_get_text_to_image' => [ $this, 'ajax_ai_get_text_to_image' ],
73 60 'ai_get_image_to_image' => [ $this, 'ajax_ai_get_image_to_image' ],
74 61 '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 62 'ai_get_image_to_image_outpainting' => [ $this, 'ajax_ai_get_image_to_image_outpainting' ],
77 63 'ai_get_image_to_image_upscale' => [ $this, 'ajax_ai_get_image_to_image_upscale' ],
78 64 'ai_get_image_to_image_remove_background' => [ $this, 'ajax_ai_get_image_to_image_remove_background' ],
79 65 'ai_get_image_to_image_replace_background' => [ $this, 'ajax_ai_get_image_to_image_replace_background' ],
@@ -82,11 +68,8 @@
82 68 'ai_get_layout_prompt_enhancer' => [ $this, 'ajax_ai_get_layout_prompt_enhancer' ],
83 69 'ai_get_history' => [ $this, 'ajax_ai_get_history' ],
84 70 'ai_delete_history_item' => [ $this, 'ajax_ai_delete_history_item' ],
85 71 '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 72 ];
90 73
91 74 foreach ( $handlers as $tag => $callback ) {
92 75 $ajax->register_ajax_action( $tag, $callback );
@@ -94,9 +77,12 @@
94 77 } );
95 78
96 79 add_action( 'elementor/editor/before_enqueue_scripts', function() {
97 80 $this->enqueue_main_script();
98 - $this->enqueue_layout_script();
81 +
82 + if ( $this->is_layout_active() ) {
83 + $this->enqueue_layout_script();
84 + }
99 85 } );
100 86
101 87 add_action( 'elementor/editor/after_enqueue_styles', function() {
102 88 wp_enqueue_style(
@@ -107,27 +93,20 @@
107 93 );
108 94 } );
109 95
110 96 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 - );
97 + if ( $this->is_layout_active() ) {
98 + wp_enqueue_style(
99 + 'elementor-ai-layout-preview',
100 + $this->get_css_assets_url( 'modules/ai/layout-preview' ),
101 + [],
102 + ELEMENTOR_VERSION
103 + );
104 + }
117 105 } );
118 106
119 107 if ( is_admin() ) {
120 108 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 109 }
131 110
132 111 add_action( 'enqueue_block_editor_assets', function() {
133 112 wp_enqueue_script( 'elementor-ai-gutenberg',
@@ -135,27 +114,31 @@
135 114 [
136 115 'jquery',
137 116 'elementor-v2-ui',
138 117 '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 118 ],
149 119 ELEMENTOR_VERSION, true );
150 120
121 + $session_id = 'elementor-editor-session-' . Utils::generate_random_string();
122 +
123 + $config = [
124 + 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
125 + 'connect_url' => $this->get_ai_connect_url(),
126 + 'client_session_id' => $session_id,
127 + ];
128 +
129 + if ( $this->get_ai_app()->is_connected() ) {
130 + $usage = $this->get_ai_app()->get_usage( 'gutenberg-loader', $session_id );
131 +
132 + if ( ! is_wp_error( $usage ) ) {
133 + $config['usage'] = $usage;
134 + }
135 + }
136 +
151 137 wp_localize_script(
152 138 'elementor-ai-gutenberg',
153 139 'ElementorAiConfig',
154 - [
155 - 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
156 - 'connect_url' => $this->get_ai_connect_url(),
157 - ]
140 + $config
158 141 );
159 142
160 143 wp_set_script_translations( 'elementor-ai-gutenberg', 'elementor' );
161 144 });
@@ -160,274 +143,29 @@
160 143 wp_set_script_translations( 'elementor-ai-gutenberg', 'elementor' );
161 144 });
162 145
163 146 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;
147 + if ( $this->is_layout_active() ) {
148 + return $this->remove_temporary_containers( $data );
345 149 }
346 150
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();
151 + return $data;
152 + } );
367 153 }
368 154
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 - ],
155 + private function register_layout_experiment() {
156 + Plugin::$instance->experiments->add_feature( [
157 + 'name' => static::LAYOUT_EXPERIMENT,
158 + 'title' => esc_html__( 'Build with AI', 'elementor' ),
159 + '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' ),
160 + 'default' => Experiments_Manager::STATE_ACTIVE,
161 + 'release_status' => Experiments_Manager::RELEASE_STATUS_STABLE,
162 + 'dependencies' => [
163 + 'container',
378 164 ],
379 - 'fields' => 'ids',
380 - 'numberposts' => 1,
381 165 ] );
382 -
383 - return ! empty( $attachments ) ? $attachments[0] : null;
384 166 }
385 167
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 168 public function enqueue_ai_media_library() {
431 169 wp_enqueue_script( 'elementor-ai-media-library',
432 170 $this->get_js_assets_url( 'ai-media-library' ),
433 171 [
@@ -439,15 +177,20 @@
439 177 ELEMENTOR_VERSION,
440 178 true
441 179 );
442 180
181 + $session_id = 'wp-media-library-session-' . Utils::generate_random_string();
182 +
183 + $config = [
184 + 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
185 + 'connect_url' => $this->get_ai_connect_url(),
186 + 'client_session_id' => $session_id,
187 + ];
188 +
443 189 wp_localize_script(
444 190 'elementor-ai-media-library',
445 191 'ElementorAiConfig',
446 - [
447 - 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
448 - 'connect_url' => $this->get_ai_connect_url(),
449 - ]
192 + $config
450 193 );
451 194
452 195 wp_set_script_translations( 'elementor-ai-media-library', 'elementor' );
453 196 }
@@ -471,13 +214,24 @@
471 214 ELEMENTOR_VERSION,
472 215 true
473 216 );
474 217
218 + $session_id = 'elementor-editor-session-' . Utils::generate_random_string();
219 +
475 220 $config = [
476 221 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
477 222 'connect_url' => $this->get_ai_connect_url(),
223 + 'client_session_id' => $session_id,
478 224 ];
479 225
226 + if ( $this->get_ai_app()->is_connected() ) {
227 + $usage = $this->get_ai_app()->get_usage( 'elementor-loader', $session_id );
228 +
229 + if ( ! is_wp_error( $usage ) ) {
230 + $config['usage'] = $usage;
231 + }
232 + }
233 +
480 234 wp_localize_script(
481 235 'elementor-ai',
482 236 'ElementorAiConfig',
483 237 $config
@@ -483,8 +237,31 @@
483 237 $config
484 238 );
485 239
486 240 wp_set_script_translations( 'elementor-ai', 'elementor' );
241 +
242 + if ( $this->get_ai_app()->is_connected() && ! empty( $config['is_get_started'] ) ) {
243 + $remote_config = Utils::get_cached_callback( [ $this->get_ai_app(), 'get_remote_config' ], 'ai_remote_config-' . get_current_user_id(), HOUR_IN_SECONDS );
244 +
245 + if ( ! is_wp_error( $remote_config ) && ! empty( $remote_config['config']['remoteIntegrationUrl'] ) ) {
246 + wp_enqueue_script(
247 + 'elementor-ai-integration',
248 + $remote_config['config']['remoteIntegrationUrl'],
249 + [
250 + 'elementor-ai',
251 + ],
252 + ELEMENTOR_VERSION,
253 + true
254 + );
255 + }
256 +
257 + add_filter( 'script_loader_tag', function( $tag, $handle ) {
258 + if ( 'elementor-ai-integration' === $handle ) {
259 + return str_replace( ' src', ' type="module" src', $tag );
260 + }
261 + return $tag;
262 + }, 10, 2 );
263 + }
487 264 }
488 265
489 266 private function enqueue_layout_script() {
490 267 wp_enqueue_script(
@@ -507,10 +284,14 @@
507 284
508 285 wp_set_script_translations( 'elementor-ai-layout', 'elementor' );
509 286 }
510 287
288 + private function is_layout_active() {
289 + return Plugin::$instance->experiments->is_feature_active( self::LAYOUT_EXPERIMENT );
290 + }
291 +
511 292 private function remove_temporary_containers( $data ) {
512 - if ( empty( $data['elements'] ) || ! is_array( $data['elements'] ) ) {
293 + if ( empty( $data['elements'] ) ) {
513 294 return $data;
514 295 }
515 296
516 297 // If for some reason the document has been saved during an AI Layout session,
@@ -545,9 +326,9 @@
545 326 'connect_url' => $this->get_ai_connect_url(),
546 327 ];
547 328 }
548 329
549 - $user_usage = wp_parse_args( $app->get_usage(), [
330 + $user_usage = wp_parse_args( $app->get_usage( 'elementor-editor', $data['editor_session_id'] ), [
550 331 'hasAiSubscription' => false,
551 332 'usedQuota' => 0,
552 333 'quota' => 100,
553 334 ] );
@@ -567,17 +348,8 @@
567 348 }
568 349
569 350 return $app->get_remote_config();
570 351 }
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 352 public function verify_upload_permissions( $data ) {
581 353 $referer = wp_get_referer();
582 354
583 355 if ( str_contains( $referer, 'wp-admin/upload.php' ) && current_user_can( 'upload_files' ) ) {
@@ -584,9 +356,8 @@
584 356 return;
585 357 }
586 358 $this->verify_permissions( $data['editor_post_id'] );
587 359 }
588 -
589 360 private function verify_permissions( $editor_post_id ) {
590 361 $document = Plugin::$instance->documents->get( $editor_post_id );
591 362
592 363 if ( ! $document ) {
@@ -592,14 +363,10 @@
592 363 if ( ! $document ) {
593 364 throw new \Exception( 'Document not found' );
594 365 }
595 366
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' );
367 + if ( ! $document->is_editable_by_current_user() ) {
368 + throw new \Exception( 'Access denied' );
602 369 }
603 370 }
604 371
605 372 public function ajax_ai_get_image_prompt_enhancer( $data ) {
@@ -705,9 +472,9 @@
705 472 'usage' => $result['usage'],
706 473 ];
707 474 }
708 475
709 - private function get_ai_app(): Ai {
476 + private function get_ai_app() : Ai {
710 477 return Plugin::$instance->common->get_component( 'connect' )->get_app( 'ai' );
711 478 }
712 479
713 480 private function get_request_context( $data ) {
@@ -1020,42 +787,8 @@
1020 787 if ( empty( $data['payload']['settings'] ) ) {
1021 788 throw new \Exception( 'Missing prompt settings' );
1022 789 }
1023 790
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 791 if ( ! $app->is_connected() ) {
1059 792 throw new \Exception( 'not_connected' );
1060 793 }
1061 794
@@ -1065,9 +798,10 @@
1065 798
1066 799 $context = $this->get_request_context( $data );
1067 800 $request_ids = $this->get_request_ids( $data['payload'] );
1068 801
1069 - $result = $app->get_image_to_image_mask_cleanup( [
802 + $result = $app->get_image_to_image_mask( [
803 + 'prompt' => $data['payload']['prompt'],
1070 804 'attachment_id' => $data['payload']['image']['id'],
1071 805 'mask' => $data['payload']['mask'],
1072 806 ], $context, $request_ids );
1073 807
@@ -1078,9 +812,8 @@
1078 812 'response_id' => $result['responseId'],
1079 813 'usage' => $result['usage'],
1080 814 ];
1081 815 }
1082 -
1083 816 public function ajax_ai_get_image_to_image_outpainting( $data ) {
1084 817 $this->verify_upload_permissions( $data );
1085 818
1086 819 $app = $this->get_ai_app();
@@ -1111,10 +844,8 @@
1111 844 ];
1112 845 }
1113 846
1114 847 public function ajax_ai_upload_image( $data ) {
1115 - $this->verify_upload_permissions( $data );
1116 -
1117 848 if ( empty( $data['image'] ) ) {
1118 849 throw new \Exception( 'Missing image data' );
1119 850 }
1120 851
@@ -1126,14 +857,14 @@
1126 857
1127 858 $image_data = $this->upload_image( $image['image_url'], $data['prompt'], $data['editor_post_id'] );
1128 859
1129 860 if ( is_wp_error( $image_data ) ) {
1130 - throw new \Exception( esc_html( $image_data->get_error_message() ) );
861 + throw new \Exception( $image_data->get_error_message() );
1131 862 }
1132 863
1133 864 if ( ! empty( $image['use_gallery_image'] ) && ! empty( $image['id'] ) ) {
1134 - $app = $this->get_ai_app();
1135 - $app->set_used_gallery_image( $image['id'] );
865 + $app = $this->get_ai_app();
866 + $app->set_used_gallery_image( $image['id'] );
1136 867 }
1137 868
1138 869 return [
1139 870 'image' => array_merge( $image_data, $data ),
@@ -1162,9 +893,9 @@
1162 893 $message = $result->get_error_message();
1163 894
1164 895 if ( is_array( $message ) ) {
1165 896 $message = implode( ', ', $message );
1166 - throw new \Exception( esc_html( $message ) );
897 + throw new \Exception( $message );
1167 898 }
1168 899
1169 900 $this->throw_on_error( $result );
1170 901 }
@@ -1312,24 +1043,10 @@
1312 1043 if ( ! current_user_can( 'upload_files' ) ) {
1313 1044 throw new \Exception( 'Not Allowed to Upload images' );
1314 1045 }
1315 1046
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 1047 $attachment_id = media_sideload_image( $image_url, $parent_post_id, $image_title, 'id' );
1327 1048
1328 - if ( is_wp_error( $attachment_id ) ) {
1329 - return new \WP_Error( 'upload_error', $attachment_id->get_error_message() );
1330 - }
1331 -
1332 1049 if ( ! empty( $attachment_id['error'] ) ) {
1333 1050 return new \WP_Error( 'upload_error', $attachment_id['error'] );
1334 1051 }
1335 1052
@@ -1334,10 +1051,10 @@
1334 1051 }
1335 1052
1336 1053 return [
1337 1054 'id' => $attachment_id,
1338 - 'url' => esc_url( wp_get_attachment_image_url( $attachment_id, 'full' ) ),
1339 - 'alt' => esc_attr( $image_title ),
1055 + 'url' => wp_get_attachment_image_url( $attachment_id, 'full' ),
1056 + 'alt' => $image_title,
1340 1057 'source' => 'library',
1341 1058 ];
1342 1059 }
1343 1060
@@ -1361,9 +1078,9 @@
1361 1078
1362 1079 $result = $app->get_history_by_type( $type, $page, $limit, $context );
1363 1080
1364 1081 if ( is_wp_error( $result ) ) {
1365 - throw new \Exception( esc_html( $result->get_error_message() ) );
1082 + throw new \Exception( $result->get_error_message() );
1366 1083 }
1367 1084
1368 1085 return $result;
1369 1086 }
@@ -1383,9 +1100,9 @@
1383 1100
1384 1101 $result = $app->delete_history_item( $data['id'], $context );
1385 1102
1386 1103 if ( is_wp_error( $result ) ) {
1387 - throw new \Exception( esc_html( $result->get_error_message() ) );
1104 + throw new \Exception( $result->get_error_message() );
1388 1105 }
1389 1106
1390 1107 return [];
1391 1108 }
@@ -1405,83 +1122,14 @@
1405 1122
1406 1123 $result = $app->toggle_favorite_history_item( $data['id'], $context );
1407 1124
1408 1125 if ( is_wp_error( $result ) ) {
1409 - throw new \Exception( esc_html( $result->get_error_message() ) );
1126 + throw new \Exception( $result->get_error_message() );
1410 1127 }
1411 1128
1412 1129 return [];
1413 1130 }
1414 1131
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 1132 /**
1485 1133 * @param mixed $result
1486 1134 */
1487 1135 private function throw_on_error( $result ): void {
@@ -1486,112 +1134,10 @@
1486 1134 */
1487 1135 private function throw_on_error( $result ): void {
1488 1136 if ( is_wp_error( $result ) ) {
1489 1137 wp_send_json_error( [
1490 - 'message' => esc_html( $result->get_error_message() ),
1138 + 'message' => $result->get_error_message(),
1491 1139 'extra_data' => $result->get_error_data(),
1492 1140 ] );
1493 1141 }
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 1142 }
1597 1143 }