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 +106 -642 4.3.3 → 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,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'] ) ) {
@@ -646,62 +335,9 @@
646 335 'usage' => $result['usage'],
647 336 ];
648 337 }
649 338
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 {
339 + private function get_ai_app() : Ai {
704 340 return Plugin::$instance->common->get_component( 'connect' )->get_app( 'ai' );
705 341 }
706 342
707 343 private function get_request_context( $data ) {
@@ -840,9 +476,9 @@
840 476 return [];
841 477 }
842 478
843 479 public function ajax_ai_get_text_to_image( $data ) {
844 - $this->verify_upload_permissions( $data );
480 + $this->verify_permissions( $data['editor_post_id'] );
845 481
846 482 if ( empty( $data['payload']['prompt'] ) ) {
847 483 throw new \Exception( 'Missing prompt' );
848 484 }
@@ -867,12 +503,16 @@
867 503 ];
868 504 }
869 505
870 506 public function ajax_ai_get_image_to_image( $data ) {
871 - $this->verify_upload_permissions( $data );
507 + $this->verify_permissions( $data['editor_post_id'] );
872 508
873 509 $app = $this->get_ai_app();
874 510
511 + if ( empty( $data['payload']['prompt'] ) ) {
512 + throw new \Exception( 'Missing prompt' );
513 + }
514 +
875 515 if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
876 516 throw new \Exception( 'Missing Image' );
877 517 }
878 518
@@ -902,9 +542,9 @@
902 542 ];
903 543 }
904 544
905 545 public function ajax_ai_get_image_to_image_upscale( $data ) {
906 - $this->verify_upload_permissions( $data );
546 + $this->verify_permissions( $data['editor_post_id'] );
907 547
908 548 $app = $this->get_ai_app();
909 549
910 550 if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
@@ -936,9 +576,9 @@
936 576 ];
937 577 }
938 578
939 579 public function ajax_ai_get_image_to_image_replace_background( $data ) {
940 - $this->verify_upload_permissions( $data );
580 + $this->verify_permissions( $data['editor_post_id'] );
941 581
942 582 $app = $this->get_ai_app();
943 583
944 584 if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
@@ -970,9 +610,9 @@
970 610 ];
971 611 }
972 612
973 613 public function ajax_ai_get_image_to_image_remove_background( $data ) {
974 - $this->verify_upload_permissions( $data );
614 + $this->verify_permissions( $data['editor_post_id'] );
975 615
976 616 $app = $this->get_ai_app();
977 617
978 618 if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
@@ -998,9 +638,9 @@
998 638 ];
999 639 }
1000 640
1001 641 public function ajax_ai_get_image_to_image_mask( $data ) {
1002 - $this->verify_upload_permissions( $data );
642 + $this->verify_permissions( $data['editor_post_id'] );
1003 643
1004 644 $app = $this->get_ai_app();
1005 645
1006 646 if ( empty( $data['payload']['prompt'] ) ) {
@@ -1014,42 +654,8 @@
1014 654 if ( empty( $data['payload']['settings'] ) ) {
1015 655 throw new \Exception( 'Missing prompt settings' );
1016 656 }
1017 657
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 658 if ( ! $app->is_connected() ) {
1053 659 throw new \Exception( 'not_connected' );
1054 660 }
1055 661
@@ -1059,9 +665,11 @@
1059 665
1060 666 $context = $this->get_request_context( $data );
1061 667 $request_ids = $this->get_request_ids( $data['payload'] );
1062 668
1063 - $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'],
1064 672 'attachment_id' => $data['payload']['image']['id'],
1065 673 'mask' => $data['payload']['mask'],
1066 674 ], $context, $request_ids );
1067 675
@@ -1072,14 +680,17 @@
1072 680 'response_id' => $result['responseId'],
1073 681 'usage' => $result['usage'],
1074 682 ];
1075 683 }
1076 -
1077 684 public function ajax_ai_get_image_to_image_outpainting( $data ) {
1078 - $this->verify_upload_permissions( $data );
685 + $this->verify_permissions( $data['editor_post_id'] );
1079 686
1080 687 $app = $this->get_ai_app();
1081 688
689 + if ( empty( $data['payload']['prompt'] ) ) {
690 + throw new \Exception( 'Missing prompt' );
691 + }
692 +
1082 693 if ( ! $app->is_connected() ) {
1083 694 throw new \Exception( 'not_connected' );
1084 695 }
1085 696
@@ -1089,12 +700,10 @@
1089 700
1090 701 $context = $this->get_request_context( $data );
1091 702 $request_ids = $this->get_request_ids( $data['payload'] );
1092 703 $result = $app->get_image_to_image_out_painting( [
1093 - 'size' => $data['payload']['size'],
1094 - 'position' => $data['payload']['position'],
704 + 'prompt' => $data['payload']['prompt'],
1095 705 'mask' => $data['payload']['mask'],
1096 - 'image_base64' => $data['payload']['image_base64'],
1097 706 ], $context, $request_ids );
1098 707
1099 708 $this->throw_on_error( $result );
1100 709
@@ -1105,10 +714,8 @@
1105 714 ];
1106 715 }
1107 716
1108 717 public function ajax_ai_upload_image( $data ) {
1109 - $this->verify_upload_permissions( $data );
1110 -
1111 718 if ( empty( $data['image'] ) ) {
1112 719 throw new \Exception( 'Missing image data' );
1113 720 }
1114 721
@@ -1120,14 +727,14 @@
1120 727
1121 728 $image_data = $this->upload_image( $image['image_url'], $data['prompt'], $data['editor_post_id'] );
1122 729
1123 730 if ( is_wp_error( $image_data ) ) {
1124 - throw new \Exception( esc_html( $image_data->get_error_message() ) );
731 + throw new \Exception( $image_data->get_error_message() );
1125 732 }
1126 733
1127 734 if ( ! empty( $image['use_gallery_image'] ) && ! empty( $image['id'] ) ) {
1128 - $app = $this->get_ai_app();
1129 - $app->set_used_gallery_image( $image['id'] );
735 + $app = $this->get_ai_app();
736 + $app->set_used_gallery_image( $image['id'] );
1130 737 }
1131 738
1132 739 return [
1133 740 'image' => array_merge( $image_data, $data ),
@@ -1148,9 +755,9 @@
1148 755 }
1149 756
1150 757 $result = $app->generate_layout(
1151 758 $data,
1152 - $this->prepare_generate_layout_context( $data )
759 + $this->prepare_generate_layout_context()
1153 760 );
1154 761
1155 762 if ( is_wp_error( $result ) ) {
1156 763 $message = $result->get_error_message();
@@ -1156,9 +763,9 @@
1156 763 $message = $result->get_error_message();
1157 764
1158 765 if ( is_array( $message ) ) {
1159 766 $message = implode( ', ', $message );
1160 - throw new \Exception( esc_html( $message ) );
767 + throw new \Exception( $message );
1161 768 }
1162 769
1163 770 $this->throw_on_error( $result );
1164 771 }
@@ -1221,9 +828,9 @@
1221 828
1222 829 $result = $app->get_layout_prompt_enhanced(
1223 830 $data['prompt'],
1224 831 $data['enhance_type'],
1225 - $this->prepare_generate_layout_context( $data )
832 + $this->prepare_generate_layout_context()
1226 833 );
1227 834
1228 835 $this->throw_on_error( $result );
1229 836
@@ -1233,14 +840,13 @@
1233 840 'usage' => $result['usage'] ?? '',
1234 841 ];
1235 842 }
1236 843
1237 - private function prepare_generate_layout_context( $data ) {
1238 - $request_context = $this->get_request_context( $data );
844 + private function prepare_generate_layout_context() {
1239 845 $kit = Plugin::$instance->kits_manager->get_active_kit();
1240 846
1241 847 if ( ! $kit ) {
1242 - return $request_context;
848 + return [];
1243 849 }
1244 850
1245 851 $kits_data = Collection::make( $kit->get_data()['settings'] ?? [] );
1246 852
@@ -1293,14 +899,14 @@
1293 899 ],
1294 900 ];
1295 901 } );
1296 902
1297 - $request_context['globals'] = [
1298 - 'colors' => $colors->all(),
1299 - 'typography' => $typography->all(),
903 + return [
904 + 'globals' => [
905 + 'colors' => $colors->all(),
906 + 'typography' => $typography->all(),
907 + ],
1300 908 ];
1301 -
1302 - return $request_context;
1303 909 }
1304 910
1305 911 private function upload_image( $image_url, $image_title, $parent_post_id = 0 ) {
1306 912 if ( ! current_user_can( 'upload_files' ) ) {
@@ -1306,24 +912,10 @@
1306 912 if ( ! current_user_can( 'upload_files' ) ) {
1307 913 throw new \Exception( 'Not Allowed to Upload images' );
1308 914 }
1309 915
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 916 $attachment_id = media_sideload_image( $image_url, $parent_post_id, $image_title, 'id' );
1321 917
1322 - if ( is_wp_error( $attachment_id ) ) {
1323 - return new \WP_Error( 'upload_error', $attachment_id->get_error_message() );
1324 - }
1325 -
1326 918 if ( ! empty( $attachment_id['error'] ) ) {
1327 919 return new \WP_Error( 'upload_error', $attachment_id['error'] );
1328 920 }
1329 921
@@ -1328,10 +920,10 @@
1328 920 }
1329 921
1330 922 return [
1331 923 'id' => $attachment_id,
1332 - 'url' => esc_url( wp_get_attachment_image_url( $attachment_id, 'full' ) ),
1333 - 'alt' => esc_attr( $image_title ),
924 + 'url' => wp_get_attachment_image_url( $attachment_id, 'full' ),
925 + 'alt' => $image_title,
1334 926 'source' => 'library',
1335 927 ];
1336 928 }
1337 929
@@ -1355,9 +947,9 @@
1355 947
1356 948 $result = $app->get_history_by_type( $type, $page, $limit, $context );
1357 949
1358 950 if ( is_wp_error( $result ) ) {
1359 - throw new \Exception( esc_html( $result->get_error_message() ) );
951 + throw new \Exception( $result->get_error_message() );
1360 952 }
1361 953
1362 954 return $result;
1363 955 }
@@ -1377,9 +969,9 @@
1377 969
1378 970 $result = $app->delete_history_item( $data['id'], $context );
1379 971
1380 972 if ( is_wp_error( $result ) ) {
1381 - throw new \Exception( esc_html( $result->get_error_message() ) );
973 + throw new \Exception( $result->get_error_message() );
1382 974 }
1383 975
1384 976 return [];
1385 977 }
@@ -1399,83 +991,14 @@
1399 991
1400 992 $result = $app->toggle_favorite_history_item( $data['id'], $context );
1401 993
1402 994 if ( is_wp_error( $result ) ) {
1403 - throw new \Exception( esc_html( $result->get_error_message() ) );
995 + throw new \Exception( $result->get_error_message() );
1404 996 }
1405 997
1406 998 return [];
1407 999 }
1408 1000
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 1001 /**
1479 1002 * @param mixed $result
1480 1003 */
1481 1004 private function throw_on_error( $result ): void {
@@ -1480,69 +1003,10 @@
1480 1003 */
1481 1004 private function throw_on_error( $result ): void {
1482 1005 if ( is_wp_error( $result ) ) {
1483 1006 wp_send_json_error( [
1484 - 'message' => esc_html( $result->get_error_message() ),
1007 + 'message' => $result->get_error_message(),
1485 1008 'extra_data' => $result->get_error_data(),
1486 1009 ] );
1487 1010 }
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 1011 }
1548 1012 }