ult['warnings'] ); $warning_codes = array_merge( $warning_codes, $config_result['warning_codes'] ?? [] ); } } if ( $has_classes ) { if ( ! is_array( $classes ) ) { return new \WP_Error( 'invalid_input', __( 'classes must be an array of global class labels.', 'elementor' ) ); } $class_applier = new Class_Applier( $this->create_global_classes_repository() ); $class_error = $class_applier->apply( $index, [ $element_id => $classes ] ); if ( $class_error ) { return $class_error; } $applied_classes = $classes; } if ( $has_style ) { $style_applier = new Style_Applier( $this->create_css_converter( $variables_service ), $this->get_active_breakpoints() ); $style_result = $style_applier->apply( $index, [ $element_id => $style ], $style_apply_mode, $widget_configs ); if ( $style_result['error'] ) { return $style_result['error']; } $warnings = array_merge( $warnings, $style_result['warnings'] ); $warning_codes = array_merge( $warning_codes, $style_result['warning_codes'] ?? [] ); $variable_connections = $style_result['variable_connections'][ $element_id ] ?? []; } return [ 'tree' => $tree, 'warnings' => $warnings, 'warning_codes' => $warning_codes, 'event_metadata' => [ 'element_id' => $element_id, 'element_type' => (string) $element_type, 'applied_classes' => $applied_classes, 'variable_connections' => $variable_connections, 'interactions_events' => $interactions_events, ], ]; } protected function build_interactions_events( string $element_type, array $previous_items, array $new_items ): array { if ( [] === $new_items ) { return [ [ 'event_name' => 'interactions_cleared', 'payload' => [ 'affected_element_type' => $element_type, 'target_value' => count( $previous_items ), ], ], ]; } $previous_by_id = []; foreach ( $previous_items as $item ) { if ( ! is_array( $item ) ) { continue; } $id = $this->extract_interaction_id( $item ); if ( null !== $id ) { $previous_by_id[ $id ] = true; } } $events = []; foreach ( $new_items as $item ) { if ( ! is_array( $item ) ) { continue; } $id = $item['interaction_id'] ?? ''; $is_update = is_string( $id ) && '' !== $id && isset( $previous_by_id[ $id ] ); $event_name = $is_update ? 'interaction_updated' : 'interaction_created'; $events[] = [ 'event_name' => $event_name, 'payload' => [ 'affected_element_type' => $element_type, 'interaction_trigger' => $this->stringify_interaction_field( $item['trigger'] ?? '' ), 'interaction_effect' => $this->stringify_interaction_field( $item['animation'] ?? '' ), ], ]; } return $events; } private function extract_interaction_id( array $item ): ?string { if ( isset( $item['interaction_id'] ) && is_string( $item['interaction_id'] ) && '' !== $item['interaction_id'] ) { return $item['interaction_id']; } $nested = $item['value']['interaction_id']['value'] ?? $item['interaction_id']['value'] ?? null; if ( is_string( $nested ) && '' !== $nested ) { return $nested; } return null; } private function stringify_interaction_field( $value ): string { if ( is_string( $value ) ) { return $value; } if ( is_array( $value ) ) { if ( isset( $value['effect'] ) && is_string( $value['effect'] ) ) { return $value['effect']; } $nested_effect = $value['value']['effect']['value'] ?? $value['effect']['value'] ?? null; if ( is_string( $nested_effect ) && '' !== $nested_effect ) { return $nested_effect; } if ( isset( $value['value'] ) && ( is_string( $value['value'] ) || is_numeric( $value['value'] ) ) ) { return (string) $value['value']; } if ( isset( $value['$$type'] ) && is_string( $value['$$type'] ) ) { return (string) $value['$$type']; } } return ''; } private function resolve_document( int $post_id ) { $document = Plugin::$instance->documents->get_doc_or_auto_save( $post_id, get_current_user_id() ) ?? Plugin::$instance->documents->get( $post_id ); if ( ! $document ) { return new \WP_Error( 'elementor_not_found', __( 'Post not found.', 'elementor' ), [ 'status' => \WP_Http::NOT_FOUND ] ); } return $document; } private function get_tree( Document $document ): array { $tree = $document->get_elements_data(); return is_array( $tree ) ? $tree : []; } private function to_public_error( \WP_Error $error ): \WP_Error { $data = $error->get_error_data(); $status = is_array( $data ) && isset( $data['status'] ) ? $data['status'] : \WP_Http::NOT_FOUND; if ( \WP_Http::NOT_FOUND === $status ) { return new \WP_Error( 'elementor_not_found', $error->get_error_message(), [ 'status' => $status ] ); } return $error; } private function bad_request( string $message ): \WP_Error { return new \WP_Error( 'invalid_input', $message, [ 'status' => \WP_Http::BAD_REQUEST ] ); } private function as_map( $value ): array { if ( is_object( $value ) ) { $value = (array) $value; } return is_array( $value ) ? $value : []; } private function get_mutator(): Document_Mutator { return $this->mutator ?? Document_Mutator::instance(); } private function create_global_classes_repository(): Global_Classes_Repository { $kit = Plugin::$instance->kits_manager->get_active_kit(); return Global_Classes_Repository::make( $kit ); } private function create_css_converter( ?Variables_Service $variables_service ): Css_Converter { $variable_transformer = $variables_service ? new Variable_Prop_Value_Transformer( $variables_service ) : null; return new Css_Converter( Converter_Registry_Factory::create( $variables_service ), new Null_Failure_Reporter(), Expander_Registry_Factory::create( $variables_service ), $variable_transformer ); } private function create_variables_service(): ?Variables_Service { if ( ! $this->is_variables_active() ) { return null; } $kit = Plugin::$instance->kits_manager->get_active_kit(); if ( ! $kit ) { return null; } return new Variables_Service( new Variables_Repository( $kit ), new Batch_Processor() ); } private function get_plain_values_resolver(): Plain_Values_Resolver { return AtomicWidgetsModule::instance()->get_settings_plain_values_resolver(); } private function is_variables_active(): bool { $experiments = Plugin::$instance->experiments; return $experiments->is_feature_active( Variables_Module::EXPERIMENT_NAME ) && $experiments->is_feature_active( AtomicWidgetsModule::EXPERIMENT_NAME ); } private function get_active_breakpoints(): array { return array_keys( Plugin::$instance->breakpoints->get_active_breakpoints() ); } private function emit_update_events( array $pending_events, array $tree ): void { if ( empty( $pending_events ) ) { return; } $all_labels = $this->create_global_classes_repository()->all_labels(); $class_counts = $this->count_class_usage_in_tree( $tree ); foreach ( $pending_events as $meta ) { $action = $meta['action'] ?? ''; if ( 'duplicate' === $action ) { foreach ( $meta['duplicated_element_types'] ?? [] as $element_name ) { Mcp_Event_Dispatcher::emit( 'element_added', [ 'element_name' => $element_name, ] ); } continue; } $element_type = $meta['element_type'] ?? ''; foreach ( $meta['applied_classes'] ?? [] as $label ) { $class_id = array_search( $label, $all_labels, true ); if ( false === $class_id ) { continue; } Mcp_Event_Dispatcher::emit( 'class_applied', [ 'target_name' => 'apply_class', 'id' => (string) $class_id, 'name' => $label, 'affected_element_type' => $element_type, 'total_instances_after_apply' => $class_counts[ $class_id ] ?? 0, ] ); } foreach ( $meta['variable_connections'] as $connection ) { Mcp_Event_Dispatcher::emit( 'variable_connected', [ 'id' => $connection['variable_id'], 'var_type' => $connection['var_type'], 'control_path' => $connection['control_path'], ] ); } foreach ( $meta['interactions_events'] ?? [] as $ie ) { Mcp_Event_Dispatcher::emit( $ie['event_name'], $ie['payload'] ); } } } private function count_class_usage_in_tree( array $tree ): array { $counts = []; $this->walk_tree_for_class_counts( $tree, $counts ); return $counts; } private function walk_tree_for_class_counts( array $elements, array &$counts ): void { foreach ( $elements as $element ) { $class_values = $element['settings']['classes']['value'] ?? []; if ( is_array( $class_values ) ) { foreach ( $class_values as $class_id ) { if ( is_string( $class_id ) && ! str_starts_with( $class_id, Style_Applier::LOCAL_STYLE_ID_PREFIX ) ) { $counts[ $class_id ] = ( $counts[ $class_id ] ?? 0 ) + 1; } } } if ( ! empty( $element['elements'] ) ) { $this->walk_tree_for_class_counts( $element['elements'], $counts ); } } } private function emit_mcp_manage_elements_executed( int $started_at, int $post_id, ?Document $document = null, ?\WP_Error $top_level_error = null, array $operations = [], array $response = [], array $warning_codes = [], int $class_attachments = 0, int $interactions_count = 0 ): void { $duration_ms = Tool_Performance_Metrics::duration_ms_since( $started_at ); [ 'status' => $status, 'error_code' => $error_code ] = Tool_Performance_Metrics::resolve_status( $response, $top_level_error ); $failed_results = array_filter( $response['results'] ?? [], fn( $r ) => 'error' === ( $r['status'] ?? '' ) ); $failed_count = count( $failed_results ); $failed_codes = array_values( array_unique( array_column( $failed_results, 'code' ) ) ); $ops_count = count( $operations ); $by_action = []; foreach ( $operations as $op ) { $action = $op['action'] ?? ''; if ( is_string( $action ) && '' !== $action ) { $by_action[ $action ] = ( $by_action[ $action ] ?? 0 ) + 1; } } $dominant_action = ''; if ( ! empty( $by_action ) ) { arsort( $by_action ); $dominant_action = (string) array_key_first( $by_action ); } $payload = [ 'tool_name' => $this->get_ability_id(), 'status' => $status, 'duration_ms' => $duration_ms, 'post_id' => $post_id, 'action' => $dominant_action, 'operations_count' => $ops_count, 'operations_by_type' => $by_action, 'failed_operations_count' => $failed_count, 'failed_operation_codes' => $failed_codes, 'class_attachments_count' => $class_attachments, 'interactions_applied_count' => $interactions_count, 'warning_count' => count( array_unique( $warning_codes ) ), 'warning_types' => array_values( array_unique( $warning_codes ) ), ]; if ( null !== $document ) { $payload['document_type'] = $document->get_name(); } if ( null !== $error_code ) { $payload['error_code'] = $error_code; } Mcp_Event_Dispatcher::emit( 'mcp_manage_elements_executed', $payload ); } }