PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.3.1
Defender Security – Malware Scanner, Login Security & Firewall v5.3.1
6.3.0 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 All 141 releases
defender-security / framework / db / class-mapper.php

class-mapper.php in Defender Security – Malware Scanner, Login Security & Firewall 5.3.1, at framework/db/class-mapper.php

549 lines 13.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Data mapper for CRUD.
4 *
5 * @package Calotes\DB
6 */
7
8 namespace Calotes\DB;
9
10 use ReflectionClass;
11 use Calotes\Base\Model;
12 use Calotes\Base\Component;
13
14 /**
15 * Responsible for performing CRUD operations.
16 */
17 class Mapper extends Component {
18
19 /**
20 * Contain the current model class name.
21 *
22 * @var string
23 */
24 private $repository;
25
26 /**
27 * The columns to select in the query.
28 *
29 * @var string
30 */
31 private $select = '';
32
33 /**
34 * Where statements for the query.
35 *
36 * @var array
37 */
38 private $where = array();
39
40 /**
41 * The grouping parameter for the query.
42 *
43 * @var string
44 */
45 private $group = '';
46
47 /**
48 * The ordering parameter for the query.
49 *
50 * @var string
51 */
52 private $order = '';
53
54 /**
55 * The limit for the query results.
56 *
57 * @var string
58 */
59 private $limit = '';
60
61 /**
62 * Cache for storing retrieved records.
63 *
64 * @var array
65 */
66 private $known = array();
67
68 /**
69 * Store the last executed query.
70 *
71 * @var string
72 */
73 public $saved_queries = '';
74
75 /**
76 * Set the repository class name.
77 *
78 * @param mixed $class_name The class name to set for the repository.
79 *
80 * @return $this
81 */
82 public function get_repository( $class_name ) {
83 $this->repository = $class_name;
84
85 return $this;
86 }
87
88 /**
89 * Set the columns to select in the SQL query.
90 *
91 * @param mixed $select The columns to select.
92 *
93 * @return $this
94 */
95 public function select( $select ) {
96 $this->select = $select;
97
98 return $this;
99 }
100
101 /**
102 * Set the WHERE clause for the query based on the provided arguments.
103 *
104 * @param mixed ...$args The conditions to apply in the WHERE clause.
105 *
106 * @return $this
107 */
108 public function where( ...$args ) {
109 global $wpdb;
110 if ( 2 === count( $args ) ) {
111 list($key, $value) = $args;
112 $this->where[] = $wpdb->prepare( "`$key` = " . $this->guess_var_type( $value ), $value ); // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
113
114 return $this;
115 }
116
117 [$key, $operator, $value] = $args;
118 if ( ! $this->valid_operator( $operator ) ) {
119 // Prevent this operator.
120 return $this;
121 }
122 if ( in_array( strtolower( $operator ), array( 'in', 'not in' ), true ) ) {
123 $tmp = $key . " {$operator} (" . implode(
124 ', ',
125 array_fill( 0, count( $value ), $this->guess_var_type( $value ) )
126 ) . ')';
127 $sql = call_user_func_array(
128 array(
129 $wpdb,
130 'prepare',
131 ),
132 array_merge( array( $tmp ), $value )
133 );
134 $this->where[] = $sql;
135 } elseif ( 'between' === strtolower( $operator ) ) {
136 $this->where[] = $wpdb->prepare(
137 "{$key} {$operator} {$this->guess_var_type($value[0])} AND {$this->guess_var_type($value[1])}", // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
138 $value[0],
139 $value[1]
140 );
141 } else {
142 $this->where[] = $wpdb->prepare( "`$key` $operator {$this->guess_var_type($value)}", $value ); // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
143 }
144
145 return $this;
146 }
147
148 /**
149 * Guess the type of value for correcting placeholder.
150 *
151 * @param mixed $value The value to guess.
152 *
153 * @return string
154 */
155 private function guess_var_type( $value ) {
156 if ( filter_var( $value, FILTER_VALIDATE_INT ) ) {
157 return '%d';
158 }
159
160 if ( filter_var( $value, FILTER_VALIDATE_FLOAT ) ) {
161 return '%f';
162 }
163
164 return '%s';
165 }
166
167 /**
168 * Find a record by its ID.
169 *
170 * @param int $id The ID of the record.
171 *
172 * @return $this
173 */
174 public function find_by_id( $id ) {
175 global $wpdb;
176 $this->where[] = $wpdb->prepare( 'id = %d', $id );
177
178 return $this;
179 }
180
181 /**
182 * Set the group by clause for the SQL query based on the provided argument.
183 *
184 * @param string $group_by The column to group by.
185 *
186 * @return $this
187 */
188 public function group_by( $group_by ) {
189 global $wpdb;
190 $this->group = str_replace(
191 "'",
192 '',
193 $wpdb->prepare( 'GROUP BY %s', $group_by )
194 );
195
196 return $this;
197 }
198
199 /**
200 * Set the order for the SQL query based on the provided arguments.
201 *
202 * @param mixed $order_by The column to order by.
203 * @param string $order The order direction, defaults to 'asc'.
204 *
205 * @return $this
206 */
207 public function order_by( $order_by, $order = 'asc' ) {
208 global $wpdb;
209 if ( ! in_array( $order, array( 'asc', 'desc' ), true ) ) {
210 // Fall it back.
211 $order = 'asc';
212 }
213 $this->order = str_replace(
214 "'",
215 '',
216 $wpdb->prepare( 'ORDER BY %s %s', $order_by, $order )
217 );
218
219 return $this;
220 }
221
222 /**
223 * Set the limit for the SQL query based on the provided offset.
224 *
225 * @param mixed $offset The offset value for the query limit.
226 *
227 * @return $this
228 */
229 public function limit( $offset ) {
230 global $wpdb;
231 $this->limit = str_replace(
232 "'",
233 '',
234 $wpdb->prepare( 'LIMIT ' . $this->guess_var_type( $offset ), $offset ) // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQL.NotPrepared
235 );
236
237 return $this;
238 }
239
240 /**
241 * Find the first.
242 *
243 * @return null|Model
244 */
245 public function first() {
246 $this->limit = 'LIMIT 0,1';
247 $sql = $this->query_build(); // SQL is prepared here. We will ignore prepare rules.
248 $this->saved_queries = $sql;
249 global $wpdb;
250 $data = $wpdb->get_row( $sql, ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery
251 if ( is_null( $data ) ) {
252 return null;
253 }
254 // Check if we have any json string in property.
255 foreach ( $data as &$datum ) {
256 if ( is_string( $datum ) ) {
257 $tmp = json_decode( $datum, true );
258 if ( is_array( $tmp ) ) {
259 $datum = $tmp;
260 }
261 }
262 }
263 $class_name = $this->repository;
264 $model = new $class_name();
265 $model->import( $data );
266
267 return $model;
268 }
269
270 /**
271 * Retrieves the models based on the data obtained from get_results().
272 *
273 * @return array
274 */
275 public function get() {
276 $data = $this->get_results();
277 $models = array();
278 foreach ( $data as $row ) {
279 foreach ( $row as &$property ) {
280 if ( is_string( $property ) ) {
281 $tmp = json_decode( $property, true );
282 if ( is_array( $tmp ) ) {
283 $property = $tmp;
284 }
285 }
286 }
287 $class_name = $this->repository;
288 $model = new $class_name();
289 $model->import( $row );
290 $models[] = $model;
291 }
292
293 return $models;
294 }
295
296 /**
297 * Get records in array form.
298 *
299 * @return array
300 * @since 2.7.0
301 */
302 public function get_results() {
303 $sql = $this->query_build(); // SQL is prepared here.
304 $this->saved_queries = $sql;
305
306 global $wpdb;
307 $data = $wpdb->get_results( $sql, ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery
308 if ( is_null( $data ) ) {
309 $data = array();
310 }
311
312 return $data;
313 }
314
315 /**
316 * Get the count of records based on the provided query.
317 *
318 * @return string|null The count of records.
319 */
320 public function count() {
321 global $wpdb;
322 $sql = $this->query_build( 'COUNT(*)' ); // SQL is prepared here.
323
324 $result = $wpdb->get_var( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery
325
326 return $result;
327 }
328
329 /**
330 * Handle the insert/update of current model.
331 *
332 * @param Model $model The model to save.
333 *
334 * @return int|bool The ID of current record OR false.
335 * @throws \ReflectionException If class is not defined.
336 */
337 public function save( Model &$model ) {
338 global $wpdb;
339 $data = $model->export();
340 $data_type = array();
341 $exported_type = $model->export_type();
342 unset( $data['table'] );
343 unset( $data['safe'] );
344 foreach ( $data as $key => &$val ) {
345 if ( is_array( $val ) ) {
346 $val = wp_json_encode( $val );
347 } elseif ( is_bool( $val ) ) {
348 $val = $val ? 1 : 0;
349 }
350
351 $data_type[] = $exported_type[ $key ] ?? '%s';
352 }
353 $table = self::table( $model );
354 if ( $model->id ) {
355 $ret = $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
356 $table,
357 $data,
358 array( 'id' => $model->id ),
359 $data_type,
360 array( '%d' )
361 );
362 } else {
363 $ret = $wpdb->insert( $table, $data, $data_type ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
364 // Bind this for later use.
365 $model->id = $wpdb->insert_id;
366 }
367
368 if ( false === $ret ) {
369 return false;
370 }
371
372 return $wpdb->insert_id;
373 }
374
375 /**
376 * Delete a record from the database table based on the provided conditions.
377 *
378 * @param mixed $where The conditions to apply when deleting the record.
379 *
380 * @return int|false The number of rows affected or false on failure.
381 * @throws \ReflectionException If class is not defined.
382 */
383 public function delete( $where ) {
384 $table = self::table();
385 global $wpdb;
386
387 return $wpdb->delete( $table, $where ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
388 }
389
390 /**
391 * Delete all records from the database table based on the provided conditions.
392 *
393 * @return int|false The number of rows affected or false on failure.
394 * @throws \ReflectionException If class is not defined.
395 */
396 public function delete_all() {
397 $table = self::table();
398 global $wpdb;
399
400 $where = implode( ' AND ', $this->where );
401 $sql = "DELETE FROM $table WHERE $where";
402 $this->clear();
403
404 return $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
405 }
406
407 /**
408 * Delete records from the database table based on the provided conditions and limit.
409 *
410 * @return int|false The number of rows affected or false on failure.
411 * @throws \ReflectionException If class is not defined.
412 */
413 public function delete_by_limit() {
414 $table = self::table();
415 global $wpdb;
416
417 $where = implode( ' AND ', $this->where );
418 $limit = $this->limit;
419 $order = $this->order;
420 $sql = "DELETE FROM $table WHERE $where $order $limit";
421 $this->clear();
422
423 return $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
424 }
425
426 /**
427 * Handle the truncation of a database table.
428 *
429 * @return int|false The number of rows affected or false on failure.
430 * @throws \ReflectionException If class is not defined.
431 */
432 public function truncate() {
433 $table = self::table();
434 global $wpdb;
435
436 $query = "TRUNCATE TABLE $table"; // SQL is prepared here. so we can ignore WordPress.DB.PreparedSQL.NotPrepared.
437
438 return $wpdb->query( $query ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
439 }
440
441 /**
442 * It is used to retrieve the table name associated with a given model.
443 *
444 * @param mixed $model (optional) The model object or class name. If not provided, it uses the "repository"
445 * property of the class.
446 *
447 * @return string|false The table name with the WordPress database prefix, or false if the table property doesn't
448 * exist or an exception occurs.
449 * @throws \ReflectionException If the model class doesn't exist.
450 */
451 private function table( $model = null ) {
452 if ( is_null( $model ) ) {
453 $class_name = $this->repository;
454 $model = $this->get_model();
455 } else {
456 $class_name = $model;
457 }
458 $refection = new ReflectionClass( $class_name );
459 if ( $refection->hasProperty( 'table' ) ) {
460 $property = $refection->getProperty( 'table' );
461 $property->setAccessible( true );
462
463 $table = $property->getValue( $model );
464 global $wpdb;
465
466 // Have to set the prefix.
467 return $wpdb->base_prefix . $table;
468 }
469 // This when class doesn't exist.
470 return false;
471 }
472
473 /**
474 * Reset all the queries prepare after an action.
475 */
476 private function clear() {
477 $this->select = '';
478 $this->where = array();
479 $this->group = '';
480 $this->order = '';
481 $this->limit = '';
482 }
483
484 /**
485 * Join the stuff on the table to make a full query statement.
486 * SQL params e.g. WHERE, ORDER or LIMIT were escaped on separate methods.
487 *
488 * @param string $select Columns to select.
489 *
490 * @return string
491 * @throws \ReflectionException If class is not defined.
492 */
493 private function query_build( $select = '*' ) {
494 $table = $this->table();
495 $where = implode( ' AND ', $this->where );
496
497 $select = ! empty( $this->select ) ? $this->select : $select;
498 $group_by = $this->group;
499 $order_by = $this->order;
500 $limit = $this->limit;
501 $sql = "SELECT $select FROM $table WHERE $where $group_by $order_by $limit";
502 $this->clear();
503
504 return $sql;
505 }
506
507 /**
508 * Checks if the given operator is valid.
509 *
510 * @param string $operator The operator to check.
511 *
512 * @return bool True if the operator is valid, false otherwise.
513 */
514 private function valid_operator( $operator ) {
515 $operator = strtolower( $operator );
516 $allowed = array(
517 'in',
518 'not in',
519 '>',
520 '<',
521 '=',
522 '<=',
523 '>=',
524 'like',
525 'between',
526 'regexp',
527 'not regexp',
528 );
529
530 return in_array( $operator, $allowed, true );
531 }
532
533 /**
534 * Cache the model instance for clone & reference use.
535 *
536 * @return mixed
537 */
538 private function get_model() {
539 if ( isset( $this->known[ $this->repository ] ) ) {
540 return $this->known[ $this->repository ];
541 }
542 $class = $this->repository;
543 $model = new $class();
544 $this->known[ $this->repository ] = $model;
545
546 return $model;
547 }
548 }
549