[CLEANUP] Replace wrong/old file copyright comments
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Validation / ValidatorResolver.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Validation;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2010-2012 Extbase Team (http://forge.typo3.org/projects/typo3v4-mvc)
8 * Extbase is a backport of TYPO3 Flow. All credits go to the TYPO3 Flow team.
9 * All rights reserved
10 *
11 * This script is part of the TYPO3 project. The TYPO3 project is
12 * free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * The GNU General Public License can be found at
18 * http://www.gnu.org/copyleft/gpl.html.
19 * A copy is found in the textfile GPL.txt and important notices to the license
20 * from the author is found in LICENSE.txt distributed with these scripts.
21 *
22 *
23 * This script is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
27 *
28 * This copyright notice MUST APPEAR in all copies of the script!
29 ***************************************************************/
30 /**
31 * Validator resolver to automatically find a appropriate validator for a given subject
32 */
33 class ValidatorResolver implements \TYPO3\CMS\Core\SingletonInterface {
34
35 /**
36 * Match validator names and options
37 *
38 * @var string
39 */
40 const PATTERN_MATCH_VALIDATORS = '/
41 (?:^|,\\s*)
42 (?P<validatorName>[a-z0-9_:.\\\\]+)
43 \\s*
44 (?:\\(
45 (?P<validatorOptions>(?:\\s*[a-z0-9]+\\s*=\\s*(?:
46 "(?:\\\\"|[^"])*"
47 |\'(?:\\\\\'|[^\'])*\'
48 |(?:\\s|[^,"\']*)
49 )(?:\\s|,)*)*)
50 \\))?
51 /ixS';
52
53 /**
54 * Match validator options (to parse actual options)
55 *
56 * @var string
57 */
58 const PATTERN_MATCH_VALIDATOROPTIONS = '/
59 \\s*
60 (?P<optionName>[a-z0-9]+)
61 \\s*=\\s*
62 (?P<optionValue>
63 "(?:\\\\"|[^"])*"
64 |\'(?:\\\\\'|[^\'])*\'
65 |(?:\\s|[^,"\']*)
66 )
67 /ixS';
68
69 /**
70 * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
71 */
72 protected $objectManager;
73
74 /**
75 * @var \TYPO3\CMS\Extbase\Reflection\ReflectionService
76 */
77 protected $reflectionService;
78
79 /**
80 * @var array
81 */
82 protected $baseValidatorConjunctions = array();
83
84 /**
85 * Injects the object manager
86 *
87 * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager A reference to the object manager
88 * @return void
89 */
90 public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager) {
91 $this->objectManager = $objectManager;
92 }
93
94 /**
95 * Injects the reflection service
96 *
97 * @param \TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService
98 * @return void
99 */
100 public function injectReflectionService(\TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService) {
101 $this->reflectionService = $reflectionService;
102 }
103
104 /**
105 * Get a validator for a given data type. Returns a validator implementing
106 * the \TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface or NULL if no validator
107 * could be resolved.
108 *
109 * @param string $validatorName Either one of the built-in data types or fully qualified validator class name
110 * @param array $validatorOptions Options to be passed to the validator
111 * @return \TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface Validator or NULL if none found.
112 */
113 public function createValidator($validatorName, array $validatorOptions = array()) {
114 $validatorClassName = $this->resolveValidatorObjectName($validatorName);
115 if ($validatorClassName === FALSE) {
116 return NULL;
117 }
118 $validator = $this->objectManager->get($validatorClassName, $validatorOptions);
119 if (!$validator instanceof \TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface) {
120 return NULL;
121 }
122 if (method_exists($validator, 'setOptions')) {
123 // @deprecated since Extbase 1.4.0, will be removed in Extbase 6.1
124 $validator->setOptions($validatorOptions);
125 }
126 return $validator;
127 }
128
129 /**
130 * Resolves and returns the base validator conjunction for the given data type.
131 *
132 * If no validator could be resolved (which usually means that no validation is necessary),
133 * NULL is returned.
134 *
135 * @param string $dataType The data type to search a validator for. Usually the fully qualified object name
136 * @return \TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator The validator conjunction or NULL
137 */
138 public function getBaseValidatorConjunction($dataType) {
139 if (!isset($this->baseValidatorConjunctions[$dataType])) {
140 $this->baseValidatorConjunctions[$dataType] = $this->buildBaseValidatorConjunction($dataType);
141 }
142 return $this->baseValidatorConjunctions[$dataType];
143 }
144
145 /**
146 * Detects and registers any validators for arguments:
147 * - by the data type specified in the
148 *
149 * @param string $className
150 * @param string $methodName
151 * @throws Exception\NoSuchValidatorException
152 * @throws Exception\InvalidValidationConfigurationException
153 * @return array An Array of ValidatorConjunctions for each method parameters.
154 */
155 public function buildMethodArgumentsValidatorConjunctions($className, $methodName) {
156 $validatorConjunctions = array();
157 $methodParameters = $this->reflectionService->getMethodParameters($className, $methodName);
158 $methodTagsValues = $this->reflectionService->getMethodTagsValues($className, $methodName);
159 if (!count($methodParameters)) {
160 // early return in case no parameters were found.
161 return $validatorConjunctions;
162 }
163 foreach ($methodParameters as $parameterName => $methodParameter) {
164 $validatorConjunction = $this->createValidator('Conjunction');
165 $typeValidator = $this->createValidator($methodParameter['type']);
166 if ($typeValidator !== NULL) {
167 $validatorConjunction->addValidator($typeValidator);
168 }
169 $validatorConjunctions[$parameterName] = $validatorConjunction;
170 }
171 if (isset($methodTagsValues['validate'])) {
172 foreach ($methodTagsValues['validate'] as $validateValue) {
173 $parsedAnnotation = $this->parseValidatorAnnotation($validateValue);
174 foreach ($parsedAnnotation['validators'] as $validatorConfiguration) {
175 $newValidator = $this->createValidator($validatorConfiguration['validatorName'], $validatorConfiguration['validatorOptions']);
176 if ($newValidator === NULL) {
177 throw new \TYPO3\CMS\Extbase\Validation\Exception\NoSuchValidatorException('Invalid validate annotation in ' . $className . '->' . $methodName . '(): Could not resolve class name for validator "' . $validatorConfiguration['validatorName'] . '".', 1239853109);
178 }
179 if (isset($validatorConjunctions[$parsedAnnotation['argumentName']])) {
180 $validatorConjunctions[$parsedAnnotation['argumentName']]->addValidator($newValidator);
181 } else {
182 throw new \TYPO3\CMS\Extbase\Validation\Exception\InvalidValidationConfigurationException('Invalid validate annotation in ' . $className . '->' . $methodName . '(): Validator specified for argument name "' . $parsedAnnotation['argumentName'] . '", but this argument does not exist.', 1253172726);
183 }
184 }
185 }
186 }
187 return $validatorConjunctions;
188 }
189
190 /**
191 * Builds a base validator conjunction for the given data type.
192 *
193 * The base validation rules are those which were declared directly in a class (typically
194 * a model) through some @validate annotations on properties.
195 *
196 * Additionally, if a custom validator was defined for the class in question, it will be added
197 * to the end of the conjunction. A custom validator is found if it follows the naming convention
198 * "Replace '\Model\' by '\Validator\' and append "Validator".
199 *
200 * Example: $dataType is F3\Foo\Domain\Model\Quux, then the Validator will be found if it has the
201 * name F3\Foo\Domain\Validator\QuuxValidator
202 *
203 * @param string $dataType The data type to build the validation conjunction for. Needs to be the fully qualified object name.
204 * @throws Exception\NoSuchValidatorException
205 * @return \TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator The validator conjunction or NULL
206 */
207 protected function buildBaseValidatorConjunction($dataType) {
208 $validatorConjunction = $this->objectManager->get('TYPO3\\CMS\\Extbase\\Validation\\Validator\\ConjunctionValidator');
209 // Model based validator
210 if (class_exists($dataType)) {
211 $validatorCount = 0;
212 $objectValidator = $this->createValidator('GenericObject');
213 foreach ($this->reflectionService->getClassPropertyNames($dataType) as $classPropertyName) {
214 $classPropertyTagsValues = $this->reflectionService->getPropertyTagsValues($dataType, $classPropertyName);
215 if (!isset($classPropertyTagsValues['validate'])) {
216 continue;
217 }
218 foreach ($classPropertyTagsValues['validate'] as $validateValue) {
219 $parsedAnnotation = $this->parseValidatorAnnotation($validateValue);
220 foreach ($parsedAnnotation['validators'] as $validatorConfiguration) {
221 $newValidator = $this->createValidator($validatorConfiguration['validatorName'], $validatorConfiguration['validatorOptions']);
222 if ($newValidator === NULL) {
223 throw new \TYPO3\CMS\Extbase\Validation\Exception\NoSuchValidatorException('Invalid validate annotation in ' . $dataType . '::' . $classPropertyName . ': Could not resolve class name for validator "' . $validatorConfiguration['validatorName'] . '".', 1241098027);
224 }
225 $objectValidator->addPropertyValidator($classPropertyName, $newValidator);
226 $validatorCount++;
227 }
228 }
229 }
230 if ($validatorCount > 0) {
231 $validatorConjunction->addValidator($objectValidator);
232 }
233 }
234 // Custom validator for the class
235 $possibleValidatorClassName = str_replace(array('_Model_', '\\Model\\'), array('_Validator_', '\\Validator\\'), $dataType) . 'Validator';
236 $customValidator = $this->createValidator($possibleValidatorClassName);
237 if ($customValidator !== NULL) {
238 $validatorConjunction->addValidator($customValidator);
239 }
240 return $validatorConjunction;
241 }
242
243 /**
244 * Parses the validator options given in @validate annotations.
245 *
246 * @param string $validateValue
247 * @return array
248 */
249 protected function parseValidatorAnnotation($validateValue) {
250 $matches = array();
251 if ($validateValue[0] === '$') {
252 $parts = explode(' ', $validateValue, 2);
253 $validatorConfiguration = array('argumentName' => ltrim($parts[0], '$'), 'validators' => array());
254 preg_match_all(self::PATTERN_MATCH_VALIDATORS, $parts[1], $matches, PREG_SET_ORDER);
255 } else {
256 $validatorConfiguration = array('validators' => array());
257 preg_match_all(self::PATTERN_MATCH_VALIDATORS, $validateValue, $matches, PREG_SET_ORDER);
258 }
259 foreach ($matches as $match) {
260 $validatorOptions = array();
261 if (isset($match['validatorOptions'])) {
262 $validatorOptions = $this->parseValidatorOptions($match['validatorOptions']);
263 }
264 $validatorConfiguration['validators'][] = array('validatorName' => $match['validatorName'], 'validatorOptions' => $validatorOptions);
265 }
266 return $validatorConfiguration;
267 }
268
269 /**
270 * Parses $rawValidatorOptions not containing quoted option values.
271 * $rawValidatorOptions will be an empty string afterwards (pass by ref!).
272 *
273 * @param string &$rawValidatorOptions
274 * @return array An array of optionName/optionValue pairs
275 */
276 protected function parseValidatorOptions($rawValidatorOptions) {
277 $validatorOptions = array();
278 $parsedValidatorOptions = array();
279 preg_match_all(self::PATTERN_MATCH_VALIDATOROPTIONS, $rawValidatorOptions, $validatorOptions, PREG_SET_ORDER);
280 foreach ($validatorOptions as $validatorOption) {
281 $parsedValidatorOptions[trim($validatorOption['optionName'])] = trim($validatorOption['optionValue']);
282 }
283 array_walk($parsedValidatorOptions, array($this, 'unquoteString'));
284 return $parsedValidatorOptions;
285 }
286
287 /**
288 * Removes escapings from a given argument string and trims the outermost
289 * quotes.
290 *
291 * This method is meant as a helper for regular expression results.
292 *
293 * @param string &$quotedValue Value to unquote
294 * @return void
295 */
296 protected function unquoteString(&$quotedValue) {
297 switch ($quotedValue[0]) {
298 case '"':
299 $quotedValue = str_replace('\\"', '"', trim($quotedValue, '"'));
300 break;
301 case '\'':
302 $quotedValue = str_replace('\\\'', '\'', trim($quotedValue, '\''));
303 break;
304 }
305 $quotedValue = str_replace('\\\\', '\\', $quotedValue);
306 }
307
308 /**
309 * Returns an object of an appropriate validator for the given class. If no validator is available
310 * FALSE is returned
311 *
312 * @param string $validatorName Either the fully qualified class name of the validator or the short name of a built-in validator
313 * @return string|boolean Name of the validator object or FALSE
314 */
315 protected function resolveValidatorObjectName($validatorName) {
316 if (strpbrk($validatorName, '_\\') !== FALSE && class_exists($validatorName)) {
317 return $validatorName;
318 }
319 list($extensionName, $extensionValidatorName) = explode(':', $validatorName);
320 if (empty($extensionValidatorName)) {
321 $possibleClassName = 'TYPO3\\CMS\\Extbase\\Validation\\Validator\\' . $this->unifyDataType($validatorName) . 'Validator';
322 } else {
323 if (strpos($extensionName, '.') !== FALSE) {
324 $extensionNameParts = explode('.', $extensionName);
325 $extensionName = array_pop($extensionNameParts);
326 $vendorName = implode('\\', $extensionNameParts);
327 $possibleClassName = $vendorName . '\\' . $extensionName . '\\Validation\\Validator\\' . $extensionValidatorName . 'Validator';
328 } else {
329 $possibleClassName = 'Tx_' . $extensionName . '_Validation_Validator_' . $extensionValidatorName . 'Validator';
330 }
331 }
332 if (class_exists($possibleClassName)) {
333 return $possibleClassName;
334 }
335 return FALSE;
336 }
337
338 /**
339 * Preprocess data types. Used to map primitive PHP types to DataTypes used in Extbase.
340 *
341 * @param string $type Data type to unify
342 * @return string unified data type
343 */
344 protected function unifyDataType($type) {
345 switch ($type) {
346 case 'int':
347 $type = 'Integer';
348 break;
349 case 'bool':
350 $type = 'Boolean';
351 break;
352 case 'double':
353 $type = 'Float';
354 break;
355 case 'numeric':
356 $type = 'Number';
357 break;
358 case 'mixed':
359 $type = 'Raw';
360 break;
361 }
362 return ucfirst($type);
363 }
364 }
365
366 ?>