| @@ -5,21 +5,19 @@ | ||
| 5 | 5 | if ( ! defined( 'ABSPATH' ) ) { |
| 6 | 6 | exit; // Exit if accessed directly. |
| 7 | 7 | } |
| 8 | 8 | |
| 9 | -use Elementor\Modules\AtomicWidgets\OptIn\Opt_In; | |
| 10 | -use Elementor\Plugin; | |
| 11 | -use Elementor\Utils; | |
| 12 | -use Elementor\Core\Utils\Api\Parse_Result; | |
| 13 | -use Elementor\Modules\AtomicWidgets\Styles\Style_States; | |
| 14 | - | |
| 15 | 9 | class Style_Parser { |
| 16 | - const VALID_TYPES = [ | |
| 17 | - 'class', | |
| 10 | + const VALID_STATES = [ | |
| 11 | + 'hover', | |
| 12 | + 'active', | |
| 13 | + 'focus', | |
| 14 | + null, | |
| 18 | 15 | ]; |
| 19 | 16 | |
| 20 | - | |
| 21 | 17 | private array $schema; |
| 18 | + private array $errors_bag = []; | |
| 19 | + private $should_validate_id = true; | |
| 22 | 20 | |
| 23 | 21 | public function __construct( array $schema ) { |
| 24 | 22 | $this->schema = $schema; |
| 25 | 23 | } |
| @@ -27,40 +25,40 @@ | ||
| 27 | 25 | public static function make( array $schema ): self { |
| 28 | 26 | return new static( $schema ); |
| 29 | 27 | } |
| 30 | 28 | |
| 29 | + public function without_id() { | |
| 30 | + $this->should_validate_id = false; | |
| 31 | + | |
| 32 | + return $this; | |
| 33 | + } | |
| 34 | + | |
| 31 | 35 | /** |
| 32 | 36 | * @param array $style |
| 33 | 37 | * the style object to validate |
| 38 | + * | |
| 39 | + * @return array{ | |
| 40 | + * 0: bool, | |
| 41 | + * 1: array<string, mixed>, | |
| 42 | + * 2: array<string> | |
| 43 | + * } | |
| 34 | 44 | */ |
| 35 | - private function validate( array $style ): Parse_Result { | |
| 45 | + public function validate( array $style ): array { | |
| 36 | 46 | $validated_style = $style; |
| 37 | - $result = Parse_Result::make(); | |
| 38 | 47 | |
| 39 | - if ( ! isset( $style['id'] ) || ! is_string( $style['id'] ) ) { | |
| 40 | - $result->errors()->add( 'id', 'missing_or_invalid' ); | |
| 48 | + if ( $this->should_validate_id && ( ! isset( $style['id'] ) || ! is_string( $style['id'] ) ) ) { | |
| 49 | + $this->errors_bag[] = 'id'; | |
| 41 | 50 | } |
| 42 | 51 | |
| 43 | - if ( ! isset( $style['type'] ) || ! in_array( $style['type'], self::VALID_TYPES, true ) ) { | |
| 44 | - $result->errors()->add( 'type', 'missing_or_invalid' ); | |
| 45 | - } | |
| 46 | - | |
| 47 | - if ( ! isset( $style['label'] ) || ! is_string( $style['label'] ) ) { | |
| 48 | - $result->errors()->add( 'label', 'missing_or_invalid' ); | |
| 49 | - } elseif ( Plugin::$instance->experiments->is_feature_active( Opt_In::EXPERIMENT_NAME ) ) { | |
| 50 | - $label_validation = $this->validate_style_label( $style['label'] ); | |
| 51 | - | |
| 52 | - if ( ! $label_validation['is_valid'] ) { | |
| 53 | - $result->errors()->add( 'label', $label_validation['error_message'] ); | |
| 54 | - } | |
| 55 | - } | |
| 56 | - | |
| 57 | 52 | if ( ! isset( $style['variants'] ) || ! is_array( $style['variants'] ) ) { |
| 58 | - $result->errors()->add( 'variants', 'missing_or_invalid' ); | |
| 59 | - | |
| 53 | + $this->errors_bag[] = 'variants'; | |
| 60 | 54 | unset( $validated_style['variants'] ); |
| 61 | 55 | |
| 62 | - return $result->wrap( $validated_style ); | |
| 56 | + return [ | |
| 57 | + false, | |
| 58 | + $validated_style, | |
| 59 | + $this->errors_bag, | |
| 60 | + ]; | |
| 63 | 61 | } |
| 64 | 62 | |
| 65 | 63 | $props_parser = Props_Parser::make( $this->schema ); |
| 66 | 64 | |
| @@ -65,199 +63,87 @@ | ||
| 65 | 63 | $props_parser = Props_Parser::make( $this->schema ); |
| 66 | 64 | |
| 67 | 65 | foreach ( $style['variants'] as $variant_index => $variant ) { |
| 68 | 66 | if ( ! isset( $variant['meta'] ) ) { |
| 69 | - $result->errors()->add( 'meta', 'missing' ); | |
| 70 | - | |
| 67 | + $this->errors_bag[] = 'meta'; | |
| 71 | 68 | continue; |
| 72 | 69 | } |
| 73 | 70 | |
| 74 | - $meta_result = $this->validate_meta( $variant['meta'] ); | |
| 75 | - $custom_css_result = $this->validate_custom_css( $variant ); | |
| 71 | + $is_variant_meta_valid = $this->validate_meta( $variant['meta'] ); | |
| 76 | 72 | |
| 77 | - $result->errors()->merge( $meta_result->errors(), 'meta' ); | |
| 78 | - $result->errors()->merge( $custom_css_result->errors(), 'custom_css' ); | |
| 73 | + if ( $is_variant_meta_valid ) { | |
| 74 | + [, $validated_props, $variant_errors] = $props_parser->validate( $variant['props'] ); | |
| 75 | + $this->errors_bag = array_merge( $this->errors_bag, $variant_errors ); | |
| 79 | 76 | |
| 80 | - if ( $meta_result->is_valid() ) { | |
| 81 | - $variant_result = $props_parser->validate( $variant['props'] ); | |
| 82 | - | |
| 83 | - $result->errors()->merge( $variant_result->errors(), "variants[$variant_index]" ); | |
| 84 | - | |
| 85 | - $validated_style['variants'][ $variant_index ]['props'] = $variant_result->unwrap(); | |
| 77 | + $validated_style['variants'][ $variant_index ]['props'] = $validated_props; | |
| 86 | 78 | } else { |
| 87 | 79 | unset( $validated_style['variants'][ $variant_index ] ); |
| 88 | 80 | } |
| 89 | 81 | } |
| 90 | 82 | |
| 91 | - return $result->wrap( $validated_style ); | |
| 92 | - } | |
| 83 | + $is_valid = empty( $this->errors_bag ); | |
| 93 | 84 | |
| 94 | - private function validate_style_label( string $label ): array { | |
| 95 | - $label = strtolower( $label ); | |
| 96 | - | |
| 97 | - $reserved_class_names = [ 'container' ]; | |
| 98 | - | |
| 99 | - if ( strlen( $label ) > 50 ) { | |
| 100 | - return [ | |
| 101 | - 'is_valid' => false, | |
| 102 | - 'error_message' => 'class_name_too_long', | |
| 103 | - ]; | |
| 104 | - } | |
| 105 | - | |
| 106 | - if ( strlen( $label ) < 2 ) { | |
| 107 | - return [ | |
| 108 | - 'is_valid' => false, | |
| 109 | - 'error_message' => 'class_name_too_short', | |
| 110 | - ]; | |
| 111 | - } | |
| 112 | - | |
| 113 | - if ( in_array( $label, $reserved_class_names, true ) ) { | |
| 114 | - return [ | |
| 115 | - 'is_valid' => false, | |
| 116 | - 'error_message' => 'reserved_class_name', | |
| 117 | - ]; | |
| 118 | - } | |
| 119 | - | |
| 120 | - $regexes = [ | |
| 121 | - [ | |
| 122 | - 'pattern' => '/^(|[^0-9].*)$/', | |
| 123 | - 'message' => 'class_name_starts_with_digit', | |
| 124 | - ], | |
| 125 | - [ | |
| 126 | - 'pattern' => '/^\S*$/', | |
| 127 | - 'message' => 'class_name_contains_spaces', | |
| 128 | - ], | |
| 129 | - [ | |
| 130 | - 'pattern' => '/^(|[a-zA-Z0-9_-]+)$/', | |
| 131 | - 'message' => 'class_name_invalid_chars', | |
| 132 | - ], | |
| 133 | - [ | |
| 134 | - 'pattern' => '/^(?!--).*/', | |
| 135 | - 'message' => 'class_name_double_hyphen', | |
| 136 | - ], | |
| 137 | - [ | |
| 138 | - 'pattern' => '/^(?!-[0-9])/', | |
| 139 | - 'message' => 'class_name_starts_with_hyphen_digit', | |
| 140 | - ], | |
| 141 | - ]; | |
| 142 | - | |
| 143 | - foreach ( $regexes as $rule ) { | |
| 144 | - if ( ! preg_match( $rule['pattern'], $label ) ) { | |
| 145 | - return [ | |
| 146 | - 'is_valid' => false, | |
| 147 | - 'error_message' => $rule['message'], | |
| 148 | - ]; | |
| 149 | - } | |
| 150 | - } | |
| 151 | 85 | return [ |
| 152 | - 'is_valid' => true, | |
| 153 | - 'error_message' => null, | |
| 86 | + $is_valid, | |
| 87 | + $validated_style, | |
| 88 | + $this->errors_bag, | |
| 154 | 89 | ]; |
| 155 | 90 | } |
| 156 | 91 | |
| 157 | - private function validate_meta( $meta ): Parse_Result { | |
| 158 | - $result = Parse_Result::make(); | |
| 159 | - | |
| 92 | + public function validate_meta( $meta ): bool { | |
| 160 | 93 | if ( ! is_array( $meta ) ) { |
| 161 | - $result->errors()->add( 'meta', 'invalid_type' ); | |
| 162 | - | |
| 163 | - return $result; | |
| 94 | + $this->errors_bag[] = 'meta'; | |
| 95 | + return false; | |
| 164 | 96 | } |
| 165 | 97 | |
| 166 | - if ( ! array_key_exists( 'state', $meta ) || ! Style_States::is_valid_state( $meta['state'] ) ) { | |
| 167 | - $result->errors()->add( 'state', 'missing_or_invalid_value' ); | |
| 168 | - | |
| 169 | - return $result; | |
| 98 | + if ( ! array_key_exists( 'state', $meta ) || ! in_array( $meta['state'], self::VALID_STATES, true ) ) { | |
| 99 | + $this->errors_bag[] = 'meta'; | |
| 100 | + return false; | |
| 170 | 101 | } |
| 171 | 102 | |
| 172 | 103 | // TODO: Validate breakpoint based on the existing breakpoints in the system [EDS-528] |
| 173 | 104 | if ( ! isset( $meta['breakpoint'] ) || ! is_string( $meta['breakpoint'] ) ) { |
| 174 | - $result->errors()->add( 'breakpoint', 'missing_or_invalid_value' ); | |
| 175 | - | |
| 176 | - return $result; | |
| 105 | + $this->errors_bag[] = 'meta'; | |
| 106 | + return false; | |
| 177 | 107 | } |
| 178 | 108 | |
| 179 | - return $result; | |
| 109 | + return true; | |
| 180 | 110 | } |
| 181 | 111 | |
| 182 | - private function validate_custom_css( array $variant ): Parse_Result { | |
| 183 | - $result = Parse_Result::make(); | |
| 184 | - | |
| 185 | - if ( ! empty( $variant['custom_css']['raw'] ) && ( | |
| 186 | - ! is_string( $variant['custom_css']['raw'] ) || | |
| 187 | - null === Utils::decode_string( $variant['custom_css']['raw'], null ) | |
| 188 | - ) | |
| 189 | - ) { | |
| 190 | - $result->errors()->add( 'custom_css', 'invalid_type' ); | |
| 191 | - } | |
| 192 | - | |
| 193 | - return $result; | |
| 194 | - } | |
| 195 | - | |
| 196 | - private function sanitize_meta( $meta ) { | |
| 197 | - if ( ! is_array( $meta ) ) { | |
| 198 | - return []; | |
| 199 | - } | |
| 200 | - | |
| 201 | - if ( isset( $meta['breakpoint'] ) ) { | |
| 202 | - $meta['breakpoint'] = sanitize_key( $meta['breakpoint'] ); | |
| 203 | - } | |
| 204 | - | |
| 205 | - return $meta; | |
| 206 | - } | |
| 207 | - | |
| 208 | - private function sanitize_custom_css( array $variant ) { | |
| 209 | - if ( empty( $variant['custom_css']['raw'] ) ) { | |
| 210 | - return null; | |
| 211 | - } | |
| 212 | - | |
| 213 | - $custom_css = Utils::decode_string( $variant['custom_css']['raw'] ); | |
| 214 | - $custom_css = sanitize_textarea_field( $custom_css ); | |
| 215 | - $custom_css = [ 'raw' => Utils::encode_string( $custom_css ) ]; | |
| 216 | - | |
| 217 | - return empty( $custom_css['raw'] ) ? null : $custom_css; | |
| 218 | - } | |
| 219 | - | |
| 220 | 112 | /** |
| 221 | 113 | * @param array $style |
| 222 | 114 | * the style object to sanitize |
| 115 | + * | |
| 116 | + * @return array<string, mixed> | |
| 223 | 117 | */ |
| 224 | - private function sanitize( array $style ): Parse_Result { | |
| 118 | + public function sanitize( array $style ): array { | |
| 225 | 119 | $props_parser = Props_Parser::make( $this->schema ); |
| 226 | 120 | |
| 227 | - if ( isset( $style['label'] ) ) { | |
| 228 | - $style['label'] = sanitize_text_field( $style['label'] ); | |
| 229 | - } | |
| 230 | - | |
| 231 | - if ( isset( $style['id'] ) ) { | |
| 232 | - $style['id'] = sanitize_key( $style['id'] ); | |
| 233 | - } | |
| 234 | - | |
| 235 | - if ( isset( $style['sync_to_v3'] ) ) { | |
| 236 | - $style['sync_to_v3'] = (bool) $style['sync_to_v3']; | |
| 237 | - } | |
| 238 | - | |
| 239 | 121 | if ( ! empty( $style['variants'] ) ) { |
| 240 | 122 | foreach ( $style['variants'] as $variant_index => $variant ) { |
| 241 | - $style['variants'][ $variant_index ]['props'] = $props_parser->sanitize( $variant['props'] )->unwrap(); | |
| 242 | - $style['variants'][ $variant_index ]['meta'] = $this->sanitize_meta( $variant['meta'] ); | |
| 243 | - $style['variants'][ $variant_index ]['custom_css'] = $this->sanitize_custom_css( $variant ); | |
| 123 | + $style['variants'][ $variant_index ]['props'] = $props_parser->sanitize( $variant['props'] ); | |
| 244 | 124 | } |
| 245 | 125 | } |
| 246 | 126 | |
| 247 | - return Parse_Result::make()->wrap( $style ); | |
| 127 | + return $style; | |
| 248 | 128 | } |
| 249 | 129 | |
| 250 | 130 | /** |
| 251 | 131 | * @param array $style |
| 252 | 132 | * the style object to parse |
| 133 | + * | |
| 134 | + * @return array{ | |
| 135 | + * 0: bool, | |
| 136 | + * 1: array<string, mixed>, | |
| 137 | + * 2: array<string> | |
| 138 | + * } | |
| 253 | 139 | */ |
| 254 | - public function parse( array $style ): Parse_Result { | |
| 255 | - $validate_result = $this->validate( $style ); | |
| 140 | + public function parse( array $style ): array { | |
| 141 | + [ $is_valid, $validated, $errors_bag ] = $this->validate( $style ); | |
| 256 | 142 | |
| 257 | - $sanitize_result = $this->sanitize( $validate_result->unwrap() ); | |
| 258 | - | |
| 259 | - $sanitize_result->errors()->merge( $validate_result->errors() ); | |
| 260 | - | |
| 261 | - return $sanitize_result; | |
| 143 | + return [ | |
| 144 | + $is_valid, | |
| 145 | + $this->sanitize( $validated ), | |
| 146 | + $errors_bag, | |
| 147 | + ]; | |
| 262 | 148 | } |
| 263 | 149 | } |