PluginProbe
Elementor Website Builder – more than just a page builder / 4.3.0
Elementor Website Builder – more than just a page builder v4.3.0
4.3.2 4.3.1 4.3.0 4.3.0-beta3 4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 4.1.0-beta2 4.1.0-dev2 All 455 releases
elementor / modules / mcp / abilities / appliers / v3 / v3-style-mapper.php

v3-style-mapper.php in Elementor Website Builder – more than just a page builder 4.3.0, at modules/mcp/abilities/appliers/v3/v3-style-mapper.php

200 lines 6.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Elementor\Modules\Mcp\Abilities\Appliers\V3;
4
5 use Elementor\Modules\AtomicWidgets\CssConverter\Css_Converter;
6 use Elementor\Modules\AtomicWidgets\CssConverter\Css_Media_Splitter;
7 use Elementor\Modules\Mcp\Abilities\Appliers\V3\Converter\V3_Context_Meta;
8 use Elementor\Modules\Mcp\Abilities\Appliers\V3\Converter\V3_Conversion_Context;
9 use Elementor\Modules\Mcp\Abilities\Appliers\V3\Converter\V3_Converter_Registry;
10 use Elementor\Modules\Mcp\Abilities\Appliers\V3\Mapper\Css_Declaration_Parser;
11 use Elementor\Modules\Mcp\Abilities\Appliers\V3\Mapper\Responsive_Key_Resolver;
12 use Elementor\Modules\Mcp\Abilities\Appliers\V3\Mapper\Unmapped_Css_Serializer;
13
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit;
16 }
17
18 /**
19 * Maps LLM CSS strings onto legacy V3 style settings; unmapped rules become custom_css.
20 *
21 * Breakpoints:
22 * The input CSS is split by `Css_Media_Splitter` using the site's active breakpoint names.
23 * Only `desktop` writes to bare setting keys. Non-desktop breakpoints look up
24 * `<setting>_<breakpoint>` on the widget config; if the responsive variant does not exist
25 * and the base setting does, the rule is dropped (to avoid overwriting desktop with mobile).
26 *
27 * Overrides (per-widget CSS -> V3 setting map, see {@see V3_Widget_Bridge_Registry}):
28 * Four mutually-exclusive shapes are dispatched by the {@see V3_Converter_Registry},
29 * one converter class per shape. A fallback `Generic_Index_Converter` uses
30 * {@see V3_Style_Settings_Index} for auto-discovered mappings.
31 */
32 class V3_Style_Mapper {
33
34 private Css_Converter $css_converter;
35 private array $active_breakpoints;
36 private V3_Converter_Registry $converter_registry;
37 private Css_Declaration_Parser $declaration_parser;
38 private Unmapped_Css_Serializer $unmapped_serializer;
39 private Responsive_Key_Resolver $responsive_resolver;
40
41 public function __construct(
42 Css_Converter $css_converter,
43 array $active_breakpoints,
44 V3_Converter_Registry $converter_registry,
45 Css_Declaration_Parser $declaration_parser,
46 Unmapped_Css_Serializer $unmapped_serializer,
47 Responsive_Key_Resolver $responsive_resolver
48 ) {
49 $this->css_converter = $css_converter;
50 $this->active_breakpoints = $active_breakpoints;
51 $this->converter_registry = $converter_registry;
52 $this->declaration_parser = $declaration_parser;
53 $this->unmapped_serializer = $unmapped_serializer;
54 $this->responsive_resolver = $responsive_resolver;
55 }
56
57 /**
58 * @param string $css_string
59 * @param string $widget_type
60 * @param array $widget_config From Widget_Context_Helper::get_widget_config().
61 * @return array{settings_patch: array<string, mixed>, unmapped_css: string, warnings: string[]}
62 */
63 public function apply( string $css_string, string $widget_type, array $widget_config ): array {
64 $css_string = trim( $css_string );
65
66 if ( '' === $css_string ) {
67 return $this->empty_result();
68 }
69
70 $meta = $this->build_meta( $widget_type, $widget_config );
71 $ctx = new V3_Conversion_Context();
72
73 $split = ( new Css_Media_Splitter( $this->get_active_breakpoints() ) )->split( $css_string );
74 if ( null !== $split['error'] ) {
75 $ctx->warn( $split['error'] );
76 $ctx->mark_unmapped( $css_string );
77
78 return $this->finalize( $ctx, $meta );
79 }
80
81 foreach ( $split['breakpoints'] as $breakpoint => $css ) {
82 if ( '' === trim( $css ) ) {
83 continue;
84 }
85
86 $this->process_breakpoint( $ctx, $meta, (string) $breakpoint, (string) $css );
87 }
88
89 if ( ! empty( $split['custom_css'] ) ) {
90 $ctx->mark_unmapped( (string) $split['custom_css'] );
91 }
92
93 return $this->finalize( $ctx, $meta );
94 }
95
96 private function process_breakpoint( V3_Conversion_Context $ctx, V3_Context_Meta $meta, string $breakpoint, string $css ): void {
97 $parsed = $this->css_converter->parse_nested( $css );
98
99 if ( isset( $parsed['error'] ) ) {
100 $ctx->mark_unmapped( $this->unmapped_serializer->serialize_breakpoint_block( $breakpoint, $css ) );
101 $ctx->warn( (string) $parsed['error'] );
102
103 return;
104 }
105
106 foreach ( $parsed['blocks'] as $block ) {
107 $this->process_block( $ctx, $meta, $breakpoint, $block );
108 }
109 }
110
111 private function process_block( V3_Conversion_Context $ctx, V3_Context_Meta $meta, string $breakpoint, array $block ): void {
112 $selector = $block['selector'] ?? null;
113 $block_css = (string) ( $block['css'] ?? '' );
114 $state = $this->declaration_parser->normalize_state( $selector );
115
116 if ( null !== $selector && null === $state ) {
117 $ctx->mark_unmapped( $this->unmapped_serializer->serialize_nested_block( $breakpoint, (string) $selector, $block_css ) );
118
119 return;
120 }
121
122 foreach ( $this->declaration_parser->parse_declarations( $block_css ) as $declaration ) {
123 $rule = [
124 'property' => $declaration['property'],
125 'value' => $declaration['value'],
126 'state' => $state,
127 'breakpoint' => $breakpoint,
128 ];
129
130 if ( ! $this->dispatch_rule( $ctx, $meta, $rule ) ) {
131 $ctx->mark_unmapped( $this->unmapped_serializer->serialize_declaration(
132 $breakpoint,
133 $state,
134 $rule['property'],
135 $rule['value']
136 ) );
137 }
138 }
139 }
140
141 private function dispatch_rule( V3_Conversion_Context $ctx, V3_Context_Meta $meta, array $rule ): bool {
142 foreach ( $this->converter_registry->all() as $converter ) {
143 if ( ! $converter->is_supported( $rule, $meta ) ) {
144 continue;
145 }
146
147 return $converter->convert( $ctx, $rule, $meta );
148 }
149
150 return false;
151 }
152
153 private function build_meta( string $widget_type, array $widget_config ): V3_Context_Meta {
154 $overrides = V3_Widget_Bridge_Registry::get_style_overrides( $widget_type );
155 $controls = $widget_config['controls'] ?? [];
156 $generic_index = V3_Style_Settings_Index::build( is_array( $controls ) ? $controls : [], $overrides );
157
158 return new V3_Context_Meta( $widget_type, $widget_config, $overrides, $generic_index );
159 }
160
161 private function finalize( V3_Conversion_Context $ctx, V3_Context_Meta $meta ): array {
162 $settings_patch = $ctx->settings_patch();
163
164 foreach ( $ctx->typography_buckets() as $bucket ) {
165 $group_patch = V3_Value_Resolvers::resolve_typography_group(
166 $bucket['declarations'],
167 $bucket['prefix']
168 );
169
170 if ( ! empty( $bucket['responsive'] ) && Responsive_Key_Resolver::BASE_BREAKPOINT !== $bucket['breakpoint'] ) {
171 $group_patch = $this->responsive_resolver->suffix_patch( $group_patch, $bucket['breakpoint'], $meta );
172 }
173
174 $settings_patch = array_merge( $settings_patch, $group_patch );
175 }
176
177 return [
178 'settings_patch' => $settings_patch,
179 'unmapped_css' => $this->unmapped_serializer->join( $ctx->unmapped_parts() ),
180 'warnings' => $ctx->warnings(),
181 ];
182 }
183
184 private function empty_result(): array {
185 return [
186 'settings_patch' => [],
187 'unmapped_css' => '',
188 'warnings' => [],
189 ];
190 }
191
192 private function get_active_breakpoints(): array {
193 if ( ! empty( $this->active_breakpoints ) ) {
194 return $this->active_breakpoints;
195 }
196
197 return [ Responsive_Key_Resolver::BASE_BREAKPOINT ];
198 }
199 }
200