Format::addExtension()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace League\JsonGuard\Constraint\DraftFour;
4
5
use League\JsonGuard\Assert;
6
use League\JsonGuard\Constraint\DraftFour\Format\FormatExtensionInterface;
7
use League\JsonGuard\ConstraintInterface;
8
use League\JsonGuard\Validator;
9
use function League\JsonGuard\error;
10
use League\JsonGuard\Exception\InvalidSchemaException;
11
12
final class Format implements ConstraintInterface
13
{
14
    const KEYWORD = 'format';
15
16
    /**
17
     * @see https://tools.ietf.org/html/rfc3339#section-5.6
18
     */
19
    const DATE_TIME_PATTERN =
20
        '/^(?<fullyear>\d{4})-(?<month>0[1-9]|1[0-2])-(?<mday>0[1-9]|[12][0-9]|3[01])' . 'T' .
21
        '(?<hour>[01][0-9]|2[0-3]):(?<minute>[0-5][0-9]):(?<second>[0-5][0-9]|60)(?<secfrac>\.[0-9]+)?' .
22
        '(Z|(\+|-)(?<offset_hour>[01][0-9]|2[0-3]):(?<offset_minute>[0-5][0-9]))$/i';
23
24
    /**
25
     * @internal
26
     */
27
    const HOST_NAME_PATTERN = '/^[_a-z]+\.([_a-z]+\.?)+$/i';
28
29
    /**
30
     * @internal
31
     *
32
     * @var string[]
33
     */
34
    const KNOWN_FORMATS = ['date-time', 'uri', 'email', 'ipv4', 'ipv6','hostname'];
35
36
    /**
37
     * @var \League\JsonGuard\Constraint\DraftFour\Format\FormatExtensionInterface[]
38
     */
39
    private $extensions = [];
40
41
    /**
42
     * @var bool
43
     */
44
    private $ignoreUnknownFormats = true;
45
46
    /**
47
     * Any custom format extensions to use, indexed by the format name.
48
     *
49
     * @param array \League\JsonGuard\Constraint\DraftFour\Format\FormatExtensionInterface[] $extensions
50
     * @param bool                                                                           $ignoreUnknownFormats
51
     */
52 82
    public function __construct(array $extensions = [], $ignoreUnknownFormats = true)
53
    {
54 82
        foreach ($extensions as $format => $extension) {
55 8
            $this->addExtension($format, $extension);
56
        }
57
58 82
        $this->ignoreUnknownFormats = $ignoreUnknownFormats;
59 82
    }
60
61
    /**
62
     * Add a custom format extension.
63
     *
64
     * @param string                                                                 $format
65
     * @param \League\JsonGuard\Constraint\DraftFour\Format\FormatExtensionInterface $extension
66
     */
67 12
    public function addExtension($format, FormatExtensionInterface $extension)
68
    {
69 12
        $this->extensions[$format] = $extension;
70 12
    }
71
72
    /**
73
     * Define if unknown formats shall be ignored
74
     *
75
     * @param boolean
76
     */
77 16
    public function setIgnoreUnknownFormats($ignoreUnknownFormats)
78
    {
79 16
        $this->ignoreUnknownFormats = $ignoreUnknownFormats;
80 16
    }
81
82
    /**
83
     * {@inheritdoc}
84
     */
85 82
    public function validate($value, $parameter, Validator $validator)
86
    {
87 82
        Assert::type($parameter, 'string', self::KEYWORD, $validator->getSchemaPath());
88
89 80
        if (isset($this->extensions[$parameter])) {
90 12
            return $this->extensions[$parameter]->validate($value, $validator);
91
        }
92
93 34
        switch ($parameter) {
94 68
            case 'date-time':
95 32
                return self::validateRegex(
96 32
                    $value,
97 32
                    self::DATE_TIME_PATTERN,
98 32
                    $validator
99
                );
100 38
            case 'uri':
101 4
                return self::validateFilter(
102 4
                    $value,
103 4
                    FILTER_VALIDATE_URL,
104 4
                    null,
105 4
                    $validator
106
                );
107 36
            case 'email':
108 4
                return self::validateFilter(
109 4
                    $value,
110 4
                    FILTER_VALIDATE_EMAIL,
111 4
                    null,
112 4
                    $validator
113
                );
114 34
            case 'ipv4':
115 2
                return self::validateFilter(
116 2
                    $value,
117 2
                    FILTER_VALIDATE_IP,
118 2
                    FILTER_FLAG_IPV4,
119 2
                    $validator
120
                );
121 34
            case 'ipv6':
122 2
                return self::validateFilter(
123 2
                    $value,
124 2
                    FILTER_VALIDATE_IP,
125 2
                    FILTER_FLAG_IPV6,
126 2
                    $validator
127
                );
128 34
            case 'hostname':
129 2
                return self::validateRegex(
130 2
                    $value,
131 2
                    self::HOST_NAME_PATTERN,
132 2
                    $validator
133
                );
134
            default:
135 32
                if (!$this->ignoreUnknownFormats) {
136 16
                    throw InvalidSchemaException::invalidParameter(
137 16
                        $parameter,
138 16
                        array_merge(self::KNOWN_FORMATS, array_keys($this->extensions)),
139 16
                        self::KEYWORD,
140 16
                        $validator->getSchemaPath()
141
                    );
142
                }
143
        }
144 16
    }
145
146
    /**
147
     * @param mixed                       $value
148
     * @param string                      $pattern
149
     * @param \League\JsonGuard\Validator $validator
150
     *
151
     * @return \League\JsonGuard\ValidationError|null
152
     */
153 32
    private static function validateRegex($value, $pattern, Validator $validator)
154
    {
155 32
        if (!is_string($value) || preg_match($pattern, $value) === 1) {
156 20
            return null;
157
        }
158
159 14
        return error('The value {data} must match the format {parameter}.', $validator);
160
    }
161
162
    /**
163
     * @param mixed                       $value
164
     * @param int                         $filter
165
     * @param mixed                       $options
166
     * @param \League\JsonGuard\Validator $validator
167
     *
168
     * @return \League\JsonGuard\ValidationError|null
169
     */
170 6
    private static function validateFilter($value, $filter, $options, Validator $validator)
171
    {
172 6
        if (!is_string($value) || filter_var($value, $filter, $options) !== false) {
173 6
            return null;
174
        }
175
176
        // This workaround allows otherwise valid protocol relative urls to pass.
177
        // @see https://bugs.php.net/bug.php?id=72301
178 2
        if ($filter === FILTER_VALIDATE_URL && is_string($value) && strpos($value, '//') === 0) {
179 2
            if (filter_var('http:' . $value, $filter, $options) !== false) {
180 2
                return null;
181
            }
182
        }
183
184 2
        return error('The value must match the format {parameter}.', $validator);
185
    }
186
}
187