PluginProbe
GiveWP – Donation Plugin and Fundraising Platform / 4.15.0
GiveWP – Donation Plugin and Fundraising Platform v4.15.0
4.16.9 4.16.8.1 4.16.8 4.16.7.2 4.16.7.1 4.16.7 4.16.6.1 4.16.6 4.16.5.1 4.16.5 4.16.4 4.16.3 4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 2.3.1 2.3.2 2.30.0 All 255 releases
give / vendor / vendor-prefixed / stellarwp / validation / src / ValidationRuleSet.php

ValidationRuleSet.php in GiveWP – Donation Plugin and Fundraising Platform 4.15.0, at vendor/vendor-prefixed/stellarwp/validation/src/ValidationRuleSet.php

372 lines 9.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Give\Vendors\StellarWP\Validation;
6
7 use ArrayIterator;
8 use Closure;
9 use IteratorAggregate;
10 use JsonSerializable;
11 use ReflectionException;
12 use ReflectionFunction;
13 use ReflectionParameter;
14 use Give\Vendors\StellarWP\Validation\Contracts\ValidatesOnFrontEnd;
15 use Give\Vendors\StellarWP\Validation\Contracts\ValidationRule;
16 use Traversable;
17
18 /**
19 * @implements IteratorAggregate<int, ValidationRule|Closure>
20 */
21 class ValidationRuleSet implements IteratorAggregate, JsonSerializable
22 {
23 private ValidationRulesRegistrar $register;
24
25 /**
26 * @var array<int, ValidationRule|Closure>
27 */
28 private array $rules = [];
29
30 /**
31 * @since 1.0.0
32 */
33 public function __construct(ValidationRulesRegistrar $register)
34 {
35 $this->register = $register;
36 }
37
38 /**
39 * Pass a set of validation rules in the form of the rule id, a rule instance, or a closure.
40 *
41 * @since 1.0.0
42 *
43 * @param string|ValidationRule|Closure ...$rules
44 */
45 public function rules(...$rules): self
46 {
47 foreach ($rules as $rule) {
48 $this->rules[] = $this->sanitizeRule($rule);
49 }
50
51 return $this;
52 }
53
54 /**
55 * Prepends a given rule to the start of the rules array.
56 *
57 * @since 1.3.0
58 *
59 * @param string|ValidationRule|Closure $rule
60 */
61 public function prependRule($rule): self
62 {
63 array_unshift($this->rules, $this->sanitizeRule($rule));
64
65 return $this;
66 }
67
68 /**
69 * Replaces the given rule at the same index position or appends it if it doesn't exist.
70 *
71 * @since 1.3.0
72 *
73 * @param string|ValidationRule|Closure $rule
74 *
75 * @return bool True if the rule was replaced, false if it was appended.
76 */
77 public function replaceOrAppendRule(string $ruleId, $rule): bool
78 {
79 $replaced = $this->replaceRule($ruleId, $rule);
80
81 if (!$replaced) {
82 $this->rules($rule);
83
84 return false;
85 }
86
87 return true;
88 }
89
90 /**
91 * Replaces the given rule at the same index position or prepends it if it doesn't exist.
92 *
93 * @since 1.3.0
94 *
95 * @param string|ValidationRule|Closure $rule
96 *
97 * @return bool True if the rule was replaced, false if it was prepended.
98 */
99 public function replaceOrPrependRule(string $ruleId, $rule): bool
100 {
101 $replaced = $this->replaceRule($ruleId, $rule);
102
103 if (!$replaced) {
104 $this->prependRule($rule);
105
106 return false;
107 }
108
109 return true;
110 }
111
112 /**
113 * Replace a rule with the given id with the given rule at the same index position. Returns true if the rule was
114 * replaced, false otherwise.
115 *
116 * @since 1.3.0
117 *
118 * @param string|ValidationRule|Closure $rule
119 */
120 public function replaceRule(string $ruleId, $rule): bool
121 {
122 foreach ($this->rules as $index => $validationRule) {
123 if ($validationRule instanceof ValidationRule && $validationRule::id() === $ruleId) {
124 $this->rules[$index] = $this->sanitizeRule($rule);
125
126 return true;
127 }
128 }
129
130 return false;
131 }
132
133 /**
134 * Finds and returns the validation rule by id. Does not work for Closure rules.
135 *
136 * @since 1.0.0
137 *
138 * @return ValidationRule|null
139 */
140 public function getRule(string $rule)
141 {
142 foreach ($this->rules as $validationRule) {
143 if ($validationRule instanceof ValidationRule && $validationRule::id() === $rule) {
144 return $validationRule;
145 }
146 }
147
148 return null;
149 }
150
151 /**
152 * Removes the rules with the given id.
153 *
154 * @since 1.0.0
155 *
156 * @return self
157 */
158 public function removeRuleWithId(string $id): self
159 {
160 $this->rules = array_filter($this->rules, static function ($rule) use ($id) {
161 return $rule instanceof ValidationRule && $rule::id() !== $id;
162 });
163
164 return $this;
165 }
166
167 /**
168 * Returns the validation rules.
169 *
170 * @since 1.0.0
171 *
172 * @return array<int, ValidationRule|Closure>
173 */
174 public function getRules(): array
175 {
176 return $this->rules;
177 }
178
179 /**
180 * Returns whether the given rule is present in the validation rules. Does not work with Closure Rules.
181 *
182 * @since 1.0.0
183 */
184 public function hasRule(string $rule): bool
185 {
186 foreach ($this->rules as $validationRule) {
187 if ($validationRule instanceof ValidationRule && $validationRule::id() === $rule) {
188 return true;
189 }
190 }
191
192 return false;
193 }
194
195 /**
196 * Returns whether the array has any rules set.
197 *
198 * @since 1.0.0
199 */
200 public function hasRules(): bool
201 {
202 return !empty($this->rules);
203 }
204
205 /**
206 * Along with the IteratorAggregate interface, we can iterate over the validation rules.
207 *
208 * @since 1.0.0
209 *
210 * @inheritDoc
211 */
212 public function getIterator(): Traversable
213 {
214 return new ArrayIterator($this->rules);
215 }
216
217 /**
218 * Runs through the validation rules and compiles a list of rules that can be used by the front end.
219 *
220 * Resulting data:
221 * [
222 * ruleId => ruleOption,
223 * ...
224 * ]
225 *
226 * @inheritDoc
227 *
228 * @since 1.0.0
229 */
230 #[\ReturnTypeWillChange]
231 public function jsonSerialize()
232 {
233 $rules = [];
234
235 foreach ($this->rules as $rule) {
236 if ($rule instanceof ValidatesOnFrontEnd) {
237 $rules[$rule::id()] = $rule->serializeOption();
238 }
239 }
240
241 return $rules;
242 }
243
244 /**
245 * Sanitizes a given rule by validating the rule and making sure it's safe to use.
246 *
247 * @since 1.3.0
248 *
249 * @param mixed $rule
250 *
251 * @return Closure|ValidationRule
252 */
253 private function sanitizeRule($rule)
254 {
255 if ($rule instanceof Closure) {
256 $this->validateClosureRule($rule);
257
258 return $rule;
259 } elseif ($rule instanceof ValidationRule) {
260 return $rule;
261 } elseif (is_string($rule)) {
262 return $this->getRuleFromString($rule);
263 }
264
265 Config::throwInvalidArgumentException(
266 sprintf(
267 'Validation rule must be a string, instance of %s, or a closure',
268 ValidationRule::class
269 )
270 );
271 }
272
273 /**
274 * Validates that a closure rule has the proper parameters to be used as a validation rule.
275 *
276 * @since 1.0.0
277 *
278 * @return void
279 */
280 private function validateClosureRule(Closure $closure)
281 {
282 $reflection = null;
283 try {
284 $reflection = new ReflectionFunction($closure);
285 } catch (ReflectionException $e) {
286 Config::throwInvalidArgumentException(
287 'Unable to validate closure parameters. Please ensure that the closure is valid.'
288 );
289 }
290
291 $parameters = $reflection->getParameters();
292 $parameterCount = count($parameters);
293
294 if ($parameterCount < 2 || $parameterCount > 4) {
295 Config::throwInvalidArgumentException(
296 "Validation rule closure must accept between 2 and 4 parameters, $parameterCount given."
297 );
298 }
299
300 $parameterType = $this->getParameterTypeName($parameters[1]);
301 if ($parameterType !== null && $parameterType !== 'Closure') {
302 Config::throwInvalidArgumentException(
303 "Validation rule closure must accept a Closure as the second parameter, {$parameterType} given."
304 );
305 }
306
307 $parameterType = $parameterCount > 2 ? $this->getParameterTypeName($parameters[2]) : null;
308 if ($parameterType !== null && $parameterType !== 'string') {
309 Config::throwInvalidArgumentException(
310 "Validation rule closure must accept a string as the third parameter, {$parameterType} given."
311 );
312 }
313
314 $parameterType = $parameterCount > 3 ? $this->getParameterTypeName($parameters[3]) : null;
315 if ($parameterType !== null && $parameterType !== 'array') {
316 Config::throwInvalidArgumentException(
317 "Validation rule closure must accept a array as the fourth parameter, {$parameterType} given."
318 );
319 }
320 }
321
322 /**
323 * Retrieves the parameter type with PHP 7.0 compatibility.
324 *
325 * @since 1.0.0
326 *
327 * @return string|null
328 */
329 private function getParameterTypeName(ReflectionParameter $parameter)
330 {
331 $type = $parameter->getType();
332
333 if ($type === null) {
334 return null;
335 }
336
337 // Check if the method exists for PHP 7.0 compatibility (it exits as of PHP 7.1)
338 if (method_exists($type, 'getName')) {
339 return $type->getName();
340 }
341
342 return (string)$type;
343 }
344
345 /**
346 * Takes a validation rule string and returns the corresponding rule instance.
347 *
348 * @since 1.3.2 use list syntax for PHP 7.0 compatibility
349 * @since 1.0.0
350 */
351 private function getRuleFromString(string $rule): ValidationRule
352 {
353 list($ruleId, $ruleOptions) = array_pad(explode(':', $rule, 2), 2, null);
354
355 /**
356 * @var ValidationRule $ruleClass
357 */
358 $ruleClass = $this->register->getRule($ruleId);
359
360 if (!$ruleClass) {
361 Config::throwInvalidArgumentException(
362 sprintf(
363 'Validation rule with id %s has not been registered.',
364 $ruleId
365 )
366 );
367 }
368
369 return $ruleClass::fromString($ruleOptions);
370 }
371 }
372