PluginProbe
Elementor Website Builder – more than just a page builder / 3.20.3
Elementor Website Builder – more than just a page builder v3.20.3
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 +106 -692 4.2.33.20.3 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'] ) ) {
@@ -613,11 +296,10 @@
613 296
614 297 if ( ! $app->is_connected() ) {
615 298 throw new \Exception( 'not_connected' );
616 299 }
617 - $request_ids = $this->get_request_ids( $data['payload'] );
618 300
619 - $result = $app->get_image_prompt_enhanced( $data['prompt'], [], $request_ids );
301 + $result = $app->get_image_prompt_enhanced( $data['prompt'] );
620 302 $this->throw_on_error( $result );
621 303
622 304 return [
623 305 'text' => $result['text'],
@@ -652,62 +334,9 @@
652 334 'usage' => $result['usage'],
653 335 ];
654 336 }
655 337
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 {
338 + private function get_ai_app() : Ai {
710 339 return Plugin::$instance->common->get_component( 'connect' )->get_app( 'ai' );
711 340 }
712 341
713 342 private function get_request_context( $data ) {
@@ -846,9 +475,9 @@
846 475 return [];
847 476 }
848 477
849 478 public function ajax_ai_get_text_to_image( $data ) {
850 - $this->verify_upload_permissions( $data );
479 + $this->verify_permissions( $data['editor_post_id'] );
851 480
852 481 if ( empty( $data['payload']['prompt'] ) ) {
853 482 throw new \Exception( 'Missing prompt' );
854 483 }
@@ -873,12 +502,16 @@
873 502 ];
874 503 }
875 504
876 505 public function ajax_ai_get_image_to_image( $data ) {
877 - $this->verify_upload_permissions( $data );
506 + $this->verify_permissions( $data['editor_post_id'] );
878 507
879 508 $app = $this->get_ai_app();
880 509
510 + if ( empty( $data['payload']['prompt'] ) ) {
511 + throw new \Exception( 'Missing prompt' );
512 + }
513 +
881 514 if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
882 515 throw new \Exception( 'Missing Image' );
883 516 }
884 517
@@ -908,9 +541,9 @@
908 541 ];
909 542 }
910 543
911 544 public function ajax_ai_get_image_to_image_upscale( $data ) {
912 - $this->verify_upload_permissions( $data );
545 + $this->verify_permissions( $data['editor_post_id'] );
913 546
914 547 $app = $this->get_ai_app();
915 548
916 549 if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
@@ -942,9 +575,9 @@
942 575 ];
943 576 }
944 577
945 578 public function ajax_ai_get_image_to_image_replace_background( $data ) {
946 - $this->verify_upload_permissions( $data );
579 + $this->verify_permissions( $data['editor_post_id'] );
947 580
948 581 $app = $this->get_ai_app();
949 582
950 583 if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
@@ -976,9 +609,9 @@
976 609 ];
977 610 }
978 611
979 612 public function ajax_ai_get_image_to_image_remove_background( $data ) {
980 - $this->verify_upload_permissions( $data );
613 + $this->verify_permissions( $data['editor_post_id'] );
981 614
982 615 $app = $this->get_ai_app();
983 616
984 617 if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
@@ -1004,9 +637,9 @@
1004 637 ];
1005 638 }
1006 639
1007 640 public function ajax_ai_get_image_to_image_mask( $data ) {
1008 - $this->verify_upload_permissions( $data );
641 + $this->verify_permissions( $data['editor_post_id'] );
1009 642
1010 643 $app = $this->get_ai_app();
1011 644
1012 645 if ( empty( $data['payload']['prompt'] ) ) {
@@ -1020,42 +653,8 @@
1020 653 if ( empty( $data['payload']['settings'] ) ) {
1021 654 throw new \Exception( 'Missing prompt settings' );
1022 655 }
1023 656
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 657 if ( ! $app->is_connected() ) {
1059 658 throw new \Exception( 'not_connected' );
1060 659 }
1061 660
@@ -1065,9 +664,11 @@
1065 664
1066 665 $context = $this->get_request_context( $data );
1067 666 $request_ids = $this->get_request_ids( $data['payload'] );
1068 667
1069 - $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'],
1070 671 'attachment_id' => $data['payload']['image']['id'],
1071 672 'mask' => $data['payload']['mask'],
1072 673 ], $context, $request_ids );
1073 674
@@ -1078,14 +679,17 @@
1078 679 'response_id' => $result['responseId'],
1079 680 'usage' => $result['usage'],
1080 681 ];
1081 682 }
1082 -
1083 683 public function ajax_ai_get_image_to_image_outpainting( $data ) {
1084 - $this->verify_upload_permissions( $data );
684 + $this->verify_permissions( $data['editor_post_id'] );
1085 685
1086 686 $app = $this->get_ai_app();
1087 687
688 + if ( empty( $data['payload']['prompt'] ) ) {
689 + throw new \Exception( 'Missing prompt' );
690 + }
691 +
1088 692 if ( ! $app->is_connected() ) {
1089 693 throw new \Exception( 'not_connected' );
1090 694 }
1091 695
@@ -1095,12 +699,10 @@
1095 699
1096 700 $context = $this->get_request_context( $data );
1097 701 $request_ids = $this->get_request_ids( $data['payload'] );
1098 702 $result = $app->get_image_to_image_out_painting( [
1099 - 'size' => $data['payload']['size'],
1100 - 'position' => $data['payload']['position'],
703 + 'prompt' => $data['payload']['prompt'],
1101 704 'mask' => $data['payload']['mask'],
1102 - 'image_base64' => $data['payload']['image_base64'],
1103 705 ], $context, $request_ids );
1104 706
1105 707 $this->throw_on_error( $result );
1106 708
@@ -1111,10 +713,8 @@
1111 713 ];
1112 714 }
1113 715
1114 716 public function ajax_ai_upload_image( $data ) {
1115 - $this->verify_upload_permissions( $data );
1116 -
1117 717 if ( empty( $data['image'] ) ) {
1118 718 throw new \Exception( 'Missing image data' );
1119 719 }
1120 720
@@ -1126,14 +726,14 @@
1126 726
1127 727 $image_data = $this->upload_image( $image['image_url'], $data['prompt'], $data['editor_post_id'] );
1128 728
1129 729 if ( is_wp_error( $image_data ) ) {
1130 - throw new \Exception( esc_html( $image_data->get_error_message() ) );
730 + throw new \Exception( $image_data->get_error_message() );
1131 731 }
1132 732
1133 733 if ( ! empty( $image['use_gallery_image'] ) && ! empty( $image['id'] ) ) {
1134 - $app = $this->get_ai_app();
1135 - $app->set_used_gallery_image( $image['id'] );
734 + $app = $this->get_ai_app();
735 + $app->set_used_gallery_image( $image['id'] );
1136 736 }
1137 737
1138 738 return [
1139 739 'image' => array_merge( $image_data, $data ),
@@ -1154,9 +754,9 @@
1154 754 }
1155 755
1156 756 $result = $app->generate_layout(
1157 757 $data,
1158 - $this->prepare_generate_layout_context( $data )
758 + $this->prepare_generate_layout_context()
1159 759 );
1160 760
1161 761 if ( is_wp_error( $result ) ) {
1162 762 $message = $result->get_error_message();
@@ -1162,9 +762,9 @@
1162 762 $message = $result->get_error_message();
1163 763
1164 764 if ( is_array( $message ) ) {
1165 765 $message = implode( ', ', $message );
1166 - throw new \Exception( esc_html( $message ) );
766 + throw new \Exception( $message );
1167 767 }
1168 768
1169 769 $this->throw_on_error( $result );
1170 770 }
@@ -1227,9 +827,9 @@
1227 827
1228 828 $result = $app->get_layout_prompt_enhanced(
1229 829 $data['prompt'],
1230 830 $data['enhance_type'],
1231 - $this->prepare_generate_layout_context( $data )
831 + $this->prepare_generate_layout_context()
1232 832 );
1233 833
1234 834 $this->throw_on_error( $result );
1235 835
@@ -1239,14 +839,13 @@
1239 839 'usage' => $result['usage'] ?? '',
1240 840 ];
1241 841 }
1242 842
1243 - private function prepare_generate_layout_context( $data ) {
1244 - $request_context = $this->get_request_context( $data );
843 + private function prepare_generate_layout_context() {
1245 844 $kit = Plugin::$instance->kits_manager->get_active_kit();
1246 845
1247 846 if ( ! $kit ) {
1248 - return $request_context;
847 + return [];
1249 848 }
1250 849
1251 850 $kits_data = Collection::make( $kit->get_data()['settings'] ?? [] );
1252 851
@@ -1299,14 +898,14 @@
1299 898 ],
1300 899 ];
1301 900 } );
1302 901
1303 - $request_context['globals'] = [
1304 - 'colors' => $colors->all(),
1305 - 'typography' => $typography->all(),
902 + return [
903 + 'globals' => [
904 + 'colors' => $colors->all(),
905 + 'typography' => $typography->all(),
906 + ],
1306 907 ];
1307 -
1308 - return $request_context;
1309 908 }
1310 909
1311 910 private function upload_image( $image_url, $image_title, $parent_post_id = 0 ) {
1312 911 if ( ! current_user_can( 'upload_files' ) ) {
@@ -1312,24 +911,10 @@
1312 911 if ( ! current_user_can( 'upload_files' ) ) {
1313 912 throw new \Exception( 'Not Allowed to Upload images' );
1314 913 }
1315 914
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 915 $attachment_id = media_sideload_image( $image_url, $parent_post_id, $image_title, 'id' );
1327 916
1328 - if ( is_wp_error( $attachment_id ) ) {
1329 - return new \WP_Error( 'upload_error', $attachment_id->get_error_message() );
1330 - }
1331 -
1332 917 if ( ! empty( $attachment_id['error'] ) ) {
1333 918 return new \WP_Error( 'upload_error', $attachment_id['error'] );
1334 919 }
1335 920
@@ -1334,10 +919,10 @@
1334 919 }
1335 920
1336 921 return [
1337 922 'id' => $attachment_id,
1338 - 'url' => esc_url( wp_get_attachment_image_url( $attachment_id, 'full' ) ),
1339 - 'alt' => esc_attr( $image_title ),
923 + 'url' => wp_get_attachment_image_url( $attachment_id, 'full' ),
924 + 'alt' => $image_title,
1340 925 'source' => 'library',
1341 926 ];
1342 927 }
1343 928
@@ -1361,9 +946,9 @@
1361 946
1362 947 $result = $app->get_history_by_type( $type, $page, $limit, $context );
1363 948
1364 949 if ( is_wp_error( $result ) ) {
1365 - throw new \Exception( esc_html( $result->get_error_message() ) );
950 + throw new \Exception( $result->get_error_message() );
1366 951 }
1367 952
1368 953 return $result;
1369 954 }
@@ -1383,9 +968,9 @@
1383 968
1384 969 $result = $app->delete_history_item( $data['id'], $context );
1385 970
1386 971 if ( is_wp_error( $result ) ) {
1387 - throw new \Exception( esc_html( $result->get_error_message() ) );
972 + throw new \Exception( $result->get_error_message() );
1388 973 }
1389 974
1390 975 return [];
1391 976 }
@@ -1405,83 +990,14 @@
1405 990
1406 991 $result = $app->toggle_favorite_history_item( $data['id'], $context );
1407 992
1408 993 if ( is_wp_error( $result ) ) {
1409 - throw new \Exception( esc_html( $result->get_error_message() ) );
994 + throw new \Exception( $result->get_error_message() );
1410 995 }
1411 996
1412 997 return [];
1413 998 }
1414 999
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 1000 /**
1485 1001 * @param mixed $result
1486 1002 */
1487 1003 private function throw_on_error( $result ): void {
@@ -1486,112 +1002,10 @@
1486 1002 */
1487 1003 private function throw_on_error( $result ): void {
1488 1004 if ( is_wp_error( $result ) ) {
1489 1005 wp_send_json_error( [
1490 - 'message' => esc_html( $result->get_error_message() ),
1006 + 'message' => $result->get_error_message(),
1491 1007 'extra_data' => $result->get_error_data(),
1492 1008 ] );
1493 1009 }
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 1010 }
1597 1011 }