← All changes
|
modules/mcp/abilities/manage-component-ability.php
+118
-12
4.3.0-beta1
→
4.3.1
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 | } |