PluginProbe
Elementor Website Builder – more than just a page builder / 3.22.0-beta1
Elementor Website Builder – more than just a page builder v3.22.0-beta1
4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.3.0-beta3 4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 All 457 releases
← All changes | modules/ai/module.php +92 -678 4.3.1 → 3.22.0-beta1 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'] ) ) {
@@ -650,62 +335,9 @@
650 335 'usage' => $result['usage'],
651 336 ];
652 337 }
653 338
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 {
339 + private function get_ai_app() : Ai {
708 340 return Plugin::$instance->common->get_component( 'connect' )->get_app( 'ai' );
709 341 }
710 342
711 343 private function get_request_context( $data ) {
@@ -844,9 +476,9 @@
844 476 return [];
845 477 }
846 478
847 479 public function ajax_ai_get_text_to_image( $data ) {
848 - $this->verify_upload_permissions( $data );
480 + $this->verify_permissions( $data['editor_post_id'] );
849 481
850 482 if ( empty( $data['payload']['prompt'] ) ) {
851 483 throw new \Exception( 'Missing prompt' );
852 484 }
@@ -871,9 +503,9 @@
871 503 ];
872 504 }
873 505
874 506 public function ajax_ai_get_image_to_image( $data ) {
875 - $this->verify_upload_permissions( $data );
507 + $this->verify_permissions( $data['editor_post_id'] );
876 508
877 509 $app = $this->get_ai_app();
878 510
879 511 if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
@@ -906,9 +538,9 @@
906 538 ];
907 539 }
908 540
909 541 public function ajax_ai_get_image_to_image_upscale( $data ) {
910 - $this->verify_upload_permissions( $data );
542 + $this->verify_permissions( $data['editor_post_id'] );
911 543
912 544 $app = $this->get_ai_app();
913 545
914 546 if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
@@ -940,9 +572,9 @@
940 572 ];
941 573 }
942 574
943 575 public function ajax_ai_get_image_to_image_replace_background( $data ) {
944 - $this->verify_upload_permissions( $data );
576 + $this->verify_permissions( $data['editor_post_id'] );
945 577
946 578 $app = $this->get_ai_app();
947 579
948 580 if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
@@ -974,9 +606,9 @@
974 606 ];
975 607 }
976 608
977 609 public function ajax_ai_get_image_to_image_remove_background( $data ) {
978 - $this->verify_upload_permissions( $data );
610 + $this->verify_permissions( $data['editor_post_id'] );
979 611
980 612 $app = $this->get_ai_app();
981 613
982 614 if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
@@ -1002,9 +634,9 @@
1002 634 ];
1003 635 }
1004 636
1005 637 public function ajax_ai_get_image_to_image_mask( $data ) {
1006 - $this->verify_upload_permissions( $data );
638 + $this->verify_permissions( $data['editor_post_id'] );
1007 639
1008 640 $app = $this->get_ai_app();
1009 641
1010 642 if ( empty( $data['payload']['prompt'] ) ) {
@@ -1018,42 +650,8 @@
1018 650 if ( empty( $data['payload']['settings'] ) ) {
1019 651 throw new \Exception( 'Missing prompt settings' );
1020 652 }
1021 653
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 654 if ( ! $app->is_connected() ) {
1057 655 throw new \Exception( 'not_connected' );
1058 656 }
1059 657
@@ -1063,9 +661,11 @@
1063 661
1064 662 $context = $this->get_request_context( $data );
1065 663 $request_ids = $this->get_request_ids( $data['payload'] );
1066 664
1067 - $result = $app->get_image_to_image_mask_cleanup( [
665 + $result = $app->get_image_to_image_mask( [
666 + 'prompt' => $data['payload']['prompt'],
667 + 'promptSettings' => $data['payload']['settings'],
1068 668 'attachment_id' => $data['payload']['image']['id'],
1069 669 'mask' => $data['payload']['mask'],
1070 670 ], $context, $request_ids );
1071 671
@@ -1076,14 +676,17 @@
1076 676 'response_id' => $result['responseId'],
1077 677 'usage' => $result['usage'],
1078 678 ];
1079 679 }
1080 -
1081 680 public function ajax_ai_get_image_to_image_outpainting( $data ) {
1082 - $this->verify_upload_permissions( $data );
681 + $this->verify_permissions( $data['editor_post_id'] );
1083 682
1084 683 $app = $this->get_ai_app();
1085 684
685 + if ( empty( $data['payload']['prompt'] ) ) {
686 + throw new \Exception( 'Missing prompt' );
687 + }
688 +
1086 689 if ( ! $app->is_connected() ) {
1087 690 throw new \Exception( 'not_connected' );
1088 691 }
1089 692
@@ -1093,12 +696,10 @@
1093 696
1094 697 $context = $this->get_request_context( $data );
1095 698 $request_ids = $this->get_request_ids( $data['payload'] );
1096 699 $result = $app->get_image_to_image_out_painting( [
1097 - 'size' => $data['payload']['size'],
1098 - 'position' => $data['payload']['position'],
700 + 'prompt' => $data['payload']['prompt'],
1099 701 'mask' => $data['payload']['mask'],
1100 - 'image_base64' => $data['payload']['image_base64'],
1101 702 ], $context, $request_ids );
1102 703
1103 704 $this->throw_on_error( $result );
1104 705
@@ -1109,10 +710,8 @@
1109 710 ];
1110 711 }
1111 712
1112 713 public function ajax_ai_upload_image( $data ) {
1113 - $this->verify_upload_permissions( $data );
1114 -
1115 714 if ( empty( $data['image'] ) ) {
1116 715 throw new \Exception( 'Missing image data' );
1117 716 }
1118 717
@@ -1124,14 +723,14 @@
1124 723
1125 724 $image_data = $this->upload_image( $image['image_url'], $data['prompt'], $data['editor_post_id'] );
1126 725
1127 726 if ( is_wp_error( $image_data ) ) {
1128 - throw new \Exception( esc_html( $image_data->get_error_message() ) );
727 + throw new \Exception( $image_data->get_error_message() );
1129 728 }
1130 729
1131 730 if ( ! empty( $image['use_gallery_image'] ) && ! empty( $image['id'] ) ) {
1132 - $app = $this->get_ai_app();
1133 - $app->set_used_gallery_image( $image['id'] );
731 + $app = $this->get_ai_app();
732 + $app->set_used_gallery_image( $image['id'] );
1134 733 }
1135 734
1136 735 return [
1137 736 'image' => array_merge( $image_data, $data ),
@@ -1160,9 +759,9 @@
1160 759 $message = $result->get_error_message();
1161 760
1162 761 if ( is_array( $message ) ) {
1163 762 $message = implode( ', ', $message );
1164 - throw new \Exception( esc_html( $message ) );
763 + throw new \Exception( $message );
1165 764 }
1166 765
1167 766 $this->throw_on_error( $result );
1168 767 }
@@ -1310,24 +909,10 @@
1310 909 if ( ! current_user_can( 'upload_files' ) ) {
1311 910 throw new \Exception( 'Not Allowed to Upload images' );
1312 911 }
1313 912
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 913 $attachment_id = media_sideload_image( $image_url, $parent_post_id, $image_title, 'id' );
1325 914
1326 - if ( is_wp_error( $attachment_id ) ) {
1327 - return new \WP_Error( 'upload_error', $attachment_id->get_error_message() );
1328 - }
1329 -
1330 915 if ( ! empty( $attachment_id['error'] ) ) {
1331 916 return new \WP_Error( 'upload_error', $attachment_id['error'] );
1332 917 }
1333 918
@@ -1332,10 +917,10 @@
1332 917 }
1333 918
1334 919 return [
1335 920 'id' => $attachment_id,
1336 - 'url' => esc_url( wp_get_attachment_image_url( $attachment_id, 'full' ) ),
1337 - 'alt' => esc_attr( $image_title ),
921 + 'url' => wp_get_attachment_image_url( $attachment_id, 'full' ),
922 + 'alt' => $image_title,
1338 923 'source' => 'library',
1339 924 ];
1340 925 }
1341 926
@@ -1359,9 +944,9 @@
1359 944
1360 945 $result = $app->get_history_by_type( $type, $page, $limit, $context );
1361 946
1362 947 if ( is_wp_error( $result ) ) {
1363 - throw new \Exception( esc_html( $result->get_error_message() ) );
948 + throw new \Exception( $result->get_error_message() );
1364 949 }
1365 950
1366 951 return $result;
1367 952 }
@@ -1381,9 +966,9 @@
1381 966
1382 967 $result = $app->delete_history_item( $data['id'], $context );
1383 968
1384 969 if ( is_wp_error( $result ) ) {
1385 - throw new \Exception( esc_html( $result->get_error_message() ) );
970 + throw new \Exception( $result->get_error_message() );
1386 971 }
1387 972
1388 973 return [];
1389 974 }
@@ -1403,83 +988,14 @@
1403 988
1404 989 $result = $app->toggle_favorite_history_item( $data['id'], $context );
1405 990
1406 991 if ( is_wp_error( $result ) ) {
1407 - throw new \Exception( esc_html( $result->get_error_message() ) );
992 + throw new \Exception( $result->get_error_message() );
1408 993 }
1409 994
1410 995 return [];
1411 996 }
1412 997
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 998 /**
1483 999 * @param mixed $result
1484 1000 */
1485 1001 private function throw_on_error( $result ): void {
@@ -1484,112 +1000,10 @@
1484 1000 */
1485 1001 private function throw_on_error( $result ): void {
1486 1002 if ( is_wp_error( $result ) ) {
1487 1003 wp_send_json_error( [
1488 - 'message' => esc_html( $result->get_error_message() ),
1004 + 'message' => $result->get_error_message(),
1489 1005 'extra_data' => $result->get_error_data(),
1490 1006 ] );
1491 1007 }
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 1008 }
1595 1009 }