PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / trunk
Defender Security – Malware Scanner, Login Security & Firewall vtrunk
6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 3.1.1 All 140 releases
defender-security / framework / base / class-model.php

class-model.php in Defender Security – Malware Scanner, Login Security & Firewall trunk, at framework/base/class-model.php

452 lines 9.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Base model class.
4 *
5 * @package Calotes\Base
6 */
7
8 namespace Calotes\Base;
9
10 use Exception;
11 use ReflectionClass;
12 use Valitron\Validator;
13 use ReflectionProperty;
14
15 /**
16 * Base model class for all models.
17 */
18 abstract class Model extends Component {
19
20 /**
21 * Table name | option name | post type name.
22 *
23 * @var string
24 */
25 protected $table;
26
27 /**
28 * Let validate to run on special scenario.
29 *
30 * @var string
31 */
32 protected $scenario;
33
34 /**
35 * Validation errors.
36 *
37 * @var array
38 */
39 protected $errors = array();
40
41 /**
42 * Array of safe properties allow for mass assign, default is empty, mean accept all.
43 *
44 * @var array
45 */
46 protected $safe = array();
47
48 /**
49 * Store the attribute that should not be export.
50 *
51 * @var array
52 */
53 protected $exclude = array();
54
55 /**
56 * An array hold info to map class attributes to db field name.
57 *
58 * @var array
59 */
60 protected $mapping = array();
61
62 /**
63 * Get the table name.
64 *
65 * @return string|null
66 */
67 public function get_table(): ?string {
68 return $this->table;
69 }
70
71 /**
72 * Run the validation.
73 *
74 * @return bool
75 */
76 public function validate(): bool {
77 $this->before_validate();
78 if ( array() === $this->annotations ) {
79 $this->log( 'Empty annotations.', wd_internal_log() );
80 }
81 $validate = $this->get_validate_rules();
82 $validate->validate();
83 $this->errors = is_array( $validate->errors() ) ? $validate->errors() : array();
84 $this->after_validate();
85
86 return ! count( $this->errors );
87 }
88
89 /**
90 * Override this if you want to trigger something before validation process.
91 */
92 protected function before_validate(): void {
93 }
94
95 /**
96 * Override this if you want to trigger something after validation process.
97 */
98 protected function after_validate(): void {
99 }
100
101 /**
102 * Export the class data.
103 *
104 * @return array
105 */
106 public function export(): array {
107 if ( array() === $this->annotations ) {
108 return $this->export_oldway();
109 }
110 $return = array();
111 foreach ( array_keys( $this->annotations ) as $property ) {
112 if ( $this->has_property( $property ) ) {
113 $return[ $property ] = $this->$property;
114 }
115 }
116
117 return $return;
118 }
119
120 /**
121 * This is for backward compatibility.
122 *
123 * @return array
124 */
125 private function export_oldway(): array {
126 try {
127 $reflection = new ReflectionClass( $this );
128 $props = $reflection->getProperties( ReflectionProperty::IS_PUBLIC );
129 $values = array();
130 foreach ( $props as $prop ) {
131 if ( 'annotations' === $prop->getName() ) {
132 continue;
133 }
134
135 if ( in_array( $prop->getName(), $this->exclude, true ) ) {
136 continue;
137 }
138
139 $value = $prop->getValue( $this );
140 if ( is_null( $value ) ) {
141 $value = '';
142 }
143 $values[ $prop->getName() ] = $value;
144 }
145
146 $this->exclude = array();
147
148 return $values;
149 } catch ( Exception $e ) {
150 return array();
151 }
152 }
153
154 /**
155 * Get the validation errors.
156 *
157 * @return array The validation errors.
158 */
159 public function get_errors(): array {
160 return $this->errors;
161 }
162
163 /**
164 * Get error as formatted string.
165 *
166 * @return string
167 */
168 public function get_formatted_errors(): string {
169 return implode( '<br/>', $this->errors );
170 }
171
172 /**
173 * Get error as formatted string.
174 *
175 * @return int[]|string[]
176 */
177 public function get_error_keys() {
178 return array_keys( $this->errors );
179 }
180
181 /**
182 * Set properties that we should not return on export. Note that this will be wiped after an export done.
183 *
184 * @param array $properties Properties that we should not return on export.
185 */
186 public function set_excludes( array $properties ) {
187 $this->exclude = $properties;
188 }
189
190 /**
191 * Set properties allow for mass assign.
192 *
193 * @param array $properties Properties allow for mass assign.
194 */
195 public function set_safe( array $properties ) {
196 $this->safe = $properties;
197 }
198
199 /**
200 * Loops through the data array and imports the values if they meet certain conditions.
201 *
202 * @param mixed $data The data array to import values from.
203 *
204 * @return void
205 */
206 public function import_old_way( $data ): void {
207 foreach ( $data as $key => $val ) {
208 // Check if we have a safe list.
209 if ( array() !== $this->safe && ! in_array( $key, $this->safe, true ) ) {
210 continue;
211 }
212
213 $allowed = array_keys( $this->export() );
214 if ( ! in_array( $key, $allowed, true ) ) {
215 continue;
216 }
217
218 if ( $this->has_property( $key ) ) {
219 $this->$key = $val;
220 }
221 }
222 }
223
224 /**
225 * Import data into the model.
226 *
227 * @param mixed $data The data array to import values from.
228 *
229 * @return void
230 */
231 public function import( $data ): void {
232 if ( array() === $this->annotations ) {
233 $this->import_old_way( $data );
234 } else {
235 foreach ( array_keys( $this->annotations ) as $property ) {
236 if ( isset( $data[ $property ] ) && $this->has_property( $property ) ) {
237 $value = $data[ $property ];
238 $type = $this->annotations[ $property ]['type'];
239 if ( false !== $type ) {
240 if ( 'boolean' === $type || 'bool' === $type ) {
241 $value = filter_var( $value, FILTER_VALIDATE_BOOLEAN );
242 } else {
243 settype( $value, $type );
244 }
245 }
246 $this->$property = $value;
247 }
248 }
249
250 $this->sanitize();
251 }
252 }
253
254 /**
255 * This method prepares the data for saving in the database.
256 *
257 * @param array $data The data array to import values from.
258 *
259 * @return array The prepared data array.
260 */
261 protected function prepare_data( $data = array() ): array {
262 $scenario = 'import';
263 if ( ! count( $data ) ) {
264 $data = $this->export();
265 $scenario = 'export';
266 }
267 if ( array() === $this->mapping ) {
268 return $data;
269 }
270 foreach ( $this->mapping as $key => $val ) {
271 if ( 'export' === $scenario && isset( $data[ $key ] ) ) {
272 $data[ $val ] = $data[ $key ];
273 unset( $data[ $key ] );
274 } elseif ( 'import' === $scenario && isset( $data[ $val ] ) ) {
275 $data[ $key ] = $data[ $val ];
276 unset( $data[ $val ] );
277 }
278 }
279
280 return $data;
281 }
282
283 /**
284 * Run a filter for casting type.
285 *
286 * @return void
287 */
288 protected function sanitize() {
289 if ( array() === $this->annotations ) {
290 return;
291 }
292
293 foreach ( $this->annotations as $property => $meta ) {
294 if ( ! $this->has_property( $property ) ) {
295 // Todo: log it as this is not a good behavior.
296 continue;
297 }
298 $type = $meta['type'];
299 if ( false === $type ) {
300 // Without a type, won't allow it.
301 $this->$property = null;
302 continue;
303 }
304
305 $value = $this->$property;
306 // Cast it first.
307 if ( 'boolean' === $type || 'bool' === $type ) {
308 $value = filter_var( $value, FILTER_VALIDATE_BOOLEAN );
309 } else {
310 settype( $value, $type );
311 }
312
313 if ( false !== $meta['sanitize'] ) {
314 $func = $meta['sanitize'];
315 if ( ! function_exists( $func ) ) {
316 // The formatting.php still need to be included.
317 include_once ABSPATH . WPINC . '/formatting.php';
318 }
319 if ( is_array( $value ) ) {
320 $value = $this->sanitize_array( $value, $func );
321 } else {
322 $value = $func( $value );
323 }
324 }
325
326 $this->$property = $value;
327 }
328 }
329
330 /**
331 * Sanitize an array recursively.
332 *
333 * @param array $arr The array to be sanitized.
334 * @param callable $sanitize The function to sanitize the array values.
335 *
336 * @return array The sanitized array.
337 */
338 protected function sanitize_array( $arr, $sanitize ) {
339 foreach ( $arr as &$value ) {
340 if ( is_array( $value ) ) {
341 $value = $this->sanitize_array( $value, $sanitize );
342 } else {
343 $value = $sanitize( $value );
344 }
345 }
346
347 return $arr;
348 }
349
350 /**
351 * Prepare the validation object.
352 *
353 * @return Validator
354 */
355 protected function get_validate_rules(): Validator {
356 $v = new Validator( $this->export() );
357 foreach ( $this->annotations as $property => $meta ) {
358 if ( ! $this->has_property( $property ) ) {
359 continue;
360 }
361 if ( false === $meta['rule'] ) {
362 continue;
363 }
364
365 $rules = explode( '|', $meta['rule'] );
366 foreach ( $rules as $str ) {
367 $v->rule( $str, $property );
368 }
369 }
370
371 return $v;
372 }
373
374 /**
375 * Export the data type of public properties.
376 *
377 * @since 4.8.0
378 * @return array
379 */
380 public function export_type(): array {
381 $reflection = new ReflectionClass( $this );
382 $props = $reflection->getProperties( ReflectionProperty::IS_PUBLIC );
383
384 if ( array() === $this->annotations ) {
385 return $this->export_type_oldway();
386 }
387
388 $types = array();
389 foreach ( array_keys( $this->annotations ) as $property ) {
390 if ( $this->has_property( $property ) ) {
391 $type = isset( $this->annotations[ $property ]['type'] )
392 ? (string) $this->annotations[ $property ]['type']
393 : 'string';
394
395 $types[ $property ] = $this->map_format( $type );
396 }
397 }
398
399 return $types;
400 }
401
402 /**
403 * Backward compatibility for exporting the data type of class properties.
404 *
405 * @since 4.8.0
406 * @return array
407 */
408 private function export_type_oldway(): array {
409 $types = array();
410 $reflection = new ReflectionClass( $this );
411 $props = $reflection->getProperties( ReflectionProperty::IS_PUBLIC );
412
413 foreach ( $props as $prop ) {
414 $rp = new ReflectionProperty( $prop->class, $prop->name );
415 if ( preg_match( '/@var\s+([^\s]+)/', $rp->getDocComment(), $matches ) ) {
416 $type = isset( $matches[1] ) ? (string) $matches[1] : 'string';
417 $types[] = $this->map_format( $type );
418 }
419 }
420
421 return $types;
422 }
423
424 /**
425 * Map the format for a given data type.
426 *
427 * @param string $type The data type.
428 *
429 * @since 4.8.0
430 * @return string
431 */
432 private function map_format( string $type ): string {
433 $format = '';
434 switch ( $type ) {
435 case 'int':
436 case 'integer':
437 case 'bool':
438 case 'boolean':
439 $format = '%d';
440 break;
441 case 'float':
442 $format = '%f';
443 break;
444 // Handle other data types like array, object, string.
445 default:
446 $format = '%s';
447 break;
448 }
449 return $format;
450 }
451 }
452