PluginProbe
Elementor Website Builder – more than just a page builder / 3.20.2
Elementor Website Builder – more than just a page builder v3.20.2
4.3.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 +107 -644 4.3.3 → 3.20.2 View file →
@@ -1,14 +1,13 @@
1 1 <?php
2 2 namespace Elementor\Modules\Ai;
3 3
4 -use Elementor\Controls_Manager;
5 4 use Elementor\Core\Base\Module as BaseModule;
6 5 use Elementor\Core\Common\Modules\Connect\Module as ConnectModule;
7 -use Elementor\Element_Base;
8 -use Elementor\Plugin;
6 +use Elementor\Core\Experiments\Manager as Experiments_Manager;
9 7 use Elementor\Core\Utils\Collection;
10 8 use Elementor\Modules\Ai\Connect\Ai;
9 +use Elementor\Plugin;
11 10 use Elementor\User;
12 11 use Elementor\Utils;
13 12
14 13 if ( ! defined( 'ABSPATH' ) ) {
@@ -27,8 +26,11 @@
27 26 self::HISTORY_TYPE_CODE,
28 27 self::HISTORY_TYPE_IMAGE,
29 28 self::HISTORY_TYPE_BLOCK,
30 29 ];
30 +
31 + const LAYOUT_EXPERIMENT = 'ai-layout';
32 +
31 33 public function get_name() {
32 34 return 'ai';
33 35 }
34 36
@@ -34,19 +36,10 @@
34 36
35 37 public function __construct() {
36 38 parent::__construct();
37 39
38 - ( new SitePlannerConnect\Module() );
40 + $this->register_layout_experiment();
39 41
40 - if ( is_admin() ) {
41 - ( new Preferences() )->register();
42 - add_action( 'elementor/import-export/import-kit/runner/after-run', [ $this, 'handle_kit_install' ] );
43 - }
44 -
45 - if ( ! $this->is_ai_enabled() ) {
46 - return;
47 - }
48 -
49 42 add_action( 'elementor/connect/apps/register', function ( ConnectModule $connect_module ) {
50 43 $connect_module->register_app( 'ai', Ai::get_class_name() );
51 44 } );
52 45
@@ -53,12 +46,9 @@
53 46 add_action( 'elementor/ajax/register_actions', function( $ajax ) {
54 47 $handlers = [
55 48 'ai_get_user_information' => [ $this, 'ajax_ai_get_user_information' ],
56 49 'ai_get_remote_config' => [ $this, 'ajax_ai_get_remote_config' ],
57 - 'ai_get_remote_frontend_config' => [ $this, 'ajax_ai_get_remote_frontend_config' ],
58 50 'ai_get_completion_text' => [ $this, 'ajax_ai_get_completion_text' ],
59 - 'ai_get_excerpt' => [ $this, 'ajax_ai_get_excerpt' ],
60 - 'ai_get_featured_image' => [ $this, 'ajax_ai_get_featured_image' ],
61 51 'ai_get_edit_text' => [ $this, 'ajax_ai_get_edit_text' ],
62 52 'ai_get_custom_code' => [ $this, 'ajax_ai_get_custom_code' ],
63 53 'ai_get_custom_css' => [ $this, 'ajax_ai_get_custom_css' ],
64 54 'ai_set_get_started' => [ $this, 'ajax_ai_set_get_started' ],
@@ -66,9 +56,8 @@
66 56 'ai_get_image_prompt_enhancer' => [ $this, 'ajax_ai_get_image_prompt_enhancer' ],
67 57 'ai_get_text_to_image' => [ $this, 'ajax_ai_get_text_to_image' ],
68 58 'ai_get_image_to_image' => [ $this, 'ajax_ai_get_image_to_image' ],
69 59 'ai_get_image_to_image_mask' => [ $this, 'ajax_ai_get_image_to_image_mask' ],
70 - 'ai_get_image_to_image_mask_cleanup' => [ $this, 'ajax_ai_get_image_to_image_mask_cleanup' ],
71 60 'ai_get_image_to_image_outpainting' => [ $this, 'ajax_ai_get_image_to_image_outpainting' ],
72 61 'ai_get_image_to_image_upscale' => [ $this, 'ajax_ai_get_image_to_image_upscale' ],
73 62 'ai_get_image_to_image_remove_background' => [ $this, 'ajax_ai_get_image_to_image_remove_background' ],
74 63 'ai_get_image_to_image_replace_background' => [ $this, 'ajax_ai_get_image_to_image_replace_background' ],
@@ -77,11 +66,8 @@
77 66 'ai_get_layout_prompt_enhancer' => [ $this, 'ajax_ai_get_layout_prompt_enhancer' ],
78 67 'ai_get_history' => [ $this, 'ajax_ai_get_history' ],
79 68 'ai_delete_history_item' => [ $this, 'ajax_ai_delete_history_item' ],
80 69 'ai_toggle_favorite_history_item' => [ $this, 'ajax_ai_toggle_favorite_history_item' ],
81 - 'ai_get_product_image_unification' => [ $this, 'ajax_ai_get_product_image_unification' ],
82 - 'ai_get_animation' => [ $this, 'ajax_ai_get_animation' ],
83 - 'ai_get_image_to_image_isolate_objects' => [ $this, 'ajax_ai_get_product_image_unification' ],
84 70 ];
85 71
86 72 foreach ( $handlers as $tag => $callback ) {
87 73 $ajax->register_ajax_action( $tag, $callback );
@@ -89,9 +75,12 @@
89 75 } );
90 76
91 77 add_action( 'elementor/editor/before_enqueue_scripts', function() {
92 78 $this->enqueue_main_script();
93 - $this->enqueue_layout_script();
79 +
80 + if ( $this->is_layout_active() ) {
81 + $this->enqueue_layout_script();
82 + }
94 83 } );
95 84
96 85 add_action( 'elementor/editor/after_enqueue_styles', function() {
97 86 wp_enqueue_style(
@@ -102,351 +91,40 @@
102 91 );
103 92 } );
104 93
105 94 add_action( 'elementor/preview/enqueue_styles', function() {
106 - wp_enqueue_style(
107 - 'elementor-ai-layout-preview',
108 - $this->get_css_assets_url( 'modules/ai/layout-preview' ),
109 - [],
110 - ELEMENTOR_VERSION
111 - );
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 + }
112 103 } );
113 104
114 - if ( is_admin() ) {
115 - add_action( 'wp_enqueue_media', [ $this, 'enqueue_ai_media_library' ] );
116 - add_action( 'admin_head', [ $this, 'enqueue_ai_media_library_upload_screen' ] );
117 -
118 - if ( current_user_can( 'edit_products' ) || current_user_can( 'publish_products' ) ) {
119 - add_action( 'admin_init', [ $this, 'enqueue_ai_products_page_scripts' ] );
120 - add_action( 'current_screen', [ $this, 'enqueue_ai_single_product_page_scripts' ] );
121 - add_action( 'wp_ajax_elementor-ai-get-product-images', [ $this, 'get_product_images_ajax' ] );
122 - 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 );
123 108 }
124 - }
125 109
126 - add_action( 'enqueue_block_editor_assets', function() {
127 - wp_enqueue_script( 'elementor-ai-gutenberg',
128 - $this->get_js_assets_url( 'ai-gutenberg' ),
129 - [
130 - 'jquery',
131 - 'elementor-v2-ui',
132 - 'elementor-v2-icons',
133 - 'wp-blocks',
134 - 'wp-element',
135 - 'wp-editor',
136 - 'wp-data',
137 - 'wp-components',
138 - 'wp-compose',
139 - 'wp-i18n',
140 - 'wp-hooks',
141 - 'elementor-ai-media-library',
142 - ],
143 - ELEMENTOR_VERSION, true );
144 -
145 - wp_localize_script(
146 - 'elementor-ai-gutenberg',
147 - 'ElementorAiConfig',
148 - [
149 - 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
150 - 'connect_url' => $this->get_ai_connect_url(),
151 - ]
152 - );
153 -
154 - wp_set_script_translations( 'elementor-ai-gutenberg', 'elementor' );
155 - });
156 -
157 - add_filter( 'elementor/document/save/data', function ( $data ) {
158 - return $this->remove_temporary_containers( $data );
110 + return $data;
159 111 } );
160 -
161 - add_action( 'elementor/element/common/section_effects/after_section_start', [ $this, 'register_ai_motion_effect_control' ], 10, 1 );
162 - add_action( 'elementor/element/container/section_effects/after_section_start', [ $this, 'register_ai_motion_effect_control' ], 10, 1 );
163 - add_action( 'elementor/element/common/_section_transform/after_section_end', [ $this, 'register_ai_hover_effect_control' ], 10, 1 );
164 - add_action( 'elementor/element/container/_section_transform/after_section_end', [ $this, 'register_ai_hover_effect_control' ], 10, 1 );
165 112 }
166 113
167 - public function is_ai_enabled() {
168 - if ( ! Plugin::$instance->experiments->is_feature_active( 'container' ) ) {
169 - return false;
170 - }
171 -
172 - return Preferences::is_ai_enabled( get_current_user_id() );
173 - }
174 -
175 - public function handle_kit_install( $imported_data ) {
176 - if ( ! $this->is_ai_enabled() ) {
177 - return;
178 - }
179 -
180 - if ( ! isset( $imported_data['status'] ) || 'success' !== $imported_data['status'] ) {
181 - return;
182 - }
183 -
184 - if ( ! isset( $imported_data['runner'] ) || 'site-settings' !== $imported_data['runner'] ) {
185 - return;
186 - }
187 -
188 - if ( ! isset( $imported_data['configData']['lastImportedSession']['instance_data']['site_settings']['settings']['ai'] ) ) {
189 - return;
190 - }
191 -
192 - $is_connected = $this->get_ai_app()->is_connected() && User::get_introduction_meta( 'ai_get_started' );
193 -
194 - if ( ! $is_connected ) {
195 - return;
196 - }
197 -
198 - $last_imported_session = $imported_data['configData']['lastImportedSession'];
199 - $imported_ai_data = $last_imported_session['instance_data']['site_settings']['settings']['ai'];
200 -
201 - $this->get_ai_app()->send_event( [
202 - 'name' => 'kit_installed',
203 - 'data' => $imported_ai_data,
204 - 'client' => [
205 - 'name' => 'elementor',
206 - 'version' => ELEMENTOR_VERSION,
207 - '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',
208 123 ],
209 124 ] );
210 125 }
211 126
212 - public function register_ai_hover_effect_control( Element_Base $element ) {
213 - if ( ! $element->get_controls( 'ai_hover_animation' ) ) {
214 - $element->add_control(
215 - 'ai_hover_animation',
216 - [
217 - 'tabs_wrapper' => '_tabs_positioning',
218 - 'inner_tab' => '_tab_positioning_hover',
219 - 'label' => esc_html__( 'Animate With AI', 'elementor' ),
220 - 'type' => Controls_Manager::RAW_HTML,
221 - 'raw' => '
222 -<style>
223 - .elementor-control-ai_hover_animation .elementor-control-content {
224 - display: flex;
225 - flex-direction: row;
226 - justify-content: space-between;
227 - align-items: center;
228 - }
229 - .elementor-control-ai_hover_animation .elementor-control-raw-html {
230 - display: none;
231 - }
232 -</style>',
233 - 'render_type' => 'none',
234 - 'ai' => [
235 - 'active' => true,
236 - 'type' => 'hover_animation',
237 - ],
238 - ],
239 - [
240 - 'position' => [
241 - 'of' => '_transform_rotate_popover_hover',
242 - 'type' => 'control',
243 - 'at' => 'before',
244 - ],
245 - ]
246 - );
247 - }
248 - }
249 - public function register_ai_motion_effect_control( $element ) {
250 - if ( Utils::has_pro() && ! $element->get_controls( 'ai_animation' ) ) {
251 - $element->add_control(
252 - 'ai_animation',
253 - [
254 - 'label' => esc_html__( 'Animate With AI', 'elementor' ),
255 - 'type' => Controls_Manager::RAW_HTML,
256 - 'raw' => '
257 - <style>
258 - .elementor-control-ai_animation .elementor-control-content {
259 - display: flex;
260 - flex-direction: row;
261 - justify-content: space-between;
262 - align-items: center;
263 - }
264 - .elementor-control-ai_animation .elementor-control-raw-html {
265 - display: none;
266 - }
267 - </style>',
268 - 'render_type' => 'none',
269 - 'ai' => [
270 - 'active' => true,
271 - 'type' => 'animation',
272 - ],
273 - ]
274 - );
275 - }
276 - }
277 -
278 - private function get_current_screen() {
279 - $is_wc = class_exists( 'WooCommerce' ) && post_type_exists( 'product' );
280 - if ( ! $is_wc ) {
281 - return 'other';
282 - }
283 -
284 - $is_products_page = isset( $_GET['post_type'] ) && 'product' === $_GET['post_type'];
285 - if ( $is_products_page ) {
286 - return 'wc-products';
287 - }
288 -
289 - $screen = get_current_screen();
290 - $is_single_product_page = isset( $screen->post_type ) && ( 'product' === $screen->post_type && 'post' === $screen->base );
291 - if ( $is_single_product_page ) {
292 - return 'wc-single-product';
293 - }
294 -
295 - return 'other';
296 - }
297 -
298 - public function enqueue_ai_products_page_scripts() {
299 - if ( 'wc-products' !== $this->get_current_screen() ) {
300 - return;
301 - }
302 -
303 - $this->add_wc_scripts();
304 - }
305 -
306 - public function enqueue_ai_single_product_page_scripts() {
307 - if ( 'wc-single-product' !== $this->get_current_screen() ) {
308 - return;
309 - }
310 -
311 - $this->add_wc_scripts();
312 - }
313 -
314 - private function add_products_bulk_action( $bulk_actions ) {
315 - $bulk_actions['elementor-ai-unify-product-images'] = __( 'Unify with Elementor AI', 'elementor' );
316 - return $bulk_actions;
317 - }
318 -
319 - public function get_product_images_ajax() {
320 - check_ajax_referer( 'elementor-ai-unify-product-images_nonce', 'nonce' );
321 -
322 - $post_ids = isset( $_POST['post_ids'] ) ? array_map( 'intval', $_POST['post_ids'] ) : [];
323 - $is_galley_only = isset( $_POST['is_galley_only'] ) && sanitize_text_field( wp_unslash( $_POST['is_galley_only'] ) );
324 -
325 - $image_ids = [];
326 -
327 - foreach ( $post_ids as $post_id ) {
328 - if ( $is_galley_only ) {
329 - $product = wc_get_product( $post_id );
330 - $gallery_image_ids = $product->get_gallery_image_ids();
331 - foreach ( $gallery_image_ids as $image_id ) {
332 - $image_ids[] = [
333 - 'productId' => $post_id,
334 - 'id' => $image_id,
335 - 'image_url' => wp_get_attachment_url( $image_id ),
336 - ];
337 - }
338 - continue;
339 - }
340 -
341 - $image_id = get_post_thumbnail_id( $post_id );
342 -
343 - if ( ! $image_id ) {
344 - $product = wc_get_product( $post_id );
345 - $gallery_image_ids = $product->get_gallery_image_ids();
346 - if ( ! empty( $gallery_image_ids ) ) {
347 - $image_id = $gallery_image_ids[0];
348 - }
349 - }
350 -
351 - $image_ids[] = [
352 - 'productId' => $post_id,
353 - 'id' => $image_id ? $image_id : 'No Image',
354 - 'image_url' => $image_id ? wp_get_attachment_url( $image_id ) : 'No Image',
355 - ];
356 - }
357 -
358 - wp_send_json_success( [ 'product_images' => array_slice( $image_ids, 0, 10 ) ] );
359 -
360 - wp_die();
361 - }
362 -
363 - private function get_attachment_id_by_url( $url ) {
364 - $attachments = get_posts( [
365 - 'post_type' => 'attachment',
366 - 'meta_query' => [
367 - [
368 - 'key' => '_wp_attached_file',
369 - 'value' => basename( $url ),
370 - 'compare' => 'LIKE',
371 - ],
372 - ],
373 - 'fields' => 'ids',
374 - 'numberposts' => 1,
375 - ] );
376 -
377 - return ! empty( $attachments ) ? $attachments[0] : null;
378 - }
379 -
380 - public function set_product_images_ajax() {
381 - check_ajax_referer( 'elementor-ai-unify-product-images_nonce', 'nonce' );
382 -
383 - $product_id = isset( $_POST['productId'] ) ? sanitize_text_field( wp_unslash( $_POST['productId'] ) ) : '';
384 - $image_url = isset( $_POST['image_url'] ) ? sanitize_text_field( wp_unslash( $_POST['image_url'] ) ) : '';
385 - $image_to_add = isset( $_POST['image_to_add'] ) ? intval( wp_unslash( $_POST['image_to_add'] ) ) : null;
386 - $image_to_remove = isset( $_POST['image_to_remove'] ) ? intval( wp_unslash( $_POST['image_to_remove'] ) ) : null;
387 - $is_product_gallery = isset( $_POST['is_product_gallery'] ) && sanitize_text_field( wp_unslash( $_POST['is_product_gallery'] ) ) === 'true';
388 -
389 - if ( ! $product_id || ! $image_url ) {
390 - throw new \Exception( 'Product ID and Image URL are required' );
391 - }
392 -
393 - $product = wc_get_product( $product_id );
394 - if ( ! $product ) {
395 - throw new \Exception( 'Product not found' );
396 - }
397 -
398 - $attachment_id = $this->get_attachment_id_by_url( $image_url );
399 - if ( is_wp_error( $attachment_id ) ) {
400 - throw new \Exception( 'Image upload failed' );
401 - }
402 -
403 - if ( $is_product_gallery ) {
404 - $this->update_product_gallery( $product, $image_to_remove, $image_to_add );
405 - } else {
406 - $product->set_image_id( $attachment_id );
407 - $product->save();
408 - }
409 -
410 - wp_send_json_success( [
411 - 'message' => __( 'Image added successfully', 'elementor' ),
412 - ] );
413 - }
414 -
415 - public function enqueue_ai_media_library_upload_screen() {
416 - $screen = get_current_screen();
417 - if ( ! $screen || 'upload' !== $screen->id ) {
418 - return;
419 - }
420 -
421 - $this->enqueue_ai_media_library();
422 - }
423 -
424 - public function enqueue_ai_media_library() {
425 - wp_enqueue_script( 'elementor-ai-media-library',
426 - $this->get_js_assets_url( 'ai-media-library' ),
427 - [
428 - 'jquery',
429 - 'elementor-v2-ui',
430 - 'elementor-v2-icons',
431 - 'media-grid',
432 - ],
433 - ELEMENTOR_VERSION,
434 - true
435 - );
436 -
437 - wp_localize_script(
438 - 'elementor-ai-media-library',
439 - 'ElementorAiConfig',
440 - [
441 - 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
442 - 'connect_url' => $this->get_ai_connect_url(),
443 - ]
444 - );
445 -
446 - wp_set_script_translations( 'elementor-ai-media-library', 'elementor' );
447 - }
448 -
449 127 private function enqueue_main_script() {
450 128 wp_enqueue_script(
451 129 'elementor-ai',
452 130 $this->get_js_assets_url( 'ai' ),
@@ -470,8 +148,13 @@
470 148 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
471 149 'connect_url' => $this->get_ai_connect_url(),
472 150 ];
473 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 +
474 157 wp_localize_script(
475 158 'elementor-ai',
476 159 'ElementorAiConfig',
477 160 $config
@@ -477,8 +160,31 @@
477 160 $config
478 161 );
479 162
480 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 + }
481 187 }
482 188
483 189 private function enqueue_layout_script() {
484 190 wp_enqueue_script(
@@ -501,10 +207,14 @@
501 207
502 208 wp_set_script_translations( 'elementor-ai-layout', 'elementor' );
503 209 }
504 210
211 + private function is_layout_active() {
212 + return Plugin::$instance->experiments->is_feature_active( self::LAYOUT_EXPERIMENT );
213 + }
214 +
505 215 private function remove_temporary_containers( $data ) {
506 - if ( empty( $data['elements'] ) || ! is_array( $data['elements'] ) ) {
216 + if ( empty( $data['elements'] ) ) {
507 217 return $data;
508 218 }
509 219
510 220 // If for some reason the document has been saved during an AI Layout session,
@@ -562,25 +272,8 @@
562 272
563 273 return $app->get_remote_config();
564 274 }
565 275
566 - public function ajax_ai_get_remote_frontend_config( $data ) {
567 - $callback = function () use ( $data ) {
568 - return $this->get_ai_app()->get_remote_frontend_config( $data );
569 - };
570 -
571 - return Utils::get_cached_callback( $callback, 'ai_remote_frontend_config-' . get_current_user_id(), HOUR_IN_SECONDS );
572 - }
573 -
574 - public function verify_upload_permissions( $data ) {
575 - $referer = wp_get_referer();
576 -
577 - if ( str_contains( $referer, 'wp-admin/upload.php' ) && current_user_can( 'upload_files' ) ) {
578 - return;
579 - }
580 - $this->verify_permissions( $data['editor_post_id'] );
581 - }
582 -
583 276 private function verify_permissions( $editor_post_id ) {
584 277 $document = Plugin::$instance->documents->get( $editor_post_id );
585 278
586 279 if ( ! $document ) {
@@ -586,19 +279,15 @@
586 279 if ( ! $document ) {
587 280 throw new \Exception( 'Document not found' );
588 281 }
589 282
590 - if ( $document->is_built_with_elementor() ) {
591 - if ( ! $document->is_editable_by_current_user() ) {
592 - throw new \Exception( 'Access denied' );
593 - }
594 - } elseif ( ! current_user_can( 'edit_post', $editor_post_id ) ) {
595 - throw new \Exception( 'Access denied' );
283 + if ( ! $document->is_built_with_elementor() || ! $document->is_editable_by_current_user() ) {
284 + throw new \Exception( 'Access denied' );
596 285 }
597 286 }
598 287
599 288 public function ajax_ai_get_image_prompt_enhancer( $data ) {
600 - $this->verify_upload_permissions( $data );
289 + $this->verify_permissions( $data['editor_post_id'] );
601 290
602 291 $app = $this->get_ai_app();
603 292
604 293 if ( empty( $data['prompt'] ) ) {
@@ -607,11 +296,10 @@
607 296
608 297 if ( ! $app->is_connected() ) {
609 298 throw new \Exception( 'not_connected' );
610 299 }
611 - $request_ids = $this->get_request_ids( $data['payload'] );
612 300
613 - $result = $app->get_image_prompt_enhanced( $data['prompt'], [], $request_ids );
301 + $result = $app->get_image_prompt_enhanced( $data['prompt'] );
614 302 $this->throw_on_error( $result );
615 303
616 304 return [
617 305 'text' => $result['text'],
@@ -646,62 +334,9 @@
646 334 'usage' => $result['usage'],
647 335 ];
648 336 }
649 337
650 -
651 - public function ajax_ai_get_excerpt( $data ): array {
652 - $app = $this->get_ai_app();
653 -
654 - if ( empty( $data['payload']['content'] ) ) {
655 - throw new \Exception( 'Missing content' );
656 - }
657 -
658 - if ( ! $app->is_connected() ) {
659 - throw new \Exception( 'Not connected' );
660 - }
661 -
662 - $context = $this->get_request_context( $data );
663 -
664 - $request_ids = $this->get_request_ids( $data['payload'] );
665 -
666 - $result = $app->get_excerpt( $data['payload']['content'], $context, $request_ids );
667 - $this->throw_on_error( $result );
668 -
669 - return [
670 - 'text' => $result['text'],
671 - 'response_id' => $result['responseId'],
672 - 'usage' => $result['usage'],
673 - ];
674 - }
675 -
676 - public function ajax_ai_get_featured_image( $data ): array {
677 - $this->verify_upload_permissions( $data );
678 -
679 - if ( empty( $data['payload']['prompt'] ) ) {
680 - throw new \Exception( 'Missing prompt' );
681 - }
682 -
683 - $app = $this->get_ai_app();
684 -
685 - if ( ! $app->is_connected() ) {
686 - throw new \Exception( 'not_connected' );
687 - }
688 -
689 - $context = $this->get_request_context( $data );
690 - $request_ids = $this->get_request_ids( $data['payload'] );
691 -
692 - $result = $app->get_featured_image( $data, $context, $request_ids );
693 -
694 - $this->throw_on_error( $result );
695 -
696 - return [
697 - 'images' => $result['images'],
698 - 'response_id' => $result['responseId'],
699 - 'usage' => $result['usage'],
700 - ];
701 - }
702 -
703 - private function get_ai_app(): Ai {
338 + private function get_ai_app() : Ai {
704 339 return Plugin::$instance->common->get_component( 'connect' )->get_app( 'ai' );
705 340 }
706 341
707 342 private function get_request_context( $data ) {
@@ -840,9 +475,9 @@
840 475 return [];
841 476 }
842 477
843 478 public function ajax_ai_get_text_to_image( $data ) {
844 - $this->verify_upload_permissions( $data );
479 + $this->verify_permissions( $data['editor_post_id'] );
845 480
846 481 if ( empty( $data['payload']['prompt'] ) ) {
847 482 throw new \Exception( 'Missing prompt' );
848 483 }
@@ -867,12 +502,16 @@
867 502 ];
868 503 }
869 504
870 505 public function ajax_ai_get_image_to_image( $data ) {
871 - $this->verify_upload_permissions( $data );
506 + $this->verify_permissions( $data['editor_post_id'] );
872 507
873 508 $app = $this->get_ai_app();
874 509
510 + if ( empty( $data['payload']['prompt'] ) ) {
511 + throw new \Exception( 'Missing prompt' );
512 + }
513 +
875 514 if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
876 515 throw new \Exception( 'Missing Image' );
877 516 }
878 517
@@ -902,9 +541,9 @@
902 541 ];
903 542 }
904 543
905 544 public function ajax_ai_get_image_to_image_upscale( $data ) {
906 - $this->verify_upload_permissions( $data );
545 + $this->verify_permissions( $data['editor_post_id'] );
907 546
908 547 $app = $this->get_ai_app();
909 548
910 549 if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
@@ -936,9 +575,9 @@
936 575 ];
937 576 }
938 577
939 578 public function ajax_ai_get_image_to_image_replace_background( $data ) {
940 - $this->verify_upload_permissions( $data );
579 + $this->verify_permissions( $data['editor_post_id'] );
941 580
942 581 $app = $this->get_ai_app();
943 582
944 583 if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
@@ -970,9 +609,9 @@
970 609 ];
971 610 }
972 611
973 612 public function ajax_ai_get_image_to_image_remove_background( $data ) {
974 - $this->verify_upload_permissions( $data );
613 + $this->verify_permissions( $data['editor_post_id'] );
975 614
976 615 $app = $this->get_ai_app();
977 616
978 617 if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
@@ -998,9 +637,9 @@
998 637 ];
999 638 }
1000 639
1001 640 public function ajax_ai_get_image_to_image_mask( $data ) {
1002 - $this->verify_upload_permissions( $data );
641 + $this->verify_permissions( $data['editor_post_id'] );
1003 642
1004 643 $app = $this->get_ai_app();
1005 644
1006 645 if ( empty( $data['payload']['prompt'] ) ) {
@@ -1014,42 +653,8 @@
1014 653 if ( empty( $data['payload']['settings'] ) ) {
1015 654 throw new \Exception( 'Missing prompt settings' );
1016 655 }
1017 656
1018 - if ( empty( $data['payload']['mask'] ) ) {
1019 - throw new \Exception( 'Missing Mask' );
1020 - }
1021 -
1022 - $context = $this->get_request_context( $data );
1023 - $request_ids = $this->get_request_ids( $data['payload'] );
1024 -
1025 - $result = $app->get_image_to_image_mask( [
1026 - 'prompt' => $data['payload']['prompt'],
1027 - 'attachment_id' => $data['payload']['image']['id'],
1028 - 'mask' => $data['payload']['mask'],
1029 - ], $context, $request_ids );
1030 -
1031 - $this->throw_on_error( $result );
1032 -
1033 - return [
1034 - 'images' => $result['images'],
1035 - 'response_id' => $result['responseId'],
1036 - 'usage' => $result['usage'],
1037 - ];
1038 - }
1039 - public function ajax_ai_get_image_to_image_mask_cleanup( $data ) {
1040 - $this->verify_upload_permissions( $data );
1041 -
1042 - $app = $this->get_ai_app();
1043 -
1044 - if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
1045 - throw new \Exception( 'Missing Image' );
1046 - }
1047 -
1048 - if ( empty( $data['payload']['settings'] ) ) {
1049 - throw new \Exception( 'Missing prompt settings' );
1050 - }
1051 -
1052 657 if ( ! $app->is_connected() ) {
1053 658 throw new \Exception( 'not_connected' );
1054 659 }
1055 660
@@ -1059,9 +664,11 @@
1059 664
1060 665 $context = $this->get_request_context( $data );
1061 666 $request_ids = $this->get_request_ids( $data['payload'] );
1062 667
1063 - $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'],
1064 671 'attachment_id' => $data['payload']['image']['id'],
1065 672 'mask' => $data['payload']['mask'],
1066 673 ], $context, $request_ids );
1067 674
@@ -1072,14 +679,17 @@
1072 679 'response_id' => $result['responseId'],
1073 680 'usage' => $result['usage'],
1074 681 ];
1075 682 }
1076 -
1077 683 public function ajax_ai_get_image_to_image_outpainting( $data ) {
1078 - $this->verify_upload_permissions( $data );
684 + $this->verify_permissions( $data['editor_post_id'] );
1079 685
1080 686 $app = $this->get_ai_app();
1081 687
688 + if ( empty( $data['payload']['prompt'] ) ) {
689 + throw new \Exception( 'Missing prompt' );
690 + }
691 +
1082 692 if ( ! $app->is_connected() ) {
1083 693 throw new \Exception( 'not_connected' );
1084 694 }
1085 695
@@ -1089,12 +699,10 @@
1089 699
1090 700 $context = $this->get_request_context( $data );
1091 701 $request_ids = $this->get_request_ids( $data['payload'] );
1092 702 $result = $app->get_image_to_image_out_painting( [
1093 - 'size' => $data['payload']['size'],
1094 - 'position' => $data['payload']['position'],
703 + 'prompt' => $data['payload']['prompt'],
1095 704 'mask' => $data['payload']['mask'],
1096 - 'image_base64' => $data['payload']['image_base64'],
1097 705 ], $context, $request_ids );
1098 706
1099 707 $this->throw_on_error( $result );
1100 708
@@ -1105,10 +713,8 @@
1105 713 ];
1106 714 }
1107 715
1108 716 public function ajax_ai_upload_image( $data ) {
1109 - $this->verify_upload_permissions( $data );
1110 -
1111 717 if ( empty( $data['image'] ) ) {
1112 718 throw new \Exception( 'Missing image data' );
1113 719 }
1114 720
@@ -1120,14 +726,14 @@
1120 726
1121 727 $image_data = $this->upload_image( $image['image_url'], $data['prompt'], $data['editor_post_id'] );
1122 728
1123 729 if ( is_wp_error( $image_data ) ) {
1124 - throw new \Exception( esc_html( $image_data->get_error_message() ) );
730 + throw new \Exception( $image_data->get_error_message() );
1125 731 }
1126 732
1127 733 if ( ! empty( $image['use_gallery_image'] ) && ! empty( $image['id'] ) ) {
1128 - $app = $this->get_ai_app();
1129 - $app->set_used_gallery_image( $image['id'] );
734 + $app = $this->get_ai_app();
735 + $app->set_used_gallery_image( $image['id'] );
1130 736 }
1131 737
1132 738 return [
1133 739 'image' => array_merge( $image_data, $data ),
@@ -1148,9 +754,9 @@
1148 754 }
1149 755
1150 756 $result = $app->generate_layout(
1151 757 $data,
1152 - $this->prepare_generate_layout_context( $data )
758 + $this->prepare_generate_layout_context()
1153 759 );
1154 760
1155 761 if ( is_wp_error( $result ) ) {
1156 762 $message = $result->get_error_message();
@@ -1156,9 +762,9 @@
1156 762 $message = $result->get_error_message();
1157 763
1158 764 if ( is_array( $message ) ) {
1159 765 $message = implode( ', ', $message );
1160 - throw new \Exception( esc_html( $message ) );
766 + throw new \Exception( $message );
1161 767 }
1162 768
1163 769 $this->throw_on_error( $result );
1164 770 }
@@ -1221,9 +827,9 @@
1221 827
1222 828 $result = $app->get_layout_prompt_enhanced(
1223 829 $data['prompt'],
1224 830 $data['enhance_type'],
1225 - $this->prepare_generate_layout_context( $data )
831 + $this->prepare_generate_layout_context()
1226 832 );
1227 833
1228 834 $this->throw_on_error( $result );
1229 835
@@ -1233,14 +839,13 @@
1233 839 'usage' => $result['usage'] ?? '',
1234 840 ];
1235 841 }
1236 842
1237 - private function prepare_generate_layout_context( $data ) {
1238 - $request_context = $this->get_request_context( $data );
843 + private function prepare_generate_layout_context() {
1239 844 $kit = Plugin::$instance->kits_manager->get_active_kit();
1240 845
1241 846 if ( ! $kit ) {
1242 - return $request_context;
847 + return [];
1243 848 }
1244 849
1245 850 $kits_data = Collection::make( $kit->get_data()['settings'] ?? [] );
1246 851
@@ -1293,14 +898,14 @@
1293 898 ],
1294 899 ];
1295 900 } );
1296 901
1297 - $request_context['globals'] = [
1298 - 'colors' => $colors->all(),
1299 - 'typography' => $typography->all(),
902 + return [
903 + 'globals' => [
904 + 'colors' => $colors->all(),
905 + 'typography' => $typography->all(),
906 + ],
1300 907 ];
1301 -
1302 - return $request_context;
1303 908 }
1304 909
1305 910 private function upload_image( $image_url, $image_title, $parent_post_id = 0 ) {
1306 911 if ( ! current_user_can( 'upload_files' ) ) {
@@ -1306,24 +911,10 @@
1306 911 if ( ! current_user_can( 'upload_files' ) ) {
1307 912 throw new \Exception( 'Not Allowed to Upload images' );
1308 913 }
1309 914
1310 - $uploads_manager = new \Elementor\Core\Files\Uploads_Manager();
1311 - if ( $uploads_manager::are_unfiltered_uploads_enabled() ) {
1312 - Plugin::$instance->uploads_manager->set_elementor_upload_state( true );
1313 - add_filter( 'wp_handle_sideload_prefilter', [ Plugin::$instance->uploads_manager, 'handle_elementor_upload' ] );
1314 - add_filter( 'image_sideload_extensions', function( $extensions ) {
1315 - $extensions[] = 'svg';
1316 - return $extensions;
1317 - });
1318 - }
1319 -
1320 915 $attachment_id = media_sideload_image( $image_url, $parent_post_id, $image_title, 'id' );
1321 916
1322 - if ( is_wp_error( $attachment_id ) ) {
1323 - return new \WP_Error( 'upload_error', $attachment_id->get_error_message() );
1324 - }
1325 -
1326 917 if ( ! empty( $attachment_id['error'] ) ) {
1327 918 return new \WP_Error( 'upload_error', $attachment_id['error'] );
1328 919 }
1329 920
@@ -1328,10 +919,10 @@
1328 919 }
1329 920
1330 921 return [
1331 922 'id' => $attachment_id,
1332 - 'url' => esc_url( wp_get_attachment_image_url( $attachment_id, 'full' ) ),
1333 - 'alt' => esc_attr( $image_title ),
923 + 'url' => wp_get_attachment_image_url( $attachment_id, 'full' ),
924 + 'alt' => $image_title,
1334 925 'source' => 'library',
1335 926 ];
1336 927 }
1337 928
@@ -1355,9 +946,9 @@
1355 946
1356 947 $result = $app->get_history_by_type( $type, $page, $limit, $context );
1357 948
1358 949 if ( is_wp_error( $result ) ) {
1359 - throw new \Exception( esc_html( $result->get_error_message() ) );
950 + throw new \Exception( $result->get_error_message() );
1360 951 }
1361 952
1362 953 return $result;
1363 954 }
@@ -1377,9 +968,9 @@
1377 968
1378 969 $result = $app->delete_history_item( $data['id'], $context );
1379 970
1380 971 if ( is_wp_error( $result ) ) {
1381 - throw new \Exception( esc_html( $result->get_error_message() ) );
972 + throw new \Exception( $result->get_error_message() );
1382 973 }
1383 974
1384 975 return [];
1385 976 }
@@ -1399,83 +990,14 @@
1399 990
1400 991 $result = $app->toggle_favorite_history_item( $data['id'], $context );
1401 992
1402 993 if ( is_wp_error( $result ) ) {
1403 - throw new \Exception( esc_html( $result->get_error_message() ) );
994 + throw new \Exception( $result->get_error_message() );
1404 995 }
1405 996
1406 997 return [];
1407 998 }
1408 999
1409 - public function ajax_ai_get_product_image_unification( $data ): array {
1410 - if ( ! empty( $data['payload']['postId'] ) ) {
1411 - $data['editor_post_id'] = $data['payload']['postId'];
1412 - }
1413 - $this->verify_upload_permissions( $data );
1414 -
1415 - $app = $this->get_ai_app();
1416 -
1417 - if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
1418 - throw new \Exception( 'Missing Image' );
1419 - }
1420 -
1421 - if ( empty( $data['payload']['settings'] ) ) {
1422 - throw new \Exception( 'Missing prompt settings' );
1423 - }
1424 -
1425 - if ( ! $app->is_connected() ) {
1426 - throw new \Exception( 'not_connected' );
1427 - }
1428 -
1429 - $context = $this->get_request_context( $data );
1430 - $request_ids = $this->get_request_ids( $data['payload'] );
1431 -
1432 - $result = $app->get_unify_product_images( [
1433 - 'promptSettings' => $data['payload']['settings'],
1434 - 'attachment_id' => $data['payload']['image']['id'],
1435 - 'featureIdentifier' => $data['payload']['featureIdentifier'] ?? '',
1436 - ], $context, $request_ids );
1437 -
1438 - $this->throw_on_error( $result );
1439 -
1440 - return [
1441 - 'images' => $result['images'],
1442 - 'response_id' => $result['responseId'],
1443 - 'usage' => $result['usage'],
1444 - ];
1445 - }
1446 -
1447 - public function ajax_ai_get_animation( $data ): array {
1448 - $this->verify_upload_permissions( $data );
1449 -
1450 - $app = $this->get_ai_app();
1451 -
1452 - if ( empty( $data['payload']['prompt'] ) ) {
1453 - throw new \Exception( 'Missing prompt' );
1454 - }
1455 -
1456 - if ( empty( $data['payload']['motionEffectType'] ) ) {
1457 - throw new \Exception( 'Missing animation type' );
1458 - }
1459 -
1460 - if ( ! $app->is_connected() ) {
1461 - throw new \Exception( 'not_connected' );
1462 - }
1463 -
1464 - $context = $this->get_request_context( $data );
1465 -
1466 - $request_ids = $this->get_request_ids( $data['payload'] );
1467 -
1468 - $result = $app->get_animation( $data, $context, $request_ids );
1469 - $this->throw_on_error( $result );
1470 -
1471 - return [
1472 - 'text' => $result['text'],
1473 - 'response_id' => $result['responseId'],
1474 - 'usage' => $result['usage'],
1475 - ];
1476 - }
1477 -
1478 1000 /**
1479 1001 * @param mixed $result
1480 1002 */
1481 1003 private function throw_on_error( $result ): void {
@@ -1480,69 +1002,10 @@
1480 1002 */
1481 1003 private function throw_on_error( $result ): void {
1482 1004 if ( is_wp_error( $result ) ) {
1483 1005 wp_send_json_error( [
1484 - 'message' => esc_html( $result->get_error_message() ),
1006 + 'message' => $result->get_error_message(),
1485 1007 'extra_data' => $result->get_error_data(),
1486 1008 ] );
1487 1009 }
1488 - }
1489 -
1490 - /**
1491 - * @return void
1492 - */
1493 - public function add_wc_scripts(): void {
1494 - wp_enqueue_script( 'elementor-ai-unify-product-images',
1495 - $this->get_js_assets_url( 'ai-unify-product-images' ),
1496 - [
1497 - 'jquery',
1498 - 'elementor-v2-ui',
1499 - 'elementor-v2-icons',
1500 - 'wp-components',
1501 - 'elementor-common',
1502 - ],
1503 - ELEMENTOR_VERSION,
1504 - true
1505 - );
1506 -
1507 - wp_localize_script(
1508 - 'elementor-ai-unify-product-images',
1509 - 'UnifyProductImagesConfig',
1510 - [
1511 - 'get_product_images_url' => admin_url( 'admin-ajax.php' ),
1512 - 'set_product_images_url' => admin_url( 'admin-ajax.php' ),
1513 - 'nonce' => wp_create_nonce( 'elementor-ai-unify-product-images_nonce' ),
1514 - 'placeholder' => ELEMENTOR_ASSETS_URL . 'images/app/ai/product-image-unification-example.gif?' . ELEMENTOR_VERSION,
1515 - 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
1516 - 'connect_url' => $this->get_ai_connect_url(),
1517 - ]
1518 - );
1519 -
1520 - add_filter( 'bulk_actions-edit-product', function ( $data ) {
1521 - return $this->add_products_bulk_action( $data );
1522 - });
1523 -
1524 - wp_set_script_translations( 'elementor-ai-unify-product-images', 'elementor' );
1525 - }
1526 -
1527 - /**
1528 - * @param $product
1529 - * @param int|null $image_to_remove
1530 - * @param int|null $image_to_add
1531 - * @return void
1532 - */
1533 - private function update_product_gallery( $product, ?int $image_to_remove, ?int $image_to_add ): void {
1534 - $gallery_image_ids = $product->get_gallery_image_ids();
1535 -
1536 - $index = array_search( $image_to_remove, $gallery_image_ids, true );
1537 - if ( false !== $index ) {
1538 - unset( $gallery_image_ids[ $index ] );
1539 - }
1540 -
1541 - if ( ! in_array( $image_to_add, $gallery_image_ids, true ) ) {
1542 - $gallery_image_ids[] = $image_to_add;
1543 - }
1544 -
1545 - $product->set_gallery_image_ids( $gallery_image_ids );
1546 - $product->save();
1547 1010 }
1548 1011 }