PluginProbe
Elementor Website Builder – more than just a page builder / 3.23.0-beta5
Elementor Website Builder – more than just a page builder v3.23.0-beta5
4.3.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 All 455 releases
← All changes | modules/ai/module.php +117 -561 4.1.5 → 3.23.0-beta5 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',
@@ -139,23 +118,31 @@
139 118 'wp-blocks',
140 119 'wp-element',
141 120 'wp-editor',
142 121 'wp-data',
143 - 'wp-components',
144 - 'wp-compose',
145 - 'wp-i18n',
146 - 'wp-hooks',
147 - 'elementor-ai-media-library',
148 122 ],
149 123 ELEMENTOR_VERSION, true );
150 124
125 + $session_id = 'wp-gutenberg-session-' . Utils::generate_random_string();
126 +
127 + $config = [
128 + 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
129 + 'connect_url' => $this->get_ai_connect_url(),
130 + 'client_session_id' => $session_id,
131 + ];
132 +
133 + if ( $this->get_ai_app()->is_connected() ) {
134 + $usage = $this->get_ai_app()->get_usage( 'gutenberg-loader', $session_id );
135 +
136 + if ( ! is_wp_error( $usage ) ) {
137 + $config['usage'] = $usage;
138 + }
139 + }
140 +
151 141 wp_localize_script(
152 142 'elementor-ai-gutenberg',
153 143 'ElementorAiConfig',
154 - [
155 - 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
156 - 'connect_url' => $this->get_ai_connect_url(),
157 - ]
144 + $config
158 145 );
159 146
160 147 wp_set_script_translations( 'elementor-ai-gutenberg', 'elementor' );
161 148 });
@@ -160,274 +147,29 @@
160 147 wp_set_script_translations( 'elementor-ai-gutenberg', 'elementor' );
161 148 });
162 149
163 150 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;
151 + if ( $this->is_layout_active() ) {
152 + return $this->remove_temporary_containers( $data );
345 153 }
346 154
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();
155 + return $data;
156 + } );
367 157 }
368 158
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 - ],
159 + private function register_layout_experiment() {
160 + Plugin::$instance->experiments->add_feature( [
161 + 'name' => static::LAYOUT_EXPERIMENT,
162 + 'title' => esc_html__( 'Build with AI', 'elementor' ),
163 + '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' ),
164 + 'default' => Experiments_Manager::STATE_ACTIVE,
165 + 'release_status' => Experiments_Manager::RELEASE_STATUS_STABLE,
166 + 'dependencies' => [
167 + 'container',
378 168 ],
379 - 'fields' => 'ids',
380 - 'numberposts' => 1,
381 169 ] );
382 -
383 - return ! empty( $attachments ) ? $attachments[0] : null;
384 170 }
385 171
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 172 public function enqueue_ai_media_library() {
431 173 wp_enqueue_script( 'elementor-ai-media-library',
432 174 $this->get_js_assets_url( 'ai-media-library' ),
433 175 [
@@ -439,15 +181,26 @@
439 181 ELEMENTOR_VERSION,
440 182 true
441 183 );
442 184
185 + if ( function_exists( 'get_current_screen' ) ) {
186 + if ( get_current_screen()->is_block_editor ) {
187 + return;
188 + }
189 + }
190 +
191 + $session_id = 'wp-media-library-session-' . Utils::generate_random_string();
192 +
193 + $config = [
194 + 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
195 + 'connect_url' => $this->get_ai_connect_url(),
196 + 'client_session_id' => $session_id,
197 + ];
198 +
443 199 wp_localize_script(
444 200 'elementor-ai-media-library',
445 201 'ElementorAiConfig',
446 - [
447 - 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
448 - 'connect_url' => $this->get_ai_connect_url(),
449 - ]
202 + $config
450 203 );
451 204
452 205 wp_set_script_translations( 'elementor-ai-media-library', 'elementor' );
453 206 }
@@ -471,13 +224,24 @@
471 224 ELEMENTOR_VERSION,
472 225 true
473 226 );
474 227
228 + $session_id = 'elementor-editor-session-' . Utils::generate_random_string();
229 +
475 230 $config = [
476 231 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
477 232 'connect_url' => $this->get_ai_connect_url(),
233 + 'client_session_id' => $session_id,
478 234 ];
479 235
236 + if ( $this->get_ai_app()->is_connected() ) {
237 + $usage = $this->get_ai_app()->get_usage( 'elementor-loader', $session_id );
238 +
239 + if ( ! is_wp_error( $usage ) ) {
240 + $config['usage'] = $usage;
241 + }
242 + }
243 +
480 244 wp_localize_script(
481 245 'elementor-ai',
482 246 'ElementorAiConfig',
483 247 $config
@@ -483,8 +247,31 @@
483 247 $config
484 248 );
485 249
486 250 wp_set_script_translations( 'elementor-ai', 'elementor' );
251 +
252 + if ( $this->get_ai_app()->is_connected() && ! empty( $config['is_get_started'] ) ) {
253 + $remote_config = Utils::get_cached_callback( [ $this->get_ai_app(), 'get_remote_config' ], 'ai_remote_config-' . get_current_user_id(), HOUR_IN_SECONDS );
254 +
255 + if ( ! is_wp_error( $remote_config ) && ! empty( $remote_config['config']['remoteIntegrationUrl'] ) ) {
256 + wp_enqueue_script(
257 + 'elementor-ai-integration',
258 + $remote_config['config']['remoteIntegrationUrl'],
259 + [
260 + 'elementor-ai',
261 + ],
262 + ELEMENTOR_VERSION,
263 + true
264 + );
265 + }
266 +
267 + add_filter( 'script_loader_tag', function( $tag, $handle ) {
268 + if ( 'elementor-ai-integration' === $handle ) {
269 + return str_replace( ' src', ' type="module" src', $tag );
270 + }
271 + return $tag;
272 + }, 10, 2 );
273 + }
487 274 }
488 275
489 276 private function enqueue_layout_script() {
490 277 wp_enqueue_script(
@@ -507,10 +294,14 @@
507 294
508 295 wp_set_script_translations( 'elementor-ai-layout', 'elementor' );
509 296 }
510 297
298 + private function is_layout_active() {
299 + return Plugin::$instance->experiments->is_feature_active( self::LAYOUT_EXPERIMENT );
300 + }
301 +
511 302 private function remove_temporary_containers( $data ) {
512 - if ( empty( $data['elements'] ) || ! is_array( $data['elements'] ) ) {
303 + if ( empty( $data['elements'] ) ) {
513 304 return $data;
514 305 }
515 306
516 307 // If for some reason the document has been saved during an AI Layout session,
@@ -545,9 +336,9 @@
545 336 'connect_url' => $this->get_ai_connect_url(),
546 337 ];
547 338 }
548 339
549 - $user_usage = wp_parse_args( $app->get_usage(), [
340 + $user_usage = wp_parse_args( $app->get_usage( 'elementor-editor', $data['editor_session_id'] ), [
550 341 'hasAiSubscription' => false,
551 342 'usedQuota' => 0,
552 343 'quota' => 100,
553 344 ] );
@@ -567,17 +358,8 @@
567 358 }
568 359
569 360 return $app->get_remote_config();
570 361 }
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 362 public function verify_upload_permissions( $data ) {
581 363 $referer = wp_get_referer();
582 364
583 365 if ( str_contains( $referer, 'wp-admin/upload.php' ) && current_user_can( 'upload_files' ) ) {
@@ -584,9 +366,8 @@
584 366 return;
585 367 }
586 368 $this->verify_permissions( $data['editor_post_id'] );
587 369 }
588 -
589 370 private function verify_permissions( $editor_post_id ) {
590 371 $document = Plugin::$instance->documents->get( $editor_post_id );
591 372
592 373 if ( ! $document ) {
@@ -592,14 +373,10 @@
592 373 if ( ! $document ) {
593 374 throw new \Exception( 'Document not found' );
594 375 }
595 376
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' );
377 + if ( ! $document->is_editable_by_current_user() ) {
378 + throw new \Exception( 'Access denied' );
602 379 }
603 380 }
604 381
605 382 public function ajax_ai_get_image_prompt_enhancer( $data ) {
@@ -705,9 +482,9 @@
705 482 'usage' => $result['usage'],
706 483 ];
707 484 }
708 485
709 - private function get_ai_app(): Ai {
486 + private function get_ai_app() : Ai {
710 487 return Plugin::$instance->common->get_component( 'connect' )->get_app( 'ai' );
711 488 }
712 489
713 490 private function get_request_context( $data ) {
@@ -1020,42 +797,8 @@
1020 797 if ( empty( $data['payload']['settings'] ) ) {
1021 798 throw new \Exception( 'Missing prompt settings' );
1022 799 }
1023 800
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 801 if ( ! $app->is_connected() ) {
1059 802 throw new \Exception( 'not_connected' );
1060 803 }
1061 804
@@ -1065,9 +808,10 @@
1065 808
1066 809 $context = $this->get_request_context( $data );
1067 810 $request_ids = $this->get_request_ids( $data['payload'] );
1068 811
1069 - $result = $app->get_image_to_image_mask_cleanup( [
812 + $result = $app->get_image_to_image_mask( [
813 + 'prompt' => $data['payload']['prompt'],
1070 814 'attachment_id' => $data['payload']['image']['id'],
1071 815 'mask' => $data['payload']['mask'],
1072 816 ], $context, $request_ids );
1073 817
@@ -1078,9 +822,8 @@
1078 822 'response_id' => $result['responseId'],
1079 823 'usage' => $result['usage'],
1080 824 ];
1081 825 }
1082 -
1083 826 public function ajax_ai_get_image_to_image_outpainting( $data ) {
1084 827 $this->verify_upload_permissions( $data );
1085 828
1086 829 $app = $this->get_ai_app();
@@ -1111,10 +854,8 @@
1111 854 ];
1112 855 }
1113 856
1114 857 public function ajax_ai_upload_image( $data ) {
1115 - $this->verify_upload_permissions( $data );
1116 -
1117 858 if ( empty( $data['image'] ) ) {
1118 859 throw new \Exception( 'Missing image data' );
1119 860 }
1120 861
@@ -1126,14 +867,14 @@
1126 867
1127 868 $image_data = $this->upload_image( $image['image_url'], $data['prompt'], $data['editor_post_id'] );
1128 869
1129 870 if ( is_wp_error( $image_data ) ) {
1130 - throw new \Exception( esc_html( $image_data->get_error_message() ) );
871 + throw new \Exception( $image_data->get_error_message() );
1131 872 }
1132 873
1133 874 if ( ! empty( $image['use_gallery_image'] ) && ! empty( $image['id'] ) ) {
1134 - $app = $this->get_ai_app();
1135 - $app->set_used_gallery_image( $image['id'] );
875 + $app = $this->get_ai_app();
876 + $app->set_used_gallery_image( $image['id'] );
1136 877 }
1137 878
1138 879 return [
1139 880 'image' => array_merge( $image_data, $data ),
@@ -1162,9 +903,9 @@
1162 903 $message = $result->get_error_message();
1163 904
1164 905 if ( is_array( $message ) ) {
1165 906 $message = implode( ', ', $message );
1166 - throw new \Exception( esc_html( $message ) );
907 + throw new \Exception( $message );
1167 908 }
1168 909
1169 910 $this->throw_on_error( $result );
1170 911 }
@@ -1312,24 +1053,10 @@
1312 1053 if ( ! current_user_can( 'upload_files' ) ) {
1313 1054 throw new \Exception( 'Not Allowed to Upload images' );
1314 1055 }
1315 1056
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 1057 $attachment_id = media_sideload_image( $image_url, $parent_post_id, $image_title, 'id' );
1327 1058
1328 - if ( is_wp_error( $attachment_id ) ) {
1329 - return new \WP_Error( 'upload_error', $attachment_id->get_error_message() );
1330 - }
1331 -
1332 1059 if ( ! empty( $attachment_id['error'] ) ) {
1333 1060 return new \WP_Error( 'upload_error', $attachment_id['error'] );
1334 1061 }
1335 1062
@@ -1334,10 +1061,10 @@
1334 1061 }
1335 1062
1336 1063 return [
1337 1064 'id' => $attachment_id,
1338 - 'url' => esc_url( wp_get_attachment_image_url( $attachment_id, 'full' ) ),
1339 - 'alt' => esc_attr( $image_title ),
1065 + 'url' => wp_get_attachment_image_url( $attachment_id, 'full' ),
1066 + 'alt' => $image_title,
1340 1067 'source' => 'library',
1341 1068 ];
1342 1069 }
1343 1070
@@ -1361,9 +1088,9 @@
1361 1088
1362 1089 $result = $app->get_history_by_type( $type, $page, $limit, $context );
1363 1090
1364 1091 if ( is_wp_error( $result ) ) {
1365 - throw new \Exception( esc_html( $result->get_error_message() ) );
1092 + throw new \Exception( $result->get_error_message() );
1366 1093 }
1367 1094
1368 1095 return $result;
1369 1096 }
@@ -1383,9 +1110,9 @@
1383 1110
1384 1111 $result = $app->delete_history_item( $data['id'], $context );
1385 1112
1386 1113 if ( is_wp_error( $result ) ) {
1387 - throw new \Exception( esc_html( $result->get_error_message() ) );
1114 + throw new \Exception( $result->get_error_message() );
1388 1115 }
1389 1116
1390 1117 return [];
1391 1118 }
@@ -1405,83 +1132,14 @@
1405 1132
1406 1133 $result = $app->toggle_favorite_history_item( $data['id'], $context );
1407 1134
1408 1135 if ( is_wp_error( $result ) ) {
1409 - throw new \Exception( esc_html( $result->get_error_message() ) );
1136 + throw new \Exception( $result->get_error_message() );
1410 1137 }
1411 1138
1412 1139 return [];
1413 1140 }
1414 1141
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 1142 /**
1485 1143 * @param mixed $result
1486 1144 */
1487 1145 private function throw_on_error( $result ): void {
@@ -1486,112 +1144,10 @@
1486 1144 */
1487 1145 private function throw_on_error( $result ): void {
1488 1146 if ( is_wp_error( $result ) ) {
1489 1147 wp_send_json_error( [
1490 - 'message' => esc_html( $result->get_error_message() ),
1148 + 'message' => $result->get_error_message(),
1491 1149 'extra_data' => $result->get_error_data(),
1492 1150 ] );
1493 1151 }
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 1152 }
1597 1153 }