← All changes
|
modules/mcp/abilities/appliers/style-applier.php
+124
-18
4.3.0-beta2
→
trunk
View file →
| @@ -2,12 +2,14 @@ | ||
| 2 | 2 | |
| 3 | 3 | namespace Elementor\Modules\Mcp\Abilities\Appliers; |
| 4 | 4 | |
| 5 | 5 | use Elementor\Modules\AtomicWidgets\CssConverter\Css_Converter; |
| 6 | +use Elementor\Modules\AtomicWidgets\Styles\Atomic_Widget_Styles; | |
| 6 | 7 | use Elementor\Modules\Mcp\Abilities\Appliers\V3\V3_Style_Mapper_Factory; |
| 7 | 8 | use Elementor\Modules\Mcp\Abilities\Utils\Bulk_Operations_Result; |
| 8 | 9 | use Elementor\Modules\Mcp\Abilities\Utils\Style_Variants_Merger; |
| 9 | 10 | use Elementor\Modules\Mcp\Abilities\Utils\Widget_Context_Helper; |
| 11 | +use Elementor\Modules\Variables\Utils\Variable_Type_Keys; | |
| 10 | 12 | use Elementor\Plugin; |
| 11 | 13 | |
| 12 | 14 | if ( ! defined( 'ABSPATH' ) ) { |
| 13 | 15 | exit; |
| @@ -19,8 +21,10 @@ | ||
| 19 | 21 | const DESKTOP_BREAKPOINT = 'desktop'; |
| 20 | 22 | const LOCAL_STYLE_LABEL = 'local'; |
| 21 | 23 | const LOCAL_STYLE_TYPE = 'class'; |
| 22 | 24 | |
| 25 | + const UNSUPPORTED_CSS_CODE = 'unsupported_css'; | |
| 26 | + | |
| 23 | 27 | private Css_Converter $css_converter; |
| 24 | 28 | private array $active_breakpoints; |
| 25 | 29 | |
| 26 | 30 | public function __construct( Css_Converter $css_converter, array $active_breakpoints = [] ) { |
| @@ -32,21 +36,25 @@ | ||
| 32 | 36 | * @param array<string, array&> $config_id_index Index of subtree refs. |
| 33 | 37 | * @param array<string, string> $styles Per-config-id CSS strings. |
| 34 | 38 | * @param string $style_apply_mode `patch` or `replace`. |
| 35 | 39 | * @param array<string, array> $widget_configs Optional widget_type => config map (used for V3 mapping). |
| 36 | - * @return array{error: \WP_Error|null, warnings: string[]} | |
| 40 | + * @return array{error: \WP_Error|null, warnings: string[], variable_connections: array<string, array<string, string>>} | |
| 37 | 41 | */ |
| 38 | 42 | public function apply( array $config_id_index, array $styles, string $style_apply_mode = 'patch', array $widget_configs = [] ): array { |
| 39 | 43 | if ( empty( $styles ) ) { |
| 40 | 44 | return [ |
| 41 | - 'error' => null, | |
| 42 | - 'warnings' => [], | |
| 45 | + 'error' => null, | |
| 46 | + 'warnings' => [], | |
| 47 | + 'warning_codes' => [], | |
| 48 | + 'variable_connections' => [], | |
| 43 | 49 | ]; |
| 44 | 50 | } |
| 45 | 51 | |
| 46 | - $active_breakpoints = $this->get_active_breakpoints(); | |
| 47 | - $errors = []; | |
| 48 | - $warnings = []; | |
| 52 | + $active_breakpoints = $this->get_active_breakpoints(); | |
| 53 | + $errors = []; | |
| 54 | + $warnings = []; | |
| 55 | + $warning_codes = []; | |
| 56 | + $variable_connections = []; | |
| 49 | 57 | |
| 50 | 58 | foreach ( $styles as $config_id => $css_string ) { |
| 51 | 59 | if ( ! is_string( $css_string ) ) { |
| 52 | 60 | $errors[] = sprintf( '[%s] style must be a CSS string, got %s.', $config_id, gettype( $css_string ) ); |
| @@ -59,12 +67,13 @@ | ||
| 59 | 67 | |
| 60 | 68 | $node = &$config_id_index[ $config_id ]; |
| 61 | 69 | |
| 62 | 70 | if ( V3_Node_Bridge::is_v3_node( $node ) ) { |
| 63 | - $v3_warnings = $this->apply_v3_style( $node, $css_string, $style_apply_mode, $widget_configs ); | |
| 64 | - foreach ( $v3_warnings as $warning ) { | |
| 71 | + $v3_result = $this->apply_v3_style( $node, $css_string, $style_apply_mode, $widget_configs ); | |
| 72 | + foreach ( $v3_result['warnings'] as $warning ) { | |
| 65 | 73 | $warnings[] = sprintf( '[%s] %s', $config_id, $warning ); |
| 66 | 74 | } |
| 75 | + $warning_codes = array_merge( $warning_codes, $v3_result['codes'] ); | |
| 67 | 76 | unset( $node ); |
| 68 | 77 | continue; |
| 69 | 78 | } |
| 70 | 79 | |
| @@ -97,9 +106,25 @@ | ||
| 97 | 106 | unset( $node ); |
| 98 | 107 | continue; |
| 99 | 108 | } |
| 100 | 109 | |
| 101 | - $new_variants = Style_Variants_Merger::build_variants( $parsed['breakpoint_blocks'], $this->css_converter ); | |
| 110 | + $built_variants = Style_Variants_Merger::build_variants_with_fallback_css( $parsed['breakpoint_blocks'], $this->css_converter ); | |
| 111 | + $new_variants = $built_variants['variants']; | |
| 112 | + $unsupported_css = $this->collect_unsupported_css( $new_variants, $built_variants['fallback_css'] ); | |
| 113 | + | |
| 114 | + if ( '' !== $unsupported_css ) { | |
| 115 | + $warnings[] = sprintf( | |
| 116 | + '[%s] %s', | |
| 117 | + $config_id, | |
| 118 | + sprintf( | |
| 119 | + /* translators: %s: CSS declarations that were not saved */ | |
| 120 | + __( 'These CSS properties or values are not supported and were not saved: %s', 'elementor' ), | |
| 121 | + self::truncate_css_snippet( $unsupported_css ) | |
| 122 | + ) | |
| 123 | + ); | |
| 124 | + $warning_codes[] = self::UNSUPPORTED_CSS_CODE; | |
| 125 | + } | |
| 126 | + | |
| 102 | 127 | $affected_bps = array_column( $parsed['breakpoint_blocks'], 'breakpoint' ); |
| 103 | 128 | $removal_bps = $parsed['removal_breakpoints']; |
| 104 | 129 | $existing_style_id = $this->find_existing_local_style_id( $node ); |
| 105 | 130 | $existing_variants = $node['styles'][ $existing_style_id ]['variants'] ?? []; |
| @@ -113,19 +138,23 @@ | ||
| 113 | 138 | $style_apply_mode, |
| 114 | 139 | $affected_bps |
| 115 | 140 | ); |
| 116 | 141 | |
| 142 | + $variable_connections[ $config_id ] = $this->collect_variable_connections( $new_variants ); | |
| 143 | + | |
| 117 | 144 | $this->write_variants_to_node( $node, $merged_variants, $existing_style_id ); |
| 118 | 145 | unset( $node ); |
| 119 | 146 | } |
| 120 | 147 | |
| 121 | 148 | return [ |
| 122 | - 'error' => $errors ? new \WP_Error( | |
| 149 | + 'error' => $errors ? new \WP_Error( | |
| 123 | 150 | 'elementor_invalid_styles', |
| 124 | 151 | implode( ' ', $errors ), |
| 125 | 152 | [ 'status' => \WP_Http::BAD_REQUEST ] |
| 126 | 153 | ) : null, |
| 127 | - 'warnings' => $warnings, | |
| 154 | + 'warnings' => $warnings, | |
| 155 | + 'warning_codes' => array_values( array_unique( $warning_codes ) ), | |
| 156 | + 'variable_connections' => $variable_connections, | |
| 128 | 157 | ]; |
| 129 | 158 | } |
| 130 | 159 | |
| 131 | 160 | /** |
| @@ -132,12 +161,13 @@ | ||
| 132 | 161 | * @param array $node |
| 133 | 162 | * @param string $css_string |
| 134 | 163 | * @param string $style_apply_mode |
| 135 | 164 | * @param array<string, array> $widget_configs |
| 136 | - * @return string[] Warnings (without config-id prefix). | |
| 165 | + * @return array{warnings: string[], codes: string[]} Warnings (without config-id prefix) and stable codes. | |
| 137 | 166 | */ |
| 138 | 167 | private function apply_v3_style( array &$node, string $css_string, string $style_apply_mode = 'patch', array $widget_configs = [] ): array { |
| 139 | 168 | $warnings = []; |
| 169 | + $codes = []; | |
| 140 | 170 | $widget_type = $node['widgetType'] ?? ''; |
| 141 | 171 | $widget_config = []; |
| 142 | 172 | |
| 143 | 173 | if ( is_string( $widget_type ) && '' !== $widget_type ) { |
| @@ -153,9 +183,12 @@ | ||
| 153 | 183 | V3_Node_Bridge::clear_style_settings( $node, (string) $widget_type, $widget_config ); |
| 154 | 184 | } |
| 155 | 185 | |
| 156 | 186 | if ( $is_empty_css ) { |
| 157 | - return $warnings; | |
| 187 | + return [ | |
| 188 | + 'warnings' => $warnings, | |
| 189 | + 'codes' => $codes, | |
| 190 | + ]; | |
| 158 | 191 | } |
| 159 | 192 | |
| 160 | 193 | $mapper = V3_Style_Mapper_Factory::create( $this->css_converter, $this->get_active_breakpoints() ); |
| 161 | 194 | $result = $mapper->apply( $css_string, (string) $widget_type, $widget_config ); |
| @@ -171,26 +204,34 @@ | ||
| 171 | 204 | $unmapped = $result['unmapped_css'] ?? ''; |
| 172 | 205 | $pro_warning = V3_Node_Bridge::apply_custom_css( $node, $unmapped, (string) $widget_type ); |
| 173 | 206 | if ( null !== $pro_warning ) { |
| 174 | 207 | $warnings[] = $pro_warning; |
| 208 | + $codes[] = 'v3_style_needs_pro'; | |
| 175 | 209 | } |
| 176 | 210 | |
| 177 | 211 | if ( '' !== trim( $unmapped ) ) { |
| 178 | 212 | $snippet = self::truncate_css_snippet( $unmapped ); |
| 179 | - $warnings[] = null !== $pro_warning | |
| 180 | - ? sprintf( | |
| 213 | + if ( null !== $pro_warning ) { | |
| 214 | + $warnings[] = sprintf( | |
| 181 | 215 | /* translators: %s: CSS snippet that could not be mapped */ |
| 182 | 216 | __( 'Some CSS could not be mapped to V3 settings and was dropped: %s', 'elementor' ), |
| 183 | 217 | $snippet |
| 184 | - ) | |
| 185 | - : sprintf( | |
| 218 | + ); | |
| 219 | + $codes[] = 'css_dropped'; | |
| 220 | + } else { | |
| 221 | + $warnings[] = sprintf( | |
| 186 | 222 | /* translators: %s: CSS snippet that could not be mapped */ |
| 187 | 223 | __( 'Some CSS could not be mapped to V3 settings and was written to custom_css: %s', 'elementor' ), |
| 188 | 224 | $snippet |
| 189 | 225 | ); |
| 226 | + $codes[] = 'css_fallback_custom_css'; | |
| 227 | + } | |
| 190 | 228 | } |
| 191 | 229 | |
| 192 | - return $warnings; | |
| 230 | + return [ | |
| 231 | + 'warnings' => $warnings, | |
| 232 | + 'codes' => $codes, | |
| 233 | + ]; | |
| 193 | 234 | } |
| 194 | 235 | |
| 195 | 236 | private static function truncate_css_snippet( string $css, int $max_length = 200 ): string { |
| 196 | 237 | $css = trim( preg_replace( '/\s+/', ' ', $css ) ?? $css ); |
| @@ -200,8 +241,32 @@ | ||
| 200 | 241 | |
| 201 | 242 | return substr( $css, 0, $max_length - 3 ) . '...'; |
| 202 | 243 | } |
| 203 | 244 | |
| 245 | + /** | |
| 246 | + * @param array[] $variants | |
| 247 | + * @param array<int, string> $fallback_css Per variant index, CSS the converter could not convert natively. | |
| 248 | + */ | |
| 249 | + private function collect_unsupported_css( array $variants, array $fallback_css ): string { | |
| 250 | + if ( empty( $fallback_css ) ) { | |
| 251 | + return ''; | |
| 252 | + } | |
| 253 | + | |
| 254 | + $kept_variants = Atomic_Widget_Styles::get_license_based_filtered_styles( | |
| 255 | + [ self::LOCAL_STYLE_LABEL => [ 'variants' => array_intersect_key( $variants, $fallback_css ) ] ] | |
| 256 | + )[ self::LOCAL_STYLE_LABEL ]['variants']; | |
| 257 | + | |
| 258 | + $dropped_css = []; | |
| 259 | + | |
| 260 | + foreach ( $fallback_css as $index => $css ) { | |
| 261 | + if ( empty( $kept_variants[ $index ]['custom_css'] ) ) { | |
| 262 | + $dropped_css[] = $css; | |
| 263 | + } | |
| 264 | + } | |
| 265 | + | |
| 266 | + return implode( ' ', $dropped_css ); | |
| 267 | + } | |
| 268 | + | |
| 204 | 269 | private function get_active_breakpoints(): array { |
| 205 | 270 | if ( ! empty( $this->active_breakpoints ) ) { |
| 206 | 271 | return $this->active_breakpoints; |
| 207 | 272 | } |
| @@ -256,6 +321,47 @@ | ||
| 256 | 321 | } |
| 257 | 322 | |
| 258 | 323 | private function generate_local_style_id(): string { |
| 259 | 324 | return self::LOCAL_STYLE_ID_PREFIX . strtolower( \Elementor\Utils::generate_random_string() ) . '-' . dechex( wp_rand( 0x1000, 0xffff ) ); |
| 325 | + } | |
| 326 | + | |
| 327 | + /** | |
| 328 | + * @param array[] $variants | |
| 329 | + * @return array<int, array{variable_id: string, var_type: string, control_path: string}> | |
| 330 | + */ | |
| 331 | + private function collect_variable_connections( array $variants ): array { | |
| 332 | + $connections = []; | |
| 333 | + | |
| 334 | + foreach ( $variants as $variant ) { | |
| 335 | + foreach ( $variant['props'] ?? [] as $prop_key => $prop_value ) { | |
| 336 | + if ( ! is_array( $prop_value ) ) { | |
| 337 | + continue; | |
| 338 | + } | |
| 339 | + | |
| 340 | + $type = $prop_value['$$type'] ?? ''; | |
| 341 | + | |
| 342 | + if ( ! Variable_Type_Keys::is_variable_type( $type ) ) { | |
| 343 | + continue; | |
| 344 | + } | |
| 345 | + | |
| 346 | + $connections[] = [ | |
| 347 | + 'variable_id' => (string) ( $prop_value['value'] ?? '' ), | |
| 348 | + 'var_type' => $this->resolve_var_type_label( $type ), | |
| 349 | + 'control_path' => (string) $prop_key, | |
| 350 | + ]; | |
| 351 | + } | |
| 352 | + } | |
| 353 | + | |
| 354 | + return $connections; | |
| 355 | + } | |
| 356 | + | |
| 357 | + private function resolve_var_type_label( string $type ): string { | |
| 358 | + $labels = [ | |
| 359 | + 'global-color-variable' => 'color', | |
| 360 | + 'global-font-variable' => 'font', | |
| 361 | + 'global-size-variable' => 'size', | |
| 362 | + 'global-custom-size-variable' => 'size', | |
| 363 | + ]; | |
| 364 | + | |
| 365 | + return $labels[ $type ] ?? $type; | |
| 260 | 366 | } |
| 261 | 367 | } |