This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace ParamProcessor\PackagePrivate; |
||
4 | |||
5 | use DataValues\DataValue; |
||
6 | use Exception; |
||
7 | use ParamProcessor\IParam; |
||
8 | use ParamProcessor\IParamDefinition; |
||
9 | use ParamProcessor\Options; |
||
10 | use ParamProcessor\ParamDefinition; |
||
11 | use ParamProcessor\ParamDefinitionFactory; |
||
12 | use ParamProcessor\ProcessingError; |
||
13 | use ValueParsers\NullParser; |
||
14 | use ValueParsers\ParseException; |
||
15 | use ValueParsers\ValueParser; |
||
16 | |||
17 | /** |
||
18 | * Package private! |
||
19 | * |
||
20 | * Parameter class, representing the "instance" of a parameter. |
||
21 | * Holds a ParamDefinition, user provided input (name & value) and processing state. |
||
22 | * |
||
23 | * @licence GNU GPL v2+ |
||
24 | * @author Jeroen De Dauw < [email protected] > |
||
25 | */ |
||
26 | class Param implements IParam { |
||
27 | |||
28 | /** |
||
29 | * Indicates whether parameters not found in the criteria list |
||
30 | * should be stored in case they are not accepted. The default is false. |
||
31 | * |
||
32 | * @deprecated since 1.7 |
||
33 | */ |
||
34 | public static $accumulateParameterErrors = false; |
||
35 | |||
36 | /** |
||
37 | * The original parameter name as provided by the user. This can be the |
||
38 | * main name or an alias. |
||
39 | * @var string |
||
40 | */ |
||
41 | protected $originalName; |
||
42 | |||
43 | /** |
||
44 | * The original value as provided by the user. This is mainly retained for |
||
45 | * usage in error messages when the parameter turns out to be invalid. |
||
46 | * @var string |
||
47 | */ |
||
48 | protected $originalValue; |
||
49 | |||
50 | /** |
||
51 | * @var mixed |
||
52 | */ |
||
53 | protected $value; |
||
54 | |||
55 | /** |
||
56 | * Keeps track of how many times the parameter has been set by the user. |
||
57 | * This is used to detect overrides and for figuring out a parameter is missing. |
||
58 | * @var integer |
||
59 | */ |
||
60 | protected $setCount = 0; |
||
61 | |||
62 | /** |
||
63 | * @var ProcessingError[] |
||
64 | */ |
||
65 | protected $errors = []; |
||
66 | |||
67 | /** |
||
68 | * Indicates if the parameter was set to it's default. |
||
69 | * @var boolean |
||
70 | */ |
||
71 | protected $defaulted = false; |
||
72 | |||
73 | /** |
||
74 | * @var ParamDefinition |
||
75 | */ |
||
76 | protected $definition; |
||
77 | |||
78 | 107 | public function __construct( IParamDefinition $definition ) { |
|
79 | 107 | $this->definition = $definition; |
|
0 ignored issues
–
show
|
|||
80 | 107 | } |
|
81 | |||
82 | /** |
||
83 | * Sets and cleans the original value and name. |
||
84 | */ |
||
85 | 105 | public function setUserValue( string $paramName, $paramValue, Options $options ): bool { |
|
86 | 105 | if ( $this->setCount > 0 && !$options->acceptOverriding() ) { |
|
87 | // TODO |
||
88 | return false; |
||
89 | } |
||
90 | |||
91 | 105 | $this->originalName = $paramName; |
|
92 | 105 | $this->originalValue = $paramValue; |
|
93 | |||
94 | 105 | $this->cleanValue( $options ); |
|
95 | |||
96 | 105 | $this->setCount++; |
|
97 | |||
98 | 105 | return true; |
|
99 | } |
||
100 | |||
101 | /** |
||
102 | * @param mixed $value |
||
103 | */ |
||
104 | 93 | public function setValue( $value ) { |
|
105 | 93 | $this->value = $value; |
|
106 | 93 | } |
|
107 | |||
108 | /** |
||
109 | * Sets the $value to a cleaned value of $originalValue. |
||
110 | */ |
||
111 | 105 | protected function cleanValue( Options $options ) { |
|
112 | 105 | if ( $this->definition->isList() ) { |
|
113 | $this->value = explode( $this->definition->getDelimiter(), $this->originalValue ); |
||
114 | } |
||
115 | else { |
||
116 | 105 | $this->value = $this->originalValue; |
|
117 | } |
||
118 | |||
119 | 105 | if ( $this->shouldTrim( $options ) ) { |
|
120 | 105 | $this->trimValue(); |
|
121 | } |
||
122 | |||
123 | 105 | if ( $this->shouldLowercase( $options ) ) { |
|
124 | $this->lowercaseValue(); |
||
125 | } |
||
126 | 105 | } |
|
127 | |||
128 | 105 | private function shouldTrim( Options $options ): bool { |
|
129 | 105 | $trim = $this->definition->trimDuringClean(); |
|
130 | |||
131 | 105 | if ( $trim === true ) { |
|
132 | return true; |
||
133 | } |
||
134 | |||
135 | 105 | return is_null( $trim ) && $options->trimValues(); |
|
136 | } |
||
137 | |||
138 | 105 | private function trimValue() { |
|
139 | 105 | if ( is_string( $this->value ) ) { |
|
140 | 99 | $this->value = trim( $this->value ); |
|
141 | } |
||
142 | 58 | elseif ( $this->definition->isList() ) { |
|
143 | foreach ( $this->value as &$element ) { |
||
144 | if ( is_string( $element ) ) { |
||
145 | $element = trim( $element ); |
||
146 | } |
||
147 | } |
||
148 | } |
||
149 | 105 | } |
|
150 | |||
151 | 105 | private function shouldLowercase( Options $options ): bool { |
|
152 | 105 | if ( $options->lowercaseValues() ) { |
|
153 | return true; |
||
154 | } |
||
155 | |||
156 | 105 | $definitionOptions = $this->definition->getOptions(); |
|
157 | |||
158 | 105 | return array_key_exists( 'tolower', $definitionOptions ) && $definitionOptions['tolower']; |
|
159 | } |
||
160 | |||
161 | private function lowercaseValue() { |
||
162 | if ( $this->definition->isList() ) { |
||
163 | foreach ( $this->value as &$element ) { |
||
164 | if ( is_string( $element ) ) { |
||
165 | $element = strtolower( $element ); |
||
166 | } |
||
167 | } |
||
168 | } |
||
169 | elseif ( is_string( $this->value ) ) { |
||
170 | $this->value = strtolower( $this->value ); |
||
171 | } |
||
172 | } |
||
173 | |||
174 | /** |
||
175 | * Parameter processing entry point. |
||
176 | * Processes the parameter. This includes parsing, validation and additional formatting. |
||
177 | * |
||
178 | * @param ParamDefinition[] $definitions |
||
179 | * @param Param[] $params |
||
180 | * @param Options $options |
||
181 | * |
||
182 | * @throws Exception |
||
183 | */ |
||
184 | 105 | public function process( array &$definitions, array $params, Options $options ) { |
|
185 | 105 | if ( $this->setCount == 0 ) { |
|
186 | 1 | if ( $this->definition->isRequired() ) { |
|
187 | // This should not occur, so throw an exception. |
||
188 | throw new Exception( 'Attempted to validate a required parameter without first setting a value.' ); |
||
189 | } |
||
190 | else { |
||
191 | 1 | $this->setToDefault(); |
|
192 | } |
||
193 | } |
||
194 | else { |
||
195 | 104 | $this->parseAndValidate( $options ); |
|
196 | } |
||
197 | |||
198 | 105 | if ( !$this->hasFatalError() && ( $this->definition->shouldManipulateDefault() || !$this->wasSetToDefault() ) ) { |
|
199 | 93 | $this->definition->format( $this, $definitions, $params ); |
|
200 | } |
||
201 | 105 | } |
|
202 | |||
203 | 104 | public function getValueParser( Options $options ): ValueParser { |
|
204 | 104 | $parser = $this->definition->getValueParser(); |
|
205 | |||
206 | 104 | if ( !( $parser instanceof NullParser ) ) { |
|
207 | return $parser; |
||
208 | } |
||
209 | |||
210 | // TODO: inject factory |
||
211 | 104 | $type = ParamDefinitionFactory::singleton()->getType( $this->definition->getType() ); |
|
212 | |||
213 | 104 | $parserClass = $options->isStringlyTyped() ? $type->getStringParserClass() : $type->getTypedParserClass(); |
|
214 | |||
215 | 104 | return new $parserClass( new \ValueParsers\ParserOptions( $this->definition->getOptions() ) ); |
|
216 | } |
||
217 | |||
218 | 104 | protected function parseAndValidate( Options $options ) { |
|
219 | 104 | $parser = $this->getValueParser( $options ); |
|
220 | |||
221 | 104 | if ( $this->definition->isList() ) { |
|
222 | $values = []; |
||
223 | |||
224 | foreach ( $this->getValue() as $value ) { |
||
225 | $parsedValue = $this->parseAndValidateValue( $parser, $value ); |
||
226 | |||
227 | if ( is_array( $parsedValue ) ) { |
||
228 | $values[] = $parsedValue[0]; |
||
229 | } |
||
230 | } |
||
231 | |||
232 | $this->value = $values; |
||
233 | } |
||
234 | else { |
||
235 | 104 | $parsedValue = $this->parseAndValidateValue( $parser, $this->getValue() ); |
|
236 | |||
237 | 104 | if ( is_array( $parsedValue ) ) { |
|
238 | 99 | $this->value = $parsedValue[0]; |
|
239 | } |
||
240 | } |
||
241 | |||
242 | 104 | $this->setToDefaultIfNeeded(); |
|
243 | 104 | } |
|
244 | |||
245 | /** |
||
246 | * Parses and validates the provided with with specified parser. |
||
247 | * The result is returned in an array on success. On fail, false is returned. |
||
248 | * The result is wrapped in an array since we need to be able to distinguish |
||
249 | * between the method returning false and the value being false. |
||
250 | * |
||
251 | * Parsing and validation errors get added to $this->errors. |
||
252 | * |
||
253 | * @since 1.0 |
||
254 | * |
||
255 | * @param ValueParser $parser |
||
256 | * @param mixed $value |
||
257 | * |
||
258 | * @return array|bool |
||
259 | */ |
||
260 | 104 | protected function parseAndValidateValue( ValueParser $parser, $value ) { |
|
261 | try { |
||
262 | 104 | $value = $parser->parse( $value ); |
|
263 | } |
||
264 | 13 | catch ( ParseException $parseException ) { |
|
265 | 13 | $this->registerProcessingError( $parseException->getMessage() ); |
|
266 | 13 | return false; |
|
267 | } |
||
268 | |||
269 | 99 | if ( $value instanceof DataValue ) { |
|
270 | 58 | $value = $value->getValue(); |
|
271 | } |
||
272 | |||
273 | 99 | $this->validateValue( $value ); |
|
274 | |||
275 | 99 | return [ $value ]; |
|
276 | } |
||
277 | |||
278 | 35 | protected function registerProcessingError( string $message ) { |
|
279 | 35 | $this->errors[] = $this->newProcessingError( $message ); |
|
280 | 35 | } |
|
281 | |||
282 | 35 | protected function newProcessingError( string $message ): ProcessingError { |
|
283 | 35 | $severity = $this->isRequired() ? ProcessingError::SEVERITY_FATAL : ProcessingError::SEVERITY_NORMAL; |
|
284 | 35 | return new ProcessingError( $message, $severity ); |
|
285 | } |
||
286 | |||
287 | /** |
||
288 | * @param mixed $value |
||
289 | */ |
||
290 | 99 | protected function validateValue( $value ) { |
|
291 | 99 | $validationCallback = $this->definition->getValidationCallback(); |
|
292 | |||
293 | 99 | if ( $validationCallback !== null && $validationCallback( $value ) !== true ) { |
|
294 | 7 | $this->registerProcessingError( 'Validation callback failed' ); |
|
295 | } |
||
296 | else { |
||
297 | 99 | $validator = $this->definition->getValueValidator(); |
|
298 | 99 | if ( method_exists( $validator, 'setOptions' ) ) { |
|
299 | 99 | $validator->setOptions( $this->definition->getOptions() ); |
|
300 | } |
||
301 | 99 | $validationResult = $validator->validate( $value ); |
|
302 | |||
303 | 99 | if ( !$validationResult->isValid() ) { |
|
304 | 24 | foreach ( $validationResult->getErrors() as $error ) { |
|
305 | 24 | $this->registerProcessingError( $error->getText() ); |
|
306 | } |
||
307 | } |
||
308 | } |
||
309 | 99 | } |
|
310 | |||
311 | /** |
||
312 | * Sets the parameter value to the default if needed. |
||
313 | */ |
||
314 | 104 | protected function setToDefaultIfNeeded() { |
|
315 | 104 | if ( $this->shouldSetToDefault() ) { |
|
316 | 15 | $this->setToDefault(); |
|
317 | } |
||
318 | 104 | } |
|
319 | |||
320 | 104 | private function shouldSetToDefault(): bool { |
|
321 | 104 | if ( $this->hasFatalError() ) { |
|
322 | 20 | return false; |
|
323 | } |
||
324 | |||
325 | 92 | if ( $this->definition->isList() ) { |
|
326 | return $this->errors !== [] && $this->value === []; |
||
327 | } |
||
328 | |||
329 | 92 | return $this->errors !== []; |
|
330 | } |
||
331 | |||
332 | /** |
||
333 | * Returns the original use-provided name. |
||
334 | * |
||
335 | * @throws Exception |
||
336 | * @return string |
||
337 | */ |
||
338 | 36 | public function getOriginalName(): string { |
|
339 | 36 | if ( $this->setCount == 0 ) { |
|
340 | throw new Exception( 'No user input set to the parameter yet, so the original name does not exist' ); |
||
341 | } |
||
342 | 36 | return $this->originalName; |
|
343 | } |
||
344 | |||
345 | /** |
||
346 | * Returns the original use-provided value. |
||
347 | * |
||
348 | * @throws Exception |
||
349 | * @return mixed |
||
350 | */ |
||
351 | 36 | public function getOriginalValue() { |
|
352 | 36 | if ( $this->setCount == 0 ) { |
|
353 | throw new Exception( 'No user input set to the parameter yet, so the original value does not exist' ); |
||
354 | } |
||
355 | 36 | return $this->originalValue; |
|
356 | } |
||
357 | |||
358 | /** |
||
359 | * Returns all validation errors that occurred so far. |
||
360 | * |
||
361 | * @return ProcessingError[] |
||
362 | */ |
||
363 | 95 | public function getErrors(): array { |
|
364 | 95 | return $this->errors; |
|
365 | } |
||
366 | |||
367 | /** |
||
368 | * Sets the parameter value to the default. |
||
369 | */ |
||
370 | 16 | protected function setToDefault() { |
|
371 | 16 | $this->defaulted = true; |
|
372 | 16 | $this->value = $this->definition->getDefault(); |
|
373 | 16 | } |
|
374 | |||
375 | 49 | public function wasSetToDefault(): bool { |
|
376 | 49 | return $this->defaulted; |
|
377 | } |
||
378 | |||
379 | 105 | public function hasFatalError(): bool { |
|
380 | 105 | foreach ( $this->errors as $error ) { |
|
381 | 35 | if ( $error->isFatal() ) { |
|
382 | 20 | return true; |
|
383 | } |
||
384 | } |
||
385 | |||
386 | 93 | return false; |
|
387 | } |
||
388 | |||
389 | /** |
||
390 | * Returns the ParamDefinition this Param was constructed from. |
||
391 | */ |
||
392 | public function getDefinition(): ParamDefinition { |
||
393 | return $this->definition; |
||
394 | } |
||
395 | |||
396 | /** |
||
397 | * @return mixed |
||
398 | */ |
||
399 | 105 | public function &getValue() { |
|
400 | 105 | return $this->value; |
|
401 | } |
||
402 | |||
403 | 36 | public function isRequired(): bool { |
|
404 | 36 | return $this->definition->isRequired(); |
|
405 | } |
||
406 | |||
407 | 49 | public function getName(): string { |
|
408 | 49 | return $this->definition->getName(); |
|
409 | } |
||
410 | |||
411 | /** |
||
412 | * @return string[] |
||
413 | */ |
||
414 | public function getAliases(): array { |
||
415 | return $this->definition->getAliases(); |
||
416 | } |
||
417 | |||
418 | } |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.
Either this assignment is in error or an instanceof check should be added for that assignment.