first commit
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Parsers;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Parse_Errors {
|
||||
|
||||
/**
|
||||
* @var array<array{key: string, error: string}>
|
||||
*/
|
||||
private array $errors = [];
|
||||
|
||||
public static function make() {
|
||||
return new static();
|
||||
}
|
||||
|
||||
public function add( string $key, string $error ): self {
|
||||
$this->errors[] = [
|
||||
'key' => $key,
|
||||
'error' => $error,
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function is_empty(): bool {
|
||||
return empty( $this->errors );
|
||||
}
|
||||
|
||||
public function all(): array {
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
public function to_string(): string {
|
||||
$errors = [];
|
||||
|
||||
foreach ( $this->errors as $error ) {
|
||||
$errors[] = $error['key'] . ': ' . $error['error'];
|
||||
}
|
||||
|
||||
return implode( ', ', $errors );
|
||||
}
|
||||
|
||||
public function merge( Parse_Errors $errors, ?string $prefix = null ): self {
|
||||
foreach ( $errors->all() as $error ) {
|
||||
$new_key = $prefix ? "{$prefix}.{$error['key']}" : $error['key'];
|
||||
|
||||
$this->add( $new_key, $error['error'] );
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Parsers;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Parse_Result {
|
||||
private Parse_Errors $errors;
|
||||
|
||||
private $value;
|
||||
|
||||
public static function make() {
|
||||
return new static();
|
||||
}
|
||||
|
||||
public function __construct() {
|
||||
$this->errors = Parse_Errors::make();
|
||||
}
|
||||
|
||||
public function wrap( $value ): self {
|
||||
$this->value = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function unwrap() {
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function is_valid(): bool {
|
||||
return $this->errors->is_empty();
|
||||
}
|
||||
|
||||
public function errors(): Parse_Errors {
|
||||
return $this->errors;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Parsers;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Props_Parser {
|
||||
|
||||
private array $schema;
|
||||
|
||||
public function __construct( array $schema ) {
|
||||
$this->schema = $schema;
|
||||
}
|
||||
|
||||
public static function make( array $schema ): self {
|
||||
return new static( $schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $props
|
||||
* The key of each item represents the prop name (should match the schema),
|
||||
* and the value is the prop value to validate
|
||||
*/
|
||||
public function validate( array $props ): Parse_Result {
|
||||
$result = Parse_Result::make();
|
||||
|
||||
$validated = [];
|
||||
|
||||
foreach ( $this->schema as $key => $prop_type ) {
|
||||
if ( ! ( $prop_type instanceof Prop_Type ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $props[ $key ] ?? null;
|
||||
|
||||
$is_valid = $prop_type->validate( $value ?? $prop_type->get_default() );
|
||||
|
||||
if ( ! $is_valid ) {
|
||||
$result->errors()->add( $key, 'invalid_value' );
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! is_null( $value ) ) {
|
||||
$validated[ $key ] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $result->wrap( $validated );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $props
|
||||
* The key of each item represents the prop name (should match the schema),
|
||||
* and the value is the prop value to sanitize
|
||||
*/
|
||||
public function sanitize( array $props ): Parse_Result {
|
||||
$sanitized = [];
|
||||
|
||||
foreach ( $this->schema as $key => $prop_type ) {
|
||||
if ( ! isset( $props[ $key ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$sanitized[ $key ] = $prop_type->sanitize( $props[ $key ] );
|
||||
}
|
||||
|
||||
return Parse_Result::make()->wrap( $sanitized );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $props
|
||||
* The key of each item represents the prop name (should match the schema),
|
||||
* and the value is the prop value to parse
|
||||
*/
|
||||
public function parse( array $props ): Parse_Result {
|
||||
$validate_result = $this->validate( $props );
|
||||
|
||||
$sanitize_result = $this->sanitize( $validate_result->unwrap() );
|
||||
|
||||
$sanitize_result->errors()->merge( $validate_result->errors() );
|
||||
|
||||
return $sanitize_result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Parsers;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Module;
|
||||
use Elementor\Plugin;
|
||||
|
||||
class Style_Parser {
|
||||
const VALID_TYPES = [
|
||||
'class',
|
||||
];
|
||||
|
||||
const VALID_STATES = [
|
||||
'hover',
|
||||
'active',
|
||||
'focus',
|
||||
null,
|
||||
];
|
||||
|
||||
private array $schema;
|
||||
|
||||
public function __construct( array $schema ) {
|
||||
$this->schema = $schema;
|
||||
}
|
||||
|
||||
public static function make( array $schema ): self {
|
||||
return new static( $schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $style
|
||||
* the style object to validate
|
||||
*/
|
||||
private function validate( array $style ): Parse_Result {
|
||||
$validated_style = $style;
|
||||
$result = Parse_Result::make();
|
||||
|
||||
if ( ! isset( $style['id'] ) || ! is_string( $style['id'] ) ) {
|
||||
$result->errors()->add( 'id', 'missing_or_invalid' );
|
||||
}
|
||||
|
||||
if ( ! isset( $style['type'] ) || ! in_array( $style['type'], self::VALID_TYPES, true ) ) {
|
||||
$result->errors()->add( 'type', 'missing_or_invalid' );
|
||||
}
|
||||
|
||||
if ( ! isset( $style['label'] ) || ! is_string( $style['label'] ) ) {
|
||||
$result->errors()->add( 'label', 'missing_or_invalid' );
|
||||
} elseif ( Plugin::$instance->experiments->is_feature_active( Module::EXPERIMENT_VERSION_3_30 ) ) {
|
||||
$label_validation = $this->validate_style_label( $style['label'] );
|
||||
|
||||
if ( ! $label_validation['is_valid'] ) {
|
||||
$result->errors()->add( 'label', $label_validation['error_message'] );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! isset( $style['variants'] ) || ! is_array( $style['variants'] ) ) {
|
||||
$result->errors()->add( 'variants', 'missing_or_invalid' );
|
||||
|
||||
unset( $validated_style['variants'] );
|
||||
|
||||
return $result->wrap( $validated_style );
|
||||
}
|
||||
|
||||
$props_parser = Props_Parser::make( $this->schema );
|
||||
|
||||
foreach ( $style['variants'] as $variant_index => $variant ) {
|
||||
if ( ! isset( $variant['meta'] ) ) {
|
||||
$result->errors()->add( 'meta', 'missing' );
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$meta_result = $this->validate_meta( $variant['meta'] );
|
||||
|
||||
$result->errors()->merge( $meta_result->errors(), 'meta' );
|
||||
|
||||
if ( $meta_result->is_valid() ) {
|
||||
$variant_result = $props_parser->validate( $variant['props'] );
|
||||
|
||||
$result->errors()->merge( $variant_result->errors(), "variants[$variant_index]" );
|
||||
|
||||
$validated_style['variants'][ $variant_index ]['props'] = $variant_result->unwrap();
|
||||
} else {
|
||||
unset( $validated_style['variants'][ $variant_index ] );
|
||||
}
|
||||
}
|
||||
|
||||
return $result->wrap( $validated_style );
|
||||
}
|
||||
|
||||
private function validate_style_label( string $label ): array {
|
||||
$label = strtolower( $label );
|
||||
|
||||
$reserved_class_names = [ 'container' ];
|
||||
|
||||
if ( strlen( $label ) > 50 ) {
|
||||
return [
|
||||
'is_valid' => false,
|
||||
'error_message' => 'class_name_too_long',
|
||||
];
|
||||
}
|
||||
|
||||
if ( strlen( $label ) < 2 ) {
|
||||
return [
|
||||
'is_valid' => false,
|
||||
'error_message' => 'class_name_too_short',
|
||||
];
|
||||
}
|
||||
|
||||
if ( in_array( $label, $reserved_class_names, true ) ) {
|
||||
return [
|
||||
'is_valid' => false,
|
||||
'error_message' => 'reserved_class_name',
|
||||
];
|
||||
}
|
||||
|
||||
$regexes = [
|
||||
[
|
||||
'pattern' => '/^(|[^0-9].*)$/',
|
||||
'message' => 'class_name_starts_with_digit',
|
||||
],
|
||||
[
|
||||
'pattern' => '/^\S*$/',
|
||||
'message' => 'class_name_contains_spaces',
|
||||
],
|
||||
[
|
||||
'pattern' => '/^(|[a-zA-Z0-9_-]+)$/',
|
||||
'message' => 'class_name_invalid_chars',
|
||||
],
|
||||
[
|
||||
'pattern' => '/^(?!--).*/',
|
||||
'message' => 'class_name_double_hyphen',
|
||||
],
|
||||
[
|
||||
'pattern' => '/^(?!-[0-9])/',
|
||||
'message' => 'class_name_starts_with_hyphen_digit',
|
||||
],
|
||||
];
|
||||
|
||||
foreach ( $regexes as $rule ) {
|
||||
if ( ! preg_match( $rule['pattern'], $label ) ) {
|
||||
return [
|
||||
'is_valid' => false,
|
||||
'error_message' => $rule['message'],
|
||||
];
|
||||
}
|
||||
}
|
||||
return [
|
||||
'is_valid' => true,
|
||||
'error_message' => null,
|
||||
];
|
||||
}
|
||||
|
||||
private function validate_meta( $meta ): Parse_Result {
|
||||
$result = Parse_Result::make();
|
||||
|
||||
if ( ! is_array( $meta ) ) {
|
||||
$result->errors()->add( 'meta', 'invalid_type' );
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
if ( ! array_key_exists( 'state', $meta ) || ! in_array( $meta['state'], self::VALID_STATES, true ) ) {
|
||||
$result->errors()->add( 'state', 'missing_or_invalid_value' );
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
// TODO: Validate breakpoint based on the existing breakpoints in the system [EDS-528]
|
||||
if ( ! isset( $meta['breakpoint'] ) || ! is_string( $meta['breakpoint'] ) ) {
|
||||
$result->errors()->add( 'breakpoint', 'missing_or_invalid_value' );
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function sanitize_meta( $meta ) {
|
||||
if ( ! is_array( $meta ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ( isset( $meta['breakpoint'] ) ) {
|
||||
$meta['breakpoint'] = sanitize_key( $meta['breakpoint'] );
|
||||
}
|
||||
|
||||
return $meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $style
|
||||
* the style object to sanitize
|
||||
*/
|
||||
private function sanitize( array $style ): Parse_Result {
|
||||
$props_parser = Props_Parser::make( $this->schema );
|
||||
|
||||
if ( isset( $style['label'] ) ) {
|
||||
$style['label'] = sanitize_text_field( $style['label'] );
|
||||
}
|
||||
|
||||
if ( isset( $style['id'] ) ) {
|
||||
$style['id'] = sanitize_key( $style['id'] );
|
||||
}
|
||||
|
||||
if ( ! empty( $style['variants'] ) ) {
|
||||
foreach ( $style['variants'] as $variant_index => $variant ) {
|
||||
$style['variants'][ $variant_index ]['props'] = $props_parser->sanitize( $variant['props'] )->unwrap();
|
||||
$style['variants'][ $variant_index ]['meta'] = $this->sanitize_meta( $variant['meta'] );
|
||||
}
|
||||
}
|
||||
|
||||
return Parse_Result::make()->wrap( $style );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $style
|
||||
* the style object to parse
|
||||
*/
|
||||
public function parse( array $style ): Parse_Result {
|
||||
$validate_result = $this->validate( $style );
|
||||
|
||||
$sanitize_result = $this->sanitize( $validate_result->unwrap() );
|
||||
|
||||
$sanitize_result->errors()->merge( $validate_result->errors() );
|
||||
|
||||
return $sanitize_result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user