PluginProbe ʕ •ᴥ•ʔ
WPForms – Easy Form Builder for WordPress – Contact Forms, Payment Forms, Surveys, & More / 1.9.9.3
WPForms – Easy Form Builder for WordPress – Contact Forms, Payment Forms, Surveys, & More v1.9.9.3
1.10.1 1.10.0.5 trunk 1.1.4 1.1.4.2 1.1.5 1.1.5.1 1.1.6 1.1.6.1 1.1.7 1.1.7.1 1.1.7.2 1.1.8 1.1.8.1 1.1.8.2 1.1.8.3 1.1.8.4 1.10.0.1 1.10.0.2 1.10.0.3 1.10.0.4 1.2.0 1.2.0.1 1.2.1 1.2.2 1.2.2.1 1.2.2.2 1.2.3 1.2.3.1 1.2.3.2 1.2.4 1.2.4.1 1.2.5 1.2.5.1 1.2.6 1.2.7 1.2.8 1.2.8.1 1.2.9 1.3.0 1.3.1 1.3.1.1 1.3.1.2 1.3.2 1.3.3 1.3.5 1.3.6 1.3.6.1 1.3.6.2 1.3.7.2 1.3.7.3 1.3.7.4 1.3.8 1.3.9.1 1.4.0.1 1.4.1.1 1.4.2 1.4.2.1 1.4.2.2 1.4.3 1.4.4 1.4.4.1 1.4.5 1.4.5.1 1.4.5.2 1.4.5.3 1.4.6 1.4.7.1 1.4.7.2 1.4.8.1 1.4.9 1.5.0.1 1.5.0.3 1.5.0.4 1.5.1 1.5.1.1 1.5.1.3 1.5.2.1 1.5.2.2 1.5.2.3 1.5.3 1.5.3.1 1.5.4.1 1.5.4.2 1.5.5 1.5.5.1 1.5.6 1.5.6.2 1.5.7 1.5.8.2 1.5.9.1 1.5.9.4 1.5.9.5 1.6.0.1 1.6.0.2 1.6.1 1.6.2.2 1.6.2.3 1.6.3.1 1.6.4 1.6.4.1 1.6.5 1.6.6 1.6.7 1.6.7.1 1.6.7.2 1.6.7.3 1.6.8 1.6.8.1 1.6.9 1.7.0 1.7.1.1 1.7.1.2 1.7.2 1.7.2.1 1.7.3 1.7.4 1.7.4.1 1.7.4.2 1.7.5.1 1.7.5.2 1.7.5.3 1.7.5.5 1.7.6 1.7.7 1.7.7.1 1.7.7.2 1.7.8 1.7.9 1.7.9.1 1.8.0.1 1.8.0.2 1.8.1.1 1.8.1.2 1.8.1.3 1.8.2.1 1.8.2.2 1.8.2.3 1.8.3 1.8.3.1 1.8.4 1.8.4.1 1.8.5.2 1.8.5.3 1.8.5.4 1.8.6.2 1.8.6.3 1.8.6.4 1.8.7.2 1.8.8.2 1.8.8.3 1.8.9.1 1.8.9.2 1.8.9.4 1.8.9.5 1.8.9.6 1.9.0.1 1.9.0.2 1.9.0.3 1.9.0.4 1.9.1.1 1.9.1.2 1.9.1.3 1.9.1.4 1.9.1.5 1.9.1.6 1.9.2.1 1.9.2.2 1.9.2.3 1.9.3.1 1.9.3.2 1.9.4.1 1.9.4.2 1.9.5 1.9.5.1 1.9.5.2 1.9.6 1.9.6.1 1.9.6.2 1.9.7.1 1.9.7.2 1.9.7.3 1.9.8.1 1.9.8.2 1.9.8.4 1.9.8.7 1.9.9.2 1.9.9.3 1.9.9.4
wpforms-lite / includes / class-db.php
wpforms-lite / includes Last commit date
admin 3 months ago emails 4 months ago fields 4 months ago functions 3 months ago providers 10 months ago templates 4 months ago class-db.php 1 year ago class-fields.php 1 year ago class-form.php 4 months ago class-install.php 1 year ago class-process.php 4 months ago class-providers.php 1 year ago class-templates.php 2 years ago class-widget.php 1 year ago deprecated.php 8 months ago functions-list.php 3 years ago functions.php 11 months ago integrations.php 4 months ago
class-db.php
950 lines
1 <?php
2
3 // phpcs:disable WPForms.Comments.PHPDocHooks.RequiredHookDocumentation, WPForms.PHP.ValidateHooks.InvalidHookName
4 // phpcs:disable Generic.Commenting.DocComment.MissingShort
5 /** @noinspection AutoloadingIssuesInspection */
6 /** @noinspection PhpIllegalPsrClassPathInspection */
7 // phpcs:disable Generic.Commenting.DocComment.MissingShort
8
9 use WPForms\Helpers\DB;
10
11 /**
12 * DB class.
13 *
14 * This handy class originated from Pippin's Easy Digital Downloads.
15 * See https://github.com/easydigitaldownloads/easy-digital-downloads/blob/master/includes/class-edd-db.php
16 *
17 * Subclasses should define $table_name, $version, and $primary_key in __construct() method.
18 *
19 * @since 1.1.6
20 */
21 abstract class WPForms_DB {
22
23 /**
24 * Maximum length of index key.
25 *
26 * Indexes have a maximum size of 767 bytes. Historically, we haven't needed to be concerned about that.
27 * As of WP 4.2, however, WP moved to utf8mb4, which uses 4 bytes per character. This means that an index, which
28 * used to have room for floor(767/3) = 255 characters, now only has room for floor(767/4) = 191 characters.
29 *
30 * @since 1.8.2
31 */
32 const MAX_INDEX_LENGTH = 191;
33
34 /**
35 * The dedicated cache key to store the All Keys array.
36 *
37 * @since 1.9.0
38 */
39 const ALL_KEYS = '_all_keys';
40
41 /**
42 * Database table name.
43 *
44 * @since 1.1.6
45 *
46 * @var string
47 */
48 public $table_name;
49
50 /**
51 * Database version.
52 *
53 * @since 1.1.6
54 *
55 * @var string
56 */
57 public $version;
58
59 /**
60 * Primary key (unique field) for the database table.
61 *
62 * @since 1.1.6
63 *
64 * @var string
65 */
66 public $primary_key;
67
68 /**
69 * Database type identifier.
70 *
71 * @since 1.5.1
72 *
73 * @var string
74 */
75 public $type;
76
77 /**
78 * Cache group.
79 *
80 * @since 1.9.0
81 *
82 * @var string
83 */
84 private $cache_group;
85
86 /**
87 * Cache disabled.
88 *
89 * @since 1.9.0
90 *
91 * @var bool
92 */
93 private $cache_disabled;
94
95 /**
96 * WPForms_DB constructor.
97 *
98 * @since 1.9.0
99 */
100 public function __construct() {
101
102 $this->cache_group = static::class . '_cache';
103 $this->cache_disabled = defined( 'WPFORMS_DISABLE_DB_CACHE' ) && WPFORMS_DISABLE_DB_CACHE;
104
105 $this->hooks();
106 }
107
108 /**
109 * Query filter.
110 *
111 * @since 1.9.0
112 *
113 * @return void
114 */
115 private function hooks() {
116
117 add_filter( 'query', [ $this, 'query_filter' ] );
118 }
119
120 /**
121 * Retrieve the list of columns for the database table.
122 * Subclasses should define an array of columns here.
123 *
124 * @since 1.1.6
125 *
126 * @return array List of columns.
127 */
128 public function get_columns() {
129
130 return [];
131 }
132
133 /**
134 * Retrieve column defaults.
135 * Subclasses can define default for any/all columns defined in the get_columns() method.
136 *
137 * @since 1.1.6
138 *
139 * @return array All defined column defaults.
140 */
141 public function get_column_defaults() {
142
143 return [];
144 }
145
146 /**
147 * Filter the query.
148 *
149 * @since 1.9.0
150 *
151 * @param string|mixed $query Query.
152 *
153 * @return string
154 */
155 public function query_filter( $query ): string {
156
157 $query = (string) $query;
158
159 if ( strpos( $query, $this->table_name ) === false ) {
160 // Not a query for our table, bail out.
161 return $query;
162 }
163
164 if ( ! $this->is_select( $query ) ) {
165 // Flush cache on non-SELECT queries.
166 $this->cache_flush_group();
167 }
168
169 return $query;
170 }
171
172 /**
173 * Retrieve a row from the database based on a given row ID.
174 *
175 * @since 1.1.6
176 *
177 * @param int $row_id Row ID.
178 *
179 * @return null|object
180 */
181 public function get( $row_id ) {
182
183 global $wpdb;
184
185 $key = md5( __METHOD__ . $row_id );
186 $row = $this->cache_get( $key, $found );
187
188 if ( $found ) {
189 return $row;
190 }
191
192 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
193 $row = $wpdb->get_row(
194 $wpdb->prepare(
195 "SELECT * FROM $this->table_name WHERE $this->primary_key = %d LIMIT 1;", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
196 (int) $row_id
197 )
198 );
199
200 $this->cache_set( $key, $row );
201
202 return $row;
203 }
204
205 /**
206 * Retrieve a row based on column and row ID.
207 *
208 * @since 1.1.6
209 *
210 * @param string $column Column name.
211 * @param int|string $value Column value.
212 *
213 * @return object|null Database query result, object or null on failure.
214 */
215 public function get_by( $column, $value ) {
216
217 global $wpdb;
218
219 if (
220 empty( $value ) ||
221 ! array_key_exists( $column, $this->get_columns() )
222 ) {
223 return null;
224 }
225
226 $key = md5( __METHOD__ . $column . $value );
227 $row = $this->cache_get( $key, $found );
228
229 if ( $found ) {
230 return $row;
231 }
232
233 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
234 $row = $wpdb->get_row(
235 $wpdb->prepare(
236 "SELECT * FROM $this->table_name WHERE $column = %s LIMIT 1;", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
237 $value
238 )
239 );
240
241 $this->cache_set( $key, $row );
242
243 return $row;
244 }
245
246 /**
247 * Retrieve a value based on column name and row ID.
248 *
249 * @since 1.1.6
250 *
251 * @param string $column Column name.
252 * @param int|string $row_id Row ID.
253 *
254 * @return string|null Database query result (as string), or null on failure.
255 * @noinspection PhpUnused
256 */
257 public function get_column( $column, $row_id ) {
258
259 global $wpdb;
260
261 if ( empty( $row_id ) || ! array_key_exists( $column, $this->get_columns() ) ) {
262 return null;
263 }
264
265 $key = md5( __METHOD__ . $column . $row_id );
266 $var = $this->cache_get( $key, $found );
267
268 if ( $found ) {
269 return $var;
270 }
271
272 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
273 $var = $wpdb->get_var(
274 $wpdb->prepare(
275 "SELECT $column FROM $this->table_name WHERE $this->primary_key = %d LIMIT 1;", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
276 (int) $row_id
277 )
278 );
279
280 $this->cache_set( $key, $var );
281
282 return $var;
283 }
284
285 /**
286 * Retrieve one column value based on another given column and matching value.
287 *
288 * @since 1.1.6
289 *
290 * @param string $column Column name.
291 * @param string $column_where Column to match against in the WHERE clause.
292 * @param string $column_value Value to match to the column in the WHERE clause.
293 *
294 * @return string|null Database query result (as string), or null on failure.
295 * @noinspection PhpUnused
296 */
297 public function get_column_by( $column, $column_where, $column_value ) {
298
299 global $wpdb;
300
301 if (
302 empty( $column ) ||
303 empty( $column_where ) ||
304 empty( $column_value ) ||
305 ! array_key_exists( $column_where, $this->get_columns() ) ||
306 ! array_key_exists( $column, $this->get_columns() )
307 ) {
308 return null;
309 }
310
311 $key = md5( __METHOD__ . $column . $column_where . $column_value );
312 $var = $this->cache_get( $key, $found );
313
314 if ( $found ) {
315 return $var;
316 }
317
318 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
319 $var = $wpdb->get_var(
320 $wpdb->prepare(
321 "SELECT $column FROM $this->table_name WHERE $column_where = %s LIMIT 1;", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
322 $column_value
323 )
324 );
325
326 $this->cache_set( $key, $var );
327
328 return $var;
329 }
330
331 /**
332 * Clone of $wpdb->query() with caching.
333 *
334 * @since 1.9.0
335 *
336 * @param string $query Database query.
337 *
338 * @return int|bool Boolean true for CREATE, ALTER, TRUNCATE and DROP queries. Number of rows
339 * affected/selected for all other queries. Boolean false on error.
340 *
341 * @noinspection PhpMissingParamTypeInspection
342 */
343 public function query( $query ) {
344
345 global $wpdb;
346
347 if ( ! $this->is_select( $query ) ) {
348 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
349 return $wpdb->query( $query );
350 }
351
352 $key = md5( __METHOD__ . $query );
353 $results = $this->cache_get( $key, $found );
354
355 if ( $found ) {
356 return $results;
357 }
358
359 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
360 $results = $wpdb->query( $query );
361
362 $this->cache_set( $key, $results );
363
364 return $results;
365 }
366
367 /**
368 * Clone of $wpdb->get_results() with caching.
369 *
370 * @since 1.9.0
371 *
372 * @param string|null $query SQL query.
373 * @param string $output Any of ARRAY_A | ARRAY_N | OBJECT | OBJECT_K constants.
374 *
375 * @return array|object|null Database query results.
376 * @noinspection PhpMissingParamTypeInspection
377 */
378 public function get_results( $query = null, $output = OBJECT ) {
379
380 global $wpdb;
381
382 if ( ! $this->is_select( $query ) ) {
383 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
384 return $wpdb->get_results( $query, $output );
385 }
386
387 $key = md5( __METHOD__ . $query . $output );
388 $results = $this->cache_get( $key, $found );
389
390 if ( $found ) {
391 return $results;
392 }
393
394 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
395 $results = $wpdb->get_results( $query, $output );
396
397 $this->cache_set( $key, $results );
398
399 return $results;
400 }
401
402 /**
403 * Clone of $wpdb->get_col() with caching.
404 *
405 * @since 1.9.4
406 *
407 * @param string|null $query SQL query.
408 * @param int $x Column to return. Indexed from 0.
409 *
410 * @return array Database query results.
411 * @noinspection PhpMissingParamTypeInspection
412 */
413 public function get_col( $query = null, $x = 0 ) {
414
415 global $wpdb;
416
417 if ( ! $this->is_select( $query ) ) {
418 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
419 return $wpdb->get_col( $query );
420 }
421
422 $key = md5( __METHOD__ . $query . $x );
423 $col = $this->cache_get( $key, $found );
424
425 if ( $found ) {
426 return $col;
427 }
428
429 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
430 $col = $wpdb->get_col( $query, $x );
431
432 $this->cache_set( $key, $col );
433
434 return $col;
435 }
436
437 /**
438 * Clone of $wpdb->get_row() with caching.
439 *
440 * @since 1.9.0
441 *
442 * @param string|null $query SQL query.
443 * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which
444 * correspond to an stdClass object, an associative array, or a numeric array,
445 * respectively. Default OBJECT.
446 * @param int $y Optional. Row to return. Indexed from 0. Default 0.
447 *
448 * @return array|int|object|stdClass|null Database query result in format specified by $output or null on failure.
449 * @noinspection PhpMissingParamTypeInspection
450 */
451 public function get_row( $query = null, $output = OBJECT, $y = 0 ) {
452
453 global $wpdb;
454
455 if ( ! $query ) {
456 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
457 return $wpdb->get_row( $query, $output, $y );
458 }
459
460 $key = md5( __METHOD__ . $query . $output . $y );
461 $row = $this->cache_get( $key, $found );
462
463 if ( $found ) {
464 return $row;
465 }
466
467 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
468 $row = $wpdb->get_row( $query, $output, $y );
469
470 $this->cache_set( $key, $row );
471
472 return $row;
473 }
474
475 /**
476 * Clone of $wpdb->get_var() with caching.
477 *
478 * @since 1.9.0
479 *
480 * @param string|null $query Optional. SQL query. Defaults to null, use the result from the previous query.
481 * @param int $x Optional. Column of value to return. Indexed from 0. Default 0.
482 * @param int $y Optional. Row of value to return. Indexed from 0. Default 0.
483 *
484 * @return string|null Database query result (as string), or null on failure.
485 *
486 * @noinspection PhpMissingParamTypeInspection
487 */
488 public function get_var( $query = null, $x = 0, $y = 0 ) {
489
490 global $wpdb;
491
492 if ( ! $query ) {
493 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
494 return $wpdb->get_var( $query, $x, $y );
495 }
496
497 $key = md5( __METHOD__ . $query . $x . $y );
498 $var = $this->cache_get( $key, $found );
499
500 if ( $found ) {
501 return $var;
502 }
503
504 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
505 $var = $wpdb->get_var( $query, $x, $y );
506
507 $this->cache_set( $key, $var );
508
509 return $var;
510 }
511
512 /**
513 * Insert a new record into the database.
514 *
515 * @since 1.1.6
516 *
517 * @param array $data Column data.
518 * @param string $type Optional. Data type context.
519 *
520 * @return int ID for the newly inserted record. Zero otherwise.
521 */
522 public function add( $data, $type = '' ) {
523
524 global $wpdb;
525
526 // Set default values.
527 $data = wp_parse_args( $data, $this->get_column_defaults() );
528
529 do_action( 'wpforms_pre_insert_' . $type, $data );
530
531 // Initialise column format array.
532 $column_formats = $this->get_columns();
533
534 // Force fields to lower a case.
535 $data = array_change_key_case( $data );
536
537 // Whitelist columns.
538 $data = array_intersect_key( $data, $column_formats );
539
540 // Reorder $column_formats to match the order of columns given in $data.
541 $data_keys = array_keys( $data );
542 $column_formats = array_merge( array_flip( $data_keys ), $column_formats );
543
544 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
545 $wpdb->insert( $this->table_name, $data, $column_formats );
546
547 do_action( 'wpforms_post_insert_' . $type, $wpdb->insert_id, $data );
548
549 return $wpdb->insert_id;
550 }
551
552 /**
553 * Insert a new record into the database. This runs the add() method.
554 *
555 * @see add()
556 *
557 * @since 1.1.6
558 *
559 * @param array $data Column data.
560 *
561 * @return int ID for the newly inserted record.
562 */
563 public function insert( $data ) {
564
565 return $this->add( $data );
566 }
567
568 /**
569 * Update an existing record in the database.
570 *
571 * @since 1.1.6
572 *
573 * @param int|string $row_id Row ID for the record being updated.
574 * @param array $data Optional. Array of columns and associated data to update. Default empty array.
575 * @param string $where Optional. Column to match against in the WHERE clause. If empty, $primary_key
576 * will be used. Default empty.
577 * @param string $type Optional. Data type context, e.g. 'affiliate', 'creative', etc. Default empty.
578 *
579 * @return bool False if the record could not be updated, true otherwise.
580 */
581 public function update( $row_id, $data = [], $where = '', $type = '' ) {
582
583 global $wpdb;
584
585 // Row ID must be a positive integer.
586 $row_id = absint( $row_id );
587
588 if ( empty( $row_id ) ) {
589 return false;
590 }
591
592 if ( empty( $where ) ) {
593 $where = $this->primary_key;
594 }
595
596 /**
597 * Fires before updating a record in the database.
598 *
599 * @since 1.5.9
600 * @since 1.9.2 Added $row_id parameter.
601 *
602 * @param array $data Array of columns and associated data to update.
603 * @param int $row_id Row ID for the record being updated.
604 */
605 do_action( "wpforms_pre_update_{$type}", $data, $row_id );
606
607 // Initialise column format array.
608 $column_formats = $this->get_columns();
609
610 // Force fields to the lower case.
611 $data = array_change_key_case( $data );
612
613 // Whitelist columns.
614 $data = array_intersect_key( $data, $column_formats );
615
616 // Reorder $column_formats to match the order of columns given in $data.
617 $data_keys = array_keys( $data );
618 $column_formats = array_merge( array_flip( $data_keys ), $column_formats );
619
620 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
621 if ( $wpdb->update( $this->table_name, $data, [ $where => $row_id ], $column_formats ) === false ) {
622 return false;
623 }
624
625 /**
626 * Fires after a record has been updated in the database.
627 *
628 * @since 1.1.6
629 * @since 1.9.2 Added $row_id parameter.
630 *
631 * @param array $data Array of columns and associated data that were updated.
632 * @param int $row_id Row ID for the record that was updated.
633 */
634 do_action( "wpforms_post_update_{$type}", $data, $row_id );
635
636 return true;
637 }
638
639 /**
640 * Delete a record from the database.
641 *
642 * @since 1.1.6
643 *
644 * @param int|string $row_id Row ID.
645 *
646 * @return bool False if the record could not be deleted, true otherwise.
647 */
648 public function delete( $row_id = 0 ): bool {
649
650 global $wpdb;
651
652 // Row ID must be a positive integer.
653 $row_id = absint( $row_id );
654
655 if ( empty( $row_id ) ) {
656 return false;
657 }
658
659 /**
660 * Fires before a record is deleted from the database.
661 *
662 * @since 1.5.9
663 *
664 * @param int $row_id Row ID.
665 */
666 do_action( 'wpforms_pre_delete', $row_id );
667
668 /**
669 * Fires before a record is deleted from the database by type.
670 *
671 * @since 1.5.9
672 * @since 1.8.6 Added `$primary_key` parameter.
673 *
674 * @param int $row_id Column value.
675 * @param string $primary_key Column name.
676 */
677 do_action( 'wpforms_pre_delete_' . $this->type, $row_id, $this->primary_key );
678
679 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
680 $result = $wpdb->query(
681 $wpdb->prepare(
682 "DELETE FROM $this->table_name WHERE $this->primary_key = %d", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
683 $row_id
684 )
685 );
686
687 if ( $result === false ) {
688 return false;
689 }
690
691 do_action( 'wpforms_post_delete', $row_id );
692 do_action( 'wpforms_post_delete_' . $this->type, $row_id );
693
694 return true;
695 }
696
697 /**
698 * Delete a record from the database by column.
699 *
700 * @since 1.1.6
701 *
702 * @param string $column Column name.
703 * @param int|string $column_value Column value.
704 *
705 * @return bool False if the record could not be deleted, true otherwise.
706 */
707 public function delete_by( $column, $column_value ) {
708
709 global $wpdb;
710
711 if (
712 empty( $column ) ||
713 empty( $column_value ) ||
714 ! array_key_exists( $column, $this->get_columns() )
715 ) {
716 return false;
717 }
718
719 // This action is documented in includes/class-db.php method delete().
720 do_action( 'wpforms_pre_delete', $column_value );
721
722 // This action is documented in includes/class-db.php method delete().
723 do_action( 'wpforms_pre_delete_' . $this->type, $column_value, $column );
724
725 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
726 $result = $wpdb->query(
727 $wpdb->prepare(
728 "DELETE FROM $this->table_name WHERE $column = %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
729 $column_value
730 )
731 );
732
733 if ( $result === false ) {
734 return false;
735 }
736
737 do_action( 'wpforms_post_delete', $column_value );
738 do_action( 'wpforms_post_delete_' . $this->type, $column_value );
739
740 return true;
741 }
742
743 /**
744 * Delete record(s) from the database using WHERE IN syntax.
745 *
746 * @since 1.6.4
747 *
748 * @param string $column Column name.
749 * @param mixed $column_values Column values.
750 *
751 * @return int|bool Number of deleted records, false otherwise.
752 */
753 public function delete_where_in( $column, $column_values ) {
754
755 global $wpdb;
756
757 if ( empty( $column ) || empty( $column_values ) ) {
758 return false;
759 }
760
761 if ( ! array_key_exists( $column, $this->get_columns() ) ) {
762 return false;
763 }
764
765 $values = (array) $column_values;
766
767 foreach ( $values as $key => $value ) {
768 // Check if a string contains an integer and sanitize accordingly.
769 if ( (string) (int) $value === $value ) {
770 $values[ $key ] = (int) $value;
771 $placeholders[ $key ] = '%d';
772 } else {
773 $values[ $key ] = sanitize_text_field( $value );
774 $placeholders[ $key ] = '%s';
775 }
776 }
777
778 $placeholders = isset( $placeholders ) ? implode( ',', $placeholders ) : '';
779 $sql = "DELETE FROM $this->table_name WHERE $column IN ( $placeholders )";
780
781 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
782 return $wpdb->query( $wpdb->prepare( $sql, $values ) );
783 }
784
785 /**
786 * Check if the given table exists.
787 *
788 * @since 1.1.6
789 * @since 1.5.9 Default value is now the current child class table name.
790 *
791 * @param string $table The table name. Defaults to the child class table name.
792 *
793 * @return bool If the table name exists.
794 */
795 public function table_exists( string $table = '' ): bool {
796
797 $table = ! empty( $table ) ? sanitize_text_field( $table ) : $this->table_name;
798
799 return DB::table_exists( $table );
800 }
801
802 /**
803 * Build WHERE for a query.
804 *
805 * @since 1.7.2.2
806 *
807 * @param array $args Optional args.
808 * @param array $keys Allowed arg items.
809 * @param string|string[] $formats Formats of arg items.
810 *
811 * @return string
812 */
813 protected function build_where( $args, $keys = [], $formats = [] ) {
814
815 $formats = array_pad( $formats, count( $keys ), '%d' );
816 $where = '';
817
818 foreach ( $keys as $index => $key ) {
819 // Value `$args[ $key ]` can be a natural number and a numeric string.
820 // We should skip empty string values, but continue working with '0'.
821 if ( empty( $args[ $key ] ) && $args[ $key ] !== '0' ) {
822 continue;
823 }
824
825 $ids = wpforms_wpdb_prepare_in( $args[ $key ], $formats[ $index ] );
826
827 $where .= empty( $where ) ? 'WHERE' : 'AND';
828 $where .= " `{$key}` IN ( {$ids} ) ";
829 }
830
831 return $where;
832 }
833
834 /**
835 * WP Cache Get wrapper.
836 *
837 * @since 1.9.0
838 *
839 * @param int|string $key Cache key.
840 * @param bool|null $found Whether the key was found in the cache.
841 *
842 * @return false|mixed
843 * @noinspection PhpMissingParamTypeInspection
844 */
845 private function cache_get( $key, &$found ) {
846
847 if ( $this->cache_disabled ) {
848 $found = false;
849
850 return false;
851 }
852
853 $all_keys = wp_cache_get( self::ALL_KEYS, $this->cache_group, false, $found );
854 $all_keys = $found ? (array) $all_keys : [];
855
856 if ( ! in_array( $key, $all_keys, true ) ) {
857 $found = false;
858
859 return false;
860 }
861
862 $data = wp_cache_get( $key, $this->cache_group, false, $found );
863
864 return $found ? $data : false;
865 }
866
867 /**
868 * WP Cache Set wrapper.
869 *
870 * @since 1.9.0
871 *
872 * @param string $key Cache key.
873 * @param mixed $data Cache data.
874 *
875 * @return bool
876 * @noinspection PhpReturnValueOfMethodIsNeverUsedInspection
877 */
878 private function cache_set( string $key, $data ): bool {
879
880 if ( $this->cache_disabled ) {
881 return false;
882 }
883
884 $all_keys = wp_cache_get( self::ALL_KEYS, $this->cache_group, false, $found );
885 $all_keys = $found ? array_unique( array_merge( (array) $all_keys, [ $key ] ) ) : [ $key ];
886
887 return (
888 wp_cache_set( $key, $data, $this->cache_group ) &&
889 wp_cache_set( self::ALL_KEYS, $all_keys, $this->cache_group )
890 );
891 }
892
893 /**
894 * Flush the cache group.
895 *
896 * @since 1.9.0
897 *
898 * @return bool
899 * @noinspection PhpReturnValueOfMethodIsNeverUsedInspection
900 */
901 private function cache_flush_group(): bool {
902
903 if ( $this->cache_disabled ) {
904 return false;
905 }
906
907 $all_keys = wp_cache_get( self::ALL_KEYS, $this->cache_group, false, $found );
908
909 if ( ! $found ) {
910 return true;
911 }
912
913 $result = wp_cache_delete( self::ALL_KEYS, $this->cache_group );
914
915 foreach ( (array) $all_keys as $key ) {
916 $result = wp_cache_delete( $key, $this->cache_group ) && $result;
917 }
918
919 return $result;
920 }
921
922 /**
923 * Check if the query is a SELECT query.
924 *
925 * @since 1.9.0
926 *
927 * @param string|null $query SQL query.
928 *
929 * @return bool
930 * @noinspection PhpMissingParamTypeInspection
931 */
932 private function is_select( $query ): bool {
933
934 return stripos( trim( (string) $query ), 'SELECT' ) === 0;
935 }
936
937 /**
938 * Get an instance of the current class.
939 * Used to reload the class while going through the blogs of multisite.
940 *
941 * @see WPForms_Install::maybe_create_tables()
942 *
943 * @since 1.8.9
944 */
945 public static function get_instance(): WPForms_DB {
946
947 return new static();
948 }
949 }
950