← All changes
|
modules/mcp/abilities/get-structure-ability.php
+41
-21
4.3.0-beta2
→
trunk
View file →
| @@ -11,8 +11,9 @@ | ||
| 11 | 11 | use Elementor\Modules\Mcp\Abilities\Appliers\V3_Node_Bridge; |
| 12 | 12 | use Elementor\Modules\Mcp\Abilities\Appliers\V3\V3_Non_Style_Allowlist; |
| 13 | 13 | use Elementor\Modules\Mcp\Abilities\Appliers\V3\V3_Style_Serializer; |
| 14 | 14 | use Elementor\Modules\Mcp\Abilities\Appliers\V3\V3_Widget_Bridge_Registry; |
| 15 | +use Elementor\Modules\Mcp\Abilities\Utils\Atomic_Container_Presentation; | |
| 15 | 16 | use Elementor\Modules\Mcp\Abilities\Utils\Element_Default_Styles_Builder; |
| 16 | 17 | use Elementor\Modules\Mcp\Abilities\Utils\Element_Tag_Resolver; |
| 17 | 18 | use Elementor\Modules\Mcp\Abilities\Utils\Widget_Context_Helper; |
| 18 | 19 | use Elementor\Plugin; |
| @@ -36,9 +37,9 @@ | ||
| 36 | 37 | |
| 37 | 38 | protected function get_definition(): Ability_Definition { |
| 38 | 39 | return new Ability_Definition( |
| 39 | 40 | __( 'Get Elementor Page Structure', 'elementor' ), |
| 40 | - __( 'Returns a lean Elementor element tree skeleton (id, elType, widgetType, version, title, nested elements) for a single post or page ID. Each node is tagged with version=3 (legacy) or version=4 (atomic). Only version=4 nodes can be modified via elementor/manage-elements or referenced by elementor/build-composition element_config; version=3 nodes are returned for context only and must be edited directly in the Elementor editor. Optionally scope to a subtree via element_id. Set include_content=true (requires element_id) to also return each V4 node\'s settings, styles (as { __style_id, css } where css is a raw CSS string round-trippable to manage-elements.update.style / build-composition.style in replace mode), interactions, its rendered HTML tag when known, and default_styles (a raw CSS string of the widget base layer followed by the kit\'s site-wide default layer for that tag, in browser cascade order — includes selectors, @media(--breakpoint) blocks, and pseudo-states as the frontend renders them). V3 nodes are returned with empty settings and styles. Only works for posts that were saved with Elementor.', 'elementor' ), | |
| 41 | + __( 'Returns a lean Elementor element tree skeleton (id, elType, widgetType, version, title, nested elements) for a single post or page ID. Each node is tagged with version=3 (legacy) or version=4 (atomic). Only version=4 nodes can be modified via elementor/manage-elements or referenced by elementor/build-composition element_config; version=3 nodes are returned for context only and must be edited directly in the Elementor editor. Optionally scope to a subtree via element_id. Set include_content=true (requires element_id) to also return each V4 node\'s settings in the plain shape that manage-elements.update.settings / build-composition.element_config accept, so a value can be sent back as-is, styles (as { __style_id, css } where css is a raw CSS string round-trippable to manage-elements.update.style / build-composition.style in replace mode), interactions, its rendered HTML tag when known, and default_styles (a raw CSS string of the widget base layer followed by the kit\'s site-wide default layer for that tag, in browser cascade order — includes selectors, @media(--breakpoint) blocks, and pseudo-states as the frontend renders them). V3 nodes are returned with empty settings and styles. Only works for posts that were saved with Elementor.', 'elementor' ), | |
| 41 | 42 | 'elementor', |
| 42 | 43 | [ |
| 43 | 44 | 'type' => 'object', |
| 44 | 45 | 'properties' => [ |
| @@ -43,9 +44,9 @@ | ||
| 43 | 44 | 'type' => 'object', |
| 44 | 45 | 'properties' => [ |
| 45 | 46 | 'elements' => [ |
| 46 | 47 | 'type' => 'array', |
| 47 | - 'description' => 'Skeleton of Elementor elements (id, elType, widgetType, version, title, nested elements). When include_content is true, V4 nodes also include settings, styles (as { __style_id, css } — raw CSS string with @media(--breakpoint) + &:hover/&:focus/&:active), interactions, tag (rendered HTML wrapper tag when known), and default_styles (raw CSS string: widget base layer + kit site-wide default for that tag, in cascade order). V3 nodes always have empty settings and styles.', | |
| 48 | + 'description' => 'Skeleton of Elementor elements (id, elType, widgetType, version, title, nested elements). When include_content is true, V4 nodes also include settings, styles (as { __style_id, css } — raw CSS string with @media(--breakpoint) + &:hover/&:focus/&:active), interactions, tag (rendered HTML wrapper tag when known), and default_styles (raw CSS string, in cascade order: `.e-con` shell styles of atomic elements, including document-root rules for top-level elements + widget base layer + kit site-wide default for that tag). V3 nodes always have empty settings and styles.', | |
| 48 | 49 | ], |
| 49 | 50 | ], |
| 50 | 51 | ], |
| 51 | 52 | [ |
| @@ -72,9 +73,9 @@ | ||
| 72 | 73 | ], |
| 73 | 74 | 'include_content' => [ |
| 74 | 75 | 'type' => 'boolean', |
| 75 | 76 | 'default' => false, |
| 76 | - 'description' => 'If true, includes each V4 node\'s settings, styles (as { __style_id, css } — raw CSS string), interactions, rendered tag, and default_styles (raw CSS string: base layer + kit default for that tag). The styles.css value is round-trippable to build-composition.style / manage-elements.update.style in replace mode. Requires element_id.', | |
| 77 | + 'description' => 'If true, includes each V4 node\'s settings (plain shape, writable as-is), styles (as { __style_id, css } — raw CSS string), interactions, rendered tag, and default_styles (raw CSS string: base layer + kit default for that tag). The styles.css value is round-trippable to build-composition.style / manage-elements.update.style in replace mode. Requires element_id.', | |
| 77 | 78 | ], |
| 78 | 79 | ], |
| 79 | 80 | ] |
| 80 | 81 | ); |
| @@ -104,8 +105,10 @@ | ||
| 104 | 105 | [ 'status' => \WP_Http::BAD_REQUEST ] |
| 105 | 106 | ); |
| 106 | 107 | } |
| 107 | 108 | |
| 109 | + $document_root_ids = array_column( $elements, 'id' ); | |
| 110 | + | |
| 108 | 111 | if ( '' !== $element_id ) { |
| 109 | 112 | $subtree = Utils::find_element_recursive( $elements, $element_id ); |
| 110 | 113 | |
| 111 | 114 | if ( ! $subtree ) { |
| @@ -119,14 +122,14 @@ | ||
| 119 | 122 | $elements = [ $subtree ]; |
| 120 | 123 | } |
| 121 | 124 | |
| 122 | 125 | return [ |
| 123 | - 'elements' => $this->prune_elements( $elements, $include_content ), | |
| 126 | + 'elements' => $this->prune_elements( $elements, $include_content, $document_root_ids ), | |
| 124 | 127 | ]; |
| 125 | 128 | } |
| 126 | 129 | |
| 127 | - private function prune_elements( array $elements, bool $include_content = false ): array { | |
| 128 | - return Plugin::$instance->db->iterate_data( $elements, function ( $node ) use ( $include_content ) { | |
| 130 | + private function prune_elements( array $elements, bool $include_content = false, array $document_root_ids = [] ): array { | |
| 131 | + return Plugin::$instance->db->iterate_data( $elements, function ( $node ) use ( $include_content, $document_root_ids ) { | |
| 129 | 132 | $skeleton = [ |
| 130 | 133 | 'id' => $node['id'] ?? null, |
| 131 | 134 | 'elType' => $node['elType'] ?? null, |
| 132 | 135 | ]; |
| @@ -145,9 +148,9 @@ | ||
| 145 | 148 | $skeleton['elements'] = $node['elements']; |
| 146 | 149 | } |
| 147 | 150 | |
| 148 | 151 | if ( $include_content ) { |
| 149 | - $this->populate_content( $skeleton, $node ); | |
| 152 | + $this->populate_content( $skeleton, $node, in_array( $node['id'] ?? null, $document_root_ids, true ) ); | |
| 150 | 153 | } |
| 151 | 154 | |
| 152 | 155 | return $skeleton; |
| 153 | 156 | } ); |
| @@ -152,9 +155,9 @@ | ||
| 152 | 155 | return $skeleton; |
| 153 | 156 | } ); |
| 154 | 157 | } |
| 155 | 158 | |
| 156 | - private function populate_content( array &$skeleton, array $node ): void { | |
| 159 | + private function populate_content( array &$skeleton, array $node, bool $is_document_root ): void { | |
| 157 | 160 | $skeleton['interactions'] = $this->normalize_interactions( $node['interactions'] ?? null ); |
| 158 | 161 | |
| 159 | 162 | if ( V3_Node_Bridge::is_v3_node( $node ) ) { |
| 160 | 163 | $this->populate_v3_content( $skeleton, $node ); |
| @@ -173,10 +176,10 @@ | ||
| 173 | 176 | |
| 174 | 177 | $skeleton['settings'] = $this->serialize_settings_for_llm( $props_schema, $raw_settings ); |
| 175 | 178 | $skeleton['styles'] = Local_Style_Serializer::serialize( $node['styles'] ?? [] ); |
| 176 | 179 | |
| 177 | - $resolved_settings = is_array( $skeleton['settings'] ) ? $skeleton['settings'] : []; | |
| 178 | - $this->populate_default_styles( $skeleton, $node, $config, $resolved_settings ); | |
| 180 | + $rendered_settings = $this->resolve_rendered_settings( $props_schema, $raw_settings ); | |
| 181 | + $this->populate_default_styles( $skeleton, $node, $config, $rendered_settings, $is_document_root ); | |
| 179 | 182 | } |
| 180 | 183 | |
| 181 | 184 | private function serialize_settings_for_llm( array $props_schema, $raw_settings ) { |
| 182 | 185 | if ( ! is_array( $raw_settings ) ) { |
| @@ -182,17 +185,36 @@ | ||
| 182 | 185 | if ( ! is_array( $raw_settings ) ) { |
| 183 | 186 | return (object) []; |
| 184 | 187 | } |
| 185 | 188 | |
| 186 | - $schema = array_intersect_key( $props_schema, $raw_settings ); | |
| 187 | - $serialized = []; | |
| 189 | + [ $serialized, $static_schema ] = $this->split_dynamic_settings( $props_schema, $raw_settings ); | |
| 190 | + | |
| 191 | + if ( ! empty( $static_schema ) ) { | |
| 192 | + $serialized += Render_Props_Resolver::for_plain()->resolve( $static_schema, $raw_settings ); | |
| 193 | + } | |
| 194 | + | |
| 195 | + return ! empty( $serialized ) ? $serialized : (object) []; | |
| 196 | + } | |
| 197 | + | |
| 198 | + private function resolve_rendered_settings( array $props_schema, $raw_settings ): array { | |
| 199 | + if ( ! is_array( $raw_settings ) ) { | |
| 200 | + return []; | |
| 201 | + } | |
| 202 | + | |
| 203 | + [ , $static_schema ] = $this->split_dynamic_settings( $props_schema, $raw_settings ); | |
| 204 | + | |
| 205 | + return empty( $static_schema ) ? [] : Render_Props_Resolver::for_settings()->resolve( $static_schema, $raw_settings ); | |
| 206 | + } | |
| 207 | + | |
| 208 | + private function split_dynamic_settings( array $props_schema, array $raw_settings ): array { | |
| 209 | + $dynamic_settings = []; | |
| 188 | 210 | $static_schema = []; |
| 189 | 211 | |
| 190 | - foreach ( $schema as $key => $prop_type ) { | |
| 212 | + foreach ( array_intersect_key( $props_schema, $raw_settings ) as $key => $prop_type ) { | |
| 191 | 213 | $dynamic = Dynamic_Tag_Llm_Resolver::try_serialize( $raw_settings[ $key ] ); |
| 192 | 214 | |
| 193 | 215 | if ( null !== $dynamic ) { |
| 194 | - $serialized[ $key ] = $dynamic; | |
| 216 | + $dynamic_settings[ $key ] = $dynamic; | |
| 195 | 217 | continue; |
| 196 | 218 | } |
| 197 | 219 | |
| 198 | 220 | $static_schema[ $key ] = $prop_type; |
| @@ -197,16 +219,12 @@ | ||
| 197 | 219 | |
| 198 | 220 | $static_schema[ $key ] = $prop_type; |
| 199 | 221 | } |
| 200 | 222 | |
| 201 | - if ( ! empty( $static_schema ) ) { | |
| 202 | - $serialized += Render_Props_Resolver::for_settings()->resolve( $static_schema, $raw_settings ); | |
| 203 | - } | |
| 204 | - | |
| 205 | - return ! empty( $serialized ) ? $serialized : (object) []; | |
| 223 | + return [ $dynamic_settings, $static_schema ]; | |
| 206 | 224 | } |
| 207 | 225 | |
| 208 | - private function populate_default_styles( array &$skeleton, array $node, array $config, array $resolved_settings ): void { | |
| 226 | + private function populate_default_styles( array &$skeleton, array $node, array $config, array $resolved_settings, bool $is_document_root ): void { | |
| 209 | 227 | $base_styles = is_array( $config['base_styles'] ?? null ) ? $config['base_styles'] : []; |
| 210 | 228 | $props_schema = is_array( $config['atomic_props_schema'] ?? null ) ? $config['atomic_props_schema'] : []; |
| 211 | 229 | $tag = $this->resolve_html_tag( $node, $resolved_settings ); |
| 212 | 230 | |
| @@ -212,9 +230,11 @@ | ||
| 212 | 230 | |
| 213 | 231 | $default_styles_css = Element_Default_Styles_Builder::render( |
| 214 | 232 | $base_styles, |
| 215 | 233 | $tag, |
| 216 | - $this->get_default_styles_repository() | |
| 234 | + $this->get_default_styles_repository(), | |
| 235 | + null, | |
| 236 | + Atomic_Container_Presentation::applies_to( $config ) ? Atomic_Container_Presentation::to_css_string( $is_document_root ) : '' | |
| 217 | 237 | ); |
| 218 | 238 | |
| 219 | 239 | if ( null !== $tag ) { |
| 220 | 240 | $skeleton['tag'] = $tag; |