1
|
|
|
<?php |
2
|
|
|
namespace Romm\ConfigurationObject\Legacy\Validation; |
3
|
|
|
|
4
|
|
|
/* |
5
|
|
|
* This file is part of the TYPO3 CMS project. |
6
|
|
|
* |
7
|
|
|
* It is free software; you can redistribute it and/or modify it under |
8
|
|
|
* the terms of the GNU General Public License, either version 2 |
9
|
|
|
* of the License, or any later version. |
10
|
|
|
* |
11
|
|
|
* For the full copyright and license information, please read the |
12
|
|
|
* LICENSE.txt file that was distributed with this source code. |
13
|
|
|
* |
14
|
|
|
* The TYPO3 project - inspiring people to share! |
15
|
|
|
*/ |
16
|
|
|
|
17
|
|
|
use TYPO3\CMS\Core\Utility\ClassNamingUtility; |
18
|
|
|
use TYPO3\CMS\Core\Utility\GeneralUtility; |
19
|
|
|
use TYPO3\CMS\Extbase\Utility\TypeHandlingUtility; |
20
|
|
|
use TYPO3\CMS\Extbase\Validation\Exception\NoSuchValidatorException; |
21
|
|
|
use TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* Validator resolver to automatically find an appropriate validator for a given subject |
25
|
|
|
*/ |
26
|
|
|
class ValidatorResolver implements \TYPO3\CMS\Core\SingletonInterface |
27
|
|
|
{ |
28
|
|
|
/** |
29
|
|
|
* Match validator names and options |
30
|
|
|
* @todo: adjust [a-z0-9_:.\\\\] once Tx_Extbase_Foo syntax is outdated. |
31
|
|
|
* |
32
|
|
|
* @var string |
33
|
|
|
*/ |
34
|
|
|
const PATTERN_MATCH_VALIDATORS = '/ |
35
|
|
|
(?:^|,\s*) |
36
|
|
|
(?P<validatorName>[a-z0-9_:.\\\\]+) |
37
|
|
|
\s* |
38
|
|
|
(?:\( |
39
|
|
|
(?P<validatorOptions>(?:\s*[a-z0-9]+\s*=\s*(?: |
40
|
|
|
"(?:\\\\"|[^"])*" |
41
|
|
|
|\'(?:\\\\\'|[^\'])*\' |
42
|
|
|
|(?:\s|[^,"\']*) |
43
|
|
|
)(?:\s|,)*)*) |
44
|
|
|
\))? |
45
|
|
|
/ixS'; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* Match validator options (to parse actual options) |
49
|
|
|
* @var string |
50
|
|
|
*/ |
51
|
|
|
const PATTERN_MATCH_VALIDATOROPTIONS = '/ |
52
|
|
|
\s* |
53
|
|
|
(?P<optionName>[a-z0-9]+) |
54
|
|
|
\s*=\s* |
55
|
|
|
(?P<optionValue> |
56
|
|
|
"(?:\\\\"|[^"])*" |
57
|
|
|
|\'(?:\\\\\'|[^\'])*\' |
58
|
|
|
|(?:\s|[^,"\']*) |
59
|
|
|
) |
60
|
|
|
/ixS'; |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface |
64
|
|
|
*/ |
65
|
|
|
protected $objectManager; |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* @var \TYPO3\CMS\Extbase\Reflection\ReflectionService |
69
|
|
|
*/ |
70
|
|
|
protected $reflectionService; |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* @var array |
74
|
|
|
*/ |
75
|
|
|
protected $baseValidatorConjunctions = []; |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager |
79
|
|
|
*/ |
80
|
|
|
public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager) |
81
|
|
|
{ |
82
|
|
|
$this->objectManager = $objectManager; |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* @param \TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService |
87
|
|
|
*/ |
88
|
|
|
public function injectReflectionService(\TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService) |
89
|
|
|
{ |
90
|
|
|
$this->reflectionService = $reflectionService; |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* Get a validator for a given data type. Returns a validator implementing |
95
|
|
|
* the \TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface or NULL if no validator |
96
|
|
|
* could be resolved. |
97
|
|
|
* |
98
|
|
|
* @param string $validatorType Either one of the built-in data types or fully qualified validator class name |
99
|
|
|
* @param array $validatorOptions Options to be passed to the validator |
100
|
|
|
* @return \TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface Validator or NULL if none found. |
101
|
|
|
*/ |
102
|
|
|
public function createValidator($validatorType, array $validatorOptions = []) |
103
|
|
|
{ |
104
|
|
|
try { |
105
|
|
|
/** |
106
|
|
|
* @todo remove throwing Exceptions in resolveValidatorObjectName |
107
|
|
|
*/ |
108
|
|
|
$validatorObjectName = $this->resolveValidatorObjectName($validatorType); |
109
|
|
|
|
110
|
|
|
$validator = $this->objectManager->get($validatorObjectName, $validatorOptions); |
|
|
|
|
111
|
|
|
|
112
|
|
|
if (!($validator instanceof \TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface)) { |
113
|
|
|
throw new Exception\NoSuchValidatorException('The validator "' . $validatorObjectName . '" does not implement TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface!', 1300694875); |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
return $validator; |
117
|
|
|
} catch (NoSuchValidatorException $e) { |
118
|
|
|
GeneralUtility::devLog($e->getMessage(), 'extbase', GeneralUtility::SYSLOG_SEVERITY_INFO); |
119
|
|
|
return null; |
120
|
|
|
} |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* Resolves and returns the base validator conjunction for the given data type. |
125
|
|
|
* |
126
|
|
|
* If no validator could be resolved (which usually means that no validation is necessary), |
127
|
|
|
* NULL is returned. |
128
|
|
|
* |
129
|
|
|
* @param string $targetClassName The data type to search a validator for. Usually the fully qualified object name |
130
|
|
|
* @return ConjunctionValidator The validator conjunction or NULL |
131
|
|
|
*/ |
132
|
|
|
public function getBaseValidatorConjunction($targetClassName) |
133
|
|
|
{ |
134
|
|
|
if (!array_key_exists($targetClassName, $this->baseValidatorConjunctions)) { |
135
|
|
|
$this->buildBaseValidatorConjunction($targetClassName, $targetClassName); |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
return $this->baseValidatorConjunctions[$targetClassName]; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* Detects and registers any validators for arguments: |
143
|
|
|
* - by the data type specified in the param annotations |
144
|
|
|
* - additional validators specified in the validate annotations of a method |
145
|
|
|
* |
146
|
|
|
* @param string $className |
147
|
|
|
* @param string $methodName |
148
|
|
|
* @param array $methodParameters Optional pre-compiled array of method parameters |
149
|
|
|
* @param array $methodValidateAnnotations Optional pre-compiled array of validate annotations (as array) |
150
|
|
|
* @return ConjunctionValidator[] An Array of ValidatorConjunctions for each method parameters. |
151
|
|
|
* @throws \TYPO3\CMS\Extbase\Validation\Exception\InvalidValidationConfigurationException |
152
|
|
|
* @throws \TYPO3\CMS\Extbase\Validation\Exception\NoSuchValidatorException |
153
|
|
|
* @throws \TYPO3\CMS\Extbase\Validation\Exception\InvalidTypeHintException |
154
|
|
|
*/ |
155
|
|
|
public function buildMethodArgumentsValidatorConjunctions($className, $methodName, array $methodParameters = null, array $methodValidateAnnotations = null) |
156
|
|
|
{ |
157
|
|
|
/** @var ConjunctionValidator[] $validatorConjunctions */ |
158
|
|
|
$validatorConjunctions = []; |
159
|
|
|
|
160
|
|
|
if ($methodParameters === null) { |
161
|
|
|
$methodParameters = $this->reflectionService->getMethodParameters($className, $methodName); |
162
|
|
|
} |
163
|
|
|
if (empty($methodParameters)) { |
164
|
|
|
return $validatorConjunctions; |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
foreach ($methodParameters as $parameterName => $methodParameter) { |
168
|
|
|
/** @var ConjunctionValidator $validatorConjunction */ |
169
|
|
|
$validatorConjunction = $this->createValidator(ConjunctionValidator::class); |
170
|
|
|
|
171
|
|
|
if (!array_key_exists('type', $methodParameter)) { |
172
|
|
|
throw new Exception\InvalidTypeHintException('Missing type information, probably no @param annotation for parameter "$' . $parameterName . '" in ' . $className . '->' . $methodName . '()', 1281962564); |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
// @todo: remove check for old underscore model name syntax once it's possible |
176
|
|
|
if (strpbrk($methodParameter['type'], '_\\') === false) { |
177
|
|
|
$typeValidator = $this->createValidator($methodParameter['type']); |
178
|
|
|
} else { |
179
|
|
|
$typeValidator = null; |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
if ($typeValidator !== null) { |
183
|
|
|
$validatorConjunction->addValidator($typeValidator); |
184
|
|
|
} |
185
|
|
|
$validatorConjunctions[$parameterName] = $validatorConjunction; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
if ($methodValidateAnnotations === null) { |
189
|
|
|
$validateAnnotations = $this->getMethodValidateAnnotations($className, $methodName); |
190
|
|
|
$methodValidateAnnotations = array_map(function ($validateAnnotation) { |
191
|
|
|
return [ |
192
|
|
|
'type' => $validateAnnotation['validatorName'], |
193
|
|
|
'options' => $validateAnnotation['validatorOptions'], |
194
|
|
|
'argumentName' => $validateAnnotation['argumentName'], |
195
|
|
|
]; |
196
|
|
|
}, $validateAnnotations); |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
foreach ($methodValidateAnnotations as $annotationParameters) { |
200
|
|
|
$newValidator = $this->createValidator($annotationParameters['type'], $annotationParameters['options']); |
201
|
|
|
if ($newValidator === null) { |
202
|
|
|
throw new Exception\NoSuchValidatorException('Invalid validate annotation in ' . $className . '->' . $methodName . '(): Could not resolve class name for validator "' . $annotationParameters['type'] . '".', 1239853109); |
203
|
|
|
} |
204
|
|
|
if (isset($validatorConjunctions[$annotationParameters['argumentName']])) { |
205
|
|
|
$validatorConjunctions[$annotationParameters['argumentName']]->addValidator($newValidator); |
206
|
|
|
} elseif (strpos($annotationParameters['argumentName'], '.') !== false) { |
207
|
|
|
$objectPath = explode('.', $annotationParameters['argumentName']); |
208
|
|
|
$argumentName = array_shift($objectPath); |
209
|
|
|
$validatorConjunctions[$argumentName]->addValidator($this->buildSubObjectValidator($objectPath, $newValidator)); |
210
|
|
|
} else { |
211
|
|
|
throw new Exception\InvalidValidationConfigurationException('Invalid validate annotation in ' . $className . '->' . $methodName . '(): Validator specified for argument name "' . $annotationParameters['argumentName'] . '", but this argument does not exist.', 1253172726); |
212
|
|
|
} |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
return $validatorConjunctions; |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
/** |
219
|
|
|
* Builds a chain of nested object validators by specification of the given |
220
|
|
|
* object path. |
221
|
|
|
* |
222
|
|
|
* @param array $objectPath The object path |
223
|
|
|
* @param \TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface $propertyValidator The validator which should be added to the property specified by objectPath |
224
|
|
|
* @return \TYPO3\CMS\Extbase\Validation\Validator\GenericObjectValidator |
225
|
|
|
*/ |
226
|
|
|
protected function buildSubObjectValidator(array $objectPath, \TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface $propertyValidator) |
227
|
|
|
{ |
228
|
|
|
$rootObjectValidator = $this->objectManager->get(\TYPO3\CMS\Extbase\Validation\Validator\GenericObjectValidator::class, []); |
|
|
|
|
229
|
|
|
$parentObjectValidator = $rootObjectValidator; |
230
|
|
|
|
231
|
|
|
while (count($objectPath) > 1) { |
232
|
|
|
$subObjectValidator = $this->objectManager->get(\TYPO3\CMS\Extbase\Validation\Validator\GenericObjectValidator::class, []); |
|
|
|
|
233
|
|
|
$subPropertyName = array_shift($objectPath); |
234
|
|
|
$parentObjectValidator->addPropertyValidator($subPropertyName, $subObjectValidator); |
235
|
|
|
$parentObjectValidator = $subObjectValidator; |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
$parentObjectValidator->addPropertyValidator(array_shift($objectPath), $propertyValidator); |
239
|
|
|
|
240
|
|
|
return $rootObjectValidator; |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
/** |
244
|
|
|
* Builds a base validator conjunction for the given data type. |
245
|
|
|
* |
246
|
|
|
* The base validation rules are those which were declared directly in a class (typically |
247
|
|
|
* a model) through some validate annotations on properties. |
248
|
|
|
* |
249
|
|
|
* If a property holds a class for which a base validator exists, that property will be |
250
|
|
|
* checked as well, regardless of a validate annotation |
251
|
|
|
* |
252
|
|
|
* Additionally, if a custom validator was defined for the class in question, it will be added |
253
|
|
|
* to the end of the conjunction. A custom validator is found if it follows the naming convention |
254
|
|
|
* "Replace '\Model\' by '\Validator\' and append 'Validator'". |
255
|
|
|
* |
256
|
|
|
* Example: $targetClassName is TYPO3\Foo\Domain\Model\Quux, then the validator will be found if it has the |
257
|
|
|
* name TYPO3\Foo\Domain\Validator\QuuxValidator |
258
|
|
|
* |
259
|
|
|
* @param string $indexKey The key to use as index in $this->baseValidatorConjunctions; calculated from target class name and validation groups |
260
|
|
|
* @param string $targetClassName The data type to build the validation conjunction for. Needs to be the fully qualified class name. |
261
|
|
|
* @param array $validationGroups The validation groups to build the validator for |
262
|
|
|
* @throws \TYPO3\CMS\Extbase\Validation\Exception\NoSuchValidatorException |
263
|
|
|
* @throws \InvalidArgumentException |
264
|
|
|
*/ |
265
|
|
|
protected function buildBaseValidatorConjunction($indexKey, $targetClassName, array $validationGroups = []) |
266
|
|
|
{ |
267
|
|
|
$conjunctionValidator = new ConjunctionValidator(); |
268
|
|
|
$this->baseValidatorConjunctions[$indexKey] = $conjunctionValidator; |
269
|
|
|
|
270
|
|
|
// note: the simpleType check reduces lookups to the class loader |
271
|
|
|
if (!TypeHandlingUtility::isSimpleType($targetClassName) && class_exists($targetClassName)) { |
272
|
|
|
// Model based validator |
273
|
|
|
/** @var \TYPO3\CMS\Extbase\Validation\Validator\GenericObjectValidator $objectValidator */ |
274
|
|
|
$objectValidator = $this->objectManager->get(\TYPO3\CMS\Extbase\Validation\Validator\GenericObjectValidator::class, []); |
|
|
|
|
275
|
|
|
foreach ($this->reflectionService->getClassPropertyNames($targetClassName) as $classPropertyName) { |
276
|
|
|
$classPropertyTagsValues = $this->reflectionService->getPropertyTagsValues($targetClassName, $classPropertyName); |
277
|
|
|
|
278
|
|
|
if (!isset($classPropertyTagsValues['var'])) { |
279
|
|
|
throw new \InvalidArgumentException(sprintf('There is no @var annotation for property "%s" in class "%s".', $classPropertyName, $targetClassName), 1363778104); |
280
|
|
|
} |
281
|
|
|
try { |
282
|
|
|
$parsedType = TypeHandlingUtility::parseType(trim(implode('', $classPropertyTagsValues['var']), ' \\')); |
283
|
|
|
} catch (\TYPO3\CMS\Extbase\Utility\Exception\InvalidTypeException $exception) { |
284
|
|
|
throw new \InvalidArgumentException(sprintf(' @var annotation of ' . $exception->getMessage(), 'class "' . $targetClassName . '", property "' . $classPropertyName . '"'), 1315564744, $exception); |
285
|
|
|
} |
286
|
|
|
$propertyTargetClassName = $parsedType['type']; |
287
|
|
|
// note: the outer simpleType check reduces lookups to the class loader |
288
|
|
|
if (!TypeHandlingUtility::isSimpleType($propertyTargetClassName)) { |
289
|
|
|
if (TypeHandlingUtility::isCollectionType($propertyTargetClassName)) { |
290
|
|
|
$collectionValidator = $this->createValidator(\TYPO3\CMS\Extbase\Validation\Validator\CollectionValidator::class, ['elementType' => $parsedType['elementType'], 'validationGroups' => $validationGroups]); |
291
|
|
|
$objectValidator->addPropertyValidator($classPropertyName, $collectionValidator); |
|
|
|
|
292
|
|
|
} elseif (class_exists($propertyTargetClassName) && !TypeHandlingUtility::isCoreType($propertyTargetClassName) && $this->objectManager->isRegistered($propertyTargetClassName) && $this->objectManager->getScope($propertyTargetClassName) === \TYPO3\CMS\Extbase\Object\Container\Container::SCOPE_PROTOTYPE) { |
293
|
|
|
$validatorForProperty = $this->getBaseValidatorConjunction($propertyTargetClassName); |
294
|
|
|
if ($validatorForProperty !== null && $validatorForProperty->count() > 0) { |
295
|
|
|
$objectValidator->addPropertyValidator($classPropertyName, $validatorForProperty); |
296
|
|
|
} |
297
|
|
|
} |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
$validateAnnotations = []; |
301
|
|
|
// @todo: Resolve annotations via reflectionService once its available |
302
|
|
|
if (isset($classPropertyTagsValues['validate']) && is_array($classPropertyTagsValues['validate'])) { |
303
|
|
|
foreach ($classPropertyTagsValues['validate'] as $validateValue) { |
304
|
|
|
$parsedAnnotations = $this->parseValidatorAnnotation($validateValue); |
305
|
|
|
|
306
|
|
|
foreach ($parsedAnnotations['validators'] as $validator) { |
|
|
|
|
307
|
|
|
array_push($validateAnnotations, [ |
308
|
|
|
'argumentName' => $parsedAnnotations['argumentName'], |
309
|
|
|
'validatorName' => $validator['validatorName'], |
310
|
|
|
'validatorOptions' => $validator['validatorOptions'] |
311
|
|
|
]); |
312
|
|
|
} |
313
|
|
|
} |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
foreach ($validateAnnotations as $validateAnnotation) { |
317
|
|
|
// @todo: Respect validationGroups |
318
|
|
|
$newValidator = $this->createValidator($validateAnnotation['validatorName'], $validateAnnotation['validatorOptions']); |
319
|
|
|
if ($newValidator === null) { |
320
|
|
|
throw new Exception\NoSuchValidatorException('Invalid validate annotation in ' . $targetClassName . '::' . $classPropertyName . ': Could not resolve class name for validator "' . $validateAnnotation->type . '".', 1241098027); |
321
|
|
|
} |
322
|
|
|
$objectValidator->addPropertyValidator($classPropertyName, $newValidator); |
323
|
|
|
} |
324
|
|
|
} |
325
|
|
|
|
326
|
|
|
if (!empty($objectValidator->getPropertyValidators())) { |
327
|
|
|
$conjunctionValidator->addValidator($objectValidator); |
328
|
|
|
} |
329
|
|
|
} |
330
|
|
|
|
331
|
|
|
$this->addCustomValidators($targetClassName, $conjunctionValidator); |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
/** |
335
|
|
|
* This adds custom validators to the passed $conjunctionValidator. |
336
|
|
|
* |
337
|
|
|
* A custom validator is found if it follows the naming convention "Replace '\Model\' by '\Validator\' and |
338
|
|
|
* append 'Validator'". If found, it will be added to the $conjunctionValidator. |
339
|
|
|
* |
340
|
|
|
* In addition canValidate() will be called on all implementations of the ObjectValidatorInterface to find |
341
|
|
|
* all validators that could validate the target. The one with the highest priority will be added as well. |
342
|
|
|
* If multiple validators have the same priority, which one will be added is not deterministic. |
343
|
|
|
* |
344
|
|
|
* @param string $targetClassName |
345
|
|
|
* @param ConjunctionValidator $conjunctionValidator |
346
|
|
|
* @return Validator\ObjectValidatorInterface|null |
347
|
|
|
*/ |
348
|
|
|
protected function addCustomValidators($targetClassName, ConjunctionValidator &$conjunctionValidator) |
349
|
|
|
{ |
350
|
|
|
// @todo: get rid of ClassNamingUtility usage once we dropped underscored class name support |
351
|
|
|
$possibleValidatorClassName = ClassNamingUtility::translateModelNameToValidatorName($targetClassName); |
352
|
|
|
|
353
|
|
|
$customValidator = $this->createValidator($possibleValidatorClassName); |
354
|
|
|
if ($customValidator !== null) { |
355
|
|
|
$conjunctionValidator->addValidator($customValidator); |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
// @todo: find polytype validator for class |
359
|
|
|
} |
360
|
|
|
|
361
|
|
|
/** |
362
|
|
|
* Parses the validator options given in @validate annotations. |
363
|
|
|
* |
364
|
|
|
* @param string $validateValue |
365
|
|
|
* @return array |
366
|
|
|
*/ |
367
|
|
|
protected function parseValidatorAnnotation($validateValue) |
368
|
|
|
{ |
369
|
|
|
$matches = []; |
370
|
|
|
if ($validateValue[0] === '$') { |
371
|
|
|
$parts = explode(' ', $validateValue, 2); |
372
|
|
|
$validatorConfiguration = ['argumentName' => ltrim($parts[0], '$'), 'validators' => []]; |
373
|
|
|
preg_match_all(self::PATTERN_MATCH_VALIDATORS, $parts[1], $matches, PREG_SET_ORDER); |
374
|
|
|
} else { |
375
|
|
|
$validatorConfiguration = ['validators' => []]; |
376
|
|
|
preg_match_all(self::PATTERN_MATCH_VALIDATORS, $validateValue, $matches, PREG_SET_ORDER); |
377
|
|
|
} |
378
|
|
|
foreach ($matches as $match) { |
|
|
|
|
379
|
|
|
$validatorOptions = []; |
380
|
|
|
if (isset($match['validatorOptions'])) { |
381
|
|
|
$validatorOptions = $this->parseValidatorOptions($match['validatorOptions']); |
382
|
|
|
} |
383
|
|
|
$validatorConfiguration['validators'][] = ['validatorName' => $match['validatorName'], 'validatorOptions' => $validatorOptions]; |
384
|
|
|
} |
385
|
|
|
return $validatorConfiguration; |
386
|
|
|
} |
387
|
|
|
|
388
|
|
|
/** |
389
|
|
|
* Parses $rawValidatorOptions not containing quoted option values. |
390
|
|
|
* $rawValidatorOptions will be an empty string afterwards (pass by ref!). |
391
|
|
|
* |
392
|
|
|
* @param string $rawValidatorOptions |
393
|
|
|
* @return array An array of optionName/optionValue pairs |
394
|
|
|
*/ |
395
|
|
|
protected function parseValidatorOptions($rawValidatorOptions) |
396
|
|
|
{ |
397
|
|
|
$validatorOptions = []; |
398
|
|
|
$parsedValidatorOptions = []; |
399
|
|
|
preg_match_all(self::PATTERN_MATCH_VALIDATOROPTIONS, $rawValidatorOptions, $validatorOptions, PREG_SET_ORDER); |
400
|
|
|
foreach ($validatorOptions as $validatorOption) { |
|
|
|
|
401
|
|
|
$parsedValidatorOptions[trim($validatorOption['optionName'])] = trim($validatorOption['optionValue']); |
402
|
|
|
} |
403
|
|
|
array_walk($parsedValidatorOptions, [$this, 'unquoteString']); |
404
|
|
|
return $parsedValidatorOptions; |
405
|
|
|
} |
406
|
|
|
|
407
|
|
|
/** |
408
|
|
|
* Removes escapings from a given argument string and trims the outermost |
409
|
|
|
* quotes. |
410
|
|
|
* |
411
|
|
|
* This method is meant as a helper for regular expression results. |
412
|
|
|
* |
413
|
|
|
* @param string &$quotedValue Value to unquote |
414
|
|
|
*/ |
415
|
|
|
protected function unquoteString(&$quotedValue) |
416
|
|
|
{ |
417
|
|
|
switch ($quotedValue[0]) { |
418
|
|
|
case '"': |
419
|
|
|
$quotedValue = str_replace('\\"', '"', trim($quotedValue, '"')); |
420
|
|
|
break; |
421
|
|
|
case '\'': |
422
|
|
|
$quotedValue = str_replace('\\\'', '\'', trim($quotedValue, '\'')); |
423
|
|
|
break; |
424
|
|
|
} |
425
|
|
|
$quotedValue = str_replace('\\\\', '\\', $quotedValue); |
426
|
|
|
} |
427
|
|
|
|
428
|
|
|
/** |
429
|
|
|
* Returns an object of an appropriate validator for the given class. If no validator is available |
430
|
|
|
* FALSE is returned |
431
|
|
|
* |
432
|
|
|
* @param string $validatorName Either the fully qualified class name of the validator or the short name of a built-in validator |
433
|
|
|
* |
434
|
|
|
* @throws Exception\NoSuchValidatorException |
435
|
|
|
* @return string Name of the validator object |
436
|
|
|
*/ |
437
|
|
|
protected function resolveValidatorObjectName($validatorName) |
438
|
|
|
{ |
439
|
|
|
if (strpos($validatorName, ':') !== false || strpbrk($validatorName, '_\\') === false) { |
440
|
|
|
// Found shorthand validator, either extbase or foreign extension |
441
|
|
|
// NotEmpty or Acme.MyPck.Ext:MyValidator |
442
|
|
|
list($extensionName, $extensionValidatorName) = explode(':', $validatorName); |
443
|
|
|
|
444
|
|
|
if ($validatorName !== $extensionName && $extensionValidatorName !== '') { |
445
|
|
|
// Shorthand custom |
446
|
|
|
if (strpos($extensionName, '.') !== false) { |
447
|
|
|
$extensionNameParts = explode('.', $extensionName); |
448
|
|
|
$extensionName = array_pop($extensionNameParts); |
449
|
|
|
$vendorName = implode('\\', $extensionNameParts); |
450
|
|
|
$possibleClassName = $vendorName . '\\' . $extensionName . '\\Validation\\Validator\\' . $extensionValidatorName; |
451
|
|
|
} else { |
452
|
|
|
$possibleClassName = 'Tx_' . $extensionName . '_Validation_Validator_' . $extensionValidatorName; |
453
|
|
|
} |
454
|
|
|
} else { |
455
|
|
|
// Shorthand built in |
456
|
|
|
$possibleClassName = 'TYPO3\\CMS\\Extbase\\Validation\\Validator\\' . $this->getValidatorType($validatorName); |
457
|
|
|
} |
458
|
|
|
} else { |
459
|
|
|
// Full qualified |
460
|
|
|
// Tx_MyExt_Validation_Validator_MyValidator or \Acme\Ext\Validation\Validator\FooValidator |
461
|
|
|
$possibleClassName = $validatorName; |
462
|
|
|
if (!empty($possibleClassName) && $possibleClassName[0] === '\\') { |
463
|
|
|
$possibleClassName = substr($possibleClassName, 1); |
464
|
|
|
} |
465
|
|
|
} |
466
|
|
|
|
467
|
|
|
if (substr($possibleClassName, - strlen('Validator')) !== 'Validator') { |
468
|
|
|
$possibleClassName .= 'Validator'; |
469
|
|
|
} |
470
|
|
|
|
471
|
|
|
if (class_exists($possibleClassName)) { |
472
|
|
|
$possibleClassNameInterfaces = class_implements($possibleClassName); |
473
|
|
|
if (!in_array(\TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface::class, $possibleClassNameInterfaces)) { |
474
|
|
|
// The guessed validatorname is a valid class name, but does not implement the ValidatorInterface |
475
|
|
|
throw new NoSuchValidatorException('Validator class ' . $validatorName . ' must implement \TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface', 1365776838); |
476
|
|
|
} |
477
|
|
|
$resolvedValidatorName = $possibleClassName; |
478
|
|
|
} else { |
479
|
|
|
throw new NoSuchValidatorException('Validator class ' . $validatorName . ' does not exist', 1365799920); |
480
|
|
|
} |
481
|
|
|
|
482
|
|
|
return $resolvedValidatorName; |
483
|
|
|
} |
484
|
|
|
|
485
|
|
|
/** |
486
|
|
|
* Used to map PHP types to validator types. |
487
|
|
|
* |
488
|
|
|
* @param string $type Data type to unify |
489
|
|
|
* @return string unified data type |
490
|
|
|
*/ |
491
|
|
|
protected function getValidatorType($type) |
492
|
|
|
{ |
493
|
|
|
switch ($type) { |
494
|
|
|
case 'int': |
495
|
|
|
$type = 'Integer'; |
496
|
|
|
break; |
497
|
|
|
case 'bool': |
498
|
|
|
$type = 'Boolean'; |
499
|
|
|
break; |
500
|
|
|
case 'double': |
501
|
|
|
$type = 'Float'; |
502
|
|
|
break; |
503
|
|
|
case 'numeric': |
504
|
|
|
$type = 'Number'; |
505
|
|
|
break; |
506
|
|
|
case 'mixed': |
507
|
|
|
$type = 'Raw'; |
508
|
|
|
break; |
509
|
|
|
default: |
510
|
|
|
$type = ucfirst($type); |
511
|
|
|
} |
512
|
|
|
return $type; |
513
|
|
|
} |
514
|
|
|
|
515
|
|
|
/** |
516
|
|
|
* Temporary replacement for $this->reflectionService->getMethodAnnotations() |
517
|
|
|
* |
518
|
|
|
* @param string $className |
519
|
|
|
* @param string $methodName |
520
|
|
|
* |
521
|
|
|
* @return array |
522
|
|
|
*/ |
523
|
|
|
public function getMethodValidateAnnotations($className, $methodName) |
524
|
|
|
{ |
525
|
|
|
$validateAnnotations = []; |
526
|
|
|
$methodTagsValues = $this->reflectionService->getMethodTagsValues($className, $methodName); |
527
|
|
|
if (isset($methodTagsValues['validate']) && is_array($methodTagsValues['validate'])) { |
528
|
|
|
foreach ($methodTagsValues['validate'] as $validateValue) { |
529
|
|
|
$parsedAnnotations = $this->parseValidatorAnnotation($validateValue); |
530
|
|
|
|
531
|
|
|
foreach ($parsedAnnotations['validators'] as $validator) { |
|
|
|
|
532
|
|
|
array_push($validateAnnotations, [ |
533
|
|
|
'argumentName' => $parsedAnnotations['argumentName'], |
534
|
|
|
'validatorName' => $validator['validatorName'], |
535
|
|
|
'validatorOptions' => $validator['validatorOptions'] |
536
|
|
|
]); |
537
|
|
|
} |
538
|
|
|
} |
539
|
|
|
} |
540
|
|
|
|
541
|
|
|
return $validateAnnotations; |
542
|
|
|
} |
543
|
|
|
} |
544
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.