PluginProbe
Elementor Website Builder – more than just a page builder / 3.21.0-beta2
Elementor Website Builder – more than just a page builder v3.21.0-beta2
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 4.1.0-beta2 All 456 releases
← All changes | modules/ai/module.php +105 -688 4.3.1 → 3.21.0-beta2 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,12 +503,16 @@
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
511 + if ( empty( $data['payload']['prompt'] ) ) {
512 + throw new \Exception( 'Missing prompt' );
513 + }
514 +
879 515 if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
880 516 throw new \Exception( 'Missing Image' );
881 517 }
882 518
@@ -906,9 +542,9 @@
906 542 ];
907 543 }
908 544
909 545 public function ajax_ai_get_image_to_image_upscale( $data ) {
910 - $this->verify_upload_permissions( $data );
546 + $this->verify_permissions( $data['editor_post_id'] );
911 547
912 548 $app = $this->get_ai_app();
913 549
914 550 if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
@@ -940,9 +576,9 @@
940 576 ];
941 577 }
942 578
943 579 public function ajax_ai_get_image_to_image_replace_background( $data ) {
944 - $this->verify_upload_permissions( $data );
580 + $this->verify_permissions( $data['editor_post_id'] );
945 581
946 582 $app = $this->get_ai_app();
947 583
948 584 if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
@@ -974,9 +610,9 @@
974 610 ];
975 611 }
976 612
977 613 public function ajax_ai_get_image_to_image_remove_background( $data ) {
978 - $this->verify_upload_permissions( $data );
614 + $this->verify_permissions( $data['editor_post_id'] );
979 615
980 616 $app = $this->get_ai_app();
981 617
982 618 if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
@@ -1002,9 +638,9 @@
1002 638 ];
1003 639 }
1004 640
1005 641 public function ajax_ai_get_image_to_image_mask( $data ) {
1006 - $this->verify_upload_permissions( $data );
642 + $this->verify_permissions( $data['editor_post_id'] );
1007 643
1008 644 $app = $this->get_ai_app();
1009 645
1010 646 if ( empty( $data['payload']['prompt'] ) ) {
@@ -1018,42 +654,8 @@
1018 654 if ( empty( $data['payload']['settings'] ) ) {
1019 655 throw new \Exception( 'Missing prompt settings' );
1020 656 }
1021 657
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 658 if ( ! $app->is_connected() ) {
1057 659 throw new \Exception( 'not_connected' );
1058 660 }
1059 661
@@ -1063,9 +665,11 @@
1063 665
1064 666 $context = $this->get_request_context( $data );
1065 667 $request_ids = $this->get_request_ids( $data['payload'] );
1066 668
1067 - $result = $app->get_image_to_image_mask_cleanup( [
669 + $result = $app->get_image_to_image_mask( [
670 + 'prompt' => $data['payload']['prompt'],
671 + 'promptSettings' => $data['payload']['settings'],
1068 672 'attachment_id' => $data['payload']['image']['id'],
1069 673 'mask' => $data['payload']['mask'],
1070 674 ], $context, $request_ids );
1071 675
@@ -1076,14 +680,17 @@
1076 680 'response_id' => $result['responseId'],
1077 681 'usage' => $result['usage'],
1078 682 ];
1079 683 }
1080 -
1081 684 public function ajax_ai_get_image_to_image_outpainting( $data ) {
1082 - $this->verify_upload_permissions( $data );
685 + $this->verify_permissions( $data['editor_post_id'] );
1083 686
1084 687 $app = $this->get_ai_app();
1085 688
689 + if ( empty( $data['payload']['prompt'] ) ) {
690 + throw new \Exception( 'Missing prompt' );
691 + }
692 +
1086 693 if ( ! $app->is_connected() ) {
1087 694 throw new \Exception( 'not_connected' );
1088 695 }
1089 696
@@ -1093,12 +700,10 @@
1093 700
1094 701 $context = $this->get_request_context( $data );
1095 702 $request_ids = $this->get_request_ids( $data['payload'] );
1096 703 $result = $app->get_image_to_image_out_painting( [
1097 - 'size' => $data['payload']['size'],
1098 - 'position' => $data['payload']['position'],
704 + 'prompt' => $data['payload']['prompt'],
1099 705 'mask' => $data['payload']['mask'],
1100 - 'image_base64' => $data['payload']['image_base64'],
1101 706 ], $context, $request_ids );
1102 707
1103 708 $this->throw_on_error( $result );
1104 709
@@ -1109,10 +714,8 @@
1109 714 ];
1110 715 }
1111 716
1112 717 public function ajax_ai_upload_image( $data ) {
1113 - $this->verify_upload_permissions( $data );
1114 -
1115 718 if ( empty( $data['image'] ) ) {
1116 719 throw new \Exception( 'Missing image data' );
1117 720 }
1118 721
@@ -1124,14 +727,14 @@
1124 727
1125 728 $image_data = $this->upload_image( $image['image_url'], $data['prompt'], $data['editor_post_id'] );
1126 729
1127 730 if ( is_wp_error( $image_data ) ) {
1128 - throw new \Exception( esc_html( $image_data->get_error_message() ) );
731 + throw new \Exception( $image_data->get_error_message() );
1129 732 }
1130 733
1131 734 if ( ! empty( $image['use_gallery_image'] ) && ! empty( $image['id'] ) ) {
1132 - $app = $this->get_ai_app();
1133 - $app->set_used_gallery_image( $image['id'] );
735 + $app = $this->get_ai_app();
736 + $app->set_used_gallery_image( $image['id'] );
1134 737 }
1135 738
1136 739 return [
1137 740 'image' => array_merge( $image_data, $data ),
@@ -1152,9 +755,9 @@
1152 755 }
1153 756
1154 757 $result = $app->generate_layout(
1155 758 $data,
1156 - $this->prepare_generate_layout_context( $data )
759 + $this->prepare_generate_layout_context()
1157 760 );
1158 761
1159 762 if ( is_wp_error( $result ) ) {
1160 763 $message = $result->get_error_message();
@@ -1160,9 +763,9 @@
1160 763 $message = $result->get_error_message();
1161 764
1162 765 if ( is_array( $message ) ) {
1163 766 $message = implode( ', ', $message );
1164 - throw new \Exception( esc_html( $message ) );
767 + throw new \Exception( $message );
1165 768 }
1166 769
1167 770 $this->throw_on_error( $result );
1168 771 }
@@ -1225,9 +828,9 @@
1225 828
1226 829 $result = $app->get_layout_prompt_enhanced(
1227 830 $data['prompt'],
1228 831 $data['enhance_type'],
1229 - $this->prepare_generate_layout_context( $data )
832 + $this->prepare_generate_layout_context()
1230 833 );
1231 834
1232 835 $this->throw_on_error( $result );
1233 836
@@ -1237,14 +840,13 @@
1237 840 'usage' => $result['usage'] ?? '',
1238 841 ];
1239 842 }
1240 843
1241 - private function prepare_generate_layout_context( $data ) {
1242 - $request_context = $this->get_request_context( $data );
844 + private function prepare_generate_layout_context() {
1243 845 $kit = Plugin::$instance->kits_manager->get_active_kit();
1244 846
1245 847 if ( ! $kit ) {
1246 - return $request_context;
848 + return [];
1247 849 }
1248 850
1249 851 $kits_data = Collection::make( $kit->get_data()['settings'] ?? [] );
1250 852
@@ -1297,14 +899,14 @@
1297 899 ],
1298 900 ];
1299 901 } );
1300 902
1301 - $request_context['globals'] = [
1302 - 'colors' => $colors->all(),
1303 - 'typography' => $typography->all(),
903 + return [
904 + 'globals' => [
905 + 'colors' => $colors->all(),
906 + 'typography' => $typography->all(),
907 + ],
1304 908 ];
1305 -
1306 - return $request_context;
1307 909 }
1308 910
1309 911 private function upload_image( $image_url, $image_title, $parent_post_id = 0 ) {
1310 912 if ( ! current_user_can( 'upload_files' ) ) {
@@ -1310,24 +912,10 @@
1310 912 if ( ! current_user_can( 'upload_files' ) ) {
1311 913 throw new \Exception( 'Not Allowed to Upload images' );
1312 914 }
1313 915
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 916 $attachment_id = media_sideload_image( $image_url, $parent_post_id, $image_title, 'id' );
1325 917
1326 - if ( is_wp_error( $attachment_id ) ) {
1327 - return new \WP_Error( 'upload_error', $attachment_id->get_error_message() );
1328 - }
1329 -
1330 918 if ( ! empty( $attachment_id['error'] ) ) {
1331 919 return new \WP_Error( 'upload_error', $attachment_id['error'] );
1332 920 }
1333 921
@@ -1332,10 +920,10 @@
1332 920 }
1333 921
1334 922 return [
1335 923 'id' => $attachment_id,
1336 - 'url' => esc_url( wp_get_attachment_image_url( $attachment_id, 'full' ) ),
1337 - 'alt' => esc_attr( $image_title ),
924 + 'url' => wp_get_attachment_image_url( $attachment_id, 'full' ),
925 + 'alt' => $image_title,
1338 926 'source' => 'library',
1339 927 ];
1340 928 }
1341 929
@@ -1359,9 +947,9 @@
1359 947
1360 948 $result = $app->get_history_by_type( $type, $page, $limit, $context );
1361 949
1362 950 if ( is_wp_error( $result ) ) {
1363 - throw new \Exception( esc_html( $result->get_error_message() ) );
951 + throw new \Exception( $result->get_error_message() );
1364 952 }
1365 953
1366 954 return $result;
1367 955 }
@@ -1381,9 +969,9 @@
1381 969
1382 970 $result = $app->delete_history_item( $data['id'], $context );
1383 971
1384 972 if ( is_wp_error( $result ) ) {
1385 - throw new \Exception( esc_html( $result->get_error_message() ) );
973 + throw new \Exception( $result->get_error_message() );
1386 974 }
1387 975
1388 976 return [];
1389 977 }
@@ -1403,83 +991,14 @@
1403 991
1404 992 $result = $app->toggle_favorite_history_item( $data['id'], $context );
1405 993
1406 994 if ( is_wp_error( $result ) ) {
1407 - throw new \Exception( esc_html( $result->get_error_message() ) );
995 + throw new \Exception( $result->get_error_message() );
1408 996 }
1409 997
1410 998 return [];
1411 999 }
1412 1000
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 1001 /**
1483 1002 * @param mixed $result
1484 1003 */
1485 1004 private function throw_on_error( $result ): void {
@@ -1484,112 +1003,10 @@
1484 1003 */
1485 1004 private function throw_on_error( $result ): void {
1486 1005 if ( is_wp_error( $result ) ) {
1487 1006 wp_send_json_error( [
1488 - 'message' => esc_html( $result->get_error_message() ),
1007 + 'message' => $result->get_error_message(),
1489 1008 'extra_data' => $result->get_error_data(),
1490 1009 ] );
1491 1010 }
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 1011 }
1595 1012 }