Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit d2a1175

Browse files
authored
Merge pull request #68 from programmatordev/YAPV-36-create-csscolor-rule
Create CssColor rule
2 parents 52ce0e7 + 28ef080 commit d2a1175

8 files changed

+331
-9
lines changed

.ddev/config.yaml

+15-9
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,22 @@ omit_containers: [db]
1313
use_dns_when_possible: true
1414
composer_version: "2"
1515
web_environment: []
16+
corepack_enable: false
1617
disable_upload_dirs_warning: true
1718

1819
# Key features of DDEV's config.yaml:
1920

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

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

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

29-
# 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"
31+
# 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"
3032

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

3739
# database:
3840
# type: <dbtype> # mysql, mariadb, postgres
39-
# version: <version> # database version, like "10.4" or "8.0"
40-
# MariaDB versions can be 5.5-10.8 and 10.11, MySQL versions can be 5.5-8.0
41+
# version: <version> # database version, like "10.11" or "8.0"
42+
# MariaDB versions can be 5.5-10.8, 10.11, and 11.4.
43+
# MySQL versions can be 5.5-8.0.
4144
# PostgreSQL versions can be 9-16.
4245

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

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

92+
# corepack_enable: false
93+
# Change to 'true' to 'corepack enable' and gain access to latest versions of yarn/pnpm
94+
8995
# additional_hostnames:
9096
# - somename
9197
# - someothername
@@ -143,8 +149,8 @@ disable_upload_dirs_warning: true
143149
# - "mutagen": enables Mutagen for this project.
144150
# - "nfs": enables NFS for this project.
145151
#
146-
# See https://ddev.readthedocs.io/en/latest/users/install/performance/#nfs
147-
# See https://ddev.readthedocs.io/en/latest/users/install/performance/#mutagen
152+
# See https://ddev.readthedocs.io/en/stable/users/install/performance/#nfs
153+
# See https://ddev.readthedocs.io/en/stable/users/install/performance/#mutagen
148154

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

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

203209
# You can inject environment variables into the web container with:

docs/03-rules.md

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
## String Rules
1818

19+
- [CssColor](03-rules_css-color.md)
1920
- [Email](03-rules_email.md)
2021
- [Length](03-rules_length.md)
2122
- [PasswordStrength](03-rules_password-strength.md)

docs/03-rules_css-color.md

+126
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# CssColor
2+
3+
Validates that a value is a valid CSS color.
4+
5+
```php
6+
CssColor(
7+
?array $formats = null,
8+
?string $message = null
9+
);
10+
```
11+
12+
## Basic Usage
13+
14+
```php
15+
// by default, all possible ways to define a CSS color are considered valid
16+
Validator::cssColor()->validate('#0f0f0f'); // true
17+
Validator::cssColor()->validate('black'); // true
18+
Validator::cssColor()->validate('rgb(0, 255, 0)'); // true
19+
// ...
20+
21+
// restrict allowed formats
22+
Validator::cssColor(formats: ['hex-long'])->validate('#0f0f0f'); // true
23+
Validator::cssColor(formats: ['hex-long'])->validate('rgb(0, 255, 0)'); // false
24+
Validator::cssColor(formats: ['hex-long', 'rgb'])->validate('rgb(0, 255, 0)'); // true
25+
```
26+
27+
> [!NOTE]
28+
> An `UnexpectedValueException` will be thrown when a `formats` option is invalid.
29+
30+
## Options
31+
32+
### `formats`
33+
34+
type: `?array` default: `null`
35+
36+
By default, all possible ways to define a CSS color are considered valid.
37+
Use this options to restrict the allowed CSS formats.
38+
39+
Available options are:
40+
41+
- [`hex-long`](#hex-long)
42+
- [`hex-long-with-alpha`](#hex-long-with-alpha)
43+
- [`hex-short`](#hex-short)
44+
- [`hex-short-with-alpha`](#hex-short-with-alpha)
45+
- [`basic-named-colors`](#basic-named-colors)
46+
- [`extended-named-colors`](#extended-named-colors)
47+
- [`system-colors`](#system-colors)
48+
- [`keywords`](#keywords)
49+
- [`rgb`](#rgb)
50+
- [`rgba`](#rgba)
51+
- [`hsl`](#hsl)
52+
- [`hsla`](#hsla)
53+
54+
#### `hex-long`
55+
56+
Examples: `#0f0f0f`, `#0F0F0F`
57+
58+
#### `hex-long-with-alpha`
59+
60+
Examples: `#0f0f0f50`, `#0F0F0F50`
61+
62+
#### `hex-short`
63+
64+
Examples: `#0f0`, `#0F0`
65+
66+
#### `hex-short-with-alpha`
67+
68+
Examples: `#0f05`, `#0F05`
69+
70+
#### `basic-named-colors`
71+
72+
Colors names defined in the [W3C list of basic names colors](https://www.w3.org/wiki/CSS/Properties/color/keywords#Basic_Colors).
73+
74+
Examples: `black`, `green`
75+
76+
#### `extended-named-colors`
77+
78+
Colors names defined in the [W3C list of extended names colors](https://www.w3.org/wiki/CSS/Properties/color/keywords#Extended_colors).
79+
80+
Examples: `black`, `aqua`, `darkgoldenrod`, `green`
81+
82+
#### `system-colors`
83+
84+
Colors names defined in the [CSS WG list of system colors](https://drafts.csswg.org/css-color/#css-system-colors).
85+
86+
Examples: `AccentColor`, `VisitedText`
87+
88+
#### `keywords`
89+
90+
Colors names defined in the [CSS WG list of keywords](https://drafts.csswg.org/css-color/#transparent-color).
91+
92+
Examples: `transparent`, `currentColor`
93+
94+
#### `rgb`
95+
96+
Examples: `rgb(0, 255, 0)`, `rgb(0,255,0)`
97+
98+
#### `rgba`
99+
100+
Examples: `rgba(0, 255, 0, 50)`, `rgba(0,255,0,50)`
101+
102+
#### `hsl`
103+
104+
Examples: `hsl(0, 50%, 50%)`, `hsl(0,50%,50%)`
105+
106+
#### `hsla`
107+
108+
Examples: `hsla(0, 50%, 50%, 0.5)`, `hsla(0,50%,50%,0.5)`
109+
110+
### `message`
111+
112+
type: `?string` default: `The {{ name }} value is not a valid CSS color.`
113+
114+
Message that will be shown if the input value is not a valid CSS color.
115+
116+
The following parameters are available:
117+
118+
| Parameter | Description |
119+
|-----------------|---------------------------|
120+
| `{{ value }}` | The current invalid value |
121+
| `{{ name }}` | Name of the invalid value |
122+
| `{{ formats }}` | Selected formats |
123+
124+
## Changelog
125+
126+
- `1.2.0` Created

src/ChainedValidatorInterface.php

+5
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ public function country(
3939
?string $message = null
4040
): ChainedValidatorInterface&Validator;
4141

42+
public function cssColor(
43+
?array $formats = null,
44+
?string $message = null
45+
): ChainedValidatorInterface&Validator;
46+
4247
public function dateTime(
4348
string $format = 'Y-m-d H:i:s',
4449
?string $message = null

src/Exception/CssColorException.php

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
namespace ProgrammatorDev\Validator\Exception;
4+
5+
class CssColorException extends ValidationException {}

src/Rule/CssColor.php

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<?php
2+
3+
namespace ProgrammatorDev\Validator\Rule;
4+
5+
use ProgrammatorDev\Validator\Exception\CssColorException;
6+
use ProgrammatorDev\Validator\Exception\UnexpectedOptionException;
7+
use ProgrammatorDev\Validator\Exception\UnexpectedTypeException;
8+
9+
class CssColor extends AbstractRule implements RuleInterface
10+
{
11+
public const FORMAT_HEX_LONG = 'hex-long';
12+
public const FORMAT_HEX_LONG_WITH_ALPHA = 'hex-long-with-alpha';
13+
public const FORMAT_HEX_SHORT = 'hex-short';
14+
public const FORMAT_HEX_SHORT_WITH_ALPHA = 'hex-short-with-alpha';
15+
public const FORMAT_BASIC_NAMED_COLORS = 'basic-named-colors';
16+
public const FORMAT_EXTENDED_NAMED_COLORS = 'extended-named-colors';
17+
public const FORMAT_SYSTEM_COLORS = 'system-colors';
18+
public const FORMAT_KEYWORDS = 'keywords';
19+
public const FORMAT_RGB = 'rgb';
20+
public const FORMAT_RGBA = 'rgba';
21+
public const FORMAT_HSL = 'hsl';
22+
public const FORMAT_HSLA = 'hsla';
23+
24+
private const COLOR_FORMATS = [
25+
self::FORMAT_HEX_LONG,
26+
self::FORMAT_HEX_LONG_WITH_ALPHA,
27+
self::FORMAT_HEX_SHORT,
28+
self::FORMAT_HEX_SHORT_WITH_ALPHA,
29+
self::FORMAT_BASIC_NAMED_COLORS,
30+
self::FORMAT_EXTENDED_NAMED_COLORS,
31+
self::FORMAT_SYSTEM_COLORS,
32+
self::FORMAT_KEYWORDS,
33+
self::FORMAT_RGB,
34+
self::FORMAT_RGBA,
35+
self::FORMAT_HSL,
36+
self::FORMAT_HSLA
37+
];
38+
39+
private const PATTERN_HEX_LONG = '/^#[0-9a-f]{6}$/i';
40+
private const PATTERN_HEX_LONG_WITH_ALPHA = '/^#[0-9a-f]{8}$/i';
41+
private const PATTERN_HEX_SHORT = '/^#[0-9a-f]{3}$/i';
42+
private const PATTERN_HEX_SHORT_WITH_ALPHA = '/^#[0-9a-f]{4}$/i';
43+
// https://www.w3.org/wiki/CSS/Properties/color/keywords#Basic_Colors
44+
private const PATTERN_BASIC_NAMED_COLORS = '/^(black|silver|gray|white|maroon|red|purple|fuchsia|green|lime|olive|yellow|navy|blue|teal|aqua)$/i';
45+
// https://www.w3.org/wiki/CSS/Properties/color/keywords#Extended_colors
46+
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';
47+
// https://drafts.csswg.org/css-color/#css-system-colors
48+
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';
49+
// https://drafts.csswg.org/css-color/#transparent-color
50+
// https://drafts.csswg.org/css-color/#currentcolor-color
51+
private const PATTERN_KEYWORDS = '/^(transparent|currentColor)$/i';
52+
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';
53+
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';
54+
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';
55+
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';
56+
57+
private const COLOR_PATTERNS = [
58+
self::FORMAT_HEX_LONG => self::PATTERN_HEX_LONG,
59+
self::FORMAT_HEX_LONG_WITH_ALPHA => self::PATTERN_HEX_LONG_WITH_ALPHA,
60+
self::FORMAT_HEX_SHORT => self::PATTERN_HEX_SHORT,
61+
self::FORMAT_HEX_SHORT_WITH_ALPHA => self::PATTERN_HEX_SHORT_WITH_ALPHA,
62+
self::FORMAT_BASIC_NAMED_COLORS => self::PATTERN_BASIC_NAMED_COLORS,
63+
self::FORMAT_EXTENDED_NAMED_COLORS => self::PATTERN_EXTENDED_NAMED_COLORS,
64+
self::FORMAT_SYSTEM_COLORS => self::PATTERN_SYSTEM_COLORS,
65+
self::FORMAT_KEYWORDS => self::PATTERN_KEYWORDS,
66+
self::FORMAT_RGB => self::PATTERN_RGB,
67+
self::FORMAT_RGBA => self::PATTERN_RGBA,
68+
self::FORMAT_HSL => self::PATTERN_HSL,
69+
self::FORMAT_HSLA => self::PATTERN_HSLA
70+
];
71+
72+
private array $formats = self::COLOR_FORMATS;
73+
private string $message = 'The {{ name }} value is not a valid CSS color.';
74+
75+
public function __construct(
76+
?array $formats = null,
77+
?string $message = null
78+
)
79+
{
80+
$this->formats = $formats ?? $this->formats;
81+
$this->message = $message ?? $this->message;
82+
}
83+
84+
public function assert(mixed $value, ?string $name = null): void
85+
{
86+
foreach ($this->formats as $format) {
87+
if (!\in_array($format, self::COLOR_FORMATS, true)) {
88+
throw new UnexpectedOptionException('format', self::COLOR_FORMATS, $format);
89+
}
90+
}
91+
92+
if (!\is_string($value)) {
93+
throw new UnexpectedTypeException('string', get_debug_type($value));
94+
}
95+
96+
foreach ($this->formats as $format) {
97+
$pattern = self::COLOR_PATTERNS[$format];
98+
99+
// it is valid if at least one pattern matches
100+
if (\preg_match($pattern, $value)) {
101+
return;
102+
}
103+
}
104+
105+
throw new CssColorException(
106+
message: $this->message,
107+
parameters: [
108+
'value' => $value,
109+
'name' => $name,
110+
'formats' => $this->formats
111+
]
112+
);
113+
}
114+
}

src/StaticValidatorInterface.php

+5
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ public static function country(
3838
?string $message = null
3939
): ChainedValidatorInterface&Validator;
4040

41+
public static function cssColor(
42+
?array $formats = null,
43+
?string $message = null
44+
): ChainedValidatorInterface&Validator;
45+
4146
public static function dateTime(
4247
string $format = 'Y-m-d H:i:s',
4348
?string $message = null

0 commit comments

Comments
 (0)