Skip to content

Commit

Permalink
Merge pull request #68 from programmatordev/YAPV-36-create-csscolor-rule
Browse files Browse the repository at this point in the history
Create CssColor rule
  • Loading branch information
andrepimpao authored Jul 11, 2024
2 parents 52ce0e7 + 28ef080 commit d2a1175
Show file tree
Hide file tree
Showing 8 changed files with 331 additions and 9 deletions.
24 changes: 15 additions & 9 deletions .ddev/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,22 @@ omit_containers: [db]
use_dns_when_possible: true
composer_version: "2"
web_environment: []
corepack_enable: false
disable_upload_dirs_warning: true

# Key features of DDEV's config.yaml:

# name: <projectname> # Name of the project, automatically provides
# http://projectname.ddev.site and https://projectname.ddev.site

# type: <projecttype> # backdrop, craftcms, django4, drupal6/7/8/9/10, laravel, magento, magento2, php, python, shopware6, silverstripe, typo3, wordpress
# See https://ddev.readthedocs.io/en/latest/users/quickstart/ for more
# type: <projecttype> # backdrop, craftcms, django4, drupal, drupal6, drupal7, laravel, magento, magento2, php, python, shopware6, silverstripe, typo3, wordpress
# See https://ddev.readthedocs.io/en/stable/users/quickstart/ for more
# information on the different project types
# "drupal" covers recent Drupal 8+

# docroot: <relative_path> # Relative path to the directory containing index.php.

# php_version: "8.1" # PHP version to use, "5.6", "7.0", "7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3"
# php_version: "8.2" # PHP version to use, "5.6", "7.0", "7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3"

# You can explicitly specify the webimage but this
# is not recommended, as the images are often closely tied to DDEV's' behavior,
Expand All @@ -36,8 +38,9 @@ disable_upload_dirs_warning: true

# database:
# type: <dbtype> # mysql, mariadb, postgres
# version: <version> # database version, like "10.4" or "8.0"
# MariaDB versions can be 5.5-10.8 and 10.11, MySQL versions can be 5.5-8.0
# version: <version> # database version, like "10.11" or "8.0"
# MariaDB versions can be 5.5-10.8, 10.11, and 11.4.
# MySQL versions can be 5.5-8.0.
# PostgreSQL versions can be 9-16.

# router_http_port: <port> # Port to be used for http (defaults to global configuration, usually 80)
Expand Down Expand Up @@ -77,7 +80,7 @@ disable_upload_dirs_warning: true
# Alternatively, an explicit Composer version may be specified, for example "2.2.18".
# To reinstall Composer after the image was built, run "ddev debug refresh".

# nodejs_version: "18"
# nodejs_version: "20"
# change from the default system Node.js version to any other version.
# Numeric version numbers can be complete (i.e. 18.15.0) or
# incomplete (18, 17.2, 16). 'lts' and 'latest' can be used as well along with
Expand All @@ -86,6 +89,9 @@ disable_upload_dirs_warning: true
# Note that you can continue using 'ddev nvm' or nvm inside the web container
# to change the project's installed node version if you need to.

# corepack_enable: false
# Change to 'true' to 'corepack enable' and gain access to latest versions of yarn/pnpm

# additional_hostnames:
# - somename
# - someothername
Expand Down Expand Up @@ -143,8 +149,8 @@ disable_upload_dirs_warning: true
# - "mutagen": enables Mutagen for this project.
# - "nfs": enables NFS for this project.
#
# See https://ddev.readthedocs.io/en/latest/users/install/performance/#nfs
# See https://ddev.readthedocs.io/en/latest/users/install/performance/#mutagen
# See https://ddev.readthedocs.io/en/stable/users/install/performance/#nfs
# See https://ddev.readthedocs.io/en/stable/users/install/performance/#mutagen

# fail_on_hook_fail: False
# Decide whether 'ddev start' should be interrupted by a failing hook
Expand Down Expand Up @@ -197,7 +203,7 @@ disable_upload_dirs_warning: true

# disable_settings_management: false
# If true, DDEV will not create CMS-specific settings files like
# Drupal's settings.php/settings.ddev.php or TYPO3's AdditionalConfiguration.php
# Drupal's settings.php/settings.ddev.php or TYPO3's additional.php
# In this case the user must provide all such settings.

# You can inject environment variables into the web container with:
Expand Down
1 change: 1 addition & 0 deletions docs/03-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

## String Rules

- [CssColor](03-rules_css-color.md)
- [Email](03-rules_email.md)
- [Length](03-rules_length.md)
- [PasswordStrength](03-rules_password-strength.md)
Expand Down
126 changes: 126 additions & 0 deletions docs/03-rules_css-color.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# CssColor

Validates that a value is a valid CSS color.

```php
CssColor(
?array $formats = null,
?string $message = null
);
```

## Basic Usage

```php
// by default, all possible ways to define a CSS color are considered valid
Validator::cssColor()->validate('#0f0f0f'); // true
Validator::cssColor()->validate('black'); // true
Validator::cssColor()->validate('rgb(0, 255, 0)'); // true
// ...

// restrict allowed formats
Validator::cssColor(formats: ['hex-long'])->validate('#0f0f0f'); // true
Validator::cssColor(formats: ['hex-long'])->validate('rgb(0, 255, 0)'); // false
Validator::cssColor(formats: ['hex-long', 'rgb'])->validate('rgb(0, 255, 0)'); // true
```

> [!NOTE]
> An `UnexpectedValueException` will be thrown when a `formats` option is invalid.
## Options

### `formats`

type: `?array` default: `null`

By default, all possible ways to define a CSS color are considered valid.
Use this options to restrict the allowed CSS formats.

Available options are:

- [`hex-long`](#hex-long)
- [`hex-long-with-alpha`](#hex-long-with-alpha)
- [`hex-short`](#hex-short)
- [`hex-short-with-alpha`](#hex-short-with-alpha)
- [`basic-named-colors`](#basic-named-colors)
- [`extended-named-colors`](#extended-named-colors)
- [`system-colors`](#system-colors)
- [`keywords`](#keywords)
- [`rgb`](#rgb)
- [`rgba`](#rgba)
- [`hsl`](#hsl)
- [`hsla`](#hsla)

#### `hex-long`

Examples: `#0f0f0f`, `#0F0F0F`

#### `hex-long-with-alpha`

Examples: `#0f0f0f50`, `#0F0F0F50`

#### `hex-short`

Examples: `#0f0`, `#0F0`

#### `hex-short-with-alpha`

Examples: `#0f05`, `#0F05`

#### `basic-named-colors`

Colors names defined in the [W3C list of basic names colors](https://www.w3.org/wiki/CSS/Properties/color/keywords#Basic_Colors).

Examples: `black`, `green`

#### `extended-named-colors`

Colors names defined in the [W3C list of extended names colors](https://www.w3.org/wiki/CSS/Properties/color/keywords#Extended_colors).

Examples: `black`, `aqua`, `darkgoldenrod`, `green`

#### `system-colors`

Colors names defined in the [CSS WG list of system colors](https://drafts.csswg.org/css-color/#css-system-colors).

Examples: `AccentColor`, `VisitedText`

#### `keywords`

Colors names defined in the [CSS WG list of keywords](https://drafts.csswg.org/css-color/#transparent-color).

Examples: `transparent`, `currentColor`

#### `rgb`

Examples: `rgb(0, 255, 0)`, `rgb(0,255,0)`

#### `rgba`

Examples: `rgba(0, 255, 0, 50)`, `rgba(0,255,0,50)`

#### `hsl`

Examples: `hsl(0, 50%, 50%)`, `hsl(0,50%,50%)`

#### `hsla`

Examples: `hsla(0, 50%, 50%, 0.5)`, `hsla(0,50%,50%,0.5)`

### `message`

type: `?string` default: `The {{ name }} value is not a valid CSS color.`

Message that will be shown if the input value is not a valid CSS color.

The following parameters are available:

| Parameter | Description |
|-----------------|---------------------------|
| `{{ value }}` | The current invalid value |
| `{{ name }}` | Name of the invalid value |
| `{{ formats }}` | Selected formats |

## Changelog

- `1.2.0` Created
5 changes: 5 additions & 0 deletions src/ChainedValidatorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ public function country(
?string $message = null
): ChainedValidatorInterface&Validator;

public function cssColor(
?array $formats = null,
?string $message = null
): ChainedValidatorInterface&Validator;

public function dateTime(
string $format = 'Y-m-d H:i:s',
?string $message = null
Expand Down
5 changes: 5 additions & 0 deletions src/Exception/CssColorException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

namespace ProgrammatorDev\Validator\Exception;

class CssColorException extends ValidationException {}
114 changes: 114 additions & 0 deletions src/Rule/CssColor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php

namespace ProgrammatorDev\Validator\Rule;

use ProgrammatorDev\Validator\Exception\CssColorException;
use ProgrammatorDev\Validator\Exception\UnexpectedOptionException;
use ProgrammatorDev\Validator\Exception\UnexpectedTypeException;

class CssColor extends AbstractRule implements RuleInterface
{
public const FORMAT_HEX_LONG = 'hex-long';
public const FORMAT_HEX_LONG_WITH_ALPHA = 'hex-long-with-alpha';
public const FORMAT_HEX_SHORT = 'hex-short';
public const FORMAT_HEX_SHORT_WITH_ALPHA = 'hex-short-with-alpha';
public const FORMAT_BASIC_NAMED_COLORS = 'basic-named-colors';
public const FORMAT_EXTENDED_NAMED_COLORS = 'extended-named-colors';
public const FORMAT_SYSTEM_COLORS = 'system-colors';
public const FORMAT_KEYWORDS = 'keywords';
public const FORMAT_RGB = 'rgb';
public const FORMAT_RGBA = 'rgba';
public const FORMAT_HSL = 'hsl';
public const FORMAT_HSLA = 'hsla';

private const COLOR_FORMATS = [
self::FORMAT_HEX_LONG,
self::FORMAT_HEX_LONG_WITH_ALPHA,
self::FORMAT_HEX_SHORT,
self::FORMAT_HEX_SHORT_WITH_ALPHA,
self::FORMAT_BASIC_NAMED_COLORS,
self::FORMAT_EXTENDED_NAMED_COLORS,
self::FORMAT_SYSTEM_COLORS,
self::FORMAT_KEYWORDS,
self::FORMAT_RGB,
self::FORMAT_RGBA,
self::FORMAT_HSL,
self::FORMAT_HSLA
];

private const PATTERN_HEX_LONG = '/^#[0-9a-f]{6}$/i';
private const PATTERN_HEX_LONG_WITH_ALPHA = '/^#[0-9a-f]{8}$/i';
private const PATTERN_HEX_SHORT = '/^#[0-9a-f]{3}$/i';
private const PATTERN_HEX_SHORT_WITH_ALPHA = '/^#[0-9a-f]{4}$/i';
// https://www.w3.org/wiki/CSS/Properties/color/keywords#Basic_Colors
private const PATTERN_BASIC_NAMED_COLORS = '/^(black|silver|gray|white|maroon|red|purple|fuchsia|green|lime|olive|yellow|navy|blue|teal|aqua)$/i';
// https://www.w3.org/wiki/CSS/Properties/color/keywords#Extended_colors
private const PATTERN_EXTENDED_NAMED_COLORS = '/^(aliceblue|antiquewhite|aqua|aquamarine|azure|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen|fuchsia|gainsboro|ghostwhite|gold|goldenrod|gray|green|greenyellow|grey|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgreen|lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|lime|limegreen|linen|magenta|maroon|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|navy|oldlace|olive|olivedrab|orange|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|purple|red|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|silver|skyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue|tan|teal|thistle|tomato|turquoise|violet|wheat|white|whitesmoke|yellow|yellowgreen)$/i';
// https://drafts.csswg.org/css-color/#css-system-colors
private const PATTERN_SYSTEM_COLORS = '/^(AccentColor|AccentColorText|ActiveText|ButtonBorder|ButtonFace|ButtonText|Canvas|CanvasText|Field|FieldText|GrayText|Highlight|HighlightText|LinkText|Mark|MarkText|SelectedItem|SelectedItemText|VisitedText)$/i';
// https://drafts.csswg.org/css-color/#transparent-color
// https://drafts.csswg.org/css-color/#currentcolor-color
private const PATTERN_KEYWORDS = '/^(transparent|currentColor)$/i';
private const PATTERN_RGB = '/^rgb\(\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d)\s*\)$/i';
private const PATTERN_RGBA = '/^rgba\(\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|0?\.\d+|1(\.0)?)\s*\)$/i';
private const PATTERN_HSL = '/^hsl\(\s*(0|360|35\d|3[0-4]\d|[12]\d\d|0?\d?\d),\s*(0|100|\d{1,2})%,\s*(0|100|\d{1,2})%\s*\)$/i';
private const PATTERN_HSLA = '/^hsla\(\s*(0|360|35\d|3[0-4]\d|[12]\d\d|0?\d?\d),\s*(0|100|\d{1,2})%,\s*(0|100|\d{1,2})%,\s*(0|0?\.\d+|1(\.0)?)\s*\)$/i';

private const COLOR_PATTERNS = [
self::FORMAT_HEX_LONG => self::PATTERN_HEX_LONG,
self::FORMAT_HEX_LONG_WITH_ALPHA => self::PATTERN_HEX_LONG_WITH_ALPHA,
self::FORMAT_HEX_SHORT => self::PATTERN_HEX_SHORT,
self::FORMAT_HEX_SHORT_WITH_ALPHA => self::PATTERN_HEX_SHORT_WITH_ALPHA,
self::FORMAT_BASIC_NAMED_COLORS => self::PATTERN_BASIC_NAMED_COLORS,
self::FORMAT_EXTENDED_NAMED_COLORS => self::PATTERN_EXTENDED_NAMED_COLORS,
self::FORMAT_SYSTEM_COLORS => self::PATTERN_SYSTEM_COLORS,
self::FORMAT_KEYWORDS => self::PATTERN_KEYWORDS,
self::FORMAT_RGB => self::PATTERN_RGB,
self::FORMAT_RGBA => self::PATTERN_RGBA,
self::FORMAT_HSL => self::PATTERN_HSL,
self::FORMAT_HSLA => self::PATTERN_HSLA
];

private array $formats = self::COLOR_FORMATS;
private string $message = 'The {{ name }} value is not a valid CSS color.';

public function __construct(
?array $formats = null,
?string $message = null
)
{
$this->formats = $formats ?? $this->formats;
$this->message = $message ?? $this->message;
}

public function assert(mixed $value, ?string $name = null): void
{
foreach ($this->formats as $format) {
if (!\in_array($format, self::COLOR_FORMATS, true)) {
throw new UnexpectedOptionException('format', self::COLOR_FORMATS, $format);
}
}

if (!\is_string($value)) {
throw new UnexpectedTypeException('string', get_debug_type($value));
}

foreach ($this->formats as $format) {
$pattern = self::COLOR_PATTERNS[$format];

// it is valid if at least one pattern matches
if (\preg_match($pattern, $value)) {
return;
}
}

throw new CssColorException(
message: $this->message,
parameters: [
'value' => $value,
'name' => $name,
'formats' => $this->formats
]
);
}
}
5 changes: 5 additions & 0 deletions src/StaticValidatorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ public static function country(
?string $message = null
): ChainedValidatorInterface&Validator;

public static function cssColor(
?array $formats = null,
?string $message = null
): ChainedValidatorInterface&Validator;

public static function dateTime(
string $format = 'Y-m-d H:i:s',
?string $message = null
Expand Down
Loading

0 comments on commit d2a1175

Please sign in to comment.