PluginProbe
Elementor Website Builder – more than just a page builder / 3.26.0-dev5
Elementor Website Builder – more than just a page builder v3.26.0-dev5
4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 4.1.0-beta2 4.1.0-dev2 4.0.8 4.1.0-beta1 4.1.0-dev1 4.0.7 All 451 releases
← All changes | modules/ai/module.php +40 -238 4.3.0-beta23.26.0-dev5 View file →
@@ -1,11 +1,10 @@
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;
6 +use Elementor\Modules\Ai\Feature_Intro\Product_Image_Unification_Intro;
8 7 use Elementor\Plugin;
9 8 use Elementor\Core\Utils\Collection;
10 9 use Elementor\Modules\Ai\Connect\Ai;
11 10 use Elementor\User;
@@ -27,9 +26,8 @@
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
33 31 public function get_name() {
34 32 return 'ai';
35 33 }
@@ -36,20 +34,19 @@
36 34
37 35 public function __construct() {
38 36 parent::__construct();
39 37
40 - ( new SitePlannerConnect\Module() );
41 -
42 38 if ( is_admin() ) {
43 39 ( new Preferences() )->register();
44 - add_action( 'elementor/import-export/import-kit/runner/after-run', [ $this, 'handle_kit_install' ] );
45 40 }
46 41
47 - if ( ! $this->is_ai_enabled() ) {
42 + if ( ! Plugin::$instance->experiments->is_feature_active( 'container' ) ) {
48 43 return;
49 44 }
50 45
51 - add_filter( 'elementor/core/admin/homescreen', [ $this, 'add_create_with_ai_banner_to_homescreen' ] );
46 + if ( ! Preferences::is_ai_enabled( get_current_user_id() ) ) {
47 + return;
48 + }
52 49
53 50 add_action( 'elementor/connect/apps/register', function ( ConnectModule $connect_module ) {
54 51 $connect_module->register_app( 'ai', Ai::get_class_name() );
55 52 } );
@@ -82,10 +79,8 @@
82 79 'ai_get_history' => [ $this, 'ajax_ai_get_history' ],
83 80 'ai_delete_history_item' => [ $this, 'ajax_ai_delete_history_item' ],
84 81 'ai_toggle_favorite_history_item' => [ $this, 'ajax_ai_toggle_favorite_history_item' ],
85 82 '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 83 ];
89 84
90 85 foreach ( $handlers as $tag => $callback ) {
91 86 $ajax->register_ajax_action( $tag, $callback );
@@ -123,8 +118,9 @@
123 118 add_action( 'admin_init', [ $this, 'enqueue_ai_products_page_scripts' ] );
124 119 add_action( 'current_screen', [ $this, 'enqueue_ai_single_product_page_scripts' ] );
125 120 add_action( 'wp_ajax_elementor-ai-get-product-images', [ $this, 'get_product_images_ajax' ] );
126 121 add_action( 'wp_ajax_elementor-ai-set-product-images', [ $this, 'set_product_images_ajax' ] );
122 + Product_Image_Unification_Intro::add_hooks();
127 123 }
128 124 }
129 125
130 126 add_action( 'enqueue_block_editor_assets', function() {
@@ -160,126 +156,10 @@
160 156
161 157 add_filter( 'elementor/document/save/data', function ( $data ) {
162 158 return $this->remove_temporary_containers( $data );
163 159 } );
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 160 }
170 161
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'],
212 - ],
213 - ] );
214 - }
215 -
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 162 private function get_current_screen() {
283 163 $is_wc = class_exists( 'WooCommerce' ) && post_type_exists( 'product' );
284 164 if ( ! $is_wc ) {
285 165 return 'other';
@@ -304,8 +184,9 @@
304 184 return;
305 185 }
306 186
307 187 $this->add_wc_scripts();
188 +
308 189 }
309 190
310 191 public function enqueue_ai_single_product_page_scripts() {
311 192 if ( 'wc-single-product' !== $this->get_current_screen() ) {
@@ -312,8 +193,9 @@
312 193 return;
313 194 }
314 195
315 196 $this->add_wc_scripts();
197 +
316 198 }
317 199
318 200 private function add_products_bulk_action( $bulk_actions ) {
319 201 $bulk_actions['elementor-ai-unify-product-images'] = __( 'Unify with Elementor AI', 'elementor' );
@@ -412,8 +294,9 @@
412 294 }
413 295
414 296 wp_send_json_success( [
415 297 'message' => __( 'Image added successfully', 'elementor' ),
298 + 'refresh' => true,
416 299 ] );
417 300 }
418 301
419 302 public function enqueue_ai_media_library_upload_screen() {
@@ -474,8 +357,13 @@
474 357 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
475 358 'connect_url' => $this->get_ai_connect_url(),
476 359 ];
477 360
361 + if ( $this->get_ai_app()->is_connected() ) {
362 + // Use a cached version, don't call the API on every editor load.
363 + $config['usage'] = $this->get_ai_app()->get_cached_usage();
364 + }
365 +
478 366 wp_localize_script(
479 367 'elementor-ai',
480 368 'ElementorAiConfig',
481 369 $config
@@ -506,9 +394,9 @@
506 394 wp_set_script_translations( 'elementor-ai-layout', 'elementor' );
507 395 }
508 396
509 397 private function remove_temporary_containers( $data ) {
510 - if ( empty( $data['elements'] ) || ! is_array( $data['elements'] ) ) {
398 + if ( empty( $data['elements'] ) ) {
511 399 return $data;
512 400 }
513 401
514 402 // If for some reason the document has been saved during an AI Layout session,
@@ -594,10 +482,12 @@
594 482 if ( $document->is_built_with_elementor() ) {
595 483 if ( ! $document->is_editable_by_current_user() ) {
596 484 throw new \Exception( 'Access denied' );
597 485 }
598 - } elseif ( ! current_user_can( 'edit_post', $editor_post_id ) ) {
486 + } else {
487 + if ( ! current_user_can( 'edit_post', $editor_post_id ) ) {
599 488 throw new \Exception( 'Access denied' );
489 + }
600 490 }
601 491 }
602 492
603 493 public function ajax_ai_get_image_prompt_enhancer( $data ) {
@@ -703,9 +593,9 @@
703 593 'usage' => $result['usage'],
704 594 ];
705 595 }
706 596
707 - private function get_ai_app(): Ai {
597 + private function get_ai_app() : Ai {
708 598 return Plugin::$instance->common->get_component( 'connect' )->get_app( 'ai' );
709 599 }
710 600
711 601 private function get_request_context( $data ) {
@@ -1018,8 +908,12 @@
1018 908 if ( empty( $data['payload']['settings'] ) ) {
1019 909 throw new \Exception( 'Missing prompt settings' );
1020 910 }
1021 911
912 + if ( ! $app->is_connected() ) {
913 + throw new \Exception( 'not_connected' );
914 + }
915 +
1022 916 if ( empty( $data['payload']['mask'] ) ) {
1023 917 throw new \Exception( 'Missing Mask' );
1024 918 }
1025 919
@@ -1109,10 +1003,8 @@
1109 1003 ];
1110 1004 }
1111 1005
1112 1006 public function ajax_ai_upload_image( $data ) {
1113 - $this->verify_upload_permissions( $data );
1114 -
1115 1007 if ( empty( $data['image'] ) ) {
1116 1008 throw new \Exception( 'Missing image data' );
1117 1009 }
1118 1010
@@ -1124,14 +1016,14 @@
1124 1016
1125 1017 $image_data = $this->upload_image( $image['image_url'], $data['prompt'], $data['editor_post_id'] );
1126 1018
1127 1019 if ( is_wp_error( $image_data ) ) {
1128 - throw new \Exception( esc_html( $image_data->get_error_message() ) );
1020 + throw new \Exception( $image_data->get_error_message() );
1129 1021 }
1130 1022
1131 1023 if ( ! empty( $image['use_gallery_image'] ) && ! empty( $image['id'] ) ) {
1132 - $app = $this->get_ai_app();
1133 - $app->set_used_gallery_image( $image['id'] );
1024 + $app = $this->get_ai_app();
1025 + $app->set_used_gallery_image( $image['id'] );
1134 1026 }
1135 1027
1136 1028 return [
1137 1029 'image' => array_merge( $image_data, $data ),
@@ -1160,9 +1052,9 @@
1160 1052 $message = $result->get_error_message();
1161 1053
1162 1054 if ( is_array( $message ) ) {
1163 1055 $message = implode( ', ', $message );
1164 - throw new \Exception( esc_html( $message ) );
1056 + throw new \Exception( $message );
1165 1057 }
1166 1058
1167 1059 $this->throw_on_error( $result );
1168 1060 }
@@ -1310,24 +1202,10 @@
1310 1202 if ( ! current_user_can( 'upload_files' ) ) {
1311 1203 throw new \Exception( 'Not Allowed to Upload images' );
1312 1204 }
1313 1205
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 1206 $attachment_id = media_sideload_image( $image_url, $parent_post_id, $image_title, 'id' );
1325 1207
1326 - if ( is_wp_error( $attachment_id ) ) {
1327 - return new \WP_Error( 'upload_error', $attachment_id->get_error_message() );
1328 - }
1329 -
1330 1208 if ( ! empty( $attachment_id['error'] ) ) {
1331 1209 return new \WP_Error( 'upload_error', $attachment_id['error'] );
1332 1210 }
1333 1211
@@ -1332,10 +1210,10 @@
1332 1210 }
1333 1211
1334 1212 return [
1335 1213 'id' => $attachment_id,
1336 - 'url' => esc_url( wp_get_attachment_image_url( $attachment_id, 'full' ) ),
1337 - 'alt' => esc_attr( $image_title ),
1214 + 'url' => wp_get_attachment_image_url( $attachment_id, 'full' ),
1215 + 'alt' => $image_title,
1338 1216 'source' => 'library',
1339 1217 ];
1340 1218 }
1341 1219
@@ -1359,9 +1237,9 @@
1359 1237
1360 1238 $result = $app->get_history_by_type( $type, $page, $limit, $context );
1361 1239
1362 1240 if ( is_wp_error( $result ) ) {
1363 - throw new \Exception( esc_html( $result->get_error_message() ) );
1241 + throw new \Exception( $result->get_error_message() );
1364 1242 }
1365 1243
1366 1244 return $result;
1367 1245 }
@@ -1381,9 +1259,9 @@
1381 1259
1382 1260 $result = $app->delete_history_item( $data['id'], $context );
1383 1261
1384 1262 if ( is_wp_error( $result ) ) {
1385 - throw new \Exception( esc_html( $result->get_error_message() ) );
1263 + throw new \Exception( $result->get_error_message() );
1386 1264 }
1387 1265
1388 1266 return [];
1389 1267 }
@@ -1403,9 +1281,9 @@
1403 1281
1404 1282 $result = $app->toggle_favorite_history_item( $data['id'], $context );
1405 1283
1406 1284 if ( is_wp_error( $result ) ) {
1407 - throw new \Exception( esc_html( $result->get_error_message() ) );
1285 + throw new \Exception( $result->get_error_message() );
1408 1286 }
1409 1287
1410 1288 return [];
1411 1289 }
@@ -1410,25 +1288,23 @@
1410 1288 return [];
1411 1289 }
1412 1290
1413 1291 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 - }
1292 + $data['editor_post_id'] = $data['payload']['postId'];
1417 1293 $this->verify_upload_permissions( $data );
1418 1294
1419 1295 $app = $this->get_ai_app();
1420 1296
1421 1297 if ( empty( $data['payload']['image'] ) || empty( $data['payload']['image']['id'] ) ) {
1422 - throw new \Exception( 'Missing Image' );
1298 + throw new \Exception( __( 'Missing Image', 'elementor' ) );
1423 1299 }
1424 1300
1425 1301 if ( empty( $data['payload']['settings'] ) ) {
1426 - throw new \Exception( 'Missing prompt settings' );
1302 + throw new \Exception( __( 'Missing prompt settings', 'elementor' ) );
1427 1303 }
1428 1304
1429 1305 if ( ! $app->is_connected() ) {
1430 - throw new \Exception( 'not_connected' );
1306 + throw new \Exception( __( 'not_connected', 'elementor' ) );
1431 1307 }
1432 1308
1433 1309 $context = $this->get_request_context( $data );
1434 1310 $request_ids = $this->get_request_ids( $data['payload'] );
@@ -1435,9 +1311,8 @@
1435 1311
1436 1312 $result = $app->get_unify_product_images( [
1437 1313 'promptSettings' => $data['payload']['settings'],
1438 1314 'attachment_id' => $data['payload']['image']['id'],
1439 - 'featureIdentifier' => $data['payload']['featureIdentifier'] ?? '',
1440 1315 ], $context, $request_ids );
1441 1316
1442 1317 $this->throw_on_error( $result );
1443 1318
@@ -1447,39 +1322,8 @@
1447 1322 'usage' => $result['usage'],
1448 1323 ];
1449 1324 }
1450 1325
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 1326 /**
1483 1327 * @param mixed $result
1484 1328 */
1485 1329 private function throw_on_error( $result ): void {
@@ -1484,9 +1328,9 @@
1484 1328 */
1485 1329 private function throw_on_error( $result ): void {
1486 1330 if ( is_wp_error( $result ) ) {
1487 1331 wp_send_json_error( [
1488 - 'message' => esc_html( $result->get_error_message() ),
1332 + 'message' => $result->get_error_message(),
1489 1333 'extra_data' => $result->get_error_data(),
1490 1334 ] );
1491 1335 }
1492 1336 }
@@ -1532,64 +1376,22 @@
1532 1376 * @param $product
1533 1377 * @param int|null $image_to_remove
1534 1378 * @param int|null $image_to_add
1535 1379 * @return void
1380 + * @throws \Exception
1536 1381 */
1537 1382 private function update_product_gallery( $product, ?int $image_to_remove, ?int $image_to_add ): void {
1538 1383 $gallery_image_ids = $product->get_gallery_image_ids();
1539 1384
1540 - $index = array_search( $image_to_remove, $gallery_image_ids, true );
1385 + $index = array_search( $image_to_remove, $gallery_image_ids );
1541 1386 if ( false !== $index ) {
1542 1387 unset( $gallery_image_ids[ $index ] );
1543 1388 }
1544 1389
1545 - if ( ! in_array( $image_to_add, $gallery_image_ids, true ) ) {
1390 + if ( ! in_array( $image_to_add, $gallery_image_ids ) ) {
1546 1391 $gallery_image_ids[] = $image_to_add;
1547 1392 }
1548 1393
1549 1394 $product->set_gallery_image_ids( $gallery_image_ids );
1550 1395 $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 1396 }
1595 1397 }