PluginProbe
Elementor Website Builder – more than just a page builder / 3.20.2
Elementor Website Builder – more than just a page builder v3.20.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 +106 -690 4.3.13.20.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,9 @@
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 - 'ai_get_excerpt' => [ $this, 'ajax_ai_get_excerpt' ],
64 - 'ai_get_featured_image' => [ $this, 'ajax_ai_get_featured_image' ],
65 51 'ai_get_edit_text' => [ $this, 'ajax_ai_get_edit_text' ],
66 52 'ai_get_custom_code' => [ $this, 'ajax_ai_get_custom_code' ],
67 53 'ai_get_custom_css' => [ $this, 'ajax_ai_get_custom_css' ],
68 54 'ai_set_get_started' => [ $this, 'ajax_ai_set_get_started' ],
@@ -70,9 +56,8 @@
70 56 'ai_get_image_prompt_enhancer' => [ $this, 'ajax_ai_get_image_prompt_enhancer' ],
71 57 'ai_get_text_to_image' => [ $this, 'ajax_ai_get_text_to_image' ],
72 58 'ai_get_image_to_image' => [ $this, 'ajax_ai_get_image_to_image' ],
73 59 '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 60 'ai_get_image_to_image_outpainting' => [ $this, 'ajax_ai_get_image_to_image_outpainting' ],
76 61 'ai_get_image_to_image_upscale' => [ $this, 'ajax_ai_get_image_to_image_upscale' ],
77 62 'ai_get_image_to_image_remove_background' => [ $this, 'ajax_ai_get_image_to_image_remove_background' ],
78 63 'ai_get_image_to_image_replace_background' => [ $this, 'ajax_ai_get_image_to_image_replace_background' ],
@@ -81,11 +66,8 @@
81 66 'ai_get_layout_prompt_enhancer' => [ $this, 'ajax_ai_get_layout_prompt_enhancer' ],
82 67 'ai_get_history' => [ $this, 'ajax_ai_get_history' ],
83 68 'ai_delete_history_item' => [ $this, 'ajax_ai_delete_history_item' ],
84 69 '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 70 ];
89 71
90 72 foreach ( $handlers as $tag => $callback ) {
91 73 $ajax->register_ajax_action( $tag, $callback );
@@ -93,9 +75,12 @@
93 75 } );
94 76
95 77 add_action( 'elementor/editor/before_enqueue_scripts', function() {
96 78 $this->enqueue_main_script();
97 - $this->enqueue_layout_script();
79 +
80 + if ( $this->is_layout_active() ) {
81 + $this->enqueue_layout_script();
82 + }
98 83 } );
99 84
100 85 add_action( 'elementor/editor/after_enqueue_styles', function() {
101 86 wp_enqueue_style(
@@ -106,351 +91,40 @@
106 91 );
107 92 } );
108 93
109 94 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 - );
95 + if ( $this->is_layout_active() ) {
96 + wp_enqueue_style(
97 + 'elementor-ai-layout-preview',
98 + $this->get_css_assets_url( 'modules/ai/layout-preview' ),
99 + [],
100 + ELEMENTOR_VERSION
101 + );
102 + }
116 103 } );
117 104
118 - if ( is_admin() ) {
119 - add_action( 'wp_enqueue_media', [ $this, 'enqueue_ai_media_library' ] );
120 - add_action( 'admin_head', [ $this, 'enqueue_ai_media_library_upload_screen' ] );
121 -
122 - if ( current_user_can( 'edit_products' ) || current_user_can( 'publish_products' ) ) {
123 - add_action( 'admin_init', [ $this, 'enqueue_ai_products_page_scripts' ] );
124 - add_action( 'current_screen', [ $this, 'enqueue_ai_single_product_page_scripts' ] );
125 - add_action( 'wp_ajax_elementor-ai-get-product-images', [ $this, 'get_product_images_ajax' ] );
126 - add_action( 'wp_ajax_elementor-ai-set-product-images', [ $this, 'set_product_images_ajax' ] );
105 + add_filter( 'elementor/document/save/data', function ( $data ) {
106 + if ( $this->is_layout_active() ) {
107 + return $this->remove_temporary_containers( $data );
127 108 }
128 - }
129 109
130 - add_action( 'enqueue_block_editor_assets', function() {
131 - wp_enqueue_script( 'elementor-ai-gutenberg',
132 - $this->get_js_assets_url( 'ai-gutenberg' ),
133 - [
134 - 'jquery',
135 - 'elementor-v2-ui',
136 - 'elementor-v2-icons',
137 - 'wp-blocks',
138 - 'wp-element',
139 - 'wp-editor',
140 - 'wp-data',
141 - 'wp-components',
142 - 'wp-compose',
143 - 'wp-i18n',
144 - 'wp-hooks',
145 - 'elementor-ai-media-library',
146 - ],
147 - ELEMENTOR_VERSION, true );
148 -
149 - wp_localize_script(
150 - 'elementor-ai-gutenberg',
151 - 'ElementorAiConfig',
152 - [
153 - 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
154 - 'connect_url' => $this->get_ai_connect_url(),
155 - ]
156 - );
157 -
158 - wp_set_script_translations( 'elementor-ai-gutenberg', 'elementor' );
159 - });
160 -
161 - add_filter( 'elementor/document/save/data', function ( $data ) {
162 - return $this->remove_temporary_containers( $data );
110 + return $data;
163 111 } );
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 112 }
170 113
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'],
114 + private function register_layout_experiment() {
115 + Plugin::$instance->experiments->add_feature( [
116 + 'name' => static::LAYOUT_EXPERIMENT,
117 + 'title' => esc_html__( 'Build with AI', 'elementor' ),
118 + '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' ),
119 + 'default' => Experiments_Manager::STATE_ACTIVE,
120 + 'release_status' => Experiments_Manager::RELEASE_STATUS_STABLE,
121 + 'dependencies' => [
122 + 'container',
212 123 ],
213 124 ] );
214 125 }
215 126
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;
343 - }
344 -
345 - $image_id = get_post_thumbnail_id( $post_id );
346 -
347 - if ( ! $image_id ) {
348 - $product = wc_get_product( $post_id );
349 - $gallery_image_ids = $product->get_gallery_image_ids();
350 - if ( ! empty( $gallery_image_ids ) ) {
351 - $image_id = $gallery_image_ids[0];
352 - }
353 - }
354 -
355 - $image_ids[] = [
356 - 'productId' => $post_id,
357 - 'id' => $image_id ? $image_id : 'No Image',
358 - 'image_url' => $image_id ? wp_get_attachment_url( $image_id ) : 'No Image',
359 - ];
360 - }
361 -
362 - wp_send_json_success( [ 'product_images' => array_slice( $image_ids, 0, 10 ) ] );
363 -
364 - wp_die();
365 - }
366 -
367 - private function get_attachment_id_by_url( $url ) {
368 - $attachments = get_posts( [
369 - 'post_type' => 'attachment',
370 - 'meta_query' => [
371 - [
372 - 'key' => '_wp_attached_file',
373 - 'value' => basename( $url ),
374 - 'compare' => 'LIKE',
375 - ],
376 - ],
377 - 'fields' => 'ids',
378 - 'numberposts' => 1,
379 - ] );
380 -
381 - return ! empty( $attachments ) ? $attachments[0] : null;
382 - }
383 -
384 - public function set_product_images_ajax() {
385 - check_ajax_referer( 'elementor-ai-unify-product-images_nonce', 'nonce' );
386 -
387 - $product_id = isset( $_POST['productId'] ) ? sanitize_text_field( wp_unslash( $_POST['productId'] ) ) : '';
388 - $image_url = isset( $_POST['image_url'] ) ? sanitize_text_field( wp_unslash( $_POST['image_url'] ) ) : '';
389 - $image_to_add = isset( $_POST['image_to_add'] ) ? intval( wp_unslash( $_POST['image_to_add'] ) ) : null;
390 - $image_to_remove = isset( $_POST['image_to_remove'] ) ? intval( wp_unslash( $_POST['image_to_remove'] ) ) : null;
391 - $is_product_gallery = isset( $_POST['is_product_gallery'] ) && sanitize_text_field( wp_unslash( $_POST['is_product_gallery'] ) ) === 'true';
392 -
393 - if ( ! $product_id || ! $image_url ) {
394 - throw new \Exception( 'Product ID and Image URL are required' );
395 - }
396 -
397 - $product = wc_get_product( $product_id );
398 - if ( ! $product ) {
399 - throw new \Exception( 'Product not found' );
400 - }
401 -
402 - $attachment_id = $this->get_attachment_id_by_url( $image_url );
403 - if ( is_wp_error( $attachment_id ) ) {
404 - throw new \Exception( 'Image upload failed' );
405 - }
406 -
407 - if ( $is_product_gallery ) {
408 - $this->update_product_gallery( $product, $image_to_remove, $image_to_add );
409 - } else {
410 - $product->set_image_id( $attachment_id );
411 - $product->save();
412 - }
413 -
414 - wp_send_json_success( [
415 - 'message' => __( 'Image added successfully', 'elementor' ),
416 - ] );
417 - }
418 -
419 - public function enqueue_ai_media_library_upload_screen() {
420 - $screen = get_current_screen();
421 - if ( ! $screen || 'upload' !== $screen->id ) {
422 - return;
423 - }
424 -
425 - $this->enqueue_ai_media_library();
426 - }
427 -
428 - public function enqueue_ai_media_library() {
429 - wp_enqueue_script( 'elementor-ai-media-library',
430 - $this->get_js_assets_url( 'ai-media-library' ),
431 - [
432 - 'jquery',
433 - 'elementor-v2-ui',
434 - 'elementor-v2-icons',
435 - 'media-grid',
436 - ],
437 - ELEMENTOR_VERSION,
438 - true
439 - );
440 -
441 - wp_localize_script(
442 - 'elementor-ai-media-library',
443 - 'ElementorAiConfig',
444 - [
445 - 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
446 - 'connect_url' => $this->get_ai_connect_url(),
447 - ]
448 - );
449 -
450 - wp_set_script_translations( 'elementor-ai-media-library', 'elementor' );
451 - }
452 -
453 127 private function enqueue_main_script() {
454 128 wp_enqueue_script(
455 129 'elementor-ai',
456 130 $this->get_js_assets_url( 'ai' ),
@@ -474,8 +148,13 @@
474 148 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
475 149 'connect_url' => $this->get_ai_connect_url(),
476 150 ];
477 151
152 + if ( $this->get_ai_app()->is_connected() ) {
153 + // Use a cached version, don't call the API on every editor load.
154 + $config['usage'] = $this->get_ai_app()->get_cached_usage();
155 + }
156 +
478 157 wp_localize_script(
479 158 'elementor-ai',
480 159 'ElementorAiConfig',
481 160 $config
@@ -481,8 +160,31 @@
481 160 $config
482 161 );
483 162
484 163 wp_set_script_translations( 'elementor-ai', 'elementor' );
164 +
165 + if ( $this->get_ai_app()->is_connected() && ! empty( $config['is_get_started'] ) ) {
166 + $remote_config = Utils::get_cached_callback( [ $this->get_ai_app(), 'get_remote_config' ], 'ai_remote_config-' . get_current_user_id(), HOUR_IN_SECONDS );
167 +
168 + if ( ! is_wp_error( $remote_config ) && ! empty( $remote_config['config']['remoteIntegrationUrl'] ) ) {
169 + wp_enqueue_script(
170 + 'elementor-ai-integration',
171 + $remote_config['config']['remoteIntegrationUrl'],
172 + [
173 + 'elementor-ai',
174 + ],
175 + ELEMENTOR_VERSION,
176 + true
177 + );
178 + }
179 +
180 + add_filter( 'script_loader_tag', function( $tag, $handle ) {
181 + if ( 'elementor-ai-integration' === $handle ) {
182 + return str_replace( ' src', ' type="module" src', $tag );
183 + }
184 + return $tag;
185 + }, 10, 2 );
186 + }
485 187 }
486 188
487 189 private function enqueue_layout_script() {
488 190 wp_enqueue_script(
@@ -505,10 +207,14 @@
505 207
506 208 wp_set_script_translations( 'elementor-ai-layout', 'elementor' );
507 209 }
508 210
211 + private function is_layout_active() {
212 + return Plugin::$instance->experiments->is_feature_active( self::LAYOUT_EXPERIMENT );
213 + }
214 +
509 215 private function remove_temporary_containers( $data ) {
510 - if ( empty( $data['elements'] ) || ! is_array( $data['elements'] ) ) {
216 + if ( empty( $data['elements'] ) ) {
511 217 return $data;
512 218 }
513 219
514 220 // If for some reason the document has been saved during an AI Layout session,
@@ -566,25 +272,8 @@
566 272
567 273 return $app->get_remote_config();
568 274 }
569 275
570 - public function ajax_ai_get_remote_frontend_config( $data ) {
571 - $callback = function () use ( $data ) {
572 - return $this->get_ai_app()->get_remote_frontend_config( $data );
573 - };
574 -
575 - return Utils::get_cached_callback( $callback, 'ai_remote_frontend_config-' . get_current_user_id(), HOUR_IN_SECONDS );
576 - }
577 -
578 - public function verify_upload_permissions( $data ) {
579 - $referer = wp_get_referer();
580 -
581 - if ( str_contains( $referer, 'wp-admin/upload.php' ) && current_user_can( 'upload_files' ) ) {
582 - return;
583 - }
584 - $this->verify_permissions( $data['editor_post_id'] );
585 - }
586 -
587 276 private function verify_permissions( $editor_post_id ) {
588 277 $document = Plugin::$instance->documents->get( $editor_post_id );
589 278
590 279 if ( ! $document ) {
@@ -590,19 +279,15 @@
590 279 if ( ! $document ) {
591 280 throw new \Exception( 'Document not found' );
592 281 }
593 282
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' );
283 + if ( ! $document->is_built_with_elementor() || ! $document->is_editable_by_current_user() ) {
284 + throw new \Exception( 'Access denied' );
600 285 }
601 286 }
602 287
603 288 public function ajax_ai_get_image_prompt_enhancer( $data ) {
604 - $this->verify_upload_permissions( $data );
289 + $this->verify_permissions( $data['editor_post_id'] );
605 290
606 291 $app = $this->get_ai_app();
607 292
608 293 if ( empty( $data['prompt'] ) ) {
@@ -611,11 +296,10 @@
611 296
612 297 if ( ! $app->is_connected() ) {
613 298 throw new \Exception( 'not_connected' );
614 299 }
615 - $request_ids = $this->get_request_ids( $data['payload'] );
616 300
617 - $result = $app->get_image_prompt_enhanced( $data['prompt'], [], $request_ids );
301 + $result = $app->get_image_prompt_enhanced( $data['prompt'] );
618 302 $this->throw_on_error( $result );
619 303
620 304 return [
621 305 'text' => $result['text'],
@@ -650,62 +334,9 @@
650 334 'usage' => $result['usage'],
651 335 ];
652 336 }
653 337
654 -
655 - public function ajax_ai_get_excerpt( $data ): array {
656 - $app = $this->get_ai_app();
657 -
658 - if ( empty( $data['payload']['content'] ) ) {
659 - throw new \Exception( 'Missing content' );
660 - }
661 -
662 - if ( ! $app->is_connected() ) {
663 - throw new \Exception( 'Not connected' );
664 - }
665 -
666 - $context = $this->get_request_context( $data );
667 -
668 - $request_ids = $this->get_request_ids( $data['payload'] );
669 -
670 - $result = $app->get_excerpt( $data['payload']['content'], $context, $request_ids );
671 - $this->throw_on_error( $result );
672 -
673 - return [
674 - 'text' => $result['text'],
675 - 'response_id' => $result['responseId'],
676 - 'usage' => $result['usage'],
677 - ];
678 - }
679 -
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 {
338 + private function get_ai_app() : Ai {
708 339 return Plugin::$instance->common->get_component( 'connect' )->get_app( 'ai' );
709 340 }
710 341
711 342 private function get_request_context( $data ) {
@@ -844,9 +475,9 @@
844 475 return [];
845 476 }
846 477
847 478 public function ajax_ai_get_text_to_image( $data ) {
848 - $this->verify_upload_permissions( $data );
479 + $this->verify_permissions( $data['editor_post_id'] );
849 480
850 481 if ( empty( $data['payload']['prompt'] ) ) {
851 482 throw new \Exception( 'Missing prompt' );
852 483 }
@@ -871,12 +502,16 @@
871 502 ];
872 503 }
873 504
874 505 public function ajax_ai_get_image_to_image( $data ) {
875 - $this->verify_upload_permissions( $data );
506 + $this->verify_permissions( $data['editor_post_id'] );
876 507
877 508 $app = $this->get_ai_app();
878 509
510 + if ( empty( $data['payload']['prompt'] ) ) {
511 + throw new \Exception( 'Missing prompt' );
512 + }
513 +
879 514 if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
880 515 throw new \Exception( 'Missing Image' );
881 516 }
882 517
@@ -906,9 +541,9 @@
906 541 ];
907 542 }
908 543
909 544 public function ajax_ai_get_image_to_image_upscale( $data ) {
910 - $this->verify_upload_permissions( $data );
545 + $this->verify_permissions( $data['editor_post_id'] );
911 546
912 547 $app = $this->get_ai_app();
913 548
914 549 if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
@@ -940,9 +575,9 @@
940 575 ];
941 576 }
942 577
943 578 public function ajax_ai_get_image_to_image_replace_background( $data ) {
944 - $this->verify_upload_permissions( $data );
579 + $this->verify_permissions( $data['editor_post_id'] );
945 580
946 581 $app = $this->get_ai_app();
947 582
948 583 if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
@@ -974,9 +609,9 @@
974 609 ];
975 610 }
976 611
977 612 public function ajax_ai_get_image_to_image_remove_background( $data ) {
978 - $this->verify_upload_permissions( $data );
613 + $this->verify_permissions( $data['editor_post_id'] );
979 614
980 615 $app = $this->get_ai_app();
981 616
982 617 if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
@@ -1002,9 +637,9 @@
1002 637 ];
1003 638 }
1004 639
1005 640 public function ajax_ai_get_image_to_image_mask( $data ) {
1006 - $this->verify_upload_permissions( $data );
641 + $this->verify_permissions( $data['editor_post_id'] );
1007 642
1008 643 $app = $this->get_ai_app();
1009 644
1010 645 if ( empty( $data['payload']['prompt'] ) ) {
@@ -1018,42 +653,8 @@
1018 653 if ( empty( $data['payload']['settings'] ) ) {
1019 654 throw new \Exception( 'Missing prompt settings' );
1020 655 }
1021 656
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 657 if ( ! $app->is_connected() ) {
1057 658 throw new \Exception( 'not_connected' );
1058 659 }
1059 660
@@ -1063,9 +664,11 @@
1063 664
1064 665 $context = $this->get_request_context( $data );
1065 666 $request_ids = $this->get_request_ids( $data['payload'] );
1066 667
1067 - $result = $app->get_image_to_image_mask_cleanup( [
668 + $result = $app->get_image_to_image_mask( [
669 + 'prompt' => $data['payload']['prompt'],
670 + 'promptSettings' => $data['payload']['settings'],
1068 671 'attachment_id' => $data['payload']['image']['id'],
1069 672 'mask' => $data['payload']['mask'],
1070 673 ], $context, $request_ids );
1071 674
@@ -1076,14 +679,17 @@
1076 679 'response_id' => $result['responseId'],
1077 680 'usage' => $result['usage'],
1078 681 ];
1079 682 }
1080 -
1081 683 public function ajax_ai_get_image_to_image_outpainting( $data ) {
1082 - $this->verify_upload_permissions( $data );
684 + $this->verify_permissions( $data['editor_post_id'] );
1083 685
1084 686 $app = $this->get_ai_app();
1085 687
688 + if ( empty( $data['payload']['prompt'] ) ) {
689 + throw new \Exception( 'Missing prompt' );
690 + }
691 +
1086 692 if ( ! $app->is_connected() ) {
1087 693 throw new \Exception( 'not_connected' );
1088 694 }
1089 695
@@ -1093,12 +699,10 @@
1093 699
1094 700 $context = $this->get_request_context( $data );
1095 701 $request_ids = $this->get_request_ids( $data['payload'] );
1096 702 $result = $app->get_image_to_image_out_painting( [
1097 - 'size' => $data['payload']['size'],
1098 - 'position' => $data['payload']['position'],
703 + 'prompt' => $data['payload']['prompt'],
1099 704 'mask' => $data['payload']['mask'],
1100 - 'image_base64' => $data['payload']['image_base64'],
1101 705 ], $context, $request_ids );
1102 706
1103 707 $this->throw_on_error( $result );
1104 708
@@ -1109,10 +713,8 @@
1109 713 ];
1110 714 }
1111 715
1112 716 public function ajax_ai_upload_image( $data ) {
1113 - $this->verify_upload_permissions( $data );
1114 -
1115 717 if ( empty( $data['image'] ) ) {
1116 718 throw new \Exception( 'Missing image data' );
1117 719 }
1118 720
@@ -1124,14 +726,14 @@
1124 726
1125 727 $image_data = $this->upload_image( $image['image_url'], $data['prompt'], $data['editor_post_id'] );
1126 728
1127 729 if ( is_wp_error( $image_data ) ) {
1128 - throw new \Exception( esc_html( $image_data->get_error_message() ) );
730 + throw new \Exception( $image_data->get_error_message() );
1129 731 }
1130 732
1131 733 if ( ! empty( $image['use_gallery_image'] ) && ! empty( $image['id'] ) ) {
1132 - $app = $this->get_ai_app();
1133 - $app->set_used_gallery_image( $image['id'] );
734 + $app = $this->get_ai_app();
735 + $app->set_used_gallery_image( $image['id'] );
1134 736 }
1135 737
1136 738 return [
1137 739 'image' => array_merge( $image_data, $data ),
@@ -1152,9 +754,9 @@
1152 754 }
1153 755
1154 756 $result = $app->generate_layout(
1155 757 $data,
1156 - $this->prepare_generate_layout_context( $data )
758 + $this->prepare_generate_layout_context()
1157 759 );
1158 760
1159 761 if ( is_wp_error( $result ) ) {
1160 762 $message = $result->get_error_message();
@@ -1160,9 +762,9 @@
1160 762 $message = $result->get_error_message();
1161 763
1162 764 if ( is_array( $message ) ) {
1163 765 $message = implode( ', ', $message );
1164 - throw new \Exception( esc_html( $message ) );
766 + throw new \Exception( $message );
1165 767 }
1166 768
1167 769 $this->throw_on_error( $result );
1168 770 }
@@ -1225,9 +827,9 @@
1225 827
1226 828 $result = $app->get_layout_prompt_enhanced(
1227 829 $data['prompt'],
1228 830 $data['enhance_type'],
1229 - $this->prepare_generate_layout_context( $data )
831 + $this->prepare_generate_layout_context()
1230 832 );
1231 833
1232 834 $this->throw_on_error( $result );
1233 835
@@ -1237,14 +839,13 @@
1237 839 'usage' => $result['usage'] ?? '',
1238 840 ];
1239 841 }
1240 842
1241 - private function prepare_generate_layout_context( $data ) {
1242 - $request_context = $this->get_request_context( $data );
843 + private function prepare_generate_layout_context() {
1243 844 $kit = Plugin::$instance->kits_manager->get_active_kit();
1244 845
1245 846 if ( ! $kit ) {
1246 - return $request_context;
847 + return [];
1247 848 }
1248 849
1249 850 $kits_data = Collection::make( $kit->get_data()['settings'] ?? [] );
1250 851
@@ -1297,14 +898,14 @@
1297 898 ],
1298 899 ];
1299 900 } );
1300 901
1301 - $request_context['globals'] = [
1302 - 'colors' => $colors->all(),
1303 - 'typography' => $typography->all(),
902 + return [
903 + 'globals' => [
904 + 'colors' => $colors->all(),
905 + 'typography' => $typography->all(),
906 + ],
1304 907 ];
1305 -
1306 - return $request_context;
1307 908 }
1308 909
1309 910 private function upload_image( $image_url, $image_title, $parent_post_id = 0 ) {
1310 911 if ( ! current_user_can( 'upload_files' ) ) {
@@ -1310,24 +911,10 @@
1310 911 if ( ! current_user_can( 'upload_files' ) ) {
1311 912 throw new \Exception( 'Not Allowed to Upload images' );
1312 913 }
1313 914
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 915 $attachment_id = media_sideload_image( $image_url, $parent_post_id, $image_title, 'id' );
1325 916
1326 - if ( is_wp_error( $attachment_id ) ) {
1327 - return new \WP_Error( 'upload_error', $attachment_id->get_error_message() );
1328 - }
1329 -
1330 917 if ( ! empty( $attachment_id['error'] ) ) {
1331 918 return new \WP_Error( 'upload_error', $attachment_id['error'] );
1332 919 }
1333 920
@@ -1332,10 +919,10 @@
1332 919 }
1333 920
1334 921 return [
1335 922 'id' => $attachment_id,
1336 - 'url' => esc_url( wp_get_attachment_image_url( $attachment_id, 'full' ) ),
1337 - 'alt' => esc_attr( $image_title ),
923 + 'url' => wp_get_attachment_image_url( $attachment_id, 'full' ),
924 + 'alt' => $image_title,
1338 925 'source' => 'library',
1339 926 ];
1340 927 }
1341 928
@@ -1359,9 +946,9 @@
1359 946
1360 947 $result = $app->get_history_by_type( $type, $page, $limit, $context );
1361 948
1362 949 if ( is_wp_error( $result ) ) {
1363 - throw new \Exception( esc_html( $result->get_error_message() ) );
950 + throw new \Exception( $result->get_error_message() );
1364 951 }
1365 952
1366 953 return $result;
1367 954 }
@@ -1381,9 +968,9 @@
1381 968
1382 969 $result = $app->delete_history_item( $data['id'], $context );
1383 970
1384 971 if ( is_wp_error( $result ) ) {
1385 - throw new \Exception( esc_html( $result->get_error_message() ) );
972 + throw new \Exception( $result->get_error_message() );
1386 973 }
1387 974
1388 975 return [];
1389 976 }
@@ -1403,83 +990,14 @@
1403 990
1404 991 $result = $app->toggle_favorite_history_item( $data['id'], $context );
1405 992
1406 993 if ( is_wp_error( $result ) ) {
1407 - throw new \Exception( esc_html( $result->get_error_message() ) );
994 + throw new \Exception( $result->get_error_message() );
1408 995 }
1409 996
1410 997 return [];
1411 998 }
1412 999
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 1000 /**
1483 1001 * @param mixed $result
1484 1002 */
1485 1003 private function throw_on_error( $result ): void {
@@ -1484,112 +1002,10 @@
1484 1002 */
1485 1003 private function throw_on_error( $result ): void {
1486 1004 if ( is_wp_error( $result ) ) {
1487 1005 wp_send_json_error( [
1488 - 'message' => esc_html( $result->get_error_message() ),
1006 + 'message' => $result->get_error_message(),
1489 1007 'extra_data' => $result->get_error_data(),
1490 1008 ] );
1491 1009 }
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 1010 }
1595 1011 }