PluginProbe
Elementor Website Builder – more than just a page builder / 4.3.2
Elementor Website Builder – more than just a page builder v4.3.2
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 4.1.0-dev2 All 455 releases
← All changes | modules/mcp/abilities/manage-component-ability.php +118 -12 4.3.0-beta1 → 4.3.2 View file →
@@ -14,8 +14,9 @@
14 14 use Elementor\Modules\Mcp\Abilities\Utils\Composition_Compiler;
15 15 use Elementor\Modules\Mcp\Abilities\Utils\Insufficient_Permissions_Error;
16 16 use Elementor\Modules\Mcp\Abilities\Utils\Overridable_Props_Builder;
17 17 use Elementor\Modules\Mcp\Abilities\Utils\Prompt_Loader;
18 +use Elementor\Modules\Mcp\Events\Mcp_Event_Dispatcher;
18 19 use Elementor\Plugin;
19 20
20 21 if ( ! defined( 'ABSPATH' ) ) {
21 22 exit;
@@ -91,11 +92,12 @@
91 92 if ( is_wp_error( $source_result ) ) {
92 93 return $source_result;
93 94 }
94 95 [ 'elements' => $elements, 'warnings' => $warnings ] = $source_result;
96 + $source_id_map = $source_result['source_id_map'] ?? [];
95 97
96 98 $settings = [];
97 - $overridable_error = $this->apply_overridable_props( $elements, $input, $settings );
99 + $overridable_error = $this->apply_overridable_props( $elements, $input, $settings, $source_id_map );
98 100 if ( is_wp_error( $overridable_error ) ) {
99 101 return $overridable_error;
100 102 }
101 103
@@ -136,8 +138,10 @@
136 138 }
137 139
138 140 $component = $this->get_repository()->get( $component_id, false );
139 141
142 + $this->emit_component_created_event( $title, $component_id, $elements );
143 +
140 144 $response = [
141 145 'success' => true,
142 146 'component_id' => $component_id,
143 147 'uid' => $uid,
@@ -362,9 +366,9 @@
362 366 );
363 367 }
364 368
365 369 /**
366 - * @return array{elements: array[], warnings: string[]}|\WP_Error
370 + * @return array{elements: array[], warnings: string[], source_id_map?: array<string,string>}|\WP_Error
367 371 */
368 372 private function resolve_create_elements( array $input ) {
369 373 $has_xml = ! empty( $input['xml_structure'] ) && is_string( $input['xml_structure'] );
370 374 $has_source_element = ! empty( $input['source_post_id'] ) && ! empty( $input['element_id'] );
@@ -428,9 +432,9 @@
428 432 return $compiled;
429 433 }
430 434
431 435 /**
432 - * @return array{elements: array[], warnings: string[]}|\WP_Error
436 + * @return array{elements: array[], warnings: string[], source_id_map: array<string,string>}|\WP_Error
433 437 */
434 438 private function copy_elements_from_source( array $input ) {
435 439 $source_post_id = (int) $input['source_post_id'];
436 440 $element_id = (string) $input['element_id'];
@@ -454,11 +458,15 @@
454 458 if ( null === $found ) {
455 459 return $this->not_found( __( 'element_id was not found on source_post_id.', 'elementor' ) );
456 460 }
457 461
462 + $source_id_map = [];
463 + $elements = $this->assign_element_ids_recording_source_ids( [ $found ], $source_id_map );
464 +
458 465 return [
459 - 'elements' => $this->assign_element_ids( [ $found ] ),
466 + 'elements' => $elements,
460 467 'warnings' => [],
468 + 'source_id_map' => $source_id_map,
461 469 ];
462 470 }
463 471
464 472 /**
@@ -466,16 +474,26 @@
466 474 * onto the referenced element settings. Validation and persistence of the native
467 475 * payload itself is left to the component document's save hook, which already
468 476 * parses and persists it for every component save.
469 477 *
478 + * @param array[] $elements
479 + * @param array $input
480 + * @param array $settings
481 + * @param array<string,string> $source_id_map old-source-id => new-machine-id, when the tree
482 + * came from `copy_elements_from_source`. Used to
483 + * let callers address targets by the ids they
484 + * saw on the source document, before id regeneration.
485 + *
470 486 * @return \WP_Error|null
471 487 */
472 - private function apply_overridable_props( array &$elements, array $input, array &$settings ) {
488 + private function apply_overridable_props( array &$elements, array $input, array &$settings, array $source_id_map = [] ) {
473 489 if ( ! is_array( $input['overridable_props'] ?? null ) || empty( $input['overridable_props'] ) ) {
474 490 return null;
475 491 }
476 492
477 - $builder_result = Overridable_Props_Builder::make( $this->get_repository() )->build( $elements, $input['overridable_props'] );
493 + $definitions = $this->remap_overridable_targets( $input['overridable_props'], $source_id_map );
494 +
495 + $builder_result = Overridable_Props_Builder::make( $this->get_repository() )->build( $elements, $definitions );
478 496 if ( is_wp_error( $builder_result ) ) {
479 497 return $builder_result;
480 498 }
481 499
@@ -484,15 +502,35 @@
484 502 return null;
485 503 }
486 504
487 505 /**
488 - * Compiled subtrees have no ids yet (`Subtree_Builder` never sets one), and copied
489 - * subtrees carry ids from another document that would collide here. Both paths need
490 - * fresh, slug-safe machine ids; the caller's configuration-id stays on
491 - * `editor_settings.title` so `overridable_props.target` and other tools can still
492 - * address elements by the identifier the caller used in `xml_structure` — see
493 - * `Overridable_Props_Builder::find_element_ref`.
506 + * Rewrites `overridable_props[*].target` from source-document ids to the freshly assigned
507 + * ids, so callers can keep referencing the elements by the ids they observed on the source.
508 + * Unknown targets pass through untouched so the xml_structure `configuration-id` path (which
509 + * resolves via `editor_settings.title` in `Overridable_Props_Builder::find_element_ref`) and
510 + * regular id targets still work.
511 + *
512 + * @param array $definitions
513 + * @param array<string,string> $source_id_map
494 514 */
515 + private function remap_overridable_targets( array $definitions, array $source_id_map ): array {
516 + if ( empty( $source_id_map ) ) {
517 + return $definitions;
518 + }
519 +
520 + foreach ( $definitions as $override_key => &$definition ) {
521 + if ( ! is_array( $definition ) ) {
522 + continue;
523 + }
524 + $target = $definition['target'] ?? null;
525 + if ( is_string( $target ) && isset( $source_id_map[ $target ] ) ) {
526 + $definition['target'] = $source_id_map[ $target ];
527 + }
528 + }
529 +
530 + return $definitions;
531 + }
532 +
495 533 private function assign_element_ids( array $elements ): array {
496 534 return array_map( fn( array $element ) => $this->assign_element_id( $element ), $elements );
497 535 }
498 536
@@ -505,8 +543,39 @@
505 543
506 544 return $element;
507 545 }
508 546
547 + /**
548 + * Regenerates ids like `assign_element_ids`, but records the mapping from each element's
549 + * old id to its new id so callers can keep addressing the tree by pre-regeneration ids.
550 + *
551 + * @param array[] $elements
552 + * @param array<string,string> $source_id_map Populated by reference.
553 + */
554 + private function assign_element_ids_recording_source_ids( array $elements, array &$source_id_map ): array {
555 + $result = [];
556 + foreach ( $elements as $element ) {
557 + $result[] = $this->assign_element_id_recording_source_id( $element, $source_id_map );
558 + }
559 + return $result;
560 + }
561 +
562 + private function assign_element_id_recording_source_id( array $element, array &$source_id_map ): array {
563 + $old_id = isset( $element['id'] ) ? (string) $element['id'] : '';
564 +
565 + $element['id'] = Document_Mutator::instance()->generate_id();
566 +
567 + if ( '' !== $old_id ) {
568 + $source_id_map[ $old_id ] = $element['id'];
569 + }
570 +
571 + if ( ! empty( $element['elements'] ) && is_array( $element['elements'] ) ) {
572 + $element['elements'] = $this->assign_element_ids_recording_source_ids( $element['elements'], $source_id_map );
573 + }
574 +
575 + return $element;
576 + }
577 +
509 578 private function document_links( Component_Document $component ): array {
510 579 $editor_url = $component->get_edit_url();
511 580
512 581 return [
@@ -666,7 +735,44 @@
666 735 'items' => [ 'type' => 'integer' ],
667 736 'description' => 'archive targets.',
668 737 ],
669 738 ],
739 + ];
740 + }
741 +
742 + private function emit_component_created_event( string $title, int $component_id, array $elements ): void {
743 + [ 'elements_count' => $nested_elements_count, 'components_count' => $nested_components_count ] = $this->count_nested_elements( $elements );
744 + $top_element_type = $elements[0]['elType'] ?? ( $elements[0]['widgetType'] ?? '' );
745 +
746 + Mcp_Event_Dispatcher::emit( 'component_created', [
747 + 'id' => (string) $component_id,
748 + 'name' => $title,
749 + 'nested_elements_count' => $nested_elements_count,
750 + 'nested_components_count' => $nested_components_count,
751 + 'top_element_type' => $top_element_type,
752 + ] );
753 + }
754 +
755 + private function count_nested_elements( array $elements ): array {
756 + $elements_count = count( $elements );
757 + $components_count = 0;
758 +
759 + foreach ( $elements as $element ) {
760 + if ( 'e-component' === ( $element['widgetType'] ?? '' ) ) {
761 + $components_count++;
762 + }
763 +
764 + $children = $element['elements'] ?? [];
765 +
766 + if ( ! empty( $children ) ) {
767 + $child_counts = $this->count_nested_elements( $children );
768 + $elements_count += $child_counts['elements_count'];
769 + $components_count += $child_counts['components_count'];
770 + }
771 + }
772 +
773 + return [
774 + 'elements_count' => $elements_count,
775 + 'components_count' => $components_count,
670 776 ];
671 777 }
672 778 }