| 1 |
<?php |
| 2 |
|
| 3 |
namespace Elementor\Modules\AtomicWidgets\CssConverter\Expanders; |
| 4 |
|
| 5 |
use Elementor\Modules\AtomicWidgets\CssConverter\Css_Var_Token_Resolver; |
| 6 |
use Elementor\Modules\AtomicWidgets\CssConverter\Shorthand_Expander_Base; |
| 7 |
use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Css_Token_Splitter; |
| 8 |
use Elementor\Modules\AtomicWidgets\CssConverter\ValueParsers\Size_Value_Parser; |
| 9 |
use Elementor\Modules\Variables\Services\Variables_Service; |
| 10 |
|
| 11 |
if ( ! defined( 'ABSPATH' ) ) { |
| 12 |
exit; // Exit if accessed directly. |
| 13 |
} |
| 14 |
|
| 15 |
/** |
| 16 |
* Expands a `border` / `border-{side}` shorthand into its width / style / color longhands. There is no |
| 17 |
* aggregate Border prop type; these are independent longhands, so this is a split, not a merge. The |
| 18 |
* concrete longhand property names are injected, so the same logic serves the all-sides `border` |
| 19 |
* (border-width/style/color) and each per-side shorthand (e.g. border-top-width/style/color). |
| 20 |
* |
| 21 |
* Each token is classified once: a border-style keyword (enum from the live schema) -> style; a length |
| 22 |
* the Size parser accepts, or a width keyword (thin/medium/thick) -> width; anything else -> color (the |
| 23 |
* catch-all, mirroring the raw-passthrough color converter). Only the parts present are emitted (omitted |
| 24 |
* parts are not reset to CSS initials). A second token for an already filled role is ambiguous, so the |
| 25 |
* whole expansion declines and the original shorthand is kept for custom_css. A produced longhand can |
| 26 |
* still individually decline downstream (e.g. `border-{side}-style`, which has no converter), degrading |
| 27 |
* to custom_css for that part only. |
| 28 |
*/ |
| 29 |
class Border_Shorthand_Expander extends Shorthand_Expander_Base { |
| 30 |
const WIDTH_KEYWORDS = [ 'thin', 'medium', 'thick' ]; |
| 31 |
const ROLE_WIDTH = 'width'; |
| 32 |
const ROLE_STYLE = 'style'; |
| 33 |
const ROLE_COLOR = 'color'; |
| 34 |
|
| 35 |
private string $property; |
| 36 |
|
| 37 |
/** |
| 38 |
* @var array<string, string> Role (width|style|color) -> emitted longhand property name. |
| 39 |
*/ |
| 40 |
private array $longhands; |
| 41 |
|
| 42 |
/** |
| 43 |
* @var string[] |
| 44 |
*/ |
| 45 |
private array $style_keywords; |
| 46 |
|
| 47 |
private ?Variables_Service $variables_service; |
| 48 |
|
| 49 |
/** |
| 50 |
* @param string $property The shorthand this expander owns (border, border-top, ...). |
| 51 |
* @param array<string, string> $longhands Role -> longhand property name to emit. |
| 52 |
* @param string[] $style_keywords The border-style enum, sourced from the live schema. |
| 53 |
*/ |
| 54 |
public function __construct( |
| 55 |
string $property, |
| 56 |
array $longhands, |
| 57 |
array $style_keywords, |
| 58 |
?Variables_Service $variables_service = null |
| 59 |
) { |
| 60 |
$this->property = $property; |
| 61 |
$this->longhands = $longhands; |
| 62 |
$this->style_keywords = $style_keywords; |
| 63 |
$this->variables_service = $variables_service; |
| 64 |
} |
| 65 |
|
| 66 |
protected function get_supported_properties(): array { |
| 67 |
return [ $this->property ]; |
| 68 |
} |
| 69 |
|
| 70 |
protected function expand_null( array $rule ): array { |
| 71 |
return array_map( fn( $p ) => $this->null_rule( $p ), array_values( $this->longhands ) ); |
| 72 |
} |
| 73 |
|
| 74 |
protected function do_expand( array $rule ): array { |
| 75 |
$value = $rule['value']; |
| 76 |
|
| 77 |
$tokens = Css_Token_Splitter::split_by_whitespace( trim( $value ) ); |
| 78 |
|
| 79 |
if ( empty( $tokens ) ) { |
| 80 |
return []; |
| 81 |
} |
| 82 |
|
| 83 |
$slots = [ |
| 84 |
self::ROLE_WIDTH => null, |
| 85 |
self::ROLE_STYLE => null, |
| 86 |
self::ROLE_COLOR => null, |
| 87 |
]; |
| 88 |
|
| 89 |
foreach ( $tokens as $token ) { |
| 90 |
$role = $this->classify_token( $token ); |
| 91 |
|
| 92 |
if ( null === $role || null !== $slots[ $role ] ) { |
| 93 |
return []; |
| 94 |
} |
| 95 |
|
| 96 |
$slots[ $role ] = $token; |
| 97 |
} |
| 98 |
|
| 99 |
$rules = []; |
| 100 |
|
| 101 |
foreach ( $slots as $role => $slot_value ) { |
| 102 |
if ( null === $slot_value ) { |
| 103 |
continue; |
| 104 |
} |
| 105 |
|
| 106 |
$property = $this->longhands[ $role ]; |
| 107 |
|
| 108 |
$rules[] = [ |
| 109 |
'property' => $property, |
| 110 |
'value' => $slot_value, |
| 111 |
'declaration' => $property . ': ' . $slot_value, |
| 112 |
]; |
| 113 |
} |
| 114 |
|
| 115 |
return $rules; |
| 116 |
} |
| 117 |
|
| 118 |
private function classify_token( string $token ): ?string { |
| 119 |
if ( Css_Var_Token_Resolver::is_var_only_token( $token ) ) { |
| 120 |
$resolved_type = Css_Var_Token_Resolver::resolve_var_only_token_type( $this->variables_service, $token ); |
| 121 |
|
| 122 |
if ( 'color' === $resolved_type ) { |
| 123 |
return self::ROLE_COLOR; |
| 124 |
} |
| 125 |
|
| 126 |
if ( 'size' === $resolved_type ) { |
| 127 |
return self::ROLE_WIDTH; |
| 128 |
} |
| 129 |
|
| 130 |
return null; |
| 131 |
} |
| 132 |
|
| 133 |
return $this->classify_literal_token( $token ); |
| 134 |
} |
| 135 |
|
| 136 |
private function classify_literal_token( string $token ): string { |
| 137 |
$lower = strtolower( $token ); |
| 138 |
|
| 139 |
if ( in_array( $lower, $this->style_keywords, true ) ) { |
| 140 |
return self::ROLE_STYLE; |
| 141 |
} |
| 142 |
|
| 143 |
if ( in_array( $lower, self::WIDTH_KEYWORDS, true ) || null !== Size_Value_Parser::parse( $token ) ) { |
| 144 |
return self::ROLE_WIDTH; |
| 145 |
} |
| 146 |
|
| 147 |
return self::ROLE_COLOR; |
| 148 |
} |
| 149 |
} |
| 150 |
|