PluginProbe ʕ •ᴥ•ʔ
Secure Custom Fields / trunk
Secure Custom Fields vtrunk
6.9.1 6.9.0 6.8.9 6.8.7 6.8.8 6.8.6 6.8.4 6.8.5 trunk 6.4.0-beta1 6.4.0-beta2 6.4.1 6.4.1-beta3 6.4.1-beta4 6.4.1-beta5 6.4.1-beta6 6.4.1-beta7 6.4.2 6.5.0 6.5.1 6.5.2 6.5.3 6.5.4 6.5.5 6.5.6 6.5.7 6.6.0 6.7.0 6.7.1 6.8.0 6.8.1 6.8.2 6.8.3
secure-custom-fields / vendor / justinrainbow / json-schema / src / JsonSchema / Constraints / UndefinedConstraint.php
secure-custom-fields / vendor / justinrainbow / json-schema / src / JsonSchema / Constraints Last commit date
TypeCheck 8 months ago BaseConstraint.php 8 months ago CollectionConstraint.php 8 months ago Constraint.php 8 months ago ConstraintInterface.php 8 months ago EnumConstraint.php 8 months ago Factory.php 8 months ago FormatConstraint.php 8 months ago NumberConstraint.php 8 months ago ObjectConstraint.php 8 months ago SchemaConstraint.php 8 months ago StringConstraint.php 8 months ago TypeConstraint.php 8 months ago UndefinedConstraint.php 8 months ago
UndefinedConstraint.php
421 lines
1 <?php
2
3 /*
4 * This file is part of the JsonSchema package.
5 *
6 * For the full copyright and license information, please view the LICENSE
7 * file that was distributed with this source code.
8 */
9
10 namespace JsonSchema\Constraints;
11
12 use JsonSchema\Constraints\TypeCheck\LooseTypeCheck;
13 use JsonSchema\Entity\JsonPointer;
14 use JsonSchema\Exception\ValidationException;
15 use JsonSchema\Uri\UriResolver;
16
17 /**
18 * The UndefinedConstraint Constraints
19 *
20 * @author Robert Schönthal <seroscho@googlemail.com>
21 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
22 */
23 #[\AllowDynamicProperties]
24 class UndefinedConstraint extends Constraint
25 {
26 /**
27 * @var array List of properties to which a default value has been applied
28 */
29 protected $appliedDefaults = array();
30
31 /**
32 * {@inheritdoc}
33 */
34 public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null, $fromDefault = false)
35 {
36 if (is_null($schema) || !is_object($schema)) {
37 return;
38 }
39
40 $path = $this->incrementPath($path ?: new JsonPointer(''), $i);
41 if ($fromDefault) {
42 $path->setFromDefault();
43 }
44
45 // check special properties
46 $this->validateCommonProperties($value, $schema, $path, $i);
47
48 // check allOf, anyOf, and oneOf properties
49 $this->validateOfProperties($value, $schema, $path, '');
50
51 // check known types
52 $this->validateTypes($value, $schema, $path, $i);
53 }
54
55 /**
56 * Validates the value against the types
57 *
58 * @param mixed $value
59 * @param mixed $schema
60 * @param JsonPointer $path
61 * @param string $i
62 */
63 public function validateTypes(&$value, $schema, JsonPointer $path, $i = null)
64 {
65 // check array
66 if ($this->getTypeCheck()->isArray($value)) {
67 $this->checkArray($value, $schema, $path, $i);
68 }
69
70 // check object
71 if (LooseTypeCheck::isObject($value)) { // object processing should always be run on assoc arrays,
72 // so use LooseTypeCheck here even if CHECK_MODE_TYPE_CAST
73 // is not set (i.e. don't use $this->getTypeCheck() here).
74 $this->checkObject(
75 $value,
76 $schema,
77 $path,
78 isset($schema->properties) ? $schema->properties : null,
79 isset($schema->additionalProperties) ? $schema->additionalProperties : null,
80 isset($schema->patternProperties) ? $schema->patternProperties : null,
81 $this->appliedDefaults
82 );
83 }
84
85 // check string
86 if (is_string($value)) {
87 $this->checkString($value, $schema, $path, $i);
88 }
89
90 // check numeric
91 if (is_numeric($value)) {
92 $this->checkNumber($value, $schema, $path, $i);
93 }
94
95 // check enum
96 if (isset($schema->enum)) {
97 $this->checkEnum($value, $schema, $path, $i);
98 }
99 }
100
101 /**
102 * Validates common properties
103 *
104 * @param mixed $value
105 * @param mixed $schema
106 * @param JsonPointer $path
107 * @param string $i
108 */
109 protected function validateCommonProperties(&$value, $schema, JsonPointer $path, $i = '')
110 {
111 // if it extends another schema, it must pass that schema as well
112 if (isset($schema->extends)) {
113 if (is_string($schema->extends)) {
114 $schema->extends = $this->validateUri($schema, $schema->extends);
115 }
116 if (is_array($schema->extends)) {
117 foreach ($schema->extends as $extends) {
118 $this->checkUndefined($value, $extends, $path, $i);
119 }
120 } else {
121 $this->checkUndefined($value, $schema->extends, $path, $i);
122 }
123 }
124
125 // Apply default values from schema
126 if (!$path->fromDefault()) {
127 $this->applyDefaultValues($value, $schema, $path);
128 }
129
130 // Verify required values
131 if ($this->getTypeCheck()->isObject($value)) {
132 if (!($value instanceof self) && isset($schema->required) && is_array($schema->required)) {
133 // Draft 4 - Required is an array of strings - e.g. "required": ["foo", ...]
134 foreach ($schema->required as $required) {
135 if (!$this->getTypeCheck()->propertyExists($value, $required)) {
136 $this->addError(
137 $this->incrementPath($path ?: new JsonPointer(''), $required),
138 'The property ' . $required . ' is required',
139 'required'
140 );
141 }
142 }
143 } elseif (isset($schema->required) && !is_array($schema->required)) {
144 // Draft 3 - Required attribute - e.g. "foo": {"type": "string", "required": true}
145 if ($schema->required && $value instanceof self) {
146 $propertyPaths = $path->getPropertyPaths();
147 $propertyName = end($propertyPaths);
148 $this->addError(
149 $path,
150 'The property ' . $propertyName . ' is required',
151 'required'
152 );
153 }
154 } else {
155 // if the value is both undefined and not required, skip remaining checks
156 // in this method which assume an actual, defined instance when validating.
157 if ($value instanceof self) {
158 return;
159 }
160 }
161 }
162
163 // Verify type
164 if (!($value instanceof self)) {
165 $this->checkType($value, $schema, $path, $i);
166 }
167
168 // Verify disallowed items
169 if (isset($schema->disallow)) {
170 $initErrors = $this->getErrors();
171
172 $typeSchema = new \stdClass();
173 $typeSchema->type = $schema->disallow;
174 $this->checkType($value, $typeSchema, $path);
175
176 // if no new errors were raised it must be a disallowed value
177 if (count($this->getErrors()) == count($initErrors)) {
178 $this->addError($path, 'Disallowed value was matched', 'disallow');
179 } else {
180 $this->errors = $initErrors;
181 }
182 }
183
184 if (isset($schema->not)) {
185 $initErrors = $this->getErrors();
186 $this->checkUndefined($value, $schema->not, $path, $i);
187
188 // if no new errors were raised then the instance validated against the "not" schema
189 if (count($this->getErrors()) == count($initErrors)) {
190 $this->addError($path, 'Matched a schema which it should not', 'not');
191 } else {
192 $this->errors = $initErrors;
193 }
194 }
195
196 // Verify that dependencies are met
197 if (isset($schema->dependencies) && $this->getTypeCheck()->isObject($value)) {
198 $this->validateDependencies($value, $schema->dependencies, $path);
199 }
200 }
201
202 /**
203 * Check whether a default should be applied for this value
204 *
205 * @param mixed $schema
206 * @param mixed $parentSchema
207 * @param bool $requiredOnly
208 *
209 * @return bool
210 */
211 private function shouldApplyDefaultValue($requiredOnly, $schema, $name = null, $parentSchema = null)
212 {
213 // required-only mode is off
214 if (!$requiredOnly) {
215 return true;
216 }
217 // draft-04 required is set
218 if (
219 $name !== null
220 && isset($parentSchema->required)
221 && is_array($parentSchema->required)
222 && in_array($name, $parentSchema->required)
223 ) {
224 return true;
225 }
226 // draft-03 required is set
227 if (isset($schema->required) && !is_array($schema->required) && $schema->required) {
228 return true;
229 }
230 // default case
231 return false;
232 }
233
234 /**
235 * Apply default values
236 *
237 * @param mixed $value
238 * @param mixed $schema
239 * @param JsonPointer $path
240 */
241 protected function applyDefaultValues(&$value, $schema, $path)
242 {
243 // only apply defaults if feature is enabled
244 if (!$this->factory->getConfig(self::CHECK_MODE_APPLY_DEFAULTS)) {
245 return;
246 }
247
248 // apply defaults if appropriate
249 $requiredOnly = $this->factory->getConfig(self::CHECK_MODE_ONLY_REQUIRED_DEFAULTS);
250 if (isset($schema->properties) && LooseTypeCheck::isObject($value)) {
251 // $value is an object or assoc array, and properties are defined - treat as an object
252 foreach ($schema->properties as $currentProperty => $propertyDefinition) {
253 $propertyDefinition = $this->factory->getSchemaStorage()->resolveRefSchema($propertyDefinition);
254 if (
255 !LooseTypeCheck::propertyExists($value, $currentProperty)
256 && property_exists($propertyDefinition, 'default')
257 && $this->shouldApplyDefaultValue($requiredOnly, $propertyDefinition, $currentProperty, $schema)
258 ) {
259 // assign default value
260 if (is_object($propertyDefinition->default)) {
261 LooseTypeCheck::propertySet($value, $currentProperty, clone $propertyDefinition->default);
262 } else {
263 LooseTypeCheck::propertySet($value, $currentProperty, $propertyDefinition->default);
264 }
265 $this->appliedDefaults[] = $currentProperty;
266 }
267 }
268 } elseif (isset($schema->items) && LooseTypeCheck::isArray($value)) {
269 $items = array();
270 if (LooseTypeCheck::isArray($schema->items)) {
271 $items = $schema->items;
272 } elseif (isset($schema->minItems) && count($value) < $schema->minItems) {
273 $items = array_fill(count($value), $schema->minItems - count($value), $schema->items);
274 }
275 // $value is an array, and items are defined - treat as plain array
276 foreach ($items as $currentItem => $itemDefinition) {
277 $itemDefinition = $this->factory->getSchemaStorage()->resolveRefSchema($itemDefinition);
278 if (
279 !array_key_exists($currentItem, $value)
280 && property_exists($itemDefinition, 'default')
281 && $this->shouldApplyDefaultValue($requiredOnly, $itemDefinition)) {
282 if (is_object($itemDefinition->default)) {
283 $value[$currentItem] = clone $itemDefinition->default;
284 } else {
285 $value[$currentItem] = $itemDefinition->default;
286 }
287 }
288 $path->setFromDefault();
289 }
290 } elseif (
291 $value instanceof self
292 && property_exists($schema, 'default')
293 && $this->shouldApplyDefaultValue($requiredOnly, $schema)) {
294 // $value is a leaf, not a container - apply the default directly
295 $value = is_object($schema->default) ? clone $schema->default : $schema->default;
296 $path->setFromDefault();
297 }
298 }
299
300 /**
301 * Validate allOf, anyOf, and oneOf properties
302 *
303 * @param mixed $value
304 * @param mixed $schema
305 * @param JsonPointer $path
306 * @param string $i
307 */
308 protected function validateOfProperties(&$value, $schema, JsonPointer $path, $i = '')
309 {
310 // Verify type
311 if ($value instanceof self) {
312 return;
313 }
314
315 if (isset($schema->allOf)) {
316 $isValid = true;
317 foreach ($schema->allOf as $allOf) {
318 $initErrors = $this->getErrors();
319 $this->checkUndefined($value, $allOf, $path, $i);
320 $isValid = $isValid && (count($this->getErrors()) == count($initErrors));
321 }
322 if (!$isValid) {
323 $this->addError($path, 'Failed to match all schemas', 'allOf');
324 }
325 }
326
327 if (isset($schema->anyOf)) {
328 $isValid = false;
329 $startErrors = $this->getErrors();
330 $caughtException = null;
331 foreach ($schema->anyOf as $anyOf) {
332 $initErrors = $this->getErrors();
333 try {
334 $this->checkUndefined($value, $anyOf, $path, $i);
335 if ($isValid = (count($this->getErrors()) == count($initErrors))) {
336 break;
337 }
338 } catch (ValidationException $e) {
339 $isValid = false;
340 }
341 }
342 if (!$isValid) {
343 $this->addError($path, 'Failed to match at least one schema', 'anyOf');
344 } else {
345 $this->errors = $startErrors;
346 }
347 }
348
349 if (isset($schema->oneOf)) {
350 $allErrors = array();
351 $matchedSchemas = 0;
352 $startErrors = $this->getErrors();
353 foreach ($schema->oneOf as $oneOf) {
354 try {
355 $this->errors = array();
356 $this->checkUndefined($value, $oneOf, $path, $i);
357 if (count($this->getErrors()) == 0) {
358 $matchedSchemas++;
359 }
360 $allErrors = array_merge($allErrors, array_values($this->getErrors()));
361 } catch (ValidationException $e) {
362 // deliberately do nothing here - validation failed, but we want to check
363 // other schema options in the OneOf field.
364 }
365 }
366 if ($matchedSchemas !== 1) {
367 $this->addErrors(array_merge($allErrors, $startErrors));
368 $this->addError($path, 'Failed to match exactly one schema', 'oneOf');
369 } else {
370 $this->errors = $startErrors;
371 }
372 }
373 }
374
375 /**
376 * Validate dependencies
377 *
378 * @param mixed $value
379 * @param mixed $dependencies
380 * @param JsonPointer $path
381 * @param string $i
382 */
383 protected function validateDependencies($value, $dependencies, JsonPointer $path, $i = '')
384 {
385 foreach ($dependencies as $key => $dependency) {
386 if ($this->getTypeCheck()->propertyExists($value, $key)) {
387 if (is_string($dependency)) {
388 // Draft 3 string is allowed - e.g. "dependencies": {"bar": "foo"}
389 if (!$this->getTypeCheck()->propertyExists($value, $dependency)) {
390 $this->addError($path, "$key depends on $dependency and $dependency is missing", 'dependencies');
391 }
392 } elseif (is_array($dependency)) {
393 // Draft 4 must be an array - e.g. "dependencies": {"bar": ["foo"]}
394 foreach ($dependency as $d) {
395 if (!$this->getTypeCheck()->propertyExists($value, $d)) {
396 $this->addError($path, "$key depends on $d and $d is missing", 'dependencies');
397 }
398 }
399 } elseif (is_object($dependency)) {
400 // Schema - e.g. "dependencies": {"bar": {"properties": {"foo": {...}}}}
401 $this->checkUndefined($value, $dependency, $path, $i);
402 }
403 }
404 }
405 }
406
407 protected function validateUri($schema, $schemaUri = null)
408 {
409 $resolver = new UriResolver();
410 $retriever = $this->factory->getUriRetriever();
411
412 $jsonSchema = null;
413 if ($resolver->isValid($schemaUri)) {
414 $schemaId = property_exists($schema, 'id') ? $schema->id : null;
415 $jsonSchema = $retriever->retrieve($schemaId, $schemaUri);
416 }
417
418 return $jsonSchema;
419 }
420 }
421