← All changes
|
modules/mcp/abilities/build-composition-ability.php
+115
-13
4.3.0-beta1
→
4.3.1
View file →
| @@ -8,8 +8,10 @@ | ||
| 8 | 8 | use Elementor\Modules\Mcp\Abilities\Build_Composition\Xml_Parser; |
| 9 | 9 | use Elementor\Modules\Mcp\Abilities\Utils\Composition_Compiler; |
| 10 | 10 | use Elementor\Modules\Mcp\Abilities\Utils\Document_Mutation_Links; |
| 11 | 11 | use Elementor\Modules\Mcp\Abilities\Utils\Prompt_Loader; |
| 12 | +use Elementor\Modules\Mcp\Abilities\Utils\Tool_Performance_Metrics; | |
| 13 | +use Elementor\Modules\Mcp\Events\Mcp_Event_Dispatcher; | |
| 12 | 14 | use Elementor\Plugin; |
| 13 | 15 | |
| 14 | 16 | if ( ! defined( 'ABSPATH' ) ) { |
| 15 | 17 | exit; |
| @@ -54,30 +56,35 @@ | ||
| 54 | 56 | ); |
| 55 | 57 | } |
| 56 | 58 | |
| 57 | 59 | public function execute( $input = [] ) { |
| 58 | - $input = is_array( $input ) ? $input : []; | |
| 60 | + $started_at = hrtime( true ); | |
| 61 | + $input = is_array( $input ) ? $input : []; | |
| 59 | 62 | |
| 63 | + $post_id = isset( $input['post_id'] ) ? (int) $input['post_id'] : 0; | |
| 64 | + $parent_id = $input['parent_id'] ?? self::DEFAULT_PARENT_ID; | |
| 65 | + $dry_run = ! empty( $input['dry_run'] ); | |
| 66 | + $mode = is_string( $input['mode'] ?? null ) ? $input['mode'] : self::MODE_APPEND; | |
| 67 | + | |
| 60 | 68 | $validation_error = $this->validate_input( $input ); |
| 61 | 69 | if ( $validation_error ) { |
| 70 | + $this->emit_mcp_build_composition_executed( $started_at, $post_id, $mode, $dry_run, $validation_error ); | |
| 62 | 71 | return $validation_error; |
| 63 | 72 | } |
| 64 | 73 | |
| 65 | - $post_id = (int) $input['post_id']; | |
| 66 | - $parent_id = $input['parent_id'] ?? self::DEFAULT_PARENT_ID; | |
| 67 | - $dry_run = ! empty( $input['dry_run'] ); | |
| 68 | - $mode = $input['mode'] ?? self::MODE_APPEND; | |
| 69 | - | |
| 70 | 74 | if ( ! current_user_can( 'edit_post', $post_id ) ) { |
| 71 | - return new \WP_Error( | |
| 75 | + $error = new \WP_Error( | |
| 72 | 76 | 'elementor_forbidden', |
| 73 | 77 | __( 'You do not have permission to edit this post.', 'elementor' ), |
| 74 | 78 | [ 'status' => \WP_Http::FORBIDDEN ] |
| 75 | 79 | ); |
| 80 | + $this->emit_mcp_build_composition_executed( $started_at, $post_id, $mode, $dry_run, $error ); | |
| 81 | + return $error; | |
| 76 | 82 | } |
| 77 | 83 | |
| 78 | 84 | $document = $this->resolve_document( $post_id ); |
| 79 | 85 | if ( is_wp_error( $document ) ) { |
| 86 | + $this->emit_mcp_build_composition_executed( $started_at, $post_id, $mode, $dry_run, $document ); | |
| 80 | 87 | return $document; |
| 81 | 88 | } |
| 82 | 89 | |
| 83 | 90 | $elements_data = $document->get_elements_data(); |
| @@ -87,29 +94,124 @@ | ||
| 87 | 94 | is_array( $elements_data ) ? $elements_data : [], |
| 88 | 95 | $parent_id |
| 89 | 96 | ); |
| 90 | 97 | if ( is_wp_error( $compiled ) ) { |
| 98 | + $this->emit_mcp_build_composition_executed( $started_at, $post_id, $mode, $dry_run, $compiled, null, [], $document ); | |
| 91 | 99 | return $compiled; |
| 92 | 100 | } |
| 93 | 101 | |
| 94 | - $subtrees = $compiled['elements']; | |
| 95 | - $warnings = $compiled['warnings']; | |
| 96 | - $dom = $compiled['dom']; | |
| 97 | - $xml_parser = $compiled['xml_parser']; | |
| 102 | + $subtrees = $compiled['elements']; | |
| 103 | + $warnings = $compiled['warnings']; | |
| 104 | + $warning_codes = $compiled['warning_codes'] ?? []; | |
| 105 | + $dom = $compiled['dom']; | |
| 106 | + $xml_parser = $compiled['xml_parser']; | |
| 98 | 107 | |
| 99 | 108 | if ( $dry_run ) { |
| 100 | - return $this->build_response( $post_id, $document, $xml_parser, $dom, [], $warnings, $mode, [] ); | |
| 109 | + $response = $this->build_response( $post_id, $document, $xml_parser, $dom, [], $warnings, $mode, [] ); | |
| 110 | + $this->emit_mcp_build_composition_executed( $started_at, $post_id, $mode, $dry_run, null, $subtrees, $warning_codes, $document, $response, [] ); | |
| 111 | + return $response; | |
| 101 | 112 | } |
| 102 | 113 | |
| 103 | 114 | $persister = new Composition_Persister( $this->get_mutator(), $xml_parser ); |
| 104 | 115 | $persisted = $persister->insert_and_save( $document, $subtrees, $parent_id, $mode ); |
| 105 | 116 | if ( is_wp_error( $persisted ) ) { |
| 117 | + $this->emit_mcp_build_composition_executed( $started_at, $post_id, $mode, $dry_run, $persisted, $subtrees, $warning_codes, $document ); | |
| 106 | 118 | return $persisted; |
| 107 | 119 | } |
| 108 | 120 | |
| 109 | 121 | $persister->embed_ids_into_dom( $dom, $persisted['tree'], $parent_id, $persisted['root_ids'] ); |
| 110 | 122 | |
| 111 | - return $this->build_response( $post_id, $document, $xml_parser, $dom, $persisted['root_ids'], $warnings, $mode, $persisted['removed_ids'] ); | |
| 123 | + $response = $this->build_response( $post_id, $document, $xml_parser, $dom, $persisted['root_ids'], $warnings, $mode, $persisted['removed_ids'] ); | |
| 124 | + $this->emit_mcp_build_composition_executed( $started_at, $post_id, $mode, $dry_run, null, $subtrees, $warning_codes, $document, $response, $persisted['removed_ids'] ); | |
| 125 | + | |
| 126 | + return $response; | |
| 127 | + } | |
| 128 | + | |
| 129 | + private function emit_mcp_build_composition_executed( | |
| 130 | + int $started_at, | |
| 131 | + int $post_id, | |
| 132 | + string $mode, | |
| 133 | + bool $dry_run, | |
| 134 | + ?\WP_Error $error = null, | |
| 135 | + ?array $subtrees = null, | |
| 136 | + array $warning_codes = [], | |
| 137 | + ?Document $document = null, | |
| 138 | + array $response = [], | |
| 139 | + array $removed_ids = [] | |
| 140 | + ): void { | |
| 141 | + $duration_ms = Tool_Performance_Metrics::duration_ms_since( $started_at ); | |
| 142 | + | |
| 143 | + $status = null === $error ? 'success' : 'error'; | |
| 144 | + $error_code = null !== $error ? $error->get_error_code() : null; | |
| 145 | + | |
| 146 | + $operations_count = 0; | |
| 147 | + $operations_by_type = []; | |
| 148 | + $class_attachments = 0; | |
| 149 | + $interactions_count = 0; | |
| 150 | + $removed_count = count( $removed_ids ); | |
| 151 | + | |
| 152 | + if ( null !== $subtrees ) { | |
| 153 | + $this->collect_composition_counts( $subtrees, $operations_count, $operations_by_type, $class_attachments, $interactions_count ); | |
| 154 | + } | |
| 155 | + | |
| 156 | + $style_input = []; | |
| 157 | + $vars_referenced = 0; | |
| 158 | + $document_type = null !== $document ? $this->resolve_document_type( $document ) : null; | |
| 159 | + | |
| 160 | + $payload = [ | |
| 161 | + 'tool_name' => $this->get_ability_id(), | |
| 162 | + 'status' => $status, | |
| 163 | + 'duration_ms' => $duration_ms, | |
| 164 | + 'post_id' => $post_id, | |
| 165 | + 'mode' => $mode, | |
| 166 | + 'dry_run' => $dry_run, | |
| 167 | + 'operations_count' => $operations_count, | |
| 168 | + 'operations_by_type' => $operations_by_type, | |
| 169 | + 'class_attachments_count' => $class_attachments, | |
| 170 | + 'interactions_applied_count' => $interactions_count, | |
| 171 | + 'removed_count' => $removed_count, | |
| 172 | + 'warning_count' => count( $warning_codes ), | |
| 173 | + 'warning_types' => array_values( array_unique( $warning_codes ) ), | |
| 174 | + ]; | |
| 175 | + | |
| 176 | + if ( null !== $document_type ) { | |
| 177 | + $payload['document_type'] = $document_type; | |
| 178 | + } | |
| 179 | + | |
| 180 | + if ( null !== $error_code ) { | |
| 181 | + $payload['error_code'] = $error_code; | |
| 182 | + } | |
| 183 | + | |
| 184 | + Mcp_Event_Dispatcher::emit( 'mcp_build_composition_executed', $payload ); | |
| 185 | + } | |
| 186 | + | |
| 187 | + private function collect_composition_counts( array $subtrees, int &$count, array &$by_type, int &$class_attachments, int &$interactions ): void { | |
| 188 | + $stack = $subtrees; | |
| 189 | + | |
| 190 | + while ( ! empty( $stack ) ) { | |
| 191 | + $node = array_pop( $stack ); | |
| 192 | + $type = $node['widgetType'] ?? $node['elType'] ?? ''; | |
| 193 | + | |
| 194 | + if ( '' !== $type ) { | |
| 195 | + ++$count; | |
| 196 | + $by_type[ $type ] = ( $by_type[ $type ] ?? 0 ) + 1; | |
| 197 | + } | |
| 198 | + | |
| 199 | + $classes = $node['settings']['classes']['value'] ?? []; | |
| 200 | + $class_attachments += count( array_filter( (array) $classes, fn( $c ) => is_string( $c ) && str_starts_with( $c, 'g-' ) ) ); | |
| 201 | + | |
| 202 | + if ( ! empty( $node['interactions']['items'] ) && is_array( $node['interactions']['items'] ) ) { | |
| 203 | + $interactions += count( $node['interactions']['items'] ); | |
| 204 | + } | |
| 205 | + | |
| 206 | + foreach ( $node['elements'] ?? [] as $child ) { | |
| 207 | + $stack[] = $child; | |
| 208 | + } | |
| 209 | + } | |
| 210 | + } | |
| 211 | + | |
| 212 | + private function resolve_document_type( Document $document ): string { | |
| 213 | + return $document->get_name(); | |
| 112 | 214 | } |
| 113 | 215 | |
| 114 | 216 | private function get_ability_description(): string { |
| 115 | 217 | return Prompt_Loader::load( 'build-composition' ); |