Completed
Push — add/sync-partial-sync-checksum... ( 5335a7...f2c23a )
by
unknown
17:48 queued 09:47
created

Table_Checksum::prepare_results_for_output()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 1
dl 0
loc 19
rs 9.6333
c 0
b 0
f 0
1
<?php
2
/**
3
 * Table Checksums Class.
4
 *
5
 * @package automattic/jetpack-sync
6
 */
7
8
namespace Automattic\Jetpack\Sync\Replicastore;
9
10
use Automattic\Jetpack\Sync\Modules\Comments;
11
use Automattic\Jetpack\Sync\Settings;
12
use Exception;
13
use WP_Error;
14
15
// TODO add rest endpoints to work with this, hopefully in the same folder.
16
/**
17
 * Class to handle Table Checksums.
18
 */
19
class Table_Checksum {
20
21
	/**
22
	 * Table to be checksummed.
23
	 *
24
	 * @var string
25
	 */
26
	public $table = '';
27
28
	/**
29
	 * Table Checksum Configuration.
30
	 *
31
	 * @var array
32
	 */
33
	public $table_configuration = array();
34
35
	/**
36
	 * Field to be used for range queries.
37
	 *
38
	 * @var string
39
	 */
40
	public $range_field = '';
41
42
	/**
43
	 * ID Field(s) to be used.
44
	 *
45
	 * @var array
46
	 */
47
	public $key_fields = array();
48
49
	/**
50
	 * Field(s) to be used in generating the checksum value.
51
	 *
52
	 * @var array
53
	 */
54
	public $checksum_fields = array();
55
56
	/**
57
	 * Default filter values for the table
58
	 *
59
	 * @var array
60
	 */
61
	public $filter_values = array();
62
63
	/**
64
	 * SQL Query to be used to filter results (allow/disallow).
65
	 *
66
	 * @var string
67
	 */
68
	public $additional_filter_sql = '';
69
70
	/**
71
	 * Default Checksum Table Configurations.
72
	 *
73
	 * @var array
74
	 */
75
	public $default_tables = array();
76
77
	/**
78
	 * Salt to be used when generating checksum.
79
	 *
80
	 * @var string
81
	 */
82
	public $salt = '';
83
84
	/**
85
	 * Tables which are allowed to be checksummed.
86
	 *
87
	 * @var string
88
	 */
89
	public $allowed_tables = array();
90
91
	/**
92
	 * If the table has a "parent" table that it's related to.
93
	 *
94
	 * @var mixed|null
95
	 */
96
	private $parent_table = null;
97
98
	/**
99
	 * Table_Checksum constructor.
100
	 *
101
	 * @param string $table The table to calculate checksums for.
102
	 * @param string $salt  Optional salt to add to the checksum.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $salt not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
103
	 *
104
	 * @throws Exception Throws exception from inner functions.
105
	 */
106
	public function __construct( $table, $salt = null ) {
107
108
		if ( ! Settings::is_checksum_enabled() ) {
109
			throw new Exception( 'Checksums are currently disabled.' );
110
		}
111
112
		$this->salt = $salt;
113
114
		$this->default_tables = $this->get_default_tables();
115
116
		// TODO change filters to allow the array format.
117
		// TODO add get_fields or similar method to get things out of the table.
118
		// TODO extract this configuration in a better way, still make it work with `$wpdb` names.
119
		// TODO take over the replicastore functions and move them over to this class.
120
		// TODO make the API work.
121
122
		$this->allowed_tables = apply_filters( 'jetpack_sync_checksum_allowed_tables', $this->default_tables );
123
124
		$this->table               = $this->validate_table_name( $table );
125
		$this->table_configuration = $this->allowed_tables[ $table ];
126
127
		$this->prepare_fields( $this->table_configuration );
128
129
	}
130
131
	/**
132
	 * Get Default Table configurations.
133
	 *
134
	 * @return array
135
	 */
136
	private function get_default_tables() {
137
		global $wpdb;
138
139
		return array(
140
			'posts'              => array(
141
				'table'           => $wpdb->posts,
142
				'range_field'     => 'ID',
143
				'key_fields'      => array( 'ID' ),
144
				'checksum_fields' => array( 'post_modified_gmt' ),
145
				'filter_values'   => Settings::get_disallowed_post_types_structured(),
146
			),
147
			'postmeta'           => array(
148
				'table'           => $wpdb->postmeta,
149
				'range_field'     => 'post_id',
150
				'key_fields'      => array( 'post_id', 'meta_key' ),
151
				'checksum_fields' => array( 'meta_key', 'meta_value' ),
152
				'filter_values'   => Settings::get_allowed_post_meta_structured(),
153
				'parent_table'    => 'posts',
154
			),
155
			'comments'           => array(
156
				'table'           => $wpdb->comments,
157
				'range_field'     => 'comment_ID',
158
				'key_fields'      => array( 'comment_ID' ),
159
				'checksum_fields' => array( 'comment_content' ),
160
				'filter_values'   => array(
161
					'comment_type' => array(
162
						'operator' => 'IN',
163
						'values'   => apply_filters(
164
							'jetpack_sync_whitelisted_comment_types',
165
							array( '', 'comment', 'trackback', 'pingback', 'review' )
166
						),
167
					),
168
				),
169
				'filter_sql'      => Settings::get_comments_filter_sql(),
170
			),
171
			'commentmeta'        => array(
172
				'table'           => $wpdb->commentmeta,
173
				'range_field'     => 'comment_id',
174
				'key_fields'      => array( 'comment_id', 'meta_key' ),
175
				'checksum_fields' => array( 'meta_key', 'meta_value' ),
176
				'filter_values'   => Settings::get_allowed_comment_meta_structured(),
177
				'parent_table'    => 'comments',
178
			),
179
			'terms'              => array(
180
				'table'           => $wpdb->terms,
181
				'range_field'     => 'term_id',
182
				'key_fields'      => array( 'term_id' ),
183
				'checksum_fields' => array( 'term_id', 'name', 'slug' ),
184
			),
185
			'termmeta'           => array(
186
				'table'           => $wpdb->termmeta,
187
				'range_field'     => 'term_id',
188
				'key_fields'      => array( 'term_id', 'meta_key' ),
189
				'checksum_fields' => array( 'meta_key', 'meta_value' ),
190
				'parent_table'    => 'terms',
191
			),
192
			'term_relationships' => array(
193
				'table'           => $wpdb->term_relationships,
194
				'range_field'     => 'object_id',
195
				'key_fields'      => array( 'object_id' ),
196
				'checksum_fields' => array( 'object_id', 'term_taxonomy_id' ),
197
			),
198
			'term_taxonomy'      => array(
199
				'table'           => $wpdb->term_taxonomy,
200
				'range_field'     => 'term_taxonomy_id',
201
				'key_fields'      => array( 'term_taxonomy_id' ),
202
				'checksum_fields' => array( 'term_taxonomy_id', 'term_id', 'taxonomy', 'description', 'parent' ),
203
			),
204
			'links'              => $wpdb->links, // TODO describe in the array format or add exceptions.
205
			'options'            => $wpdb->options, // TODO describe in the array format or add exceptions.
206
		);
207
	}
208
209
	/**
210
	 * Prepare field params based off provided configuration.
211
	 *
212
	 * @param array $table_configuration The table configuration array.
213
	 */
214
	private function prepare_fields( $table_configuration ) {
215
		$this->key_fields            = $table_configuration['key_fields'];
216
		$this->range_field           = $table_configuration['range_field'];
217
		$this->checksum_fields       = $table_configuration['checksum_fields'];
218
		$this->filter_values         = isset( $table_configuration['filter_values'] ) ? $table_configuration['filter_values'] : null;
219
		$this->additional_filter_sql = ! empty( $table_configuration['filter_sql'] ) ? $table_configuration['filter_sql'] : '';
220
		$this->parent_table          = isset( $table_configuration['parent_table'] ) ? $table_configuration['parent_table'] : null;
221
	}
222
223
	/**
224
	 * Verify provided table name is valid for checksum processing.
225
	 *
226
	 * @param string $table Table name to validate.
227
	 *
228
	 * @return mixed|string
229
	 * @throws Exception Throw an exception on validation failure.
230
	 */
231
	private function validate_table_name( $table ) {
232
		if ( empty( $table ) ) {
233
			throw new Exception( 'Invalid table name: empty' );
234
		}
235
236
		if ( ! array_key_exists( $table, $this->allowed_tables ) ) {
237
			throw new Exception( "Invalid table name: $table not allowed" );
238
		}
239
240
		// TODO other checks if such are needed.
241
242
		return $this->allowed_tables[ $table ]['table'];
243
	}
244
245
	/**
246
	 * Verify provided fields are proper names.
247
	 *
248
	 * @param array $fields Array of field names to validate.
249
	 *
250
	 * @throws Exception Throw an exception on failure to validate.
251
	 */
252
	private function validate_fields( $fields ) {
253
		foreach ( $fields as $field ) {
254
			if ( ! preg_match( '/^[0-9,a-z,A-Z$_]+$/i', $field ) ) {
255
				throw new Exception( "Invalid field name: $field is not allowed" );
256
			}
257
258
			// TODO other verifications of the field names.
259
		}
260
	}
261
262
	/**
263
	 * Verify the fields exist in the table.
264
	 *
265
	 * @param array $fields Array of fields to validate.
266
	 *
267
	 * @return bool
268
	 * @throws Exception Throw an exception on failure to validate.
269
	 */
270
	private function validate_fields_against_table( $fields ) {
271
		global $wpdb;
272
273
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
274
		$result = $wpdb->get_row( "SELECT * FROM {$this->table} LIMIT 1", ARRAY_A );
275
		if ( ! is_array( $result ) ) {
276
			throw new Exception( 'Unexpected $wpdb->query output: not array' );
277
		}
278
279
		// Check if the fields are actually contained in the table.
280
		foreach ( $fields as $field_to_check ) {
281
			if ( ! array_key_exists( $field_to_check, $result ) ) {
282
				throw new Exception( "Invalid field name: field '{$field_to_check}' doesn't exist in table {$this->table}" );
283
			}
284
		}
285
286
		return true;
287
	}
288
289
	/**
290
	 * Verify the configured fields.
291
	 *
292
	 * @throws Exception Throw an exception on failure to validate in the internal functions.
293
	 */
294
	private function validate_input() {
295
		$fields = array_merge( array( $this->range_field ), $this->key_fields, $this->checksum_fields );
296
297
		$this->validate_fields( $fields );
298
		$this->validate_fields_against_table( $fields );
299
	}
300
301
	/**
302
	 * Prepare filter values as SQL statements to be added to the other filters.
303
	 *
304
	 * @param array  $filter_values The filter values array.
305
	 * @param string $table_prefix  If the values are going to be used in a sub-query, add a prefix with the table alias.
306
	 *
307
	 * @return array|null
308
	 */
309
	private function prepare_filter_values_as_sql( $filter_values = array(), $table_prefix = '' ) {
310
		global $wpdb;
311
312
		if ( ! is_array( $filter_values ) ) {
313
			return null;
314
		}
315
316
		$result = array();
317
318
		foreach ( $filter_values as $field => $filter ) {
319
			$key = ( ! empty( $table_prefix ) ? $table_prefix : $this->table ) . '.' . $field;
320
321
			switch ( $filter['operator'] ) {
322
				case 'IN':
323
				case 'NOT IN':
324
					$values_placeholders = implode( ',', array_fill( 0, count( $filter['values'] ), '%s' ) );
325
					$statement           = "{$key} {$filter['operator']} ( $values_placeholders )";
326
327
					// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
328
					$prepared_statement = $wpdb->prepare( $statement, $filter['values'] );
329
330
					$result[] = $prepared_statement;
331
					break;
332
				// TODO implement other operators if needed.
333
			}
334
		}
335
336
		return $result;
337
	}
338
339
	/**
340
	 * Build the filter query baased off range fields and values and the additional sql.
341
	 *
342
	 * @param int|null   $range_from    Start of the range.
343
	 * @param int|null   $range_to      End of the range.
344
	 * @param array|null $filter_values Additional filter values. Not used at the moment.
345
	 * @param string     $table_prefix  Table name to be prefixed to the columns. Used in sub-queries where columns can clash.
346
	 *
347
	 * @return string
348
	 */
349
	public function build_filter_statement( $range_from = null, $range_to = null, $filter_values = null, $table_prefix = '' ) {
350
		global $wpdb;
351
352
		// If there is a field prefix that we want to use with table aliases.
353
		$parent_prefix = ( ! empty( $table_prefix ) ? $table_prefix : $this->table ) . '.';
354
355
		/**
356
		 * Prepare the ranges.
357
		 */
358
359
		$filter_array = array( '1 = 1' );
360 View Code Duplication
		if ( null !== $range_from ) {
361
			// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
362
			$filter_array[] = $wpdb->prepare( "{$parent_prefix}{$this->range_field} >= %d", array( intval( $range_from ) ) );
363
		}
364 View Code Duplication
		if ( null !== $range_to ) {
365
			// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
366
			$filter_array[] = $wpdb->prepare( "{$parent_prefix}{$this->range_field} <= %d", array( intval( $range_to ) ) );
367
		}
368
369
		/**
370
		 * End prepare the ranges.
371
		 */
372
373
		/**
374
		 * Prepare data filters.
375
		 */
376
377
		// Default filters.
378
		if ( $this->filter_values ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->filter_values of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
379
			$prepared_values_statements = $this->prepare_filter_values_as_sql( $this->filter_values, $table_prefix );
380
			if ( $prepared_values_statements ) {
381
				$filter_array = array_merge( $filter_array, $prepared_values_statements );
382
			}
383
		}
384
385
		// Additional filters.
386
		if ( ! empty( $filter_values ) ) {
387
			// Prepare filtering.
388
			$prepared_values_statements = $this->prepare_filter_values_as_sql( $filter_values, $table_prefix );
389
			if ( $prepared_values_statements ) {
390
				$filter_array = array_merge( $filter_array, $prepared_values_statements );
391
			}
392
		}
393
394
		// Add any additional filters via direct SQL statement.
395
		// Currently used only because we haven't converted all filtering to happen via `filter_values`.
396
		// This SQL is NOT prefixed and column clashes can occur when used in sub-queries.
397
		if ( $this->additional_filter_sql ) {
398
			$filter_array[] = $this->additional_filter_sql;
399
		}
400
401
		/**
402
		 * End prepare data filters.
403
		 */
404
		return implode( ' AND ', $filter_array );
405
	}
406
407
	/**
408
	 * Returns the checksum query. All validation of fields and configurations are expected to occur prior to usage.
409
	 *
410
	 * @param int|null   $range_from      The start of the range.
411
	 * @param int|null   $range_to        The end of the range.
412
	 * @param array|null $filter_values   Additional filter values. Not used at the moment.
413
	 * @param bool       $granular_result If the function should return a granular result.
414
	 *
415
	 * @return string
416
	 *
417
	 * @throws Exception Throws and exception if validation fails in the internal function calls.
418
	 */
419
	private function build_checksum_query( $range_from = null, $range_to = null, $filter_values = null, $granular_result = false ) {
420
		global $wpdb;
421
422
		// Escape the salt.
423
		$salt = $wpdb->prepare( '%s', $this->salt ); // TODO escape or prepare statement.
424
425
		// Prepare the compound key.
426
		$key_fields = implode( ',', $this->key_fields );
427
428
		// Prepare the checksum fields.
429
		$checksum_fields_string = implode( ',', array_merge( $this->checksum_fields, array( $salt ) ) );
430
431
		$additional_fields = '';
432
		if ( $granular_result ) {
433
			// TODO uniq the fields as sometimes(most) range_index is the key and there's no need to select the same field twice.
434
			$additional_fields = "
435
				{$this->range_field} as range_index,
436
			    {$key_fields},
437
			";
438
		}
439
440
		$filter_stamenet = $this->build_filter_statement( $range_from, $range_to, $filter_values );
441
442
		$join_statement = '';
443
		if ( $this->parent_table ) {
444
			$parent_table_obj    = new Table_Checksum( $this->parent_table );
445
			$parent_filter_query = $parent_table_obj->build_filter_statement( $range_from, $range_to, null, 'parent_table' );
446
447
			$join_statement = "
448
				INNER JOIN {$parent_table_obj->table} as parent_table ON ({$this->table}.{$this->range_field} = parent_table.{$parent_table_obj->range_field} AND {$parent_filter_query})
449
			";
450
		}
451
452
		$query = "
453
			SELECT
454
				{$additional_fields}
455
				SUM(
456
					CRC32(
457
						CONCAT_WS( '#', {$salt}, {$checksum_fields_string} )
458
					)
459
				)  AS checksum
460
			 FROM
461
			    {$this->table}
462
				{$join_statement}
463
			 WHERE
464
				{$filter_stamenet}
465
		";
466
467
		/**
468
		 * We need the GROUP BY only for compound keys.
469
		 */
470
		if ( $granular_result ) {
471
			$query .= "
472
				GROUP BY {$key_fields}
473
			";
474
		}
475
476
		return $query;
477
	}
478
479
	/**
480
	 * Obtain the min-max values (edges) of the range.
481
	 *
482
	 * @param int|null $range_from The start of the range.
483
	 * @param int|null $range_to   The end of the range.
484
	 * @param int|null $limit      How many values to return.
485
	 *
486
	 * @return array|object|void
487
	 * @throws Exception Throws an exception if validation fails on the internal function calls.
488
	 */
489
	public function get_range_edges( $range_from = null, $range_to = null, $limit = null ) {
490
		global $wpdb;
491
492
		$this->validate_fields( array( $this->range_field ) );
493
494
		// `trim()` to make sure we don't add the statement if it's empty.
495
		$filters = trim( $this->build_filter_statement( $range_from, $range_to ) );
496
497
		$filter_statement = '';
498
		if ( ! empty( $filters ) ) {
499
			$filter_statement = "
500
				WHERE
501
					{$filters}
502
			";
503
		}
504
505
		// Only make the distinct count when we know there can be multiple entries for the range column.
506
		$distinct_count = count( $this->key_fields ) > 1 ? 'DISTINCT' : '';
507
508
		$query = "
509
			SELECT
510
			       MIN({$this->range_field}) as min_range,
511
			       MAX({$this->range_field}) as max_range,
512
			       COUNT( {$distinct_count} {$this->range_field}) as item_count
513
			FROM
514
		";
515
516
		/**
517
		 * If `$limit` is not specified, we can directly use the table.
518
		 */
519
		if ( ! $limit ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $limit of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
520
			$query .= "
521
				{$this->table}
522
	            {$filter_statement}
523
			";
524
		} else {
525
			/**
526
			 * If there is `$limit` specified, we can't directly use `MIN/MAX()` as they don't work with `LIMIT`.
527
			 * That's why we will alter the query for this case.
528
			 */
529
			$limit = intval( $limit );
530
531
			$query .= "
532
				(
533
					SELECT
534
						{$distinct_count} {$this->range_field}
535
					FROM
536
						{$this->table}
537
						{$filter_statement}
538
					ORDER BY
539
						{$this->range_field} ASC
540
					LIMIT {$limit}
541
				) as ids_query
542
			";
543
		}
544
545
		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
546
		$result = $wpdb->get_row( $query, ARRAY_A );
547
548
		if ( ! $result || ! is_array( $result ) ) {
549
			throw new Exception( 'Unable to get range edges' );
550
		}
551
552
		return $result;
553
	}
554
555
	/**
556
	 * Update the results to have key/checksum format.
557
	 *
558
	 * @param array $results Prepare the results for output of granular results.
559
	 */
560
	protected function prepare_results_for_output( &$results ) {
561
		// get the compound key.
562
		// only return range and compound key for granular results.
563
564
		foreach ( $results as &$result ) {
565
			// Working on reference to save memory here.
566
567
			$key = array();
568
			foreach ( $this->key_fields as $field ) {
569
				$key[] = $result[ $field ];
570
			}
571
572
			$result = array(
573
				'key'      => implode( '-', $key ),
574
				'checksum' => $result['checksum'],
575
			);
576
577
		}
578
	}
579
580
	/**
581
	 * Calculate the checksum based on provided range and filters.
582
	 *
583
	 * @param int|null   $range_from          The start of the range.
584
	 * @param int|null   $range_to            The end of the range.
585
	 * @param array|null $filter_values       Additional filter values. Not used at the moment.
586
	 * @param bool       $granular_result     If the returned result should be granular or only the checksum.
587
	 * @param bool       $simple_return_value If we want to use a simple return value for non-granular results (return only the checksum, without wrappers).
588
	 *
589
	 * @return array|mixed|object|WP_Error|null
590
	 */
591
	public function calculate_checksum( $range_from = null, $range_to = null, $filter_values = null, $granular_result = false, $simple_return_value = true ) {
592
593
		if ( ! Settings::is_checksum_enabled() ) {
594
			return new WP_Error( 'checksum_disabled', 'Checksums are currently disabled.' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'checksum_disabled'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
595
		}
596
597
		try {
598
			$this->validate_input();
599
		} catch ( Exception $ex ) {
600
			return new WP_Error( 'invalid_input', $ex->getMessage() );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'invalid_input'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
601
		}
602
603
		$query = $this->build_checksum_query( $range_from, $range_to, $filter_values, $granular_result );
604
605
		global $wpdb;
606
607
		if ( ! $granular_result ) {
608
			// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
609
			$result = $wpdb->get_row( $query, ARRAY_A );
610
611
			if ( ! is_array( $result ) ) {
612
				return new WP_Error( 'invalid_query', "Result wasn't an array" );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'invalid_query'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
613
			}
614
615
			if ( $simple_return_value ) {
616
				return $result['checksum'];
617
			}
618
619
			return array(
620
				'range'    => $range_from . '-' . $range_to,
621
				'checksum' => $result['checksum'],
622
			);
623
		} else {
624
			// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
625
			$result = $wpdb->get_results( $query, ARRAY_A );
626
			$this->prepare_results_for_output( $result );
627
628
			return $result;
629
		}
630
	}
631
}
632