← All changes
|
modules/mcp/abilities/manage-elements-ability.php
+332
-26
4.3.0-beta2
→
4.3.1
View file →
| @@ -23,9 +23,11 @@ | ||
| 23 | 23 | use Elementor\Modules\Mcp\Abilities\Build_Composition\Widget_Type_Resolver; |
| 24 | 24 | use Elementor\Modules\Mcp\Abilities\Build_Composition\Xml_Parser; |
| 25 | 25 | use Elementor\Modules\Mcp\Abilities\Utils\Bulk_Operations_Result; |
| 26 | 26 | use Elementor\Modules\Mcp\Abilities\Utils\Document_Mutation_Save; |
| 27 | +use Elementor\Modules\Mcp\Abilities\Utils\Tool_Performance_Metrics; | |
| 27 | 28 | use Elementor\Modules\Mcp\Abilities\Utils\Widget_Context_Helper; |
| 29 | +use Elementor\Modules\Mcp\Events\Mcp_Event_Dispatcher; | |
| 28 | 30 | use Elementor\Modules\Variables\Module as Variables_Module; |
| 29 | 31 | use Elementor\Modules\Variables\Services\Batch_Operations\Batch_Processor; |
| 30 | 32 | use Elementor\Modules\Variables\Services\Variables_Service; |
| 31 | 33 | use Elementor\Modules\Variables\Storage\Variables_Repository; |
| @@ -138,25 +140,33 @@ | ||
| 138 | 140 | ); |
| 139 | 141 | } |
| 140 | 142 | |
| 141 | 143 | public function execute( $input = [] ) { |
| 142 | - $input = is_array( $input ) ? $input : []; | |
| 144 | + $started_at = hrtime( true ); | |
| 145 | + $input = is_array( $input ) ? $input : []; | |
| 146 | + $post_id = isset( $input['post_id'] ) ? (int) $input['post_id'] : 0; | |
| 147 | + $operations = $input['operations'] ?? null; | |
| 143 | 148 | |
| 144 | 149 | if ( empty( $input['post_id'] ) ) { |
| 145 | - return $this->bad_request( __( 'post_id is required.', 'elementor' ) ); | |
| 150 | + $error = $this->bad_request( __( 'post_id is required.', 'elementor' ) ); | |
| 151 | + $this->emit_mcp_manage_elements_executed( $started_at, $post_id, null, $error ); | |
| 152 | + return $error; | |
| 146 | 153 | } |
| 147 | 154 | |
| 148 | - $operations = $input['operations'] ?? null; | |
| 149 | 155 | if ( ! is_array( $operations ) ) { |
| 150 | - return $this->bad_request( __( 'operations array is required.', 'elementor' ) ); | |
| 156 | + $error = $this->bad_request( __( 'operations array is required.', 'elementor' ) ); | |
| 157 | + $this->emit_mcp_manage_elements_executed( $started_at, $post_id, null, $error ); | |
| 158 | + return $error; | |
| 151 | 159 | } |
| 152 | 160 | |
| 153 | 161 | if ( empty( $operations ) ) { |
| 154 | - return $this->bad_request( __( 'operations must not be empty.', 'elementor' ) ); | |
| 162 | + $error = $this->bad_request( __( 'operations must not be empty.', 'elementor' ) ); | |
| 163 | + $this->emit_mcp_manage_elements_executed( $started_at, $post_id, null, $error ); | |
| 164 | + return $error; | |
| 155 | 165 | } |
| 156 | 166 | |
| 157 | 167 | if ( count( $operations ) > self::MAX_BATCH_SIZE ) { |
| 158 | - return new \WP_Error( | |
| 168 | + $error = new \WP_Error( | |
| 159 | 169 | 'batch_size_exceeded', |
| 160 | 170 | sprintf( |
| 161 | 171 | /* translators: %d: maximum operations per request */ |
| 162 | 172 | __( 'Maximum %d operations per request.', 'elementor' ), |
| @@ -166,32 +176,39 @@ | ||
| 166 | 176 | 'status' => \WP_Http::BAD_REQUEST, |
| 167 | 177 | 'max_allowed' => self::MAX_BATCH_SIZE, |
| 168 | 178 | ] |
| 169 | 179 | ); |
| 180 | + $this->emit_mcp_manage_elements_executed( $started_at, $post_id, null, $error, $operations ); | |
| 181 | + return $error; | |
| 170 | 182 | } |
| 171 | 183 | |
| 172 | - $post_id = (int) $input['post_id']; | |
| 173 | - | |
| 174 | 184 | if ( ! current_user_can( 'edit_post', $post_id ) ) { |
| 175 | - return new \WP_Error( | |
| 185 | + $error = new \WP_Error( | |
| 176 | 186 | 'elementor_forbidden', |
| 177 | 187 | __( 'You do not have permission to edit this post.', 'elementor' ), |
| 178 | 188 | [ 'status' => \WP_Http::FORBIDDEN ] |
| 179 | 189 | ); |
| 190 | + $this->emit_mcp_manage_elements_executed( $started_at, $post_id, null, $error, $operations ); | |
| 191 | + return $error; | |
| 180 | 192 | } |
| 181 | 193 | |
| 182 | 194 | $document = $this->resolve_document( $post_id ); |
| 183 | 195 | if ( is_wp_error( $document ) ) { |
| 196 | + $this->emit_mcp_manage_elements_executed( $started_at, $post_id, null, $document, $operations ); | |
| 184 | 197 | return $document; |
| 185 | 198 | } |
| 186 | 199 | |
| 187 | - return $this->handle_bulk( $document, $operations ); | |
| 200 | + return $this->handle_bulk( $document, $operations, $started_at ); | |
| 188 | 201 | } |
| 189 | 202 | |
| 190 | - private function handle_bulk( Document $document, array $operations ): array { | |
| 191 | - $results = new Bulk_Operations_Result(); | |
| 192 | - $tree = $this->get_tree( $document ); | |
| 193 | - $any_change = false; | |
| 203 | + private function handle_bulk( Document $document, array $operations, int $started_at ): array { | |
| 204 | + $results = new Bulk_Operations_Result(); | |
| 205 | + $tree = $this->get_tree( $document ); | |
| 206 | + $any_change = false; | |
| 207 | + $pending_events = []; | |
| 208 | + $all_warning_codes = []; | |
| 209 | + $class_attachments = 0; | |
| 210 | + $interactions_count = 0; | |
| 194 | 211 | |
| 195 | 212 | foreach ( $operations as $index => $operation ) { |
| 196 | 213 | $index = (int) $index; |
| 197 | 214 | |
| @@ -222,15 +239,26 @@ | ||
| 222 | 239 | if ( ! empty( $outcome['warnings'] ) ) { |
| 223 | 240 | $extra['warnings'] = $outcome['warnings']; |
| 224 | 241 | } |
| 225 | 242 | $results->add_success( $index, $action, $extra ); |
| 243 | + | |
| 244 | + $all_warning_codes = array_merge( $all_warning_codes, $outcome['warning_codes'] ?? [] ); | |
| 245 | + $class_attachments += count( $outcome['event_metadata']['applied_classes'] ?? [] ); | |
| 246 | + $interactions_count += count( $outcome['event_metadata']['interactions_events'] ?? [] ); | |
| 247 | + | |
| 248 | + if ( ! empty( $outcome['event_metadata'] ) ) { | |
| 249 | + $pending_events[] = [ 'action' => $action ] + $outcome['event_metadata']; | |
| 250 | + } | |
| 226 | 251 | } |
| 227 | 252 | |
| 228 | - $response = $results->to_array(); | |
| 253 | + $response = $results->to_array(); | |
| 229 | 254 | $response['post_id'] = (int) $document->get_main_id(); |
| 255 | + $post_id = (int) $document->get_main_id(); | |
| 230 | 256 | |
| 231 | 257 | if ( ! $any_change ) { |
| 232 | - return $this->with_edit_url( $response, $document ); | |
| 258 | + $response = $this->with_edit_url( $response, $document ); | |
| 259 | + $this->emit_mcp_manage_elements_executed( $started_at, $post_id, $document, null, $operations, $response, $all_warning_codes, $class_attachments, $interactions_count ); | |
| 260 | + return $response; | |
| 233 | 261 | } |
| 234 | 262 | |
| 235 | 263 | $save_result = Document_Mutation_Save::elements_preserving_live_status( $this->get_mutator(), $document, $tree ); |
| 236 | 264 | if ( is_wp_error( $save_result ) ) { |
| @@ -235,18 +263,22 @@ | ||
| 235 | 263 | $save_result = Document_Mutation_Save::elements_preserving_live_status( $this->get_mutator(), $document, $tree ); |
| 236 | 264 | if ( is_wp_error( $save_result ) ) { |
| 237 | 265 | $response['status'] = 'error'; |
| 238 | 266 | $response['save_error'] = $save_result->get_error_message(); |
| 239 | - | |
| 240 | - return $this->with_edit_url( $response, $document ); | |
| 267 | + $response = $this->with_edit_url( $response, $document ); | |
| 268 | + $this->emit_mcp_manage_elements_executed( $started_at, $post_id, $document, $save_result, $operations, $response, $all_warning_codes, 0, 0 ); | |
| 269 | + return $response; | |
| 241 | 270 | } |
| 242 | 271 | |
| 243 | 272 | Plugin::$instance->files_manager->clear_cache(); |
| 244 | 273 | |
| 274 | + $this->emit_update_events( $pending_events, $tree ); | |
| 275 | + | |
| 245 | 276 | $saved_post = $save_result->get_post(); |
| 246 | 277 | $response['version'] = $saved_post ? $saved_post->post_modified_gmt : current_time( 'mysql', true ); |
| 247 | - | |
| 248 | - return $this->with_edit_url( $response, $document ); | |
| 278 | + $response = $this->with_edit_url( $response, $document ); | |
| 279 | + $this->emit_mcp_manage_elements_executed( $started_at, $post_id, $document, null, $operations, $response, $all_warning_codes, $class_attachments, $interactions_count ); | |
| 280 | + return $response; | |
| 249 | 281 | } |
| 250 | 282 | |
| 251 | 283 | private function with_edit_url( array $response, Document $document ): array { |
| 252 | 284 | $response['edit_url'] = $document->get_edit_url(); |
| @@ -320,12 +352,14 @@ | ||
| 320 | 352 | |
| 321 | 353 | return [ |
| 322 | 354 | 'tree' => $new_tree, |
| 323 | 355 | 'warnings' => [], |
| 356 | + 'warning_codes' => [], | |
| 324 | 357 | ]; |
| 325 | 358 | } |
| 326 | 359 | |
| 327 | 360 | private function apply_duplicate( array $tree, string $element_id ) { |
| 361 | + $source_node = $this->get_mutator()->find_by_id( $tree, $element_id ); | |
| 328 | 362 | $new_tree = $this->get_mutator()->duplicate( $tree, $element_id ); |
| 329 | 363 | if ( is_wp_error( $new_tree ) ) { |
| 330 | 364 | return $this->to_public_error( $new_tree ); |
| 331 | 365 | } |
| @@ -332,11 +366,35 @@ | ||
| 332 | 366 | |
| 333 | 367 | return [ |
| 334 | 368 | 'tree' => $new_tree, |
| 335 | 369 | 'warnings' => [], |
| 370 | + 'warning_codes' => [], | |
| 371 | + 'event_metadata' => [ | |
| 372 | + 'duplicated_element_types' => is_array( $source_node ) ? $this->collect_element_types( $source_node ) : [], | |
| 373 | + ], | |
| 336 | 374 | ]; |
| 337 | 375 | } |
| 338 | 376 | |
| 377 | + private function collect_element_types( array $node ): array { | |
| 378 | + $types = []; | |
| 379 | + $stack = [ $node ]; | |
| 380 | + | |
| 381 | + while ( ! empty( $stack ) ) { | |
| 382 | + $current = array_pop( $stack ); | |
| 383 | + $type = $current['widgetType'] ?? $current['elType'] ?? ''; | |
| 384 | + | |
| 385 | + if ( '' !== $type ) { | |
| 386 | + $types[] = $type; | |
| 387 | + } | |
| 388 | + | |
| 389 | + foreach ( $current['elements'] ?? [] as $child ) { | |
| 390 | + $stack[] = $child; | |
| 391 | + } | |
| 392 | + } | |
| 393 | + | |
| 394 | + return $types; | |
| 395 | + } | |
| 396 | + | |
| 339 | 397 | private function apply_move( array $tree, string $element_id, array $operation ) { |
| 340 | 398 | $new_parent_id = $operation['new_parent_id'] ?? ''; |
| 341 | 399 | if ( ! is_string( $new_parent_id ) || '' === $new_parent_id ) { |
| 342 | 400 | return new \WP_Error( 'invalid_input', __( 'new_parent_id is required for action=move.', 'elementor' ) ); |
| @@ -351,8 +409,9 @@ | ||
| 351 | 409 | |
| 352 | 410 | return [ |
| 353 | 411 | 'tree' => $new_tree, |
| 354 | 412 | 'warnings' => [], |
| 413 | + 'warning_codes' => [], | |
| 355 | 414 | ]; |
| 356 | 415 | } |
| 357 | 416 | |
| 358 | 417 | private function apply_update( Document $document, array $tree, string $element_id, array $operation ) { |
| @@ -395,10 +454,14 @@ | ||
| 395 | 454 | return $widget_config; |
| 396 | 455 | } |
| 397 | 456 | $widget_configs = [ $element_type => $widget_config ]; |
| 398 | 457 | |
| 399 | - $variables_service = $this->create_variables_service(); | |
| 400 | - $warnings = []; | |
| 458 | + $variables_service = $this->create_variables_service(); | |
| 459 | + $warnings = []; | |
| 460 | + $warning_codes = []; | |
| 461 | + $applied_classes = []; | |
| 462 | + $variable_connections = []; | |
| 463 | + $interactions_events = []; | |
| 401 | 464 | |
| 402 | 465 | if ( null !== $interactions ) { |
| 403 | 466 | if ( ! is_array( $interactions ) ) { |
| 404 | 467 | return new \WP_Error( 'invalid_input', __( 'interactions must be an array of interaction items.', 'elementor' ) ); |
| @@ -409,8 +472,11 @@ | ||
| 409 | 472 | __( 'Interactions experiment is not active. Interactions were not applied.', 'elementor' ), |
| 410 | 473 | [ 'status' => \WP_Http::BAD_REQUEST ] |
| 411 | 474 | ); |
| 412 | 475 | } |
| 476 | + | |
| 477 | + $previous_items = $node_snapshot['interactions']['items'] ?? []; | |
| 478 | + | |
| 413 | 479 | $interactions_applier = new Interactions_Applier( $this->get_plain_values_resolver() ); |
| 414 | 480 | $interactions_result = $interactions_applier->apply( $index, [ $element_id => $interactions ] ); |
| 415 | 481 | if ( $interactions_result['error'] ) { |
| 416 | 482 | return $interactions_result['error']; |
| @@ -415,8 +481,10 @@ | ||
| 415 | 481 | if ( $interactions_result['error'] ) { |
| 416 | 482 | return $interactions_result['error']; |
| 417 | 483 | } |
| 418 | 484 | $warnings = array_merge( $warnings, $interactions_result['warnings'] ); |
| 485 | + | |
| 486 | + $interactions_events = $this->build_interactions_events( (string) $element_type, $previous_items, $interactions ); | |
| 419 | 487 | } |
| 420 | 488 | |
| 421 | 489 | if ( ! empty( $settings ) ) { |
| 422 | 490 | if ( Element_Config_Applier::COMPONENT_INSTANCE_WIDGET_TYPE === $element_type ) { |
| @@ -435,9 +503,10 @@ | ||
| 435 | 503 | ); |
| 436 | 504 | if ( $config_result['error'] ) { |
| 437 | 505 | return $config_result['error']; |
| 438 | 506 | } |
| 439 | - $warnings = array_merge( $warnings, $config_result['warnings'] ); | |
| 507 | + $warnings = array_merge( $warnings, $config_result['warnings'] ); | |
| 508 | + $warning_codes = array_merge( $warning_codes, $config_result['warning_codes'] ?? [] ); | |
| 440 | 509 | } |
| 441 | 510 | } |
| 442 | 511 | |
| 443 | 512 | if ( $has_classes ) { |
| @@ -448,8 +517,9 @@ | ||
| 448 | 517 | $class_error = $class_applier->apply( $index, [ $element_id => $classes ] ); |
| 449 | 518 | if ( $class_error ) { |
| 450 | 519 | return $class_error; |
| 451 | 520 | } |
| 521 | + $applied_classes = $classes; | |
| 452 | 522 | } |
| 453 | 523 | |
| 454 | 524 | if ( $has_style ) { |
| 455 | 525 | $style_applier = new Style_Applier( $this->create_css_converter( $variables_service ), $this->get_active_breakpoints() ); |
| @@ -456,17 +526,116 @@ | ||
| 456 | 526 | $style_result = $style_applier->apply( $index, [ $element_id => $style ], $style_apply_mode, $widget_configs ); |
| 457 | 527 | if ( $style_result['error'] ) { |
| 458 | 528 | return $style_result['error']; |
| 459 | 529 | } |
| 460 | - $warnings = array_merge( $warnings, $style_result['warnings'] ); | |
| 530 | + $warnings = array_merge( $warnings, $style_result['warnings'] ); | |
| 531 | + $warning_codes = array_merge( $warning_codes, $style_result['warning_codes'] ?? [] ); | |
| 532 | + $variable_connections = $style_result['variable_connections'][ $element_id ] ?? []; | |
| 461 | 533 | } |
| 462 | 534 | |
| 463 | 535 | return [ |
| 464 | - 'tree' => $tree, | |
| 465 | - 'warnings' => $warnings, | |
| 536 | + 'tree' => $tree, | |
| 537 | + 'warnings' => $warnings, | |
| 538 | + 'warning_codes' => $warning_codes, | |
| 539 | + 'event_metadata' => [ | |
| 540 | + 'element_id' => $element_id, | |
| 541 | + 'element_type' => (string) $element_type, | |
| 542 | + 'applied_classes' => $applied_classes, | |
| 543 | + 'variable_connections' => $variable_connections, | |
| 544 | + 'interactions_events' => $interactions_events, | |
| 545 | + ], | |
| 466 | 546 | ]; |
| 467 | 547 | } |
| 468 | 548 | |
| 549 | + protected function build_interactions_events( string $element_type, array $previous_items, array $new_items ): array { | |
| 550 | + if ( [] === $new_items ) { | |
| 551 | + return [ | |
| 552 | + [ | |
| 553 | + 'event_name' => 'interactions_cleared', | |
| 554 | + 'payload' => [ | |
| 555 | + 'affected_element_type' => $element_type, | |
| 556 | + 'target_value' => count( $previous_items ), | |
| 557 | + ], | |
| 558 | + ], | |
| 559 | + ]; | |
| 560 | + } | |
| 561 | + | |
| 562 | + $previous_by_id = []; | |
| 563 | + foreach ( $previous_items as $item ) { | |
| 564 | + if ( ! is_array( $item ) ) { | |
| 565 | + continue; | |
| 566 | + } | |
| 567 | + | |
| 568 | + $id = $this->extract_interaction_id( $item ); | |
| 569 | + if ( null !== $id ) { | |
| 570 | + $previous_by_id[ $id ] = true; | |
| 571 | + } | |
| 572 | + } | |
| 573 | + | |
| 574 | + $events = []; | |
| 575 | + | |
| 576 | + foreach ( $new_items as $item ) { | |
| 577 | + if ( ! is_array( $item ) ) { | |
| 578 | + continue; | |
| 579 | + } | |
| 580 | + | |
| 581 | + $id = $item['interaction_id'] ?? ''; | |
| 582 | + $is_update = is_string( $id ) && '' !== $id && isset( $previous_by_id[ $id ] ); | |
| 583 | + $event_name = $is_update ? 'interaction_updated' : 'interaction_created'; | |
| 584 | + | |
| 585 | + $events[] = [ | |
| 586 | + 'event_name' => $event_name, | |
| 587 | + 'payload' => [ | |
| 588 | + 'affected_element_type' => $element_type, | |
| 589 | + 'interaction_trigger' => $this->stringify_interaction_field( $item['trigger'] ?? '' ), | |
| 590 | + 'interaction_effect' => $this->stringify_interaction_field( $item['animation'] ?? '' ), | |
| 591 | + ], | |
| 592 | + ]; | |
| 593 | + } | |
| 594 | + | |
| 595 | + return $events; | |
| 596 | + } | |
| 597 | + | |
| 598 | + private function extract_interaction_id( array $item ): ?string { | |
| 599 | + if ( isset( $item['interaction_id'] ) && is_string( $item['interaction_id'] ) && '' !== $item['interaction_id'] ) { | |
| 600 | + return $item['interaction_id']; | |
| 601 | + } | |
| 602 | + | |
| 603 | + $nested = $item['value']['interaction_id']['value'] ?? $item['interaction_id']['value'] ?? null; | |
| 604 | + | |
| 605 | + if ( is_string( $nested ) && '' !== $nested ) { | |
| 606 | + return $nested; | |
| 607 | + } | |
| 608 | + | |
| 609 | + return null; | |
| 610 | + } | |
| 611 | + | |
| 612 | + private function stringify_interaction_field( $value ): string { | |
| 613 | + if ( is_string( $value ) ) { | |
| 614 | + return $value; | |
| 615 | + } | |
| 616 | + | |
| 617 | + if ( is_array( $value ) ) { | |
| 618 | + if ( isset( $value['effect'] ) && is_string( $value['effect'] ) ) { | |
| 619 | + return $value['effect']; | |
| 620 | + } | |
| 621 | + | |
| 622 | + $nested_effect = $value['value']['effect']['value'] ?? $value['effect']['value'] ?? null; | |
| 623 | + if ( is_string( $nested_effect ) && '' !== $nested_effect ) { | |
| 624 | + return $nested_effect; | |
| 625 | + } | |
| 626 | + | |
| 627 | + if ( isset( $value['value'] ) && ( is_string( $value['value'] ) || is_numeric( $value['value'] ) ) ) { | |
| 628 | + return (string) $value['value']; | |
| 629 | + } | |
| 630 | + if ( isset( $value['$$type'] ) && is_string( $value['$$type'] ) ) { | |
| 631 | + return (string) $value['$$type']; | |
| 632 | + } | |
| 633 | + } | |
| 634 | + | |
| 635 | + return ''; | |
| 636 | + } | |
| 637 | + | |
| 469 | 638 | private function resolve_document( int $post_id ) { |
| 470 | 639 | $document = Plugin::$instance->documents->get_doc_or_auto_save( $post_id, get_current_user_id() ) |
| 471 | 640 | ?? Plugin::$instance->documents->get( $post_id ); |
| 472 | 641 | |
| @@ -561,6 +730,143 @@ | ||
| 561 | 730 | } |
| 562 | 731 | |
| 563 | 732 | private function get_active_breakpoints(): array { |
| 564 | 733 | return array_keys( Plugin::$instance->breakpoints->get_active_breakpoints() ); |
| 734 | + } | |
| 735 | + | |
| 736 | + private function emit_update_events( array $pending_events, array $tree ): void { | |
| 737 | + if ( empty( $pending_events ) ) { | |
| 738 | + return; | |
| 739 | + } | |
| 740 | + | |
| 741 | + $all_labels = $this->create_global_classes_repository()->all_labels(); | |
| 742 | + $class_counts = $this->count_class_usage_in_tree( $tree ); | |
| 743 | + | |
| 744 | + foreach ( $pending_events as $meta ) { | |
| 745 | + $action = $meta['action'] ?? ''; | |
| 746 | + | |
| 747 | + if ( 'duplicate' === $action ) { | |
| 748 | + foreach ( $meta['duplicated_element_types'] ?? [] as $element_name ) { | |
| 749 | + Mcp_Event_Dispatcher::emit( 'element_added', [ | |
| 750 | + 'element_name' => $element_name, | |
| 751 | + ] ); | |
| 752 | + } | |
| 753 | + continue; | |
| 754 | + } | |
| 755 | + | |
| 756 | + $element_type = $meta['element_type'] ?? ''; | |
| 757 | + | |
| 758 | + foreach ( $meta['applied_classes'] ?? [] as $label ) { | |
| 759 | + $class_id = array_search( $label, $all_labels, true ); | |
| 760 | + | |
| 761 | + if ( false === $class_id ) { | |
| 762 | + continue; | |
| 763 | + } | |
| 764 | + | |
| 765 | + Mcp_Event_Dispatcher::emit( 'class_applied', [ | |
| 766 | + 'target_name' => 'apply_class', | |
| 767 | + 'id' => (string) $class_id, | |
| 768 | + 'name' => $label, | |
| 769 | + 'affected_element_type' => $element_type, | |
| 770 | + 'total_instances_after_apply' => $class_counts[ $class_id ] ?? 0, | |
| 771 | + ] ); | |
| 772 | + } | |
| 773 | + | |
| 774 | + foreach ( $meta['variable_connections'] as $connection ) { | |
| 775 | + Mcp_Event_Dispatcher::emit( 'variable_connected', [ | |
| 776 | + 'id' => $connection['variable_id'], | |
| 777 | + 'var_type' => $connection['var_type'], | |
| 778 | + 'control_path' => $connection['control_path'], | |
| 779 | + ] ); | |
| 780 | + } | |
| 781 | + | |
| 782 | + foreach ( $meta['interactions_events'] ?? [] as $ie ) { | |
| 783 | + Mcp_Event_Dispatcher::emit( $ie['event_name'], $ie['payload'] ); | |
| 784 | + } | |
| 785 | + } | |
| 786 | + } | |
| 787 | + | |
| 788 | + private function count_class_usage_in_tree( array $tree ): array { | |
| 789 | + $counts = []; | |
| 790 | + $this->walk_tree_for_class_counts( $tree, $counts ); | |
| 791 | + return $counts; | |
| 792 | + } | |
| 793 | + | |
| 794 | + private function walk_tree_for_class_counts( array $elements, array &$counts ): void { | |
| 795 | + foreach ( $elements as $element ) { | |
| 796 | + $class_values = $element['settings']['classes']['value'] ?? []; | |
| 797 | + | |
| 798 | + if ( is_array( $class_values ) ) { | |
| 799 | + foreach ( $class_values as $class_id ) { | |
| 800 | + if ( is_string( $class_id ) && ! str_starts_with( $class_id, Style_Applier::LOCAL_STYLE_ID_PREFIX ) ) { | |
| 801 | + $counts[ $class_id ] = ( $counts[ $class_id ] ?? 0 ) + 1; | |
| 802 | + } | |
| 803 | + } | |
| 804 | + } | |
| 805 | + | |
| 806 | + if ( ! empty( $element['elements'] ) ) { | |
| 807 | + $this->walk_tree_for_class_counts( $element['elements'], $counts ); | |
| 808 | + } | |
| 809 | + } | |
| 810 | + } | |
| 811 | + | |
| 812 | + private function emit_mcp_manage_elements_executed( | |
| 813 | + int $started_at, | |
| 814 | + int $post_id, | |
| 815 | + ?Document $document = null, | |
| 816 | + ?\WP_Error $top_level_error = null, | |
| 817 | + array $operations = [], | |
| 818 | + array $response = [], | |
| 819 | + array $warning_codes = [], | |
| 820 | + int $class_attachments = 0, | |
| 821 | + int $interactions_count = 0 | |
| 822 | + ): void { | |
| 823 | + $duration_ms = Tool_Performance_Metrics::duration_ms_since( $started_at ); | |
| 824 | + | |
| 825 | + [ 'status' => $status, 'error_code' => $error_code ] = Tool_Performance_Metrics::resolve_status( $response, $top_level_error ); | |
| 826 | + | |
| 827 | + $failed_results = array_filter( $response['results'] ?? [], fn( $r ) => 'error' === ( $r['status'] ?? '' ) ); | |
| 828 | + $failed_count = count( $failed_results ); | |
| 829 | + $failed_codes = array_values( array_unique( array_column( $failed_results, 'code' ) ) ); | |
| 830 | + | |
| 831 | + $ops_count = count( $operations ); | |
| 832 | + $by_action = []; | |
| 833 | + foreach ( $operations as $op ) { | |
| 834 | + $action = $op['action'] ?? ''; | |
| 835 | + if ( is_string( $action ) && '' !== $action ) { | |
| 836 | + $by_action[ $action ] = ( $by_action[ $action ] ?? 0 ) + 1; | |
| 837 | + } | |
| 838 | + } | |
| 839 | + | |
| 840 | + $dominant_action = ''; | |
| 841 | + if ( ! empty( $by_action ) ) { | |
| 842 | + arsort( $by_action ); | |
| 843 | + $dominant_action = (string) array_key_first( $by_action ); | |
| 844 | + } | |
| 845 | + | |
| 846 | + $payload = [ | |
| 847 | + 'tool_name' => $this->get_ability_id(), | |
| 848 | + 'status' => $status, | |
| 849 | + 'duration_ms' => $duration_ms, | |
| 850 | + 'post_id' => $post_id, | |
| 851 | + 'action' => $dominant_action, | |
| 852 | + 'operations_count' => $ops_count, | |
| 853 | + 'operations_by_type' => $by_action, | |
| 854 | + 'failed_operations_count' => $failed_count, | |
| 855 | + 'failed_operation_codes' => $failed_codes, | |
| 856 | + 'class_attachments_count' => $class_attachments, | |
| 857 | + 'interactions_applied_count' => $interactions_count, | |
| 858 | + 'warning_count' => count( array_unique( $warning_codes ) ), | |
| 859 | + 'warning_types' => array_values( array_unique( $warning_codes ) ), | |
| 860 | + ]; | |
| 861 | + | |
| 862 | + if ( null !== $document ) { | |
| 863 | + $payload['document_type'] = $document->get_name(); | |
| 864 | + } | |
| 865 | + | |
| 866 | + if ( null !== $error_code ) { | |
| 867 | + $payload['error_code'] = $error_code; | |
| 868 | + } | |
| 869 | + | |
| 870 | + Mcp_Event_Dispatcher::emit( 'mcp_manage_elements_executed', $payload ); | |
| 565 | 871 | } |
| 566 | 872 | } |