PluginProbe
Elementor Website Builder – more than just a page builder / 3.22.0-dev4
Elementor Website Builder – more than just a page builder v3.22.0-dev4
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 +87 -677 4.2.23.22.0-dev4 View file →
@@ -1,15 +1,13 @@
1 1 <?php
2 2 namespace Elementor\Modules\Ai;
3 3
4 -use Elementor\Controls_Manager;
5 4 use Elementor\Core\Base\Module as BaseModule;
6 5 use Elementor\Core\Common\Modules\Connect\Module as ConnectModule;
7 -use Elementor\Element_Base;
8 -use Elementor\Modules\Ai\Feature_Intro\Product_Image_Unification_Intro;
9 -use Elementor\Plugin;
6 +use Elementor\Core\Experiments\Manager as Experiments_Manager;
10 7 use Elementor\Core\Utils\Collection;
11 8 use Elementor\Modules\Ai\Connect\Ai;
9 +use Elementor\Plugin;
12 10 use Elementor\User;
13 11 use Elementor\Utils;
14 12
15 13 if ( ! defined( 'ABSPATH' ) ) {
@@ -28,10 +26,11 @@
28 26 self::HISTORY_TYPE_CODE,
29 27 self::HISTORY_TYPE_IMAGE,
30 28 self::HISTORY_TYPE_BLOCK,
31 29 ];
32 - const MIN_PAGES_FOR_CREATE_WITH_AI_BANNER = 10;
33 30
31 + const LAYOUT_EXPERIMENT = 'ai-layout';
32 +
34 33 public function get_name() {
35 34 return 'ai';
36 35 }
37 36
@@ -37,21 +36,10 @@
37 36
38 37 public function __construct() {
39 38 parent::__construct();
40 39
41 - ( new SitePlannerConnect\Module() );
40 + $this->register_layout_experiment();
42 41
43 - if ( is_admin() ) {
44 - ( new Preferences() )->register();
45 - add_action( 'elementor/import-export/import-kit/runner/after-run', [ $this, 'handle_kit_install' ] );
46 - }
47 -
48 - if ( ! $this->is_ai_enabled() ) {
49 - return;
50 - }
51 -
52 - add_filter( 'elementor/core/admin/homescreen', [ $this, 'add_create_with_ai_banner_to_homescreen' ] );
53 -
54 42 add_action( 'elementor/connect/apps/register', function ( ConnectModule $connect_module ) {
55 43 $connect_module->register_app( 'ai', Ai::get_class_name() );
56 44 } );
57 45
@@ -58,12 +46,9 @@
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 - 'ai_get_excerpt' => [ $this, 'ajax_ai_get_excerpt' ],
65 - 'ai_get_featured_image' => [ $this, 'ajax_ai_get_featured_image' ],
66 51 'ai_get_edit_text' => [ $this, 'ajax_ai_get_edit_text' ],
67 52 'ai_get_custom_code' => [ $this, 'ajax_ai_get_custom_code' ],
68 53 'ai_get_custom_css' => [ $this, 'ajax_ai_get_custom_css' ],
69 54 'ai_set_get_started' => [ $this, 'ajax_ai_set_get_started' ],
@@ -71,9 +56,8 @@
71 56 'ai_get_image_prompt_enhancer' => [ $this, 'ajax_ai_get_image_prompt_enhancer' ],
72 57 'ai_get_text_to_image' => [ $this, 'ajax_ai_get_text_to_image' ],
73 58 'ai_get_image_to_image' => [ $this, 'ajax_ai_get_image_to_image' ],
74 59 '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 60 'ai_get_image_to_image_outpainting' => [ $this, 'ajax_ai_get_image_to_image_outpainting' ],
77 61 'ai_get_image_to_image_upscale' => [ $this, 'ajax_ai_get_image_to_image_upscale' ],
78 62 'ai_get_image_to_image_remove_background' => [ $this, 'ajax_ai_get_image_to_image_remove_background' ],
79 63 'ai_get_image_to_image_replace_background' => [ $this, 'ajax_ai_get_image_to_image_replace_background' ],
@@ -82,11 +66,8 @@
82 66 'ai_get_layout_prompt_enhancer' => [ $this, 'ajax_ai_get_layout_prompt_enhancer' ],
83 67 'ai_get_history' => [ $this, 'ajax_ai_get_history' ],
84 68 'ai_delete_history_item' => [ $this, 'ajax_ai_delete_history_item' ],
85 69 '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 70 ];
90 71
91 72 foreach ( $handlers as $tag => $callback ) {
92 73 $ajax->register_ajax_action( $tag, $callback );
@@ -94,9 +75,12 @@
94 75 } );
95 76
96 77 add_action( 'elementor/editor/before_enqueue_scripts', function() {
97 78 $this->enqueue_main_script();
98 - $this->enqueue_layout_script();
79 +
80 + if ( $this->is_layout_active() ) {
81 + $this->enqueue_layout_script();
82 + }
99 83 } );
100 84
101 85 add_action( 'elementor/editor/after_enqueue_styles', function() {
102 86 wp_enqueue_style(
@@ -107,352 +91,40 @@
107 91 );
108 92 } );
109 93
110 94 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 - );
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 + }
117 103 } );
118 104
119 - if ( is_admin() ) {
120 - 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();
105 + add_filter( 'elementor/document/save/data', function ( $data ) {
106 + if ( $this->is_layout_active() ) {
107 + return $this->remove_temporary_containers( $data );
129 108 }
130 - }
131 109
132 - add_action( 'enqueue_block_editor_assets', function() {
133 - wp_enqueue_script( 'elementor-ai-gutenberg',
134 - $this->get_js_assets_url( 'ai-gutenberg' ),
135 - [
136 - 'jquery',
137 - 'elementor-v2-ui',
138 - '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 - ],
149 - ELEMENTOR_VERSION, true );
150 -
151 - wp_localize_script(
152 - 'elementor-ai-gutenberg',
153 - 'ElementorAiConfig',
154 - [
155 - 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
156 - 'connect_url' => $this->get_ai_connect_url(),
157 - ]
158 - );
159 -
160 - wp_set_script_translations( 'elementor-ai-gutenberg', 'elementor' );
161 - });
162 -
163 - add_filter( 'elementor/document/save/data', function ( $data ) {
164 - return $this->remove_temporary_containers( $data );
110 + return $data;
165 111 } );
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 112 }
172 113
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'],
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',
214 123 ],
215 124 ] );
216 125 }
217 126
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;
345 - }
346 -
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();
367 - }
368 -
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 - ],
378 - ],
379 - 'fields' => 'ids',
380 - 'numberposts' => 1,
381 - ] );
382 -
383 - return ! empty( $attachments ) ? $attachments[0] : null;
384 - }
385 -
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 - public function enqueue_ai_media_library() {
431 - wp_enqueue_script( 'elementor-ai-media-library',
432 - $this->get_js_assets_url( 'ai-media-library' ),
433 - [
434 - 'jquery',
435 - 'elementor-v2-ui',
436 - 'elementor-v2-icons',
437 - 'media-grid',
438 - ],
439 - ELEMENTOR_VERSION,
440 - true
441 - );
442 -
443 - wp_localize_script(
444 - 'elementor-ai-media-library',
445 - 'ElementorAiConfig',
446 - [
447 - 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
448 - 'connect_url' => $this->get_ai_connect_url(),
449 - ]
450 - );
451 -
452 - wp_set_script_translations( 'elementor-ai-media-library', 'elementor' );
453 - }
454 -
455 127 private function enqueue_main_script() {
456 128 wp_enqueue_script(
457 129 'elementor-ai',
458 130 $this->get_js_assets_url( 'ai' ),
@@ -476,8 +148,13 @@
476 148 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
477 149 'connect_url' => $this->get_ai_connect_url(),
478 150 ];
479 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 +
480 157 wp_localize_script(
481 158 'elementor-ai',
482 159 'ElementorAiConfig',
483 160 $config
@@ -483,8 +160,31 @@
483 160 $config
484 161 );
485 162
486 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 + }
487 187 }
488 188
489 189 private function enqueue_layout_script() {
490 190 wp_enqueue_script(
@@ -507,10 +207,14 @@
507 207
508 208 wp_set_script_translations( 'elementor-ai-layout', 'elementor' );
509 209 }
510 210
211 + private function is_layout_active() {
212 + return Plugin::$instance->experiments->is_feature_active( self::LAYOUT_EXPERIMENT );
213 + }
214 +
511 215 private function remove_temporary_containers( $data ) {
512 - if ( empty( $data['elements'] ) || ! is_array( $data['elements'] ) ) {
216 + if ( empty( $data['elements'] ) ) {
513 217 return $data;
514 218 }
515 219
516 220 // If for some reason the document has been saved during an AI Layout session,
@@ -568,25 +272,8 @@
568 272
569 273 return $app->get_remote_config();
570 274 }
571 275
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 - public function verify_upload_permissions( $data ) {
581 - $referer = wp_get_referer();
582 -
583 - if ( str_contains( $referer, 'wp-admin/upload.php' ) && current_user_can( 'upload_files' ) ) {
584 - return;
585 - }
586 - $this->verify_permissions( $data['editor_post_id'] );
587 - }
588 -
589 276 private function verify_permissions( $editor_post_id ) {
590 277 $document = Plugin::$instance->documents->get( $editor_post_id );
591 278
592 279 if ( ! $document ) {
@@ -592,19 +279,15 @@
592 279 if ( ! $document ) {
593 280 throw new \Exception( 'Document not found' );
594 281 }
595 282
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' );
283 + if ( ! $document->is_built_with_elementor() || ! $document->is_editable_by_current_user() ) {
284 + throw new \Exception( 'Access denied' );
602 285 }
603 286 }
604 287
605 288 public function ajax_ai_get_image_prompt_enhancer( $data ) {
606 - $this->verify_upload_permissions( $data );
289 + $this->verify_permissions( $data['editor_post_id'] );
607 290
608 291 $app = $this->get_ai_app();
609 292
610 293 if ( empty( $data['prompt'] ) ) {
@@ -652,62 +335,9 @@
652 335 'usage' => $result['usage'],
653 336 ];
654 337 }
655 338
656 -
657 - public function ajax_ai_get_excerpt( $data ): array {
658 - $app = $this->get_ai_app();
659 -
660 - if ( empty( $data['payload']['content'] ) ) {
661 - throw new \Exception( 'Missing content' );
662 - }
663 -
664 - if ( ! $app->is_connected() ) {
665 - throw new \Exception( 'Not connected' );
666 - }
667 -
668 - $context = $this->get_request_context( $data );
669 -
670 - $request_ids = $this->get_request_ids( $data['payload'] );
671 -
672 - $result = $app->get_excerpt( $data['payload']['content'], $context, $request_ids );
673 - $this->throw_on_error( $result );
674 -
675 - return [
676 - 'text' => $result['text'],
677 - 'response_id' => $result['responseId'],
678 - 'usage' => $result['usage'],
679 - ];
680 - }
681 -
682 - public function ajax_ai_get_featured_image( $data ): array {
683 - $this->verify_upload_permissions( $data );
684 -
685 - if ( empty( $data['payload']['prompt'] ) ) {
686 - throw new \Exception( 'Missing prompt' );
687 - }
688 -
689 - $app = $this->get_ai_app();
690 -
691 - if ( ! $app->is_connected() ) {
692 - throw new \Exception( 'not_connected' );
693 - }
694 -
695 - $context = $this->get_request_context( $data );
696 - $request_ids = $this->get_request_ids( $data['payload'] );
697 -
698 - $result = $app->get_featured_image( $data, $context, $request_ids );
699 -
700 - $this->throw_on_error( $result );
701 -
702 - return [
703 - 'images' => $result['images'],
704 - 'response_id' => $result['responseId'],
705 - 'usage' => $result['usage'],
706 - ];
707 - }
708 -
709 - private function get_ai_app(): Ai {
339 + private function get_ai_app() : Ai {
710 340 return Plugin::$instance->common->get_component( 'connect' )->get_app( 'ai' );
711 341 }
712 342
713 343 private function get_request_context( $data ) {
@@ -846,9 +476,9 @@
846 476 return [];
847 477 }
848 478
849 479 public function ajax_ai_get_text_to_image( $data ) {
850 - $this->verify_upload_permissions( $data );
480 + $this->verify_permissions( $data['editor_post_id'] );
851 481
852 482 if ( empty( $data['payload']['prompt'] ) ) {
853 483 throw new \Exception( 'Missing prompt' );
854 484 }
@@ -873,9 +503,9 @@
873 503 ];
874 504 }
875 505
876 506 public function ajax_ai_get_image_to_image( $data ) {
877 - $this->verify_upload_permissions( $data );
507 + $this->verify_permissions( $data['editor_post_id'] );
878 508
879 509 $app = $this->get_ai_app();
880 510
881 511 if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
@@ -908,9 +538,9 @@
908 538 ];
909 539 }
910 540
911 541 public function ajax_ai_get_image_to_image_upscale( $data ) {
912 - $this->verify_upload_permissions( $data );
542 + $this->verify_permissions( $data['editor_post_id'] );
913 543
914 544 $app = $this->get_ai_app();
915 545
916 546 if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
@@ -942,9 +572,9 @@
942 572 ];
943 573 }
944 574
945 575 public function ajax_ai_get_image_to_image_replace_background( $data ) {
946 - $this->verify_upload_permissions( $data );
576 + $this->verify_permissions( $data['editor_post_id'] );
947 577
948 578 $app = $this->get_ai_app();
949 579
950 580 if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
@@ -976,9 +606,9 @@
976 606 ];
977 607 }
978 608
979 609 public function ajax_ai_get_image_to_image_remove_background( $data ) {
980 - $this->verify_upload_permissions( $data );
610 + $this->verify_permissions( $data['editor_post_id'] );
981 611
982 612 $app = $this->get_ai_app();
983 613
984 614 if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
@@ -1004,9 +634,9 @@
1004 634 ];
1005 635 }
1006 636
1007 637 public function ajax_ai_get_image_to_image_mask( $data ) {
1008 - $this->verify_upload_permissions( $data );
638 + $this->verify_permissions( $data['editor_post_id'] );
1009 639
1010 640 $app = $this->get_ai_app();
1011 641
1012 642 if ( empty( $data['payload']['prompt'] ) ) {
@@ -1020,42 +650,8 @@
1020 650 if ( empty( $data['payload']['settings'] ) ) {
1021 651 throw new \Exception( 'Missing prompt settings' );
1022 652 }
1023 653
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 654 if ( ! $app->is_connected() ) {
1059 655 throw new \Exception( 'not_connected' );
1060 656 }
1061 657
@@ -1065,11 +661,13 @@
1065 661
1066 662 $context = $this->get_request_context( $data );
1067 663 $request_ids = $this->get_request_ids( $data['payload'] );
1068 664
1069 - $result = $app->get_image_to_image_mask_cleanup( [
665 + $result = $app->get_image_to_image_mask( [
666 + 'prompt' => $data['payload']['prompt'],
1070 667 'attachment_id' => $data['payload']['image']['id'],
1071 668 'mask' => $data['payload']['mask'],
669 + 'image_base64' => $data['payload']['image_base64'],
1072 670 ], $context, $request_ids );
1073 671
1074 672 $this->throw_on_error( $result );
1075 673
@@ -1078,11 +676,10 @@
1078 676 'response_id' => $result['responseId'],
1079 677 'usage' => $result['usage'],
1080 678 ];
1081 679 }
1082 -
1083 680 public function ajax_ai_get_image_to_image_outpainting( $data ) {
1084 - $this->verify_upload_permissions( $data );
681 + $this->verify_permissions( $data['editor_post_id'] );
1085 682
1086 683 $app = $this->get_ai_app();
1087 684
1088 685 if ( ! $app->is_connected() ) {
@@ -1111,10 +708,8 @@
1111 708 ];
1112 709 }
1113 710
1114 711 public function ajax_ai_upload_image( $data ) {
1115 - $this->verify_upload_permissions( $data );
1116 -
1117 712 if ( empty( $data['image'] ) ) {
1118 713 throw new \Exception( 'Missing image data' );
1119 714 }
1120 715
@@ -1126,14 +721,14 @@
1126 721
1127 722 $image_data = $this->upload_image( $image['image_url'], $data['prompt'], $data['editor_post_id'] );
1128 723
1129 724 if ( is_wp_error( $image_data ) ) {
1130 - throw new \Exception( esc_html( $image_data->get_error_message() ) );
725 + throw new \Exception( $image_data->get_error_message() );
1131 726 }
1132 727
1133 728 if ( ! empty( $image['use_gallery_image'] ) && ! empty( $image['id'] ) ) {
1134 - $app = $this->get_ai_app();
1135 - $app->set_used_gallery_image( $image['id'] );
729 + $app = $this->get_ai_app();
730 + $app->set_used_gallery_image( $image['id'] );
1136 731 }
1137 732
1138 733 return [
1139 734 'image' => array_merge( $image_data, $data ),
@@ -1162,9 +757,9 @@
1162 757 $message = $result->get_error_message();
1163 758
1164 759 if ( is_array( $message ) ) {
1165 760 $message = implode( ', ', $message );
1166 - throw new \Exception( esc_html( $message ) );
761 + throw new \Exception( $message );
1167 762 }
1168 763
1169 764 $this->throw_on_error( $result );
1170 765 }
@@ -1312,24 +907,10 @@
1312 907 if ( ! current_user_can( 'upload_files' ) ) {
1313 908 throw new \Exception( 'Not Allowed to Upload images' );
1314 909 }
1315 910
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 911 $attachment_id = media_sideload_image( $image_url, $parent_post_id, $image_title, 'id' );
1327 912
1328 - if ( is_wp_error( $attachment_id ) ) {
1329 - return new \WP_Error( 'upload_error', $attachment_id->get_error_message() );
1330 - }
1331 -
1332 913 if ( ! empty( $attachment_id['error'] ) ) {
1333 914 return new \WP_Error( 'upload_error', $attachment_id['error'] );
1334 915 }
1335 916
@@ -1334,10 +915,10 @@
1334 915 }
1335 916
1336 917 return [
1337 918 'id' => $attachment_id,
1338 - 'url' => esc_url( wp_get_attachment_image_url( $attachment_id, 'full' ) ),
1339 - 'alt' => esc_attr( $image_title ),
919 + 'url' => wp_get_attachment_image_url( $attachment_id, 'full' ),
920 + 'alt' => $image_title,
1340 921 'source' => 'library',
1341 922 ];
1342 923 }
1343 924
@@ -1361,9 +942,9 @@
1361 942
1362 943 $result = $app->get_history_by_type( $type, $page, $limit, $context );
1363 944
1364 945 if ( is_wp_error( $result ) ) {
1365 - throw new \Exception( esc_html( $result->get_error_message() ) );
946 + throw new \Exception( $result->get_error_message() );
1366 947 }
1367 948
1368 949 return $result;
1369 950 }
@@ -1383,9 +964,9 @@
1383 964
1384 965 $result = $app->delete_history_item( $data['id'], $context );
1385 966
1386 967 if ( is_wp_error( $result ) ) {
1387 - throw new \Exception( esc_html( $result->get_error_message() ) );
968 + throw new \Exception( $result->get_error_message() );
1388 969 }
1389 970
1390 971 return [];
1391 972 }
@@ -1405,83 +986,14 @@
1405 986
1406 987 $result = $app->toggle_favorite_history_item( $data['id'], $context );
1407 988
1408 989 if ( is_wp_error( $result ) ) {
1409 - throw new \Exception( esc_html( $result->get_error_message() ) );
990 + throw new \Exception( $result->get_error_message() );
1410 991 }
1411 992
1412 993 return [];
1413 994 }
1414 995
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 996 /**
1485 997 * @param mixed $result
1486 998 */
1487 999 private function throw_on_error( $result ): void {
@@ -1486,112 +998,10 @@
1486 998 */
1487 999 private function throw_on_error( $result ): void {
1488 1000 if ( is_wp_error( $result ) ) {
1489 1001 wp_send_json_error( [
1490 - 'message' => esc_html( $result->get_error_message() ),
1002 + 'message' => $result->get_error_message(),
1491 1003 'extra_data' => $result->get_error_data(),
1492 1004 ] );
1493 1005 }
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 1006 }
1597 1007 }