Issues (2873)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

classes/PodsData.php (81 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * @package Pods
5
 */
6
class PodsData {
7
8
	/**
9
	 * @var PodsData
10
	 */
11
	public static $instance = null;
12
13
	/**
14
	 * @var string
15
	 */
16
	protected static $prefix = 'pods_';
17
18
	/**
19
	 * @var array
20
	 */
21
	protected static $field_types = array();
22
23
	/**
24
	 * @var bool
25
	 */
26
	public static $display_errors = true;
27
28
	/**
29
	 * @var PodsAPI
30
	 */
31
	public $api = null;
32
33
	/**
34
	 * @var null
35
	 */
36
	public $select = null;
37
38
	/**
39
	 * @var null
40
	 */
41
	public $table = null;
42
43
	/**
44
	 * @var null
45
	 */
46
	public $pod = null;
47
48
	/**
49
	 * @var array|bool|mixed|null|void
50
	 */
51
	public $pod_data = null;
52
53
	/**
54
	 * @var int
55
	 */
56
	public $id = 0;
57
58
	/**
59
	 * @var string
60
	 */
61
	public $field_id = 'id';
62
63
	/**
64
	 * @var string
65
	 */
66
	public $field_index = 'name';
67
68
	/**
69
	 * @var string
70
	 */
71
	public $field_slug = '';
72
73
	/**
74
	 * @var string
75
	 */
76
	public $join = '';
77
78
	/**
79
	 * @var array
80
	 */
81
	public $where = array();
82
83
	/**
84
	 * @var array
85
	 */
86
	public $where_default = array();
87
88
	/**
89
	 * @var string
90
	 */
91
	public $orderby = '';
92
93
	/**
94
	 * @var array
95
	 */
96
	public $fields = array();
97
98
	/**
99
	 * @var array
100
	 */
101
	public $aliases = array();
102
103
	/**
104
	 * @var
105
	 */
106
	public $detail_page;
107
108
	/**
109
	 * @var int
110
	 */
111
	public $row_number = - 1;
112
113
	/**
114
	 * @var
115
	 */
116
	public $data;
117
118
	/**
119
	 * @var
120
	 */
121
	public $row;
122
123
	/**
124
	 * @var
125
	 */
126
	public $insert_id;
127
128
	/**
129
	 * @var
130
	 */
131
	public $total;
132
133
	/**
134
	 * @var
135
	 */
136
	public $total_found;
137
138
	/**
139
	 * @var bool
140
	 */
141
	public $total_found_calculated;
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $total_found_calculated exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
142
143
	/**
144
	 * @var string
145
	 */
146
	public $page_var = 'pg';
147
148
	/**
149
	 * @var int
150
	 */
151
	public $page = 1;
152
153
	/**
154
	 * @var int
155
	 */
156
	public $limit = 15;
157
158
	/**
159
	 * @var bool
160
	 */
161
	public $pagination = true;
162
163
	/**
164
	 * @var bool
165
	 */
166
	public $search = true;
167
168
	/**
169
	 * @var string
170
	 */
171
	public $search_var = 'search';
172
173
	/**
174
	 * int | text | text_like
175
	 *
176
	 * @var string
177
	 */
178
	public $search_mode = 'int';
179
180
	/**
181
	 * @var string
182
	 */
183
	public $search_query = '';
184
185
	/**
186
	 * @var array
187
	 */
188
	public $search_fields = array();
189
190
	/**
191
	 * @var string
192
	 */
193
	public $search_where = array();
194
195
	/**
196
	 * @var array
197
	 */
198
	public $filters = array();
199
200
	/**
201
	 * Holds Traversal information about Pods
202
	 *
203
	 * @var array
204
	 */
205
	public $traversal = array();
206
207
	/**
208
	 * Holds custom Traversals to be included
209
	 *
210
	 * @var array
211
	 */
212
	public $traverse = array();
213
214
	/**
215
	 * Last select() query SQL
216
	 *
217
	 * @var string
218
	 */
219
	public $sql = false;
220
221
	/**
222
	 * Last total sql
223
	 *
224
	 * @var string
225
	 */
226
	public $total_sql = false;
227
228
	/**
229
	 * Singleton handling for a basic pods_data() request
230
	 *
231
	 * @param string  $pod    Pod name.
0 ignored issues
show
Should the type for parameter $pod 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...
232
	 * @param integer $id     Pod Item ID.
233
	 * @param bool    $strict If true throws an error if a pod is not found.
234
	 *
235
	 * @return \PodsData
236
	 *
237
	 * @since 2.3.5
238
	 */
239
	public static function init( $pod = null, $id = 0, $strict = true ) {
240
241
		if ( ( true !== $pod && null !== $pod ) || 0 != $id ) {
242
			return new PodsData( $pod, $id, $strict );
243
		} elseif ( ! is_object( self::$instance ) ) {
244
			self::$instance = new PodsData();
245
		} else {
246
			$vars = get_class_vars( __CLASS__ );
247
248
			foreach ( $vars as $var => $default ) {
249
				if ( 'api' === $var ) {
250
					continue;
251
				}
252
253
				self::$instance->{$var} = $default;
254
			}
255
		}
256
257
		return self::$instance;
258
	}
259
260
	/**
261
	 * Data Abstraction Class for Pods
262
	 *
263
	 * @param string  $pod    Pod name.
0 ignored issues
show
Should the type for parameter $pod 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...
264
	 * @param integer $id     Pod Item ID.
265
	 * @param bool    $strict If true throws an error if a pod is not found.
266
	 *
267
	 * @return \PodsData
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
268
	 *
269
	 * @license http://www.gnu.org/licenses/gpl-2.0.html
270
	 * @since   2.0
271
	 */
272
	public function __construct( $pod = null, $id = 0, $strict = true ) {
273
274
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
275
276
		if ( is_object( $pod ) && 'PodsAPI' === get_class( $pod ) ) {
277
			$this->api = $pod;
278
			$pod       = $this->api->pod;
279
		} else {
280
			$this->api = pods_api( $pod );
281
		}
282
283
		$this->api->display_errors =& self::$display_errors;
284
285
		if ( ! empty( $pod ) ) {
286
			$this->pod_data =& $this->api->pod_data;
287
288
			if ( false === $this->pod_data ) {
289
				if ( true === $strict ) {
290
					return pods_error( 'Pod not found', $this );
0 ignored issues
show
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
291
				} else {
292
					return $this;
0 ignored issues
show
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
293
				}
294
			}
295
296
			$this->pod_id = $this->pod_data['id'];
0 ignored issues
show
The property pod_id does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
297
			$this->pod    = $this->pod_data['name'];
298
			$this->fields = $this->pod_data['fields'];
299
300
			if ( isset( $this->pod_data['options']['detail_url'] ) ) {
301
				$this->detail_page = $this->pod_data['options']['detail_url'];
302
			}
303
304
			if ( isset( $this->pod_data['select'] ) ) {
305
				$this->select = $this->pod_data['select'];
306
			}
307
308
			if ( isset( $this->pod_data['table'] ) ) {
309
				$this->table = $this->pod_data['table'];
310
			}
311
312
			if ( isset( $this->pod_data['join'] ) ) {
313
				$this->join = $this->pod_data['join'];
314
			}
315
316
			if ( isset( $this->pod_data['field_id'] ) ) {
317
				$this->field_id = $this->pod_data['field_id'];
318
			}
319
320
			if ( isset( $this->pod_data['field_index'] ) ) {
321
				$this->field_index = $this->pod_data['field_index'];
322
			}
323
324
			if ( isset( $this->pod_data['field_slug'] ) ) {
325
				$this->field_slug = $this->pod_data['field_slug'];
326
			}
327
328
			if ( isset( $this->pod_data['where'] ) ) {
329
				$this->where = $this->pod_data['where'];
330
			}
331
332
			if ( isset( $this->pod_data['where_default'] ) ) {
333
				$this->where_default = $this->pod_data['where_default'];
334
			}
335
336
			if ( isset( $this->pod_data['orderby'] ) ) {
337
				$this->orderby = $this->pod_data['orderby'];
338
			}
339
340
			if ( 'settings' === $this->pod_data['type'] ) {
341
				$this->id = $this->pod_data['id'];
342
343
				$this->fetch( $this->id );
344
			} elseif ( null !== $id && ! is_array( $id ) && ! is_object( $id ) ) {
345
				$this->id = $id;
346
347
				$this->fetch( $this->id );
348
			}
349
		}//end if
350
	}
351
352
	/**
353
	 * Handle tables like they are Pods (for traversal in select/build)
354
	 *
355
	 * @param array|string $table
356
	 * @param string       $object
357
	 */
358
	public function table( $table, $object = '' ) {
359
360
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
361
362
		if ( ! is_array( $table ) ) {
363
			$object_type = '';
364
365
			if ( $wpdb->users === $table ) {
0 ignored issues
show
Usage of users/usermeta tables is highly discouraged in VIP context, For storing user additional user metadata, you should look at User Attributes.
Loading history...
366
				$object_type = 'user';
367
			} elseif ( $wpdb->posts === $table ) {
368
				$object_type = 'post_type';
369
			} elseif ( $wpdb->terms === $table ) {
370
				$object_type = 'taxonomy';
371
			} elseif ( $wpdb->options === $table ) {
372
				$object_type = 'settings';
373
			}
374
		}
375
376
		if ( ! empty( $object_type ) ) {
377
			$table = $this->api->get_table_info( $object_type, $object );
378
		}
379
380
		if ( ! empty( $table ) && is_array( $table ) ) {
381
			$table['id']   = pods_v( 'id', $table['pod'], 0, true );
382
			$table['name'] = pods_v( 'name', $table['pod'], $table['object_type'], true );
383
			$table['type'] = pods_v( 'type', $table['pod'], $table['object_type'], true );
384
385
			$default_storage = 'meta';
386
387
			if ( 'taxonomy' === $table['type'] && ! function_exists( 'get_term_meta' ) ) {
388
				$default_storage = 'none';
389
			}
390
391
			$table['storage']       = pods_v( 'storage', $table['pod'], $default_storage, true );
392
			$table['fields']        = pods_v( 'fields', $table['pod'], array() );
393
			$table['object_fields'] = pods_v( 'object_fields', $table['pod'], $this->api->get_wp_object_fields( $table['object_type'] ), true );
394
395
			$this->pod_data = $table;
396
			$this->pod_id   = $this->pod_data['id'];
397
			$this->pod      = $this->pod_data['name'];
398
			$this->fields   = $this->pod_data['fields'];
399
400
			if ( isset( $this->pod_data['select'] ) ) {
401
				$this->select = $this->pod_data['select'];
402
			}
403
404
			if ( isset( $this->pod_data['table'] ) ) {
405
				$this->table = $this->pod_data['table'];
406
			}
407
408
			if ( isset( $this->pod_data['join'] ) ) {
409
				$this->join = $this->pod_data['join'];
410
			}
411
412
			if ( isset( $this->pod_data['field_id'] ) ) {
413
				$this->field_id = $this->pod_data['field_id'];
414
			}
415
416
			if ( isset( $this->pod_data['field_index'] ) ) {
417
				$this->field_index = $this->pod_data['field_index'];
418
			}
419
420
			if ( isset( $this->pod_data['field_slug'] ) ) {
421
				$this->field_slug = $this->pod_data['field_slug'];
422
			}
423
424
			if ( isset( $this->pod_data['where'] ) ) {
425
				$this->where = $this->pod_data['where'];
426
			}
427
428
			if ( isset( $this->pod_data['where_default'] ) ) {
429
				$this->where_default = $this->pod_data['where_default'];
430
			}
431
432
			if ( isset( $this->pod_data['orderby'] ) ) {
433
				$this->orderby = $this->pod_data['orderby'];
434
			}
435
		}//end if
436
	}
437
438
	/**
439
	 * Insert an item, eventually mapping to WPDB::insert
440
	 *
441
	 * @param string $table  Table name.
442
	 * @param array  $data   Data to insert (in column => value pairs) Both $data columns and $data values should be
443
	 *                       "raw" (neither should be SQL escaped).
444
	 * @param array  $format (optional) An array of formats to be mapped to each of the value in $data.
0 ignored issues
show
Should the type for parameter $format not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

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. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

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

Loading history...
445
	 *
446
	 * @return int|bool The ID of the item
447
	 *
448
	 * @uses  wpdb::insert
449
	 *
450
	 * @since 2.0
451
	 */
452
	public function insert( $table, $data, $format = null ) {
453
454
		/**
455
		 * @var $wpdb wpdb
456
		 */
457
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
458
459
		if ( strlen( $table ) < 1 || empty( $data ) || ! is_array( $data ) ) {
460
			return false;
461
		}
462
463
		if ( empty( $format ) ) {
464
			$format = array();
465
466
			foreach ( $data as $field ) {
467
				if ( isset( self::$field_types[ $field ] ) ) {
468
					$format[] = self::$field_types[ $field ];
469
				} elseif ( isset( $wpdb->field_types[ $field ] ) ) {
470
					$format[] = $wpdb->field_types[ $field ];
471
				} else {
472
					break;
473
				}
474
			}
475
		}
476
477
		list( $table, $data, $format ) = self::do_hook( 'insert', array( $table, $data, $format ), $this );
478
479
		$result          = $wpdb->insert( $table, $data, $format );
0 ignored issues
show
Usage of a direct database call is discouraged.
Loading history...
480
		$this->insert_id = $wpdb->insert_id;
481
482
		if ( false !== $result ) {
483
			return $this->insert_id;
484
		}
485
486
		return false;
487
	}
488
489
	/**
490
	 * @static
491
	 *
492
	 * Insert into a table, if unique key exists just update values.
493
	 *
494
	 * Data must be a key value pair array, keys act as table rows.
495
	 *
496
	 * Returns the prepared query from wpdb or false for errors
497
	 *
498
	 * @param string $table   Name of the table to update.
499
	 * @param array  $data    column => value pairs.
500
	 * @param array  $formats For $wpdb->prepare, uses sprintf formatting.
501
	 *
502
	 * @return mixed Sanitized query string
503
	 *
504
	 * @uses  wpdb::prepare
505
	 *
506
	 * @since 2.0
507
	 */
508
	public static function insert_on_duplicate( $table, $data, $formats = array() ) {
509
510
		/**
511
		 * @var $wpdb wpdb
512
		 */
513
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
514
515
		$columns = array_keys( $data );
516
517
		$update = array();
518
		$values = array();
519
520
		foreach ( $columns as $column ) {
521
			$update[] = "`{$column}` = VALUES( `{$column}` )";
522
			$values[] = '%s';
523
		}
524
525
		if ( empty( $formats ) ) {
526
			$formats = $values;
527
		}
528
529
		$columns_data = implode( '`, `', $columns );
530
		$formats      = implode( ', ', $formats );
531
		$update       = implode( ', ', $update );
532
533
		$sql = "INSERT INTO `{$table}` ( `{$columns_data}` ) VALUES ( {$formats} ) ON DUPLICATE KEY UPDATE {$update}";
534
535
		return $wpdb->prepare( $sql, $data );
536
	}
537
538
	/**
539
	 * Update an item, eventually mapping to WPDB::update
540
	 *
541
	 * @param string $table        Table name.
542
	 * @param array  $data         Data to update (in column => value pairs) Both $data columns and $data values.
543
	 *                             should be "raw" (neither should be SQL escaped).
544
	 * @param array  $where        A named array of WHERE clauses (in column => value pairs) Multiple clauses will be.
545
	 *                             joined with ANDs. Both $where columns and $where values should be "raw".
546
	 * @param array  $format       (optional) An array of formats to be mapped to each of the values in $data.
0 ignored issues
show
Should the type for parameter $format not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

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. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

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

Loading history...
547
	 * @param array  $where_format (optional) An array of formats to be mapped to each of the values in $where.
0 ignored issues
show
Should the type for parameter $where_format not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

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. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

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

Loading history...
548
	 *
549
	 * @return bool
550
	 * @since 2.0
551
	 */
552
	public function update( $table, $data, $where, $format = null, $where_format = null ) {
553
554
		/**
555
		 * @var $wpdb wpdb
556
		 */
557
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
558
559
		if ( strlen( $table ) < 1 || empty( $data ) || ! is_array( $data ) ) {
560
			return false;
561
		}
562
563
		if ( empty( $format ) ) {
564
			$format = array();
565
566
			foreach ( $data as $field ) {
567
				if ( isset( self::$field_types[ $field ] ) ) {
568
					$form = self::$field_types[ $field ];
569
				} elseif ( isset( $wpdb->field_types[ $field ] ) ) {
570
					$form = $wpdb->field_types[ $field ];
571
				} else {
572
					$form = '%s';
573
				}
574
575
				$format[] = $form;
576
			}
577
		}
578
579
		if ( empty( $where_format ) ) {
580
			$where_format = array();
581
582
			foreach ( (array) array_keys( $where ) as $field ) {
583
				if ( isset( self::$field_types[ $field ] ) ) {
584
					$form = self::$field_types[ $field ];
585
				} elseif ( isset( $wpdb->field_types[ $field ] ) ) {
586
					$form = $wpdb->field_types[ $field ];
587
				} else {
588
					$form = '%s';
589
				}
590
591
				$where_format[] = $form;
592
			}
593
		}
594
595
		list( $table, $data, $where, $format, $where_format ) = self::do_hook(
596
			'update', array(
597
				$table,
598
				$data,
599
				$where,
600
				$format,
601
				$where_format,
602
			), $this
603
		);
604
605
		$result = $wpdb->update( $table, $data, $where, $format, $where_format );
0 ignored issues
show
Usage of a direct database call is discouraged.
Loading history...
606
607
		if ( false !== $result ) {
0 ignored issues
show
This if statement, and the following return statement can be replaced with return false !== $result;.
Loading history...
608
			return true;
609
		}
610
611
		return false;
612
	}
613
614
	/**
615
	 * Delete an item
616
	 *
617
	 * @param string $table        Table name.
618
	 * @param array  $where        A named array of WHERE clauses (in column => value pairs) Multiple clauses will be.
619
	 *                             joined with ANDs. Both $where columns and $where values should be "raw".
620
	 * @param array  $where_format (optional) An array of formats to be mapped to each of the values in $where.
0 ignored issues
show
Should the type for parameter $where_format not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

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. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

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

Loading history...
621
	 *
622
	 * @return array|bool|mixed|null|void
623
	 *
624
	 * @uses  PodsData::query
625
	 * @uses  PodsData::prepare
626
	 *
627
	 * @since 2.0
628
	 */
629
	public function delete( $table, $where, $where_format = null ) {
630
631
		/**
632
		 * @var $wpdb wpdb
633
		 */
634
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
635
636
		if ( strlen( $table ) < 1 || empty( $where ) || ! is_array( $where ) ) {
637
			return false;
638
		}
639
640
		$wheres        = array();
641
		$where_formats = $where_format = (array) $where_format;
642
643
		foreach ( (array) array_keys( $where ) as $field ) {
644
			if ( ! empty( $where_format ) ) {
645
				$form = ( $form = array_shift( $where_formats ) ) ? $form : $where_format[0];
646
			} elseif ( isset( self::$field_types[ $field ] ) ) {
647
				$form = self::$field_types[ $field ];
648
			} elseif ( isset( $wpdb->field_types[ $field ] ) ) {
649
				$form = $wpdb->field_types[ $field ];
650
			} else {
651
				$form = '%s';
652
			}
653
654
			$wheres[] = "`{$field}` = {$form}";
655
		}
656
657
		$sql = "DELETE FROM `$table` WHERE " . implode( ' AND ', $wheres );
658
659
		list( $sql, $where ) = self::do_hook(
660
			'delete', array(
661
				$sql,
662
				array_values( $where ),
663
			), $table, $where, $where_format, $wheres, $this
664
		);
665
666
		return $this->query( self::prepare( $sql, $where ) );
667
	}
668
669
	/**
670
	 * Select items, eventually building dynamic query
671
	 *
672
	 * @param array $params
673
	 *
674
	 * @return array|bool|mixed
675
	 * @since 2.0
676
	 */
677
	public function select( $params ) {
678
679
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
680
681
		$cache_key = $results = false;
682
683
		/**
684
		 * Filter select parameters before the query
685
		 *
686
		 * @param array|object    $params
687
		 * @param PodsData|object $this The current PodsData class instance.
688
		 *
689
		 * @since unknown
690
		 */
691
		$params = apply_filters( 'pods_data_pre_select_params', $params, $this );
692
693
		// Debug purposes.
694
		if ( 1 === (int) pods_v( 'pods_debug_params', 'get', 0 ) && pods_is_admin( array( 'pods' ) ) ) {
695
			pods_debug( $params );
696
		}
697
698
		// Get from cache if enabled.
699
		if ( null !== pods_v( 'expires', $params, null, true ) ) {
700
			$cache_key = md5( (string) $this->pod . serialize( $params ) );
701
702
			$results = pods_view_get( $cache_key, pods_v( 'cache_mode', $params, 'cache', true ), 'pods_data_select' );
703
704
			if ( empty( $results ) ) {
705
				$results = false;
706
			}
707
		}
708
709
		if ( empty( $results ) ) {
710
			// Build.
711
			$this->sql = $this->build( $params );
712
713
			// Debug purposes.
714
			if ( ( 1 === (int) pods_v( 'pods_debug_sql', 'get', 0 ) || 1 === (int) pods_v( 'pods_debug_sql_all', 'get', 0 ) ) && pods_is_admin( array( 'pods' ) ) ) {
715
				echo '<textarea cols="100" rows="24">' . esc_textarea(
716
					str_replace(
717
						array(
718
							'@wp_users',
719
							'@wp_',
720
						), array( $wpdb->users, $wpdb->prefix ), $this->sql
0 ignored issues
show
Usage of users/usermeta tables is highly discouraged in VIP context, For storing user additional user metadata, you should look at User Attributes.
Loading history...
721
					)
722
				) . '</textarea>';
723
			}
724
725
			if ( empty( $this->sql ) ) {
726
				return array();
727
			}
728
729
			// Get Data.
730
			$results = pods_query( $this->sql, $this );
731
732
			// Cache if enabled.
733
			if ( false !== $cache_key ) {
734
				pods_view_set( $cache_key, $results, pods_v( 'expires', $params, 0, false ), pods_v( 'cache_mode', $params, 'cache', true ), 'pods_data_select' );
735
			}
736
		}//end if
737
738
		/**
739
		 * Filter results of Pods Query
740
		 *
741
		 * @param array           $results
742
		 * @param array|object    $params
743
		 * @param PodsData|object $this The current PodsData class instance.
744
		 *
745
		 * @since unknown
746
		 */
747
		$results = apply_filters( 'pods_data_select', $results, $params, $this );
748
749
		$this->data = $results;
750
751
		$this->row_number = - 1;
752
		$this->row        = null;
753
754
		// Fill in empty field data (if none provided).
755
		if ( ( ! isset( $this->fields ) || empty( $this->fields ) ) && ! empty( $this->data ) ) {
756
			$this->fields = array();
757
			$data         = (array) @current( $this->data );
0 ignored issues
show
Silencing errors is discouraged
Loading history...
758
759
			foreach ( $data as $data_key => $data_value ) {
760
				$this->fields[ $data_key ] = array( 'label' => ucwords( str_replace( '-', ' ', str_replace( '_', ' ', $data_key ) ) ) );
761
				if ( isset( $this->pod_data['object_fields'][ $data_key ] ) ) {
762
					$this->fields[ $data_key ] = $this->pod_data['object_fields'][ $data_key ];
763
				}
764
			}
765
766
			$this->fields = PodsForm::fields_setup( $this->fields );
767
		}
768
769
		$this->total_found_calculated = false;
770
771
		$this->total = 0;
772
773
		if ( ! empty( $this->data ) ) {
774
			$this->total = count( (array) $this->data );
775
		}
776
777
		return $this->data;
778
	}
779
780
	/**
781
	 * Calculate total found.
782
	 */
783
	public function calculate_totals() {
784
785
		/**
786
		 * @var $wpdb wpdb
787
		 */
788
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
789
790
		// Set totals.
791
		if ( false !== $this->total_sql ) {
792
			$total = @current( $wpdb->get_col( $this->get_sql( $this->total_sql ) ) );
0 ignored issues
show
Silencing errors is discouraged
Loading history...
Usage of a direct database call is discouraged.
Loading history...
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
793
		} else {
794
			$total = @current( $wpdb->get_col( 'SELECT FOUND_ROWS()' ) );
0 ignored issues
show
Silencing errors is discouraged
Loading history...
Usage of a direct database call is discouraged.
Loading history...
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
795
		}
796
797
		$total                        = self::do_hook( 'select_total', $total, $this );
798
		$this->total_found            = 0;
799
		$this->total_found_calculated = true;
800
801
		if ( is_numeric( $total ) ) {
802
			$this->total_found = $total;
803
		}
804
	}
805
806
	/**
807
	 * Build/Rewrite dynamic SQL and handle search/filter/sort
808
	 *
809
	 * @param array $params
810
	 *
811
	 * @return bool|mixed|string
812
	 * @since 2.0
813
	 */
814
	public function build( $params ) {
815
816
		$simple_tableless_objects = PodsForm::simple_tableless_objects();
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $simple_tableless_objects exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
817
		$file_field_types         = PodsForm::file_field_types();
818
819
		$defaults = array(
820
			'select'              => '*',
821
			'calc_rows'           => false,
822
			'distinct'            => true,
823
			'table'               => null,
824
			'join'                => null,
825
			'where'               => null,
826
			'where_default'       => null,
827
			'groupby'             => null,
828
			'having'              => null,
829
			'orderby'             => null,
830
			'limit'               => - 1,
831
			'offset'              => null,
832
833
			'id'                  => null,
834
			'index'               => null,
835
836
			'page'                => 1,
837
			'pagination'          => $this->pagination,
838
			'search'              => $this->search,
839
			'search_query'        => null,
840
			'search_mode'         => null,
841
			'search_across'       => false,
842
			'search_across_picks' => false,
843
			'search_across_files' => false,
844
			'filters'             => array(),
845
846
			'fields'              => array(),
847
			'object_fields'       => array(),
848
			'pod_table_prefix'    => null,
849
850
			'traverse'            => array(),
851
852
			'sql'                 => null,
853
854
			'strict'              => false,
855
		);
856
857
		$params = (object) array_merge( $defaults, (array) $params );
858
859
		if ( 0 < strlen( $params->sql ) ) {
860
			return $params->sql;
861
		}
862
863
		$pod = false;
864
865
		if ( is_array( $this->pod_data ) ) {
866
			$pod = $this->pod_data;
867
		}
868
869
		// Validate.
870
		$params->page = pods_absint( $params->page );
871
872
		$params->pagination = (boolean) $params->pagination;
873
874
		if ( 0 === $params->page || ! $params->pagination ) {
875
			$params->page = 1;
876
		}
877
878
		$params->limit = (int) $params->limit;
879
880
		if ( 0 === $params->limit ) {
881
			$params->limit = - 1;
882
		}
883
884
		$this->limit = $params->limit;
885
886
		$offset = ( $params->limit * ( $params->page - 1 ) );
887
888
		if ( 0 < (int) $params->offset ) {
889
			$params->offset += $offset;
890
		} else {
891
			$params->offset = $offset;
892
		}
893
894
		if ( ! $params->pagination || - 1 === $params->limit ) {
895
			$params->page   = 1;
896
			$params->offset = 0;
897
		}
898
899
		if ( ( empty( $params->fields ) || ! is_array( $params->fields ) ) && ! empty( $pod ) && isset( $this->fields ) && ! empty( $this->fields ) ) {
900
			$params->fields = $this->fields;
901
		}
902
903
		if ( ( empty( $params->object_fields ) || ! is_array( $params->object_fields ) ) && ! empty( $pod ) && isset( $pod['object_fields'] ) && ! empty( $pod['object_fields'] ) ) {
904
			$params->object_fields = $pod['object_fields'];
905
		}
906
907
		if ( empty( $params->filters ) && $params->search ) {
908
			$params->filters = array_keys( $params->fields );
909
		} elseif ( empty( $params->filters ) ) {
910
			$params->filters = array();
911
		}
912
913
		if ( empty( $params->index ) ) {
914
			$params->index = $this->field_index;
915
		}
916
917
		if ( empty( $params->id ) ) {
918
			$params->id = $this->field_id;
919
		}
920
921
		if ( empty( $params->table ) && ! empty( $pod ) && isset( $this->table ) && ! empty( $this->table ) ) {
922
			$params->table = $this->table;
923
		}
924
925
		if ( empty( $params->pod_table_prefix ) ) {
926
			$params->pod_table_prefix = 't';
927
		}
928
929
		if ( ! empty( $pod ) && ! in_array(
930
			$pod['type'], array(
931
				'pod',
932
				'table',
933
			), true
934
		) && 'table' === $pod['storage'] ) {
935
			$params->pod_table_prefix = 'd';
936
		}
937
938
		$params->meta_fields = false;
939
940
		if ( ! empty( $pod ) && ! in_array(
941
			$pod['type'], array(
942
				'pod',
943
				'table',
944
			), true
945
		) && ( 'meta' === $pod['storage'] || ( 'none' === $pod['storage'] && function_exists( 'get_term_meta' ) ) ) ) {
946
			$params->meta_fields = true;
947
		}
948
949
		if ( empty( $params->table ) ) {
950
			return false;
951
		}
952
953
		if ( false === strpos( $params->table, '(' ) && false === strpos( $params->table, '`' ) ) {
954
			$params->table = '`' . $params->table . '`';
955
		}
956
957
		if ( ! empty( $params->join ) ) {
958
			$params->join = array_merge( (array) $this->join, (array) $params->join );
959
		} elseif ( false === $params->strict ) {
960
			$params->join = $this->join;
961
		}
962
963
		$params->where_defaulted = false;
964
965
		if ( empty( $params->where_default ) && false !== $params->where_default ) {
966
			$params->where_default = $this->where_default;
967
		}
968
969
		if ( false === $params->strict ) {
970
			// Set default where.
971
			if ( ! empty( $params->where_default ) && empty( $params->where ) ) {
972
				$params->where = array_values( (array) $params->where_default );
973
974
				$params->where_defaulted = true;
975
			}
976
977
			if ( ! empty( $this->where ) ) {
978
				if ( is_array( $params->where ) && isset( $params->where['relation'] ) && 'OR' === strtoupper( $params->where['relation'] ) ) {
979
					$params->where = array_merge( array( $params->where ), array_values( (array) $this->where ) );
980
				} else {
981
					$params->where = array_merge( (array) $params->where, array_values( (array) $this->where ) );
982
				}
983
			}
984
		}
985
986
		// Allow where array ( 'field' => 'value' ) and WP_Query meta_query syntax.
987
		if ( ! empty( $params->where ) ) {
988
			$params->where = $this->query_fields( (array) $params->where, $pod, $params );
989
		}
990
991
		if ( empty( $params->where ) ) {
992
			$params->where = array();
993
		} else {
994
			$params->where = (array) $params->where;
995
		}
996
997
		// Allow having array ( 'field' => 'value' ) and WP_Query meta_query syntax.
998
		if ( ! empty( $params->having ) ) {
999
			$params->having = $this->query_fields( (array) $params->having, $pod, $params );
1000
		}
1001
1002
		if ( empty( $params->having ) ) {
1003
			$params->having = array();
1004
		} else {
1005
			$params->having = (array) $params->having;
1006
		}
1007
1008
		if ( ! empty( $params->orderby ) ) {
1009
			if ( 'post_type' === $pod['type'] && 'meta' === $pod['storage'] && is_array( $params->orderby ) ) {
1010
1011
				foreach ( $params->orderby as $i => $orderby ) {
1012
					if ( strpos( $orderby, '.meta_value_num' ) ) {
1013
						$params->orderby[ $i ] = 'CAST(' . str_replace( '.meta_value_num', '.meta_value', $orderby ) . ' AS DECIMAL)';
1014
					} elseif ( strpos( $orderby, '.meta_value_date' ) ) {
1015
						$params->orderby[ $i ] = 'CAST(' . str_replace( '.meta_value_date', '.meta_value', $orderby ) . ' AS DATE)';
1016
					}
1017
				}
1018
			}
1019
1020
			$params->orderby = (array) $params->orderby;
1021
		} else {
1022
			$params->orderby = array();
1023
		}
1024
1025
		if ( false === $params->strict && ! empty( $this->orderby ) ) {
1026
			$params->orderby = array_merge( $params->orderby, (array) $this->orderby );
1027
		}
1028
1029
		if ( ! empty( $params->traverse ) ) {
1030
			$this->traverse = $params->traverse;
1031
		}
1032
1033
		$allowed_search_modes = array( 'int', 'text', 'text_like' );
1034
1035
		if ( ! empty( $params->search_mode ) && in_array( $params->search_mode, $allowed_search_modes, true ) ) {
1036
			$this->search_mode = $params->search_mode;
1037
		}
1038
1039
		$params->search = (boolean) $params->search;
1040
1041
		if ( 1 === (int) pods_v( 'pods_debug_params_all', 'get', 0 ) && pods_is_admin( array( 'pods' ) ) ) {
1042
			pods_debug( $params );
1043
		}
1044
1045
		$params->field_table_alias = 't';
1046
1047
		// Get Aliases for future reference.
1048
		$selectsfound = '';
1049
1050
		if ( ! empty( $params->select ) ) {
1051
			if ( is_array( $params->select ) ) {
1052
				$selectsfound = implode( ', ', $params->select );
1053
			} else {
1054
				$selectsfound = $params->select;
1055
			}
1056
		}
1057
1058
		// Pull Aliases from SQL query too.
1059
		if ( null !== $params->sql ) {
1060
			$temp_sql = ' ' . trim( str_replace( array( "\n", "\r" ), ' ', $params->sql ) );
1061
			$temp_sql = preg_replace(
1062
				array(
1063
					'/\sSELECT\sSQL_CALC_FOUND_ROWS\s/i',
1064
					'/\sSELECT\s/i',
1065
				), array(
1066
					' SELECT ',
1067
					' SELECT SQL_CALC_FOUND_ROWS ',
1068
				), $temp_sql
1069
			);
1070
			preg_match( '/\sSELECT SQL_CALC_FOUND_ROWS\s(.*)\sFROM/i', $temp_sql, $selectmatches );
1071
			if ( isset( $selectmatches[1] ) && ! empty( $selectmatches[1] ) && false !== stripos( $selectmatches[1], ' AS ' ) ) {
1072
				$selectsfound .= ( ! empty( $selectsfound ) ? ', ' : '' ) . $selectmatches[1];
1073
			}
1074
		}
1075
1076
		// Build Alias list.
1077
		$this->aliases = array();
1078
1079
		if ( ! empty( $selectsfound ) && false !== stripos( $selectsfound, ' AS ' ) ) {
1080
			$theselects = array_filter( explode( ', ', $selectsfound ) );
1081
1082
			if ( empty( $theselects ) ) {
1083
				$theselects = array_filter( explode( ',', $selectsfound ) );
1084
			}
1085
1086
			foreach ( $theselects as $selected ) {
1087
				$selected = trim( $selected );
1088
1089
				if ( strlen( $selected ) < 1 ) {
1090
					continue;
1091
				}
1092
1093
				$selectfield = explode( ' AS ', str_replace( ' as ', ' AS ', $selected ) );
1094
1095
				if ( 2 === count( $selectfield ) ) {
1096
					$field                   = trim( trim( $selectfield[1] ), '`' );
1097
					$real_field              = trim( trim( $selectfield[0] ), '`' );
1098
					$this->aliases[ $field ] = $real_field;
1099
				}
1100
			}
1101
		}//end if
1102
1103
		// Search.
1104
		if ( ! empty( $params->search ) && ! empty( $params->fields ) ) {
1105
			if ( false !== $params->search_query && 0 < strlen( $params->search_query ) ) {
1106
				$where = $having = array();
1107
1108
				if ( false !== $params->search_across ) {
1109
					foreach ( $params->fields as $key => $field ) {
1110
						if ( is_array( $field ) ) {
1111
							$attributes = $field;
1112
							$field      = pods_v( 'name', $field, $key, true );
1113
						} else {
1114
							$attributes = array(
1115
								'type'    => '',
1116
								'options' => array(),
1117
							);
1118
						}
1119
1120
						if ( isset( $attributes['options']['search'] ) && ! $attributes['options']['search'] ) {
1121
							continue;
1122
						}
1123
1124
						if ( in_array(
1125
							$attributes['type'], array(
1126
								'date',
1127
								'time',
1128
								'datetime',
1129
								'number',
1130
								'decimal',
1131
								'currency',
1132
								'phone',
1133
								'password',
1134
								'boolean',
1135
							), true
1136
						) ) {
1137
							continue;
1138
						}
1139
1140
						$fieldfield = '`' . $field . '`';
1141
1142
						if ( 'pick' === $attributes['type'] && ! in_array( pods_v( 'pick_object', $attributes ), $simple_tableless_objects, true ) ) {
1143
							if ( false === $params->search_across_picks ) {
1144
								continue;
1145
							} else {
1146
								if ( empty( $attributes['table_info'] ) ) {
1147
									$attributes['table_info'] = $this->api->get_table_info( pods_v( 'pick_object', $attributes ), pods_v( 'pick_val', $attributes ) );
1148
								}
1149
1150
								if ( empty( $attributes['table_info']['field_index'] ) ) {
1151
									continue;
1152
								}
1153
1154
								$fieldfield = $fieldfield . '.`' . $attributes['table_info']['field_index'] . '`';
1155
							}
1156
						} elseif ( in_array( $attributes['type'], $file_field_types, true ) ) {
1157
							if ( false === $params->search_across_files ) {
1158
								continue;
1159
							} else {
1160
								$fieldfield = $fieldfield . '.`post_title`';
1161
							}
1162
						} elseif ( isset( $params->fields[ $field ] ) ) {
1163
							if ( $params->meta_fields ) {
1164
								$fieldfield = $fieldfield . '.`meta_value`';
1165
							} else {
1166
								$fieldfield = '`' . $params->pod_table_prefix . '`.' . $fieldfield;
1167
							}
1168
						} elseif ( ! empty( $params->object_fields ) && ! isset( $params->object_fields[ $field ] ) && 'meta' === $pod['storage'] ) {
1169
							$fieldfield = $fieldfield . '.`meta_value`';
1170
						} else {
1171
							$fieldfield = '`t`.' . $fieldfield;
1172
						}//end if
1173
1174
						if ( isset( $this->aliases[ $field ] ) ) {
1175
							$fieldfield = '`' . $this->aliases[ $field ] . '`';
1176
						}
1177
1178
						if ( ! empty( $attributes['real_name'] ) ) {
1179
							$fieldfield = $attributes['real_name'];
1180
						}
1181
1182
						if ( isset( $attributes['group_related'] ) && false !== $attributes['group_related'] ) {
1183
							$having[] = "{$fieldfield} LIKE '%" . pods_sanitize_like( $params->search_query ) . "%'";
1184
						} else {
1185
							$where[] = "{$fieldfield} LIKE '%" . pods_sanitize_like( $params->search_query ) . "%'";
1186
						}
1187
					}//end foreach
1188
				} elseif ( ! empty( $params->index ) ) {
1189
					$attributes = array();
1190
1191
					$fieldfield = '`t`.`' . $params->index . '`';
1192
1193
					if ( isset( $params->fields[ $params->index ] ) ) {
1194
						if ( $params->meta_fields ) {
1195
							$fieldfield = '`' . $params->index . '`.`' . $params->pod_table_prefix . '`';
1196
						} else {
1197
							$fieldfield = '`' . $params->pod_table_prefix . '`.`' . $params->index . '`';
1198
						}
1199
					} elseif ( ! empty( $params->object_fields ) && ! isset( $params->object_fields[ $params->index ] ) && 'meta' === $pod['storage'] ) {
1200
						$fieldfield = '`' . $params->index . '`.`meta_value`';
1201
					}
1202
1203
					if ( isset( $attributes['real_name'] ) && false !== $attributes['real_name'] && ! empty( $attributes['real_name'] ) ) {
1204
						$fieldfield = $attributes['real_name'];
1205
					}
1206
1207
					if ( isset( $attributes['group_related'] ) && false !== $attributes['group_related'] ) {
1208
						$having[] = "{$fieldfield} LIKE '%" . pods_sanitize_like( $params->search_query ) . "%'";
1209
					} else {
1210
						$where[] = "{$fieldfield} LIKE '%" . pods_sanitize_like( $params->search_query ) . "%'";
1211
					}
1212
				}//end if
1213
1214
				if ( ! empty( $where ) ) {
1215
					$params->where[] = '( ' . implode( ' OR ', $where ) . ' )';
1216
				}
1217
1218
				if ( ! empty( $having ) ) {
1219
					$params->having[] = '( ' . implode( ' OR ', $having ) . ' )';
1220
				}
1221
			}//end if
1222
1223
			// Filter.
1224
			foreach ( $params->filters as $filter ) {
1225
				$where = $having = array();
1226
1227
				if ( ! isset( $params->fields[ $filter ] ) ) {
1228
					continue;
1229
				}
1230
1231
				$attributes = $params->fields[ $filter ];
1232
				$field      = pods_v( 'name', $attributes, $filter, true );
1233
1234
				$filterfield = '`' . $field . '`';
1235
1236
				if ( 'pick' === $attributes['type'] && ! in_array( pods_v( 'pick_object', $attributes ), $simple_tableless_objects, true ) ) {
1237
					if ( empty( $attributes['table_info'] ) ) {
1238
						$attributes['table_info'] = $this->api->get_table_info( pods_v( 'pick_object', $attributes ), pods_v( 'pick_val', $attributes ) );
1239
					}
1240
1241
					if ( empty( $attributes['table_info']['field_index'] ) ) {
1242
						continue;
1243
					}
1244
1245
					$filterfield = $filterfield . '.`' . $attributes['table_info']['field_index'] . '`';
1246
				} elseif ( in_array( $attributes['type'], $file_field_types, true ) ) {
1247
					$filterfield = $filterfield . '.`post_title`';
1248
				} elseif ( isset( $params->fields[ $field ] ) ) {
1249
					if ( $params->meta_fields && 'meta' === $pod['storage'] ) {
1250
						$filterfield = $filterfield . '.`meta_value`';
1251
					} else {
1252
						$filterfield = '`' . $params->pod_table_prefix . '`.' . $filterfield;
1253
					}
1254
				} elseif ( ! empty( $params->object_fields ) && ! isset( $params->object_fields[ $field ] ) && 'meta' === $pod['storage'] ) {
1255
					$filterfield = $filterfield . '.`meta_value`';
1256
				} else {
1257
					$filterfield = '`t`.' . $filterfield;
1258
				}//end if
1259
1260
				if ( isset( $this->aliases[ $field ] ) ) {
1261
					$filterfield = '`' . $this->aliases[ $field ] . '`';
1262
				}
1263
1264
				if ( ! empty( $attributes['real_name'] ) ) {
1265
					$filterfield = $attributes['real_name'];
1266
				}
1267
1268
				if ( 'pick' === $attributes['type'] ) {
1269
					$filter_value = pods_v( 'filter_' . $field );
1270
1271
					if ( ! is_array( $filter_value ) ) {
1272
						$filter_value = (array) $filter_value;
1273
					}
1274
1275
					foreach ( $filter_value as $filter_v ) {
1276
						if ( in_array( pods_v( 'pick_object', $attributes ), $simple_tableless_objects, true ) ) {
1277
							if ( strlen( $filter_v ) < 1 ) {
1278
								continue;
1279
							}
1280
1281
							if ( isset( $attributes['group_related'] ) && false !== $attributes['group_related'] ) {
1282
								$having[] = "( {$filterfield} = '" . pods_sanitize( $filter_v ) . "'" . " OR {$filterfield} LIKE '%\"" . pods_sanitize_like( $filter_v ) . "\"%' )";
1283
							} else {
1284
								$where[] = "( {$filterfield} = '" . pods_sanitize( $filter_v ) . "'" . " OR {$filterfield} LIKE '%\"" . pods_sanitize_like( $filter_v ) . "\"%' )";
1285
							}
1286
						} else {
1287
							$filter_v = (int) $filter_v;
1288
1289
							if ( empty( $filter_v ) || empty( $attributes['table_info'] ) || empty( $attributes['table_info']['field_id'] ) ) {
1290
								continue;
1291
							}
1292
1293
							$filterfield = '`' . $field . '`.`' . $attributes['table_info']['field_id'] . '`';
1294
1295
							if ( isset( $attributes['group_related'] ) && false !== $attributes['group_related'] ) {
1296
								$having[] = "{$filterfield} = " . $filter_v;
1297
							} else {
1298
								$where[] = "{$filterfield} = " . $filter_v;
1299
							}
1300
						}//end if
1301
					}//end foreach
1302
				} elseif ( in_array(
1303
					$attributes['type'], array(
1304
						'date',
1305
						'datetime',
1306
					), true
1307
				) ) {
1308
					$start_value = pods_v( 'filter_' . $field . '_start', 'get', false );
1309
					$end_value   = pods_v( 'filter_' . $field . '_end', 'get', false );
1310
1311
					if ( empty( $start_value ) && empty( $end_value ) ) {
1312
						continue;
1313
					}
1314
1315
					if ( ! empty( $start_value ) ) {
1316
						$start = date_i18n( 'Y-m-d', strtotime( $start_value ) ) . ( 'datetime' === $attributes['type'] ? ' 00:00:00' : '' );
1317
					} else {
1318
						$start = date_i18n( 'Y-m-d' ) . ( 'datetime' === $attributes['type'] ) ? ' 00:00:00' : '';
1319
					}
1320
1321
					if ( ! empty( $end_value ) ) {
1322
						$end = date_i18n( 'Y-m-d', strtotime( $end_value ) ) . ( 'datetime' === $attributes['type'] ? ' 23:59:59' : '' );
1323
					} else {
1324
						$end = date_i18n( 'Y-m-d' ) . ( 'datetime' === $attributes['type'] ) ? ' 23:59:59' : '';
1325
					}
1326
1327
					if ( isset( $attributes['date_ongoing'] ) && true === $attributes['date_ongoing'] ) {
1328
						$date_ongoing = '`' . $attributes['date_ongoing'] . '`';
1329
1330
						if ( isset( $this->aliases[ $date_ongoing ] ) ) {
1331
							$date_ongoing = '`' . $this->aliases[ $date_ongoing ] . '`';
1332
						}
1333
1334
						if ( isset( $attributes['group_related'] ) && false !== $attributes['group_related'] ) {
1335
							$having[] = "(({$filterfield} <= '$start' OR ({$filterfield} >= '$start' AND {$filterfield} <= '$end')) AND ({$date_ongoing} >= '$start' OR ({$date_ongoing} >= '$start' AND {$date_ongoing} <= '$end')))";
1336
						} else {
1337
							$where[] = "(({$filterfield} <= '$start' OR ({$filterfield} >= '$start' AND {$filterfield} <= '$end')) AND ({$date_ongoing} >= '$start' OR ({$date_ongoing} >= '$start' AND {$date_ongoing} <= '$end')))";
1338
						}
1339
					} else {
1340
						if ( isset( $attributes['group_related'] ) && false !== $attributes['group_related'] ) {
1341
							$having[] = "({$filterfield} BETWEEN '$start' AND '$end')";
1342
						} else {
1343
							$where[] = "({$filterfield} BETWEEN '$start' AND '$end')";
1344
						}
1345
					}
1346
				} else {
1347
					$filter_value = pods_v( 'filter_' . $field, 'get', '' );
1348
1349
					if ( strlen( $filter_value ) < 1 ) {
1350
						continue;
1351
					}
1352
1353
					if ( isset( $attributes['group_related'] ) && false !== $attributes['group_related'] ) {
1354
						$having[] = "{$filterfield} LIKE '%" . pods_sanitize_like( $filter_value ) . "%'";
1355
					} else {
1356
						$where[] = "{$filterfield} LIKE '%" . pods_sanitize_like( $filter_value ) . "%'";
1357
					}
1358
				}//end if
1359
1360
				if ( ! empty( $where ) ) {
1361
					$params->where[] = implode( ' AND ', $where );
1362
				}
1363
1364
				if ( ! empty( $having ) ) {
1365
					$params->having[] = implode( ' AND ', $having );
1366
				}
1367
			}//end foreach
1368
		}//end if
1369
1370
		// Traverse the Rabbit Hole.
1371
		if ( ! empty( $this->pod ) ) {
1372
			$haystack = implode( ' ', (array) $params->select ) . ' ' . implode( ' ', (array) $params->where ) . ' ' . implode( ' ', (array) $params->groupby ) . ' ' . implode( ' ', (array) $params->having ) . ' ' . implode( ' ', (array) $params->orderby );
1373
			$haystack = preg_replace( '/\s/', ' ', $haystack );
1374
			$haystack = preg_replace( '/\w\(/', ' ', $haystack );
1375
			$haystack = str_replace( array( '(', ')', '  ', '\\\'', '\\"' ), ' ', $haystack );
1376
1377
			preg_match_all( '/`?[\w\-]+`?(?:\\.`?[\w\-]+`?)+(?=[^"\']*(?:"[^"]*"[^"]*|\'[^\']*\'[^\']*)*$)/', $haystack, $found, PREG_PATTERN_ORDER );
1378
1379
			$found = (array) @current( $found );
0 ignored issues
show
Silencing errors is discouraged
Loading history...
1380
			$find  = $replace = $traverse = array();
1381
1382
			foreach ( $found as $key => $value ) {
1383
				$value = str_replace( '`', '', $value );
1384
				$value = explode( '.', $value );
1385
				$dot   = $last_value = array_pop( $value );
1386
1387
				if ( 't' === $value[0] ) {
1388
					continue;
1389
				} elseif ( array_key_exists( $value[0], $params->join ) ) {
1390
					// Don't traverse for tables we are already going to join.
1391
					continue;
1392
				} elseif ( 1 === count( $value ) && '' === preg_replace( '/[0-9]*/', '', $value[0] ) && '' === preg_replace( '/[0-9]*/', '', $last_value ) ) {
1393
					continue;
1394
				}
1395
1396
				$found_value = str_replace( '`', '', $found[ $key ] );
1397
				$found_value = '([`]{1}|\b)' . str_replace( '.', '[`]*\.[`]*', $found_value ) . '([`]{1}|\b)';
1398
				$found_value = '/' . $found_value . '(?=[^"\']*(?:"[^"]*"[^"]*|\'[^\']*\'[^\']*)*$)/';
1399
1400
				if ( in_array( $found_value, $find, true ) ) {
1401
					continue;
1402
				}
1403
1404
				$find[ $key ] = $found_value;
1405
1406
				if ( '*' !== $dot ) {
1407
					$dot = '`' . $dot . '`';
1408
				}
1409
1410
				$replace[ $key ] = '`' . implode( '_', $value ) . '`.' . $dot;
1411
1412
				$value[] = $last_value;
1413
1414
				if ( ! in_array( $value, $traverse, true ) ) {
1415
					$traverse[ $key ] = $value;
1416
				}
1417
			}//end foreach
1418
1419
			if ( ! empty( $this->traverse ) ) {
1420
				foreach ( (array) $this->traverse as $key => $traverse ) {
1421
					$traverse      = str_replace( '`', '', $traverse );
1422
					$already_found = false;
1423
1424
					foreach ( $traverse as $traversal ) {
1425
						if ( is_array( $traversal ) ) {
1426
							$traversal = implode( '.', $traversal );
1427
						}
1428
1429
						if ( $traversal === $traverse ) {
1430
							$already_found = true;
1431
							break;
1432
						}
1433
					}
1434
1435
					if ( ! $already_found ) {
1436
						$traverse[ 'traverse_' . $key ] = explode( '.', $traverse );
1437
					}
1438
				}
1439
			}//end if
1440
1441
			$joins = array();
1442
1443
			if ( ! empty( $find ) ) {
1444
				// See: "#3294 OrderBy Failing on PHP7"  Non-zero array keys.
1445
				// here in PHP 7 cause odd behavior so just strip the keys.
1446
				$find    = array_values( $find );
1447
				$replace = array_values( $replace );
1448
1449
				$params->select  = preg_replace( $find, $replace, $params->select );
1450
				$params->where   = preg_replace( $find, $replace, $params->where );
1451
				$params->groupby = preg_replace( $find, $replace, $params->groupby );
1452
				$params->having  = preg_replace( $find, $replace, $params->having );
1453
				$params->orderby = preg_replace( $find, $replace, $params->orderby );
1454
1455
				if ( ! empty( $traverse ) ) {
1456
					$joins = $this->traverse( $traverse, $params->fields, $params );
1457
				} elseif ( false !== $params->search ) {
1458
					$joins = $this->traverse( null, $params->fields, $params );
1459
				}
1460
			}
1461
		}//end if
1462
1463
		// Traversal Search.
1464
		if ( ! empty( $params->search ) && ! empty( $this->search_where ) ) {
1465
			$params->where = array_merge( (array) $this->search_where, $params->where );
1466
		}
1467
1468
		if ( ! empty( $params->join ) && ! empty( $joins ) ) {
1469
			$params->join = array_merge( $joins, (array) $params->join );
1470
		} elseif ( ! empty( $joins ) ) {
1471
			$params->join = $joins;
1472
		}
1473
1474
		// Build.
1475
		if ( null === $params->sql ) {
1476
			$sql = '
1477
                SELECT
1478
                ' . ( $params->calc_rows ? 'SQL_CALC_FOUND_ROWS' : '' ) . '
1479
                ' . ( $params->distinct ? 'DISTINCT' : '' ) . '
1480
                ' . ( ! empty( $params->select ) ? ( is_array( $params->select ) ? implode( ', ', $params->select ) : $params->select ) : '*' ) . "
1481
                FROM {$params->table} AS `t`
1482
                " . ( ! empty( $params->join ) ? ( is_array( $params->join ) ? implode( "\n                ", $params->join ) : $params->join ) : '' ) . '
1483
                ' . ( ! empty( $params->where ) ? 'WHERE ' . ( is_array( $params->where ) ? implode( ' AND ', $params->where ) : $params->where ) : '' ) . '
1484
                ' . ( ! empty( $params->groupby ) ? 'GROUP BY ' . ( is_array( $params->groupby ) ? implode( ', ', $params->groupby ) : $params->groupby ) : '' ) . '
1485
                ' . ( ! empty( $params->having ) ? 'HAVING ' . ( is_array( $params->having ) ? implode( ' AND  ', $params->having ) : $params->having ) : '' ) . '
1486
                ' . ( ! empty( $params->orderby ) ? 'ORDER BY ' . ( is_array( $params->orderby ) ? implode( ', ', $params->orderby ) : $params->orderby ) : '' ) . '
1487
                ' . ( ( 0 < $params->page && 0 < $params->limit ) ? 'LIMIT ' . $params->offset . ', ' . ( $params->limit ) : '' ) . '
1488
            ';
1489
1490
			if ( ! $params->calc_rows ) {
1491
				// Handle COUNT() SELECT.
1492
				$total_sql_select = 'COUNT( ' . ( $params->distinct ? 'DISTINCT `t`.`' . $params->id . '`' : '*' ) . ' )';
1493
1494
				// If 'having' is set, we have to select all so it has access to anything it needs.
1495
				if ( ! empty( $params->having ) ) {
1496
					$total_sql_select .= ', ' . ( ! empty( $params->select ) ? ( is_array( $params->select ) ? implode( ', ', $params->select ) : $params->select ) : '*' );
1497
				}
1498
1499
				$this->total_sql = "
1500
					SELECT {$total_sql_select}
1501
					FROM {$params->table} AS `t`
1502
					" . ( ! empty( $params->join ) ? ( is_array( $params->join ) ? implode( "\n                ", $params->join ) : $params->join ) : '' ) . '
1503
                    ' . ( ! empty( $params->where ) ? 'WHERE ' . ( is_array( $params->where ) ? implode( ' AND  ', $params->where ) : $params->where ) : '' ) . '
1504
                    ' . ( ! empty( $params->groupby ) ? 'GROUP BY ' . ( is_array( $params->groupby ) ? implode( ', ', $params->groupby ) : $params->groupby ) : '' ) . '
1505
                    ' . ( ! empty( $params->having ) ? 'HAVING ' . ( is_array( $params->having ) ? implode( ' AND  ', $params->having ) : $params->having ) : '' ) . '
1506
                ';
1507
			}
1508
		} else {
1509
			$sql = ' ' . trim( str_replace( array( "\n", "\r" ), ' ', $params->sql ) );
1510
			$sql = preg_replace(
1511
				array(
1512
					'/\sSELECT\sSQL_CALC_FOUND_ROWS\s/i',
1513
					'/\sSELECT\s/i',
1514
				), array(
1515
					' SELECT ',
1516
					' SELECT SQL_CALC_FOUND_ROWS ',
1517
				), $sql
1518
			);
1519
1520
			// Insert variables based on existing statements.
1521
			if ( false === stripos( $sql, '%%SELECT%%' ) ) {
1522
				$sql = preg_replace( '/\sSELECT\sSQL_CALC_FOUND_ROWS\s/i', ' SELECT SQL_CALC_FOUND_ROWS %%SELECT%% ', $sql );
1523
			}
1524
			if ( false === stripos( $sql, '%%WHERE%%' ) ) {
1525
				$sql = preg_replace( '/\sWHERE\s(?!.*\sWHERE\s)/i', ' WHERE %%WHERE%% ', $sql );
1526
			}
1527
			if ( false === stripos( $sql, '%%GROUPBY%%' ) ) {
1528
				$sql = preg_replace( '/\sGROUP BY\s(?!.*\sGROUP BY\s)/i', ' GROUP BY %%GROUPBY%% ', $sql );
1529
			}
1530
			if ( false === stripos( $sql, '%%HAVING%%' ) ) {
1531
				$sql = preg_replace( '/\sHAVING\s(?!.*\sHAVING\s)/i', ' HAVING %%HAVING%% ', $sql );
1532
			}
1533
			if ( false === stripos( $sql, '%%ORDERBY%%' ) ) {
1534
				$sql = preg_replace( '/\sORDER BY\s(?!.*\sORDER BY\s)/i', ' ORDER BY %%ORDERBY%% ', $sql );
1535
			}
1536
1537
			// Insert variables based on other existing statements.
1538
			if ( false === stripos( $sql, '%%JOIN%%' ) ) {
1539
				if ( false !== stripos( $sql, ' WHERE ' ) ) {
1540
					$sql = preg_replace( '/\sWHERE\s(?!.*\sWHERE\s)/i', ' %%JOIN%% WHERE ', $sql );
1541
				} elseif ( false !== stripos( $sql, ' GROUP BY ' ) ) {
1542
					$sql = preg_replace( '/\sGROUP BY\s(?!.*\sGROUP BY\s)/i', ' %%WHERE%% GROUP BY ', $sql );
1543
				} elseif ( false !== stripos( $sql, ' ORDER BY ' ) ) {
1544
					$sql = preg_replace( '/\ORDER BY\s(?!.*\ORDER BY\s)/i', ' %%WHERE%% ORDER BY ', $sql );
1545
				} else {
1546
					$sql .= ' %%JOIN%% ';
1547
				}
1548
			}
1549
			if ( false === stripos( $sql, '%%WHERE%%' ) ) {
1550
				if ( false !== stripos( $sql, ' GROUP BY ' ) ) {
1551
					$sql = preg_replace( '/\sGROUP BY\s(?!.*\sGROUP BY\s)/i', ' %%WHERE%% GROUP BY ', $sql );
1552
				} elseif ( false !== stripos( $sql, ' ORDER BY ' ) ) {
1553
					$sql = preg_replace( '/\ORDER BY\s(?!.*\ORDER BY\s)/i', ' %%WHERE%% ORDER BY ', $sql );
1554
				} else {
1555
					$sql .= ' %%WHERE%% ';
1556
				}
1557
			}
1558
			if ( false === stripos( $sql, '%%GROUPBY%%' ) ) {
1559
				if ( false !== stripos( $sql, ' HAVING ' ) ) {
1560
					$sql = preg_replace( '/\sHAVING\s(?!.*\sHAVING\s)/i', ' %%GROUPBY%% HAVING ', $sql );
1561
				} elseif ( false !== stripos( $sql, ' ORDER BY ' ) ) {
1562
					$sql = preg_replace( '/\ORDER BY\s(?!.*\ORDER BY\s)/i', ' %%GROUPBY%% ORDER BY ', $sql );
1563
				} else {
1564
					$sql .= ' %%GROUPBY%% ';
1565
				}
1566
			}
1567
			if ( false === stripos( $sql, '%%HAVING%%' ) ) {
1568
				if ( false !== stripos( $sql, ' ORDER BY ' ) ) {
1569
					$sql = preg_replace( '/\ORDER BY\s(?!.*\ORDER BY\s)/i', ' %%HAVING%% ORDER BY ', $sql );
1570
				} else {
1571
					$sql .= ' %%HAVING%% ';
1572
				}
1573
			}
1574
			if ( false === stripos( $sql, '%%ORDERBY%%' ) ) {
1575
				$sql .= ' %%ORDERBY%% ';
1576
			}
1577
			if ( false === stripos( $sql, '%%LIMIT%%' ) ) {
1578
				$sql .= ' %%LIMIT%% ';
1579
			}
1580
1581
			// Replace variables.
1582
			if ( 0 < strlen( $params->select ) ) {
1583
				if ( false === stripos( $sql, '%%SELECT%% FROM ' ) ) {
1584
					$sql = str_ireplace( '%%SELECT%%', $params->select . ', ', $sql );
1585
				} else {
1586
					$sql = str_ireplace( '%%SELECT%%', $params->select, $sql );
1587
				}
1588
			}
1589
			if ( 0 < strlen( $params->join ) ) {
1590
				$sql = str_ireplace( '%%JOIN%%', $params->join, $sql );
1591
			}
1592
			if ( 0 < strlen( $params->where ) ) {
1593
				if ( false !== stripos( $sql, ' WHERE ' ) ) {
1594
					if ( false !== stripos( $sql, ' WHERE %%WHERE%% ' ) ) {
1595
						$sql = str_ireplace( '%%WHERE%%', $params->where . ' AND ', $sql );
1596
					} else {
1597
						$sql = str_ireplace( '%%WHERE%%', ' AND ' . $params->where, $sql );
1598
					}
1599
				} else {
1600
					$sql = str_ireplace( '%%WHERE%%', ' WHERE ' . $params->where, $sql );
1601
				}
1602
			}
1603
			if ( 0 < strlen( $params->groupby ) ) {
1604
				if ( false !== stripos( $sql, ' GROUP BY ' ) ) {
1605
					if ( false !== stripos( $sql, ' GROUP BY %%GROUPBY%% ' ) ) {
1606
						$sql = str_ireplace( '%%GROUPBY%%', $params->groupby . ', ', $sql );
1607
					} else {
1608
						$sql = str_ireplace( '%%GROUPBY%%', ', ' . $params->groupby, $sql );
1609
					}
1610
				} else {
1611
					$sql = str_ireplace( '%%GROUPBY%%', ' GROUP BY ' . $params->groupby, $sql );
1612
				}
1613
			}
1614
			if ( 0 < strlen( $params->having ) && false !== stripos( $sql, ' GROUP BY ' ) ) {
1615
				if ( false !== stripos( $sql, ' HAVING ' ) ) {
1616
					if ( false !== stripos( $sql, ' HAVING %%HAVING%% ' ) ) {
1617
						$sql = str_ireplace( '%%HAVING%%', $params->having . ' AND ', $sql );
1618
					} else {
1619
						$sql = str_ireplace( '%%HAVING%%', ' AND ' . $params->having, $sql );
1620
					}
1621
				} else {
1622
					$sql = str_ireplace( '%%HAVING%%', ' HAVING ' . $params->having, $sql );
1623
				}
1624
			}
1625
			if ( 0 < strlen( $params->orderby ) ) {
1626
				if ( false !== stripos( $sql, ' ORDER BY ' ) ) {
1627
					if ( false !== stripos( $sql, ' ORDER BY %%ORDERBY%% ' ) ) {
1628
						$sql = str_ireplace( '%%ORDERBY%%', $params->groupby . ', ', $sql );
1629
					} else {
1630
						$sql = str_ireplace( '%%ORDERBY%%', ', ' . $params->groupby, $sql );
1631
					}
1632
				} else {
1633
					$sql = str_ireplace( '%%ORDERBY%%', ' ORDER BY ' . $params->groupby, $sql );
1634
				}
1635
			}
1636
			if ( 0 < $params->page && 0 < $params->limit ) {
1637
				$start = ( $params->page - 1 ) * $params->limit;
1638
				$end   = $start + $params->limit;
1639
				$sql  .= 'LIMIT ' . (int) $start . ', ' . (int) $end;
1640
			}
1641
1642
			// Clear any unused variables.
1643
			$sql = str_ireplace(
1644
				array(
1645
					'%%SELECT%%',
1646
					'%%JOIN%%',
1647
					'%%WHERE%%',
1648
					'%%GROUPBY%%',
1649
					'%%HAVING%%',
1650
					'%%ORDERBY%%',
1651
					'%%LIMIT%%',
1652
				), '', $sql
1653
			);
1654
			$sql = str_replace( array( '``', '`' ), array( '  ', ' ' ), $sql );
1655
		}//end if
1656
1657
		return $sql;
1658
	}
1659
1660
	/**
1661
	 * Fetch the total row count returned
1662
	 *
1663
	 * @return int Number of rows returned by select()
1664
	 * @since 2.0
1665
	 */
1666
	public function total() {
1667
1668
		return (int) $this->total;
1669
	}
1670
1671
	/**
1672
	 * Fetch the total row count total
1673
	 *
1674
	 * @return int Number of rows found by select()
1675
	 * @since 2.0
1676
	 */
1677
	public function total_found() {
1678
1679
		if ( false === $this->total_found_calculated ) {
1680
			$this->calculate_totals();
1681
		}
1682
1683
		return (int) $this->total_found;
1684
	}
1685
1686
	/**
1687
	 * Fetch the zebra state
1688
	 *
1689
	 * @return bool Zebra state
1690
	 * @since 1.12
1691
	 * @see   PodsData::nth
1692
	 */
1693
	public function zebra() {
1694
1695
		return $this->nth( 'odd' );
1696
		// Odd numbers.
1697
	}
1698
1699
	/**
1700
	 * Fetch the nth state
1701
	 *
1702
	 * @param int|string $nth The $nth to match on the PodsData::row_number.
1703
	 *
1704
	 * @return bool Whether $nth matches
1705
	 * @since 2.3
1706
	 */
1707
	public function nth( $nth ) {
1708
1709
		if ( empty( $nth ) ) {
1710
			$nth = 2;
1711
		}
1712
1713
		$offset   = 0;
1714
		$negative = false;
1715
1716
		if ( 'even' === $nth ) {
1717
			$nth = 2;
1718
		} elseif ( 'odd' === $nth ) {
1719
			$negative = true;
1720
			$nth      = 2;
1721
		} elseif ( false !== strpos( $nth, '+' ) ) {
1722
			$nth = explode( '+', $nth );
1723
1724
			if ( isset( $nth[1] ) ) {
1725
				$offset += (int) trim( $nth[1] );
1726
			}
1727
1728
			$nth = (int) trim( $nth[0], ' n' );
1729
		} elseif ( false !== strpos( $nth, '-' ) ) {
1730
			$nth = explode( '-', $nth );
1731
1732
			if ( isset( $nth[1] ) ) {
1733
				$offset -= (int) trim( $nth[1] );
1734
			}
1735
1736
			$nth = (int) trim( $nth[0], ' n' );
1737
		}//end if
1738
1739
		$nth    = (int) $nth;
1740
		$offset = (int) $offset;
1741
1742
		if ( 0 === ( ( $this->row_number % $nth ) + $offset ) ) {
1743
			return ( $negative ? false : true );
1744
		}
1745
1746
		return ( $negative ? true : false );
1747
	}
1748
1749
	/**
1750
	 * Fetch the current position in the loop (starting at 1)
1751
	 *
1752
	 * @return int Current row number (+1)
1753
	 * @since 2.3
1754
	 */
1755
	public function position() {
1756
1757
		return $this->row_number + 1;
1758
	}
1759
1760
	/**
1761
	 * Create a Table
1762
	 *
1763
	 * @param string  $table         Table name.
1764
	 * @param string  $fields
1765
	 * @param boolean $if_not_exists Check if the table exists.
1766
	 *
1767
	 * @return array|bool|mixed|null|void
1768
	 *
1769
	 * @uses  PodsData::query
1770
	 *
1771
	 * @since 2.0
1772
	 */
1773
	public static function table_create( $table, $fields, $if_not_exists = false ) {
1774
1775
		/**
1776
		 * @var $wpdb wpdb
1777
		 */
1778
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1779
1780
		$sql = 'CREATE TABLE';
1781
1782
		if ( true === $if_not_exists ) {
1783
			$sql .= ' IF NOT EXISTS';
1784
		}
1785
1786
		$sql .= " `{$wpdb->prefix}" . self::$prefix . "{$table}` ({$fields})";
1787
1788
		if ( ! empty( $wpdb->charset ) ) {
1789
			$sql .= " DEFAULT CHARACTER SET {$wpdb->charset}";
1790
		}
1791
1792
		if ( ! empty( $wpdb->collate ) ) {
1793
			$sql .= " COLLATE {$wpdb->collate}";
1794
		}
1795
1796
		return self::query( $sql );
1797
	}
1798
1799
	/**
1800
	 * Alter a Table
1801
	 *
1802
	 * @param string $table Table name.
1803
	 * @param string $changes
1804
	 *
1805
	 * @return array|bool|mixed|null|void
1806
	 *
1807
	 * @uses  PodsData::query
1808
	 *
1809
	 * @since 2.0
1810
	 */
1811
	public static function table_alter( $table, $changes ) {
1812
1813
		/**
1814
		 * @var $wpdb wpdb
1815
		 */
1816
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1817
1818
		$sql = "ALTER TABLE `{$wpdb->prefix}" . self::$prefix . "{$table}` {$changes}";
1819
1820
		return self::query( $sql );
1821
	}
1822
1823
	/**
1824
	 * Truncate a Table
1825
	 *
1826
	 * @param string $table Table name.
1827
	 *
1828
	 * @return array|bool|mixed|null|void
1829
	 *
1830
	 * @uses  PodsData::query
1831
	 *
1832
	 * @since 2.0
1833
	 */
1834
	public static function table_truncate( $table ) {
1835
1836
		/**
1837
		 * @var $wpdb wpdb
1838
		 */
1839
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1840
1841
		$sql = "TRUNCATE TABLE `{$wpdb->prefix}" . self::$prefix . "{$table}`";
1842
1843
		return self::query( $sql );
1844
	}
1845
1846
	/**
1847
	 * Drop a Table
1848
	 *
1849
	 * @param string $table Table name.
1850
	 *
1851
	 * @uses  PodsData::query
1852
	 *
1853
	 * @return array|bool|mixed|null|void
1854
	 *
1855
	 * @uses  PodsData::query
1856
	 *
1857
	 * @since 2.0
1858
	 */
1859
	public static function table_drop( $table ) {
1860
1861
		/**
1862
		 * @var $wpdb wpdb
1863
		 */
1864
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1865
1866
		$sql = "DROP TABLE `{$wpdb->prefix}" . self::$prefix . "{$table}`";
1867
1868
		return self::query( $sql );
1869
	}
1870
1871
	/**
1872
	 * Reorder Items
1873
	 *
1874
	 * @param string $table Table name.
1875
	 * @param string $weight_field
1876
	 * @param string $id_field
1877
	 * @param array  $ids
1878
	 *
1879
	 * @return bool
1880
	 *
1881
	 * @uses  PodsData::update
1882
	 *
1883
	 * @since 2.0
1884
	 */
1885
	public function reorder( $table, $weight_field, $id_field, $ids ) {
1886
1887
		$success = false;
1888
		$ids     = (array) $ids;
1889
1890
		list( $table, $weight_field, $id_field, $ids ) = self::do_hook(
1891
			'reorder', array(
1892
				$table,
1893
				$weight_field,
1894
				$id_field,
1895
				$ids,
1896
			), $this
1897
		);
1898
1899
		if ( ! empty( $ids ) ) {
1900
			$success = true;
1901
1902
			foreach ( $ids as $weight => $id ) {
1903
				$updated = $this->update( $table, array( $weight_field => $weight ), array( $id_field => $id ), array( '%d' ), array( '%d' ) );
1904
1905
				if ( false === $updated ) {
1906
					$success = false;
1907
				}
1908
			}
1909
		}
1910
1911
		return $success;
1912
	}
1913
1914
	/**
1915
	 * Fetch a new row for the current pod_data
1916
	 *
1917
	 * @param int  $row          Row number to fetch.
0 ignored issues
show
Should the type for parameter $row not be integer|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...
1918
	 * @param bool $explicit_set Whether to set explicitly (use false when in loop).
1919
	 *
1920
	 * @return mixed
1921
	 *
1922
	 * @since 2.0
1923
	 */
1924
	public function fetch( $row = null, $explicit_set = true ) {
1925
1926
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1927
1928
		if ( null === $row ) {
1929
			$explicit_set = false;
1930
		}
1931
1932
		$already_cached = false;
1933
		$id             = $row;
1934
1935
		$tableless_field_types = PodsForm::tableless_field_types();
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $tableless_field_types exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
1936
1937
		if ( null === $row ) {
1938
			$this->row_number ++;
1939
1940
			$this->row = false;
1941
1942
			if ( isset( $this->data[ $this->row_number ] ) ) {
1943
				$this->row = get_object_vars( $this->data[ $this->row_number ] );
1944
1945
				$current_row_id = false;
1946
1947
				if ( in_array(
1948
					$this->pod_data['type'], array(
1949
						'post_type',
1950
						'media',
1951
					), true
1952
				) ) {
1953
					$current_row_id = pods_v( 'ID', $this->row );
1954
				} elseif ( 'taxonomy' === $this->pod_data['type'] ) {
1955
					$current_row_id = pods_v( 'term_id', $this->row );
1956
				} elseif ( 'user' === $this->pod_data['type'] ) {
1957
					$current_row_id = pods_v( 'ID', $this->row );
1958
				} elseif ( 'comment' === $this->pod_data['type'] ) {
1959
					$current_row_id = pods_v( 'comment_ID', $this->row );
1960
				} elseif ( 'settings' === $this->pod_data['type'] ) {
1961
					$current_row_id = $this->pod_data['id'];
1962
				}
1963
1964
				if ( 0 < $current_row_id ) {
1965
					$row = $current_row_id;
1966
				}
1967
			}//end if
1968
		}//end if
1969
1970
		if ( null !== $row || 'settings' === $this->pod_data['type'] ) {
1971
			if ( $explicit_set ) {
1972
				$this->row_number = - 1;
1973
			}
1974
1975
			$mode = 'id';
1976
			$id   = pods_absint( $row );
1977
1978
			if ( ! is_numeric( $row ) || 0 === strpos( $row, '0' ) || $row != preg_replace( '/[^0-9]/', '', $row ) ) {
1979
				$mode = 'slug';
1980
				$id   = $row;
1981
			}
1982
1983
			$row = false;
1984
1985
			if ( ! empty( $this->pod ) ) {
1986
				$row = pods_cache_get( $id, 'pods_items_' . $this->pod );
1987
				if ( false !== $row ) {
1988
					$already_cached = true;
1989
				}
1990
			}
1991
1992
			$current_row_id = false;
1993
			$get_table_data = false;
1994
1995
			$old_row = $this->row;
1996
1997
			if ( false !== $row && is_array( $row ) ) {
1998
				$this->row = $row;
1999
			} elseif ( in_array(
2000
				$this->pod_data['type'], array(
2001
					'post_type',
2002
					'media',
2003
				), true
2004
			) ) {
2005
				if ( 'post_type' === $this->pod_data['type'] ) {
2006
					if ( empty( $this->pod_data['object'] ) ) {
2007
						$post_type = $this->pod_data['name'];
2008
					} else {
2009
						$post_type = $this->pod_data['object'];
2010
					}
2011
				} else {
2012
					$post_type = 'attachment';
2013
				}
2014
2015
				if ( 'id' === $mode ) {
2016
					$this->row = get_post( $id, ARRAY_A );
2017
2018
					if ( is_array( $this->row ) && $this->row['post_type'] != $post_type ) {
2019
						$this->row = false;
2020
					}
2021
				} else {
2022
					$args = array(
2023
						'post_type'   => $post_type,
2024
						'name'        => $id,
2025
						'numberposts' => 5,
2026
					);
2027
2028
					$find = get_posts( $args );
2029
2030
					if ( ! empty( $find ) ) {
2031
						$this->row = get_object_vars( $find[0] );
2032
					}
2033
				}
2034
2035
				if ( is_wp_error( $this->row ) || empty( $this->row ) ) {
2036
					$this->row = false;
2037
				} else {
2038
					$current_row_id = $this->row['ID'];
2039
				}
2040
2041
				$get_table_data = true;
2042
			} elseif ( 'taxonomy' === $this->pod_data['type'] ) {
2043
				$taxonomy = $this->pod_data['object'];
2044
2045
				if ( empty( $taxonomy ) ) {
2046
					$taxonomy = $this->pod_data['name'];
2047
				}
2048
2049
				// Taxonomies are registered during init, so they aren't available before then.
2050
				if ( ! did_action( 'init' ) ) {
2051
					// hackaround :(
2052
					if ( 'id' === $mode ) {
2053
						$term_where = 't.term_id = %d';
2054
					} else {
2055
						$term_where = 't.slug = %s';
2056
					}
2057
2058
					$filter = 'raw';
2059
					$term   = $id;
2060
2061
					if ( 'id' !== $mode || ! $_term = wp_cache_get( $term, $taxonomy ) ) {
2062
						$_term = $wpdb->get_row( $wpdb->prepare( "SELECT t.*, tt.* FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id WHERE tt.taxonomy = %s AND {$term_where} LIMIT 1", $taxonomy, $term ) );
0 ignored issues
show
Usage of a direct database call is discouraged.
Loading history...
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
2063
2064
						if ( $_term ) {
2065
							wp_cache_add( $term, $_term, $taxonomy );
2066
						}
2067
					}
2068
2069
					$_term = apply_filters( 'get_term', $_term, $taxonomy );
2070
					$_term = apply_filters( "get_$taxonomy", $_term, $taxonomy );
2071
					$_term = sanitize_term( $_term, $taxonomy, $filter );
2072
2073
					if ( is_object( $_term ) ) {
2074
						$this->row = get_object_vars( $_term );
2075
					}
2076
				} elseif ( 'id' === $mode ) {
2077
					$this->row = get_term( $id, $taxonomy, ARRAY_A );
2078
				} else {
2079
					$this->row = get_term_by( 'slug', $id, $taxonomy, ARRAY_A );
2080
				}//end if
2081
2082
				if ( is_wp_error( $this->row ) || empty( $this->row ) ) {
2083
					$this->row = false;
2084
				} else {
2085
					$current_row_id = $this->row['term_id'];
2086
				}
2087
2088
				$get_table_data = true;
2089
			} elseif ( 'user' === $this->pod_data['type'] ) {
2090
				if ( 'id' === $mode ) {
2091
					$this->row = get_userdata( $id );
2092
				} else {
2093
					$this->row = get_user_by( 'slug', $id );
2094
				}
2095
2096
				if ( is_wp_error( $this->row ) || empty( $this->row ) ) {
2097
					$this->row = false;
2098
				} else {
2099
					// Get other vars.
2100
					$roles   = $this->row->roles;
2101
					$caps    = $this->row->caps;
2102
					$allcaps = $this->row->allcaps;
2103
2104
					$this->row = get_object_vars( $this->row->data );
2105
2106
					// Set other vars.
2107
					$this->row['roles']   = $roles;
2108
					$this->row['caps']    = $caps;
2109
					$this->row['allcaps'] = $allcaps;
2110
2111
					unset( $this->row['user_pass'] );
2112
2113
					$current_row_id = $this->row['ID'];
2114
				}
2115
2116
				$get_table_data = true;
2117
			} elseif ( 'comment' === $this->pod_data['type'] ) {
2118
				$this->row = get_comment( $id, ARRAY_A );
2119
2120
				// No slug handling here.
2121
				if ( is_wp_error( $this->row ) || empty( $this->row ) ) {
2122
					$this->row = false;
2123
				} else {
2124
					$current_row_id = $this->row['comment_ID'];
2125
				}
2126
2127
				$get_table_data = true;
2128
			} elseif ( 'settings' === $this->pod_data['type'] ) {
2129
				$this->row = array();
2130
2131
				if ( empty( $this->fields ) ) {
2132
					$this->row = false;
2133
				} else {
2134
					foreach ( $this->fields as $field ) {
2135
						if ( ! in_array( $field['type'], $tableless_field_types, true ) ) {
2136
							$this->row[ $field['name'] ] = get_option( $this->pod_data['name'] . '_' . $field['name'], null );
2137
						}
2138
					}
2139
2140
					// Force ID.
2141
					$this->id               = $this->pod_data['id'];
2142
					$this->row['option_id'] = $this->id;
2143
				}
2144
			} else {
2145
				$params = array(
2146
					'table'   => $this->table,
2147
					'where'   => "`t`.`{$this->field_id}` = " . (int) $id,
2148
					'orderby' => "`t`.`{$this->field_id}` DESC",
2149
					'page'    => 1,
2150
					'limit'   => 1,
2151
					'search'  => false,
2152
				);
2153
2154
				if ( 'slug' === $mode && ! empty( $this->field_slug ) ) {
2155
					$id              = pods_sanitize( $id );
2156
					$params['where'] = "`t`.`{$this->field_slug}` = '{$id}'";
2157
				}
2158
2159
				$this->row = pods_data()->select( $params );
2160
2161
				if ( empty( $this->row ) ) {
2162
					$this->row = false;
2163
				} else {
2164
					$current_row = (array) $this->row;
2165
					$this->row   = get_object_vars( (object) @current( $current_row ) );
0 ignored issues
show
Silencing errors is discouraged
Loading history...
2166
				}
2167
			}//end if
2168
2169
			if ( ! $explicit_set && ! empty( $this->row ) && is_array( $this->row ) && ! empty( $old_row ) ) {
2170
				$this->row = array_merge( $old_row, $this->row );
2171
			}
2172
2173
			if ( 'table' === $this->pod_data['storage'] && false !== $get_table_data && is_numeric( $current_row_id ) ) {
2174
				$params = array(
2175
					'table'   => $wpdb->prefix . 'pods_',
2176
					'where'   => "`t`.`id` = {$current_row_id}",
2177
					'orderby' => '`t`.`id` DESC',
2178
					'page'    => 1,
2179
					'limit'   => 1,
2180
					'search'  => false,
2181
					'strict'  => true,
2182
				);
2183
2184
				if ( empty( $this->pod_data['object'] ) ) {
2185
					$params['table'] .= $this->pod_data['name'];
2186
				} else {
2187
					$params['table'] .= $this->pod_data['object'];
2188
				}
2189
2190
				$row = pods_data()->select( $params );
2191
2192
				if ( ! empty( $row ) ) {
2193
					$current_row = (array) $row;
2194
					$row         = get_object_vars( (object) @current( $current_row ) );
0 ignored issues
show
Silencing errors is discouraged
Loading history...
2195
2196
					if ( is_array( $this->row ) && ! empty( $this->row ) ) {
2197
						$this->row = array_merge( $this->row, $row );
2198
					} else {
2199
						$this->row = $row;
2200
					}
2201
				}
2202
			}//end if
2203
2204
			if ( ! empty( $this->pod ) && ! $already_cached ) {
2205
				pods_cache_set( $id, $this->row, 'pods_items_' . $this->pod, 0 );
2206
			}
2207
		}//end if
2208
2209
		$this->row = apply_filters( 'pods_data_fetch', $this->row, $id, $this->row_number, $this );
2210
2211
		return $this->row;
2212
	}
2213
2214
	/**
2215
	 * Reset the current data
2216
	 *
2217
	 * @param int $row Row number to reset to.
0 ignored issues
show
Should the type for parameter $row not be integer|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...
2218
	 *
2219
	 * @return mixed
0 ignored issues
show
Consider making the return type a bit more specific; maybe use array|false.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
2220
	 *
2221
	 * @since 2.0
2222
	 */
2223
	public function reset( $row = null ) {
2224
2225
		$row = pods_absint( $row );
2226
2227
		$this->row = false;
2228
2229
		if ( isset( $this->data[ $row ] ) ) {
2230
			$this->row = get_object_vars( $this->data[ $row ] );
2231
		}
2232
2233
		if ( empty( $row ) ) {
2234
			$this->row_number = - 1;
2235
		} else {
2236
			$this->row_number = $row - 1;
2237
		}
2238
2239
		return $this->row;
2240
	}
2241
2242
	/**
2243
	 * @static
2244
	 *
2245
	 * Do a query on the database
2246
	 *
2247
	 * @param string|array $sql              The SQL to execute.
2248
	 * @param string       $error            Error to throw on problems.
2249
	 * @param null         $results_error    (optional).
2250
	 * @param null         $no_results_error (optional).
2251
	 *
2252
	 * @return array|bool|mixed|null|void Result of the query
2253
	 *
2254
	 * @since 2.0
2255
	 */
2256
	public static function query( $sql, $error = 'Database Error', $results_error = null, $no_results_error = null ) {
2257
2258
		/**
2259
		 * @var $wpdb wpdb
2260
		 */
2261
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2262
2263
		if ( $wpdb->show_errors ) {
2264
			self::$display_errors = true;
2265
		}
2266
2267
		$display_errors = self::$display_errors;
2268
2269
		if ( is_object( $error ) ) {
2270
			if ( isset( $error->display_errors ) && false === $error->display_errors ) {
2271
				$display_errors = false;
2272
			}
2273
2274
			$error = 'Database Error';
2275
		} elseif ( is_bool( $error ) ) {
2276
			$display_errors = $error;
2277
2278
			if ( false !== $error ) {
2279
				$error = 'Database Error';
2280
			}
2281
		}
2282
2283
		$params = (object) array(
2284
			'sql'              => $sql,
2285
			'error'            => $error,
2286
			'results_error'    => $results_error,
2287
			'no_results_error' => $no_results_error,
2288
			'display_errors'   => $display_errors,
2289
		);
2290
2291
		// Handle Preparations of Values (sprintf format).
2292
		if ( is_array( $sql ) ) {
2293
			if ( isset( $sql[0] ) && 1 < count( $sql ) ) {
2294
				if ( 2 === count( $sql ) ) {
2295
					if ( ! is_array( $sql[1] ) ) {
2296
						$sql[1] = array( $sql[1] );
2297
					}
2298
2299
					$params->sql = self::prepare( $sql[0], $sql[1] );
2300
				} elseif ( 3 === count( $sql ) ) {
2301
					$params->sql = self::prepare( $sql[0], array( $sql[1], $sql[2] ) );
2302
				} else {
2303
					$params->sql = self::prepare( $sql[0], array( $sql[1], $sql[2], $sql[3] ) );
2304
				}
2305
			} else {
2306
				$params = array_merge( $params, $sql );
2307
			}
2308
2309
			if ( 1 === (int) pods_v( 'pods_debug_sql_all', 'get', 0 ) && pods_is_admin( array( 'pods' ) ) ) {
2310
				echo '<textarea cols="100" rows="24">' . esc_textarea(
2311
					str_replace(
2312
						array(
2313
							'@wp_users',
2314
							'@wp_',
2315
						), array( $wpdb->users, $wpdb->prefix ), $params->sql
0 ignored issues
show
Usage of users/usermeta tables is highly discouraged in VIP context, For storing user additional user metadata, you should look at User Attributes.
Loading history...
2316
					)
2317
				) . '</textarea>';
2318
			}
2319
		}//end if
2320
2321
		$params->sql = trim( $params->sql );
2322
2323
		// Run Query.
2324
		$params->sql = apply_filters( 'pods_data_query', $params->sql, $params );
2325
2326
		$result = $wpdb->query( $params->sql );
0 ignored issues
show
Usage of a direct database call is discouraged.
Loading history...
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
2327
2328
		$result = apply_filters( 'pods_data_query_result', $result, $params );
2329
2330
		if ( false === $result && ! empty( $params->error ) && ! empty( $wpdb->last_error ) ) {
2331
			return pods_error( "{$params->error}; SQL: {$params->sql}; Response: {$wpdb->last_error}", $params->display_errors );
2332
		}
2333
2334
		if ( 'INSERT' === strtoupper( substr( $params->sql, 0, 6 ) ) || 'REPLACE' === strtoupper( substr( $params->sql, 0, 7 ) ) ) {
2335
			$result = $wpdb->insert_id;
2336
		} elseif ( preg_match( '/^[\s\r\n\(]*SELECT/', strtoupper( $params->sql ) ) ) {
2337
			$result = (array) $wpdb->last_result;
2338
2339
			if ( ! empty( $result ) && ! empty( $params->results_error ) ) {
2340
				return pods_error( $params->results_error, $params->display_errors );
2341
			} elseif ( empty( $result ) && ! empty( $params->no_results_error ) ) {
2342
				return pods_error( $params->no_results_error, $params->display_errors );
2343
			}
2344
		}
2345
2346
		return $result;
2347
	}
2348
2349
	/**
2350
	 * Gets all tables in the WP database, optionally exclude WP core
2351
	 * tables, and/or Pods table by settings the parameters to false.
2352
	 *
2353
	 * @param boolean $wp_core
2354
	 * @param boolean $pods_tables restrict Pods 2x tables.
2355
	 *
2356
	 * @return array
2357
	 *
2358
	 * @since 2.0
2359
	 */
2360
	public static function get_tables( $wp_core = true, $pods_tables = true ) {
2361
2362
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2363
2364
		$core_wp_tables = array(
2365
			$wpdb->options,
2366
			$wpdb->comments,
2367
			$wpdb->commentmeta,
2368
			$wpdb->posts,
2369
			$wpdb->postmeta,
2370
			$wpdb->users,
0 ignored issues
show
Usage of users/usermeta tables is highly discouraged in VIP context, For storing user additional user metadata, you should look at User Attributes.
Loading history...
2371
			$wpdb->usermeta,
0 ignored issues
show
Usage of users/usermeta tables is highly discouraged in VIP context, For storing user additional user metadata, you should look at User Attributes.
Loading history...
2372
			$wpdb->links,
2373
			$wpdb->terms,
2374
			$wpdb->term_taxonomy,
2375
			$wpdb->term_relationships,
2376
		);
2377
2378
		$showTables = $wpdb->get_results( 'SHOW TABLES in ' . DB_NAME, ARRAY_A );
0 ignored issues
show
Usage of a direct database call is discouraged.
Loading history...
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
2379
2380
		$finalTables = array();
2381
2382
		foreach ( $showTables as $table ) {
2383
			if ( ! $pods_tables && 0 === ( strpos( $table[0], $wpdb->prefix . rtrim( self::$prefix, '_' ) ) ) ) {
2384
				// don't include pods tables.
2385
				continue;
2386
			} elseif ( ! $wp_core && in_array( $table[0], $core_wp_tables, true ) ) {
2387
				continue;
2388
			} else {
2389
				$finalTables[] = $table[0];
2390
			}
2391
		}
2392
2393
		return $finalTables;
2394
	}
2395
2396
	/**
2397
	 * Gets column information from a table
2398
	 *
2399
	 * @param string $table Table Name.
2400
	 *
2401
	 * @return array
2402
	 *
2403
	 * @since 2.0
2404
	 */
2405
	public static function get_table_columns( $table ) {
2406
2407
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2408
2409
		self::query( "SHOW COLUMNS FROM `{$table}` " );
2410
2411
		$table_columns = $wpdb->last_result;
2412
2413
		$table_cols_and_types = array();
2414
2415
		foreach ( $table_columns as $table_col ) {
2416
			// Get only the type, not the attributes.
2417
			if ( false === strpos( $table_col->Type, '(' ) ) {
2418
				$modified_type = $table_col->Type;
2419
			} else {
2420
				$modified_type = substr( $table_col->Type, 0, ( strpos( $table_col->Type, '(' ) ) );
2421
			}
2422
2423
			$table_cols_and_types[ $table_col->Field ] = $modified_type;
2424
		}
2425
2426
		return $table_cols_and_types;
2427
	}
2428
2429
	/**
2430
	 * Gets column data information from a table
2431
	 *
2432
	 * @param string $column_name Column name.
2433
	 * @param string $table       Table name.
2434
	 *
2435
	 * @return array
2436
	 *
2437
	 * @since 2.0
2438
	 */
2439
	public static function get_column_data( $column_name, $table ) {
2440
2441
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2442
2443
		$column_data = $wpdb->get_results( 'DESCRIBE ' . $table, ARRAY_A );
0 ignored issues
show
Usage of a direct database call is discouraged.
Loading history...
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
2444
2445
		foreach ( $column_data as $single_column ) {
2446
			if ( $column_name === $single_column['Field'] ) {
2447
				return $single_column;
2448
			}
2449
		}
2450
2451
		return $column_data;
2452
	}
2453
2454
	/**
2455
	 * Prepare values for the DB
2456
	 *
2457
	 * @param string $sql  SQL to prepare.
2458
	 * @param array  $data Data to add to the sql prepare statement.
2459
	 *
2460
	 * @return bool|null|string
2461
	 *
2462
	 * @since 2.0
2463
	 */
2464
	public static function prepare( $sql, $data ) {
2465
2466
		/**
2467
		 * @var $wpdb wpdb
2468
		 */
2469
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2470
		list( $sql, $data ) = apply_filters( 'pods_data_prepare', array( $sql, $data ) );
2471
2472
		return $wpdb->prepare( $sql, $data );
2473
	}
2474
2475
	/**
2476
	 * Get the string to use in a query for WHERE/HAVING, uses WP_Query meta_query arguments
2477
	 *
2478
	 * @param array  $fields Array of field matches for querying.
2479
	 * @param array  $pod    Related Pod.
0 ignored issues
show
Should the type for parameter $pod not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

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. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

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

Loading history...
2480
	 * @param object $params Parameters passed from select().
0 ignored issues
show
Should the type for parameter $params not be object|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...
2481
	 *
2482
	 * @return string|null Query string for WHERE/HAVING
2483
	 *
2484
	 * @static
2485
	 * @since 2.3
2486
	 */
2487
	public static function query_fields( $fields, $pod = null, &$params = null ) {
2488
2489
		$query_fields = array();
2490
2491
		if ( ! is_object( $params ) ) {
2492
			$params = new stdClass();
2493
		}
2494
2495
		if ( ! isset( $params->query_field_level ) || 0 === $params->query_field_level ) {
2496
			$params->query_fields       = array();
2497
			$params->query_field_syntax = false;
2498
			$params->query_field_level  = 1;
2499
2500
			if ( ! isset( $params->where_default ) ) {
2501
				$params->where_default = array();
2502
			}
2503
2504
			if ( ! isset( $params->where_defaulted ) ) {
2505
				$params->where_defaulted = false;
2506
			}
2507
		}
2508
2509
		$current_level = $params->query_field_level;
2510
2511
		$relation = 'AND';
2512
2513
		if ( isset( $fields['relation'] ) ) {
2514
			$relation = strtoupper( trim( pods_v( 'relation', $fields, 'AND', true ) ) );
2515
2516
			if ( 'AND' !== $relation ) {
2517
				$relation = 'OR';
2518
			}
2519
2520
			unset( $fields['relation'] );
2521
		}
2522
2523
		foreach ( $fields as $field => $match ) {
2524
			if ( is_array( $match ) && isset( $match['relation'] ) ) {
2525
				$params->query_field_level = $current_level + 1;
2526
2527
				$query_field = self::query_fields( $match, $pod, $params );
2528
2529
				$params->query_field_level = $current_level;
2530
2531
				if ( ! empty( $query_field ) ) {
2532
					$query_fields[] = $query_field;
2533
				}
2534
			} else {
2535
				$query_field = self::query_field( $field, $match, $pod, $params );
2536
2537
				if ( ! empty( $query_field ) ) {
2538
					$query_fields[] = $query_field;
2539
				}
2540
			}
2541
		}
2542
2543
		if ( ! empty( $query_fields ) ) {
2544
			// If post_status not sent, detect it.
2545
			if ( ! empty( $pod ) && 'post_type' === $pod['type'] && 1 === $current_level && ! $params->where_defaulted && ! empty( $params->where_default ) ) {
2546
				$post_status_found = false;
2547
2548
				if ( ! $params->query_field_syntax ) {
2549
					$haystack = implode( ' ', (array) $query_fields );
2550
					$haystack = preg_replace( '/\s/', ' ', $haystack );
2551
					$haystack = preg_replace( '/\w\(/', ' ', $haystack );
2552
					$haystack = str_replace( array( '(', ')', '  ', '\\\'', '\\"' ), ' ', $haystack );
2553
2554
					preg_match_all( '/`?[\w\-]+`?(?:\\.`?[\w\-]+`?)+(?=[^"\']*(?:"[^"]*"[^"]*|\'[^\']*\'[^\']*)*$)/', $haystack, $found, PREG_PATTERN_ORDER );
2555
2556
					$found = (array) @current( $found );
0 ignored issues
show
Silencing errors is discouraged
Loading history...
2557
2558
					foreach ( $found as $value ) {
2559
						$value = str_replace( '`', '', $value );
2560
						$value = explode( '.', $value );
2561
2562
						if ( ( 'post_status' === $value[0] && 1 === count( $value ) ) || ( 2 === count( $value ) && 't' === $value[0] && 'post_status' === $value[1] ) ) {
2563
							$post_status_found = true;
2564
2565
							break;
2566
						}
2567
					}
2568
				} elseif ( ! empty( $params->query_fields ) && in_array( 'post_status', $params->query_fields, true ) ) {
2569
					$post_status_found = true;
2570
				}//end if
2571
2572
				if ( ! $post_status_found ) {
2573
					$query_fields[] = $params->where_default;
2574
				}
2575
			}//end if
2576
2577
			if ( 1 < count( $query_fields ) ) {
2578
				$query_fields = '( ( ' . implode( ' ) ' . $relation . ' ( ', $query_fields ) . ' ) )';
2579
			} else {
2580
				$query_fields = '( ' . implode( ' ' . $relation . ' ', $query_fields ) . ' )';
2581
			}
2582
		} else {
2583
			$query_fields = null;
2584
		}//end if
2585
2586
		// query_fields level complete.
2587
		if ( 1 === $params->query_field_level ) {
2588
			$params->query_field_level = 0;
2589
		}
2590
2591
		return $query_fields;
2592
	}
2593
2594
	/**
2595
	 * Get the string to use in a query for matching, uses WP_Query meta_query arguments
2596
	 *
2597
	 * @param string|int   $field  Field name or array index.
2598
	 * @param array|string $q      Query array (meta_query) or string for matching.
2599
	 * @param array        $pod    Related Pod.
0 ignored issues
show
Should the type for parameter $pod not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

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. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

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

Loading history...
2600
	 * @param object       $params Parameters passed from select().
0 ignored issues
show
Should the type for parameter $params not be object|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...
2601
	 *
2602
	 * @return string|null Query field string
2603
	 *
2604
	 * @see   PodsData::query_fields
2605
	 * @static
2606
	 * @since 2.3
2607
	 */
2608
	public static function query_field( $field, $q, $pod = null, &$params = null ) {
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $q. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
2609
2610
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2611
2612
		$simple_tableless_objects = PodsForm::simple_tableless_objects();
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $simple_tableless_objects exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
2613
2614
		$field_query = null;
2615
2616
		// Plain queries.
2617
		if ( is_numeric( $field ) && ! is_array( $q ) ) {
2618
			return $q;
2619
		} elseif ( ! is_numeric( $field ) && ( ! is_array( $q ) || ! isset( $q['key'], $q['field'] ) ) ) {
2620
			$new_q = array(
2621
				'field'           => $field,
2622
				'compare'         => pods_v( 'compare', $q, '=', true ),
2623
				'value'           => pods_v( 'value', $q, $q, true ),
2624
				'sanitize'        => pods_v( 'sanitize', $q, true ),
2625
				'sanitize_format' => pods_v( 'sanitize_format', $q ),
2626
				'cast'            => pods_v( 'cast', $q ),
2627
			);
2628
2629
			if ( is_array( $new_q['value'] ) ) {
2630
				if ( '=' === $new_q['compare'] ) {
2631
					$new_q['compare'] = 'IN';
2632
				}
2633
2634
				if ( isset( $new_q['value']['compare'] ) ) {
2635
					unset( $new_q['value']['compare'] );
2636
				}
2637
			}
2638
2639
			$q = $new_q;
2640
		}//end if
2641
2642
		$field_name  = trim( pods_v( 'field', $q, pods_v( 'key', $q, $field, true ), true ) );
2643
		$field_type  = strtoupper( trim( pods_v( 'type', $q, 'CHAR', true ) ) );
2644
		$field_value = pods_v( 'value', $q );
2645
2646
		$field_compare = '=';
2647
2648
		if ( is_array( $field_value ) ) {
2649
			$field_compare = 'IN';
2650
		}
2651
2652
		$field_compare         = strtoupper( trim( pods_v( 'compare', $q, $field_compare, true ) ) );
2653
		$field_sanitize        = (boolean) pods_v( 'sanitize', $q, true );
2654
		$field_sanitize_format = pods_v( 'sanitize_format', $q, null, true );
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $field_sanitize_format exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
2655
		$field_cast            = pods_v( 'cast', $q, null, true );
2656
2657
		if ( is_object( $params ) ) {
2658
			$params->meta_query_syntax = true;
2659
			$params->query_fields[]    = $field_name;
2660
		}
2661
2662
		// Deprecated WP type.
2663
		if ( 'NUMERIC' === $field_type ) {
2664
			$field_type = 'SIGNED';
2665
		} elseif ( ! in_array(
2666
			$field_type, array(
2667
				'BINARY',
2668
				'CHAR',
2669
				'DATE',
2670
				'DATETIME',
2671
				'DECIMAL',
2672
				'SIGNED',
2673
				'TIME',
2674
				'UNSIGNED',
2675
			), true
2676
		) ) {
2677
			$field_type = 'CHAR';
2678
		}
2679
2680
		// Alias / Casting.
2681
		if ( empty( $field_cast ) ) {
2682
			// Setup field casting from field name.
2683
			if ( false === strpos( $field_name, '`' ) && false === strpos( $field_name, '(' ) && false === strpos( $field_name, ' ' ) ) {
2684
				// Handle field naming if Pod-based.
2685
				if ( ! empty( $pod ) && false === strpos( $field_name, '.' ) ) {
2686
					$field_cast = '';
2687
2688
					$tableless_field_types = PodsForm::tableless_field_types();
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $tableless_field_types exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
2689
2690
					if ( isset( $pod['fields'][ $field_name ] ) && in_array( $pod['fields'][ $field_name ]['type'], $tableless_field_types, true ) ) {
2691
						if ( in_array( $pod['fields'][ $field_name ]['pick_object'], $simple_tableless_objects, true ) ) {
2692
							if ( 'meta' === $pod['storage'] ) {
2693
								$field_cast = "`{$field_name}`.`meta_value`";
2694
							} else {
2695
								$field_cast = "`t`.`{$field_name}`";
2696
							}
2697
						} else {
2698
							$table = pods_api()->get_table_info( $pod['fields'][ $field_name ]['pick_object'], $pod['fields'][ $field_name ]['pick_val'] );
2699
2700
							if ( ! empty( $table ) ) {
2701
								$field_cast = "`{$field_name}`.`" . $table['field_index'] . '`';
2702
							}
2703
						}
2704
					}
2705
2706
					if ( empty( $field_cast ) ) {
2707
						if ( ! in_array(
2708
							$pod['type'], array(
2709
								'pod',
2710
								'table',
2711
							), true
2712
						) ) {
2713
							if ( isset( $pod['object_fields'][ $field_name ] ) ) {
2714
								$field_cast = "`t`.`{$field_name}`";
2715
							} elseif ( isset( $pod['fields'][ $field_name ] ) ) {
2716
								if ( 'meta' === $pod['storage'] ) {
2717
									$field_cast = "`{$field_name}`.`meta_value`";
2718
								} else {
2719
									$field_cast = "`d`.`{$field_name}`";
2720
								}
2721
							} else {
2722
								foreach ( $pod['object_fields'] as $object_field => $object_field_opt ) {
2723
									if ( $object_field === $field_name || in_array( $field_name, $object_field_opt['alias'], true ) ) {
2724
										$field_cast = "`t`.`{$object_field}`";
2725
2726
										break;
2727
									}
2728
								}
2729
							}
2730
						} elseif ( isset( $pod['fields'][ $field_name ] ) ) {
2731
							if ( 'meta' === $pod['storage'] ) {
2732
								$field_cast = "`{$field_name}`.`meta_value`";
2733
							} else {
2734
								$field_cast = "`t`.`{$field_name}`";
2735
							}
2736
						}//end if
2737
2738
						if ( empty( $field_cast ) ) {
2739
							if ( 'meta' === $pod['storage'] ) {
2740
								$field_cast = "`{$field_name}`.`meta_value`";
2741
							} else {
2742
								$field_cast = "`t`.`{$field_name}`";
2743
							}
2744
						}
2745
					}//end if
2746
				} else {
2747
					$field_cast = '`' . str_replace( '.', '`.`', $field_name ) . '`';
2748
				}//end if
2749
			} else {
2750
				$field_cast = $field_name;
2751
			}//end if
2752
2753
			// Cast field if needed.
2754
			if ( 'CHAR' !== $field_type ) {
2755
				$field_cast = 'CAST( ' . $field_cast . ' AS ' . $field_type . ' )';
2756
			}
2757
		}//end if
2758
2759
		// Setup string sanitizing for $wpdb->prepare().
2760
		if ( empty( $field_sanitize_format ) ) {
2761
			// Sanitize as string.
2762
			$field_sanitize_format = '%s';
2763
2764
			// Sanitize as integer if needed.
2765
			if ( in_array( $field_type, array( 'UNSIGNED', 'SIGNED' ), true ) ) {
2766
				$field_sanitize_format = '%d';
2767
			}
2768
		}
2769
2770
		// Restrict to supported comparisons.
2771
		if ( ! in_array(
2772
			$field_compare, array(
2773
				'=',
2774
				'!=',
2775
				'>',
2776
				'>=',
2777
				'<',
2778
				'<=',
2779
				'LIKE',
2780
				'NOT LIKE',
2781
				'IN',
2782
				'NOT IN',
2783
				'ALL',
2784
				'BETWEEN',
2785
				'NOT BETWEEN',
2786
				'EXISTS',
2787
				'NOT EXISTS',
2788
				'REGEXP',
2789
				'NOT REGEXP',
2790
				'RLIKE',
2791
			), true
2792
		) ) {
2793
			$field_compare = '=';
2794
		}
2795
2796
		// Restrict to supported array comparisons.
2797
		if ( is_array( $field_value ) && ! in_array(
2798
			$field_compare, array(
2799
				'IN',
2800
				'NOT IN',
2801
				'ALL',
2802
				'BETWEEN',
2803
				'NOT BETWEEN',
2804
			), true
2805
		) ) {
2806
			if ( in_array(
2807
				$field_compare, array(
2808
					'!=',
2809
					'NOT LIKE',
2810
				), true
2811
			) ) {
2812
				$field_compare = 'NOT IN';
2813
			} else {
2814
				$field_compare = 'IN';
2815
			}
2816
		} elseif ( ! is_array( $field_value ) && in_array(
2817
			$field_compare, array(
2818
				'IN',
2819
				'NOT IN',
2820
				'ALL',
2821
				'BETWEEN',
2822
				'NOT BETWEEN',
2823
			), true
2824
		) ) {
2825
			$check_value = preg_split( '/[,\s]+/', $field_value );
2826
2827
			if ( 1 < count( $check_value ) ) {
2828
				$field_value = $check_value;
2829
			} elseif ( in_array(
2830
				$field_compare, array(
2831
					'NOT IN',
2832
					'NOT BETWEEN',
2833
				), true
2834
			) ) {
2835
				$field_compare = '!=';
2836
			} else {
2837
				$field_compare = '=';
2838
			}
2839
		}//end if
2840
2841
		// Restrict to two values, force = and != if only one value provided.
2842
		if ( in_array(
2843
			$field_compare, array(
2844
				'BETWEEN',
2845
				'NOT BETWEEN',
2846
			), true
2847
		) ) {
2848
			$field_value = array_values( array_slice( $field_value, 0, 2 ) );
2849
2850
			if ( 1 === count( $field_value ) ) {
2851
				if ( 'NOT IN' === $field_compare ) {
2852
					$field_compare = '!=';
2853
				} else {
2854
					$field_compare = '=';
2855
				}
2856
			}
2857
		}
2858
2859
		// Single array handling.
2860
		if ( 1 === count( (array) $field_value ) && 'ALL' === $field_compare ) {
2861
			$field_compare = '=';
2862
		} elseif ( empty( $field_value ) && in_array(
2863
			$field_compare, array(
2864
				'IN',
2865
				'NOT IN',
2866
				'BETWEEN',
2867
				'NOT BETWEEN',
2868
			), true
2869
		) ) {
2870
			$field_compare = 'EXISTS';
2871
		}
2872
2873
		// Rebuild $q.
2874
		$q = array(
2875
			'field'           => $field_name,
2876
			'type'            => $field_type,
2877
			'value'           => $field_value,
2878
			'compare'         => $field_compare,
2879
			'sanitize'        => $field_sanitize,
2880
			'sanitize_format' => $field_sanitize_format,
2881
			'cast'            => $field_cast,
2882
		);
2883
2884
		// Make the query.
2885
		if ( in_array(
2886
			$field_compare, array(
2887
				'=',
2888
				'!=',
2889
				'>',
2890
				'>=',
2891
				'<',
2892
				'<=',
2893
				'REGEXP',
2894
				'NOT REGEXP',
2895
				'RLIKE',
2896
			), true
2897
		) ) {
2898
			if ( $field_sanitize ) {
2899
				$field_query = $wpdb->prepare( $field_cast . ' ' . $field_compare . ' ' . $field_sanitize_format, $field_value );
2900
			} else {
2901
				$field_query = $field_cast . ' ' . $field_compare . ' "' . $field_value . '"';
2902
			}
2903
		} elseif ( in_array(
2904
			$field_compare, array(
2905
				'LIKE',
2906
				'NOT LIKE',
2907
			), true
2908
		) ) {
2909
			if ( $field_sanitize ) {
2910
				$field_query = $field_cast . ' ' . $field_compare . ' "%' . pods_sanitize_like( $field_value ) . '%"';
2911
			} else {
2912
				$field_query = $field_cast . ' ' . $field_compare . ' "' . $field_value . '"';
2913
			}
2914
		} elseif ( in_array(
2915
			$field_compare, array(
2916
				'IN',
2917
				'NOT IN',
2918
				'ALL',
2919
			), true
2920
		) ) {
2921
			if ( 'ALL' === $field_compare ) {
2922
				$field_compare = 'IN';
2923
2924
				if ( ! empty( $pod ) ) {
2925
					$params->having[] = 'COUNT( DISTINCT ' . $field_cast . ' ) = ' . count( $field_value );
2926
2927
					if ( empty( $params->groupby ) || ( ! in_array( '`t`.`' . $pod['field_id'] . '`', $params->groupby, true ) && ! in_array( 't.' . $pod['field_id'] . '', $params->groupby, true ) ) ) {
2928
						$params->groupby[] = '`t`.`' . $pod['field_id'] . '`';
2929
					}
2930
				}
2931
			}
2932
2933
			if ( $field_sanitize ) {
2934
				$field_query = $wpdb->prepare( $field_cast . ' ' . $field_compare . ' ( ' . substr( str_repeat( ', ' . $field_sanitize_format, count( $field_value ) ), 1 ) . ' )', $field_value );
2935
			} else {
2936
				$field_query = $field_cast . ' ' . $field_compare . ' ( "' . implode( '", "', $field_value ) . '" )';
2937
			}
2938
		} elseif ( in_array(
2939
			$field_compare, array(
2940
				'BETWEEN',
2941
				'NOT BETWEEN',
2942
			), true
2943
		) ) {
2944
			if ( $field_sanitize ) {
2945
				$field_query = $wpdb->prepare( $field_cast . ' ' . $field_compare . ' ' . $field_sanitize_format . ' AND ' . $field_sanitize_format, $field_value );
2946
			} else {
2947
				$field_query = $field_cast . ' ' . $field_compare . ' "' . $field_value[0] . '" AND "' . $field_value[1] . '"';
2948
			}
2949
		} elseif ( 'EXISTS' === $field_compare ) {
2950
			$field_query = $field_cast . ' IS NOT NULL';
2951
		} elseif ( 'NOT EXISTS' === $field_compare ) {
2952
			$field_query = $field_cast . ' IS NULL';
2953
		}//end if
2954
2955
		$field_query = apply_filters( 'pods_data_field_query', $field_query, $q );
2956
2957
		return $field_query;
2958
	}
2959
2960
	/**
2961
	 * Setup fields for traversal
2962
	 *
2963
	 * @param array  $fields Associative array of fields data.
0 ignored issues
show
Should the type for parameter $fields not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

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. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

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

Loading history...
2964
	 *
2965
	 * @return array Traverse feed
2966
	 *
2967
	 * @param object $params (optional) Parameters from build().
0 ignored issues
show
Should the type for parameter $params not be object|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...
2968
	 *
2969
	 * @since 2.0
2970
	 */
2971
	public function traverse_build( $fields = null, $params = null ) {
2972
2973
		if ( null === $fields ) {
2974
			$fields = $this->fields;
2975
		}
2976
2977
		$feed = array();
2978
2979
		foreach ( $fields as $field => $data ) {
2980
			if ( ! is_array( $data ) ) {
2981
				$field = $data;
2982
			}
2983
2984
			if ( ! isset( $_GET[ 'filter_' . $field ] ) ) {
2985
				continue;
2986
			}
2987
2988
			$field_value = pods_v( 'filter_' . $field, 'get', false, true );
2989
2990
			if ( ! empty( $field_value ) || 0 < strlen( $field_value ) ) {
2991
				$feed[ 'traverse_' . $field ] = array( $field );
2992
			}
2993
		}
2994
2995
		return $feed;
2996
	}
2997
2998
	/**
2999
	 * Recursively join tables based on fields
3000
	 *
3001
	 * @param array $traverse_recurse Array of traversal options.
3002
	 *
3003
	 * @return array Array of table joins
3004
	 *
3005
	 * @since 2.0
3006
	 */
3007
	public function traverse_recurse( $traverse_recurse ) {
3008
3009
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
3010
3011
		$defaults = array(
3012
			'pod'             => null,
3013
			'fields'          => array(),
3014
			'joined'          => 't',
3015
			'depth'           => 0,
3016
			'joined_id'       => 'id',
3017
			'joined_index'    => 'id',
3018
			'params'          => new stdClass(),
3019
			'last_table_info' => array(),
3020
		);
3021
3022
		$traverse_recurse = array_merge( $defaults, $traverse_recurse );
3023
3024
		$joins = array();
3025
3026
		if ( 0 === $traverse_recurse['depth'] && ! empty( $traverse_recurse['pod'] ) && ! empty( $traverse_recurse ['last_table_info'] ) && isset( $traverse_recurse ['last_table_info']['id'] ) ) {
3027
			$pod_data = $traverse_recurse ['last_table_info'];
3028
		} elseif ( empty( $traverse_recurse['pod'] ) ) {
3029
			if ( ! empty( $traverse_recurse['params'] ) && ! empty( $traverse_recurse['params']->table ) && 0 === strpos( $traverse_recurse['params']->table, $wpdb->prefix ) ) {
3030
				if ( $wpdb->posts === $traverse_recurse['params']->table ) {
3031
					$traverse_recurse['pod'] = 'post_type';
3032
				} elseif ( $wpdb->terms === $traverse_recurse['params']->table ) {
3033
					$traverse_recurse['pod'] = 'taxonomy';
3034
				} elseif ( $wpdb->users === $traverse_recurse['params']->table ) {
0 ignored issues
show
Usage of users/usermeta tables is highly discouraged in VIP context, For storing user additional user metadata, you should look at User Attributes.
Loading history...
3035
					$traverse_recurse['pod'] = 'user';
3036
				} elseif ( $wpdb->comments === $traverse_recurse['params']->table ) {
3037
					$traverse_recurse['pod'] = 'comment';
3038
				} else {
3039
					return $joins;
3040
				}
3041
3042
				$pod_data = array();
3043
3044
				if ( in_array(
3045
					$traverse_recurse['pod'], array(
3046
						'user',
3047
						'comment',
3048
					), true
3049
				) ) {
3050
					$pod = $this->api->load_pod(
3051
						array(
3052
							'name'       => $traverse_recurse['pod'],
3053
							'table_info' => true,
3054
						)
3055
					);
3056
3057
					if ( ! empty( $pod ) && $pod['type'] === $pod ) {
3058
						$pod_data = $pod;
3059
					}
3060
				}
3061
3062
				if ( empty( $pod_data ) ) {
3063
					$default_storage = 'meta';
3064
3065
					if ( 'taxonomy' === $traverse_recurse['pod'] && ! function_exists( 'get_term_meta' ) ) {
3066
						$default_storage = 'none';
3067
					}
3068
3069
					$pod_data = array(
3070
						'id'            => 0,
3071
						'name'          => '_table_' . $traverse_recurse['pod'],
3072
						'type'          => $traverse_recurse['pod'],
3073
						'storage'       => $default_storage,
3074
						'fields'        => array(),
3075
						'object_fields' => $this->api->get_wp_object_fields( $traverse_recurse['pod'] ),
3076
					);
3077
3078
					$pod_data = array_merge( $this->api->get_table_info( $traverse_recurse['pod'], '' ), $pod_data );
3079
				} elseif ( 'taxonomy' === $pod_data['type'] && 'none' === $pod_data['storage'] && function_exists( 'get_term_meta' ) ) {
3080
					$pod_data['storage'] = 'meta';
3081
				}
3082
3083
				$traverse_recurse['pod'] = $pod_data['name'];
3084
			} else {
3085
				return $joins;
3086
			}//end if
3087
		} else {
3088
			$pod_data = $this->api->load_pod(
3089
				array(
3090
					'name'       => $traverse_recurse['pod'],
3091
					'table_info' => true,
3092
				), false
3093
			);
3094
3095
			if ( empty( $pod_data ) ) {
3096
				return $joins;
3097
			}
3098
		}//end if
3099
3100
		if ( isset( $pod_data['object_fields'] ) ) {
3101
			$pod_data['fields'] = array_merge( $pod_data['fields'], $pod_data['object_fields'] );
3102
		}
3103
3104
		$tableless_field_types    = PodsForm::tableless_field_types();
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $tableless_field_types exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
3105
		$simple_tableless_objects = PodsForm::simple_tableless_objects();
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $simple_tableless_objects exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
3106
		$file_field_types         = PodsForm::file_field_types();
3107
3108
		if ( ! isset( $this->traversal[ $traverse_recurse['pod'] ] ) ) {
3109
			$this->traversal[ $traverse_recurse['pod'] ] = array();
3110
		}
3111
3112
		if ( ( empty( $pod_data['meta_table'] ) || $pod_data['meta_table'] === $pod_data['table'] ) && ( empty( $traverse_recurse['fields'] ) || empty( $traverse_recurse['fields'][ $traverse_recurse['depth'] ] ) ) ) {
3113
			return $joins;
3114
		}
3115
3116
		$field = $traverse_recurse['fields'][ $traverse_recurse['depth'] ];
3117
3118
		$ignore_aliases = array(
3119
			'wpml_languages',
3120
			'polylang_languages',
3121
		);
3122
3123
		$ignore_aliases = apply_filters( 'pods_data_traverse_recurse_ignore_aliases', $ignore_aliases, $field, $traverse_recurse, $this );
3124
3125
		if ( in_array( $field, $ignore_aliases, true ) ) {
3126
			return $joins;
3127
		}
3128
3129
		$meta_data_table = false;
3130
3131
		if ( ! isset( $pod_data['fields'][ $field ] ) && 'd' === $field && isset( $traverse_recurse['fields'][ $traverse_recurse['depth'] - 1 ] ) ) {
3132
			$field = $traverse_recurse['fields'][ $traverse_recurse['depth'] - 1 ];
3133
3134
			$field_type = 'pick';
3135
3136
			if ( isset( $traverse_recurse['last_table_info']['pod']['fields'][ $field ] ) ) {
3137
				$field_type = $traverse_recurse['last_table_info']['pod']['fields'][ $field ]['type'];
3138
			} elseif ( isset( $traverse_recurse['last_table_info']['pod']['object_fields'][ $field ] ) ) {
3139
				$field_type = $traverse_recurse['last_table_info']['pod']['object_fields'][ $field ]['type'];
3140
			}
3141
3142
			$pod_data['fields'][ $field ] = array(
3143
				'id'          => 0,
3144
				'name'        => $field,
3145
				'type'        => $field_type,
3146
				'pick_object' => $traverse_recurse['last_table_info']['pod']['type'],
3147
				'pick_val'    => $traverse_recurse['last_table_info']['pod']['name'],
3148
			);
3149
3150
			$meta_data_table = true;
3151
		}//end if
3152
3153
		// Fallback to meta table if the pod type supports it.
3154
		if ( ! isset( $pod_data['fields'][ $field ] ) ) {
3155
			$last = end( $traverse_recurse['fields'] );
3156
3157
			if ( 'post_type' === $pod_data['type'] && ! isset( $pod_data['object_fields'] ) ) {
3158
				$pod_data['object_fields'] = $this->api->get_wp_object_fields( 'post_type', $pod_data );
3159
			}
3160
3161
			if ( 'post_type' === $pod_data['type'] && isset( $pod_data['object_fields'][ $field ] ) && in_array( $pod_data['object_fields'][ $field ]['type'], $tableless_field_types, true ) ) {
3162
				$pod_data['fields'][ $field ] = $pod_data['object_fields'][ $field ];
3163
			} elseif ( 'meta_value' === $last && in_array(
3164
				$pod_data['type'], array(
3165
					'post_type',
3166
					'media',
3167
					'taxonomy',
3168
					'user',
3169
					'comment',
3170
				), true
3171
			) ) {
3172
				$pod_data['fields'][ $field ] = PodsForm::field_setup( array( 'name' => $field ) );
3173
			} else {
3174
				if ( 'post_type' === $pod_data['type'] ) {
3175
					$pod_data['object_fields'] = $this->api->get_wp_object_fields( 'post_type', $pod_data, true );
3176
3177
					if ( 'post_type' === $pod_data['type'] && isset( $pod_data['object_fields'][ $field ] ) && in_array( $pod_data['object_fields'][ $field ]['type'], $tableless_field_types, true ) ) {
3178
						$pod_data['fields'][ $field ] = $pod_data['object_fields'][ $field ];
3179
					} else {
3180
						return $joins;
3181
					}
3182
				} else {
3183
					return $joins;
3184
				}
3185
			}//end if
3186
		} elseif ( isset( $pod_data['object_fields'] ) && isset( $pod_data['object_fields'][ $field ] ) && ! in_array( $pod_data['object_fields'][ $field ]['type'], $tableless_field_types, true ) ) {
3187
			return $joins;
3188
		}//end if
3189
3190
		$traverse = $pod_data['fields'][ $field ];
3191
3192
		if ( in_array( $traverse['type'], $file_field_types, true ) ) {
3193
			$traverse['table_info'] = $this->api->get_table_info( 'post_type', 'attachment' );
3194
		} elseif ( ! in_array( $traverse['type'], $tableless_field_types, true ) ) {
3195
			$traverse['table_info'] = $this->api->get_table_info( $pod_data['type'], $pod_data['name'], $pod_data['name'], $pod_data );
3196
		} elseif ( empty( $traverse['table_info'] ) || ( in_array( $traverse['pick_object'], $simple_tableless_objects, true ) && ! empty( $traverse_recurse['last_table_info'] ) ) ) {
3197
			if ( in_array( $traverse['pick_object'], $simple_tableless_objects, true ) && ! empty( $traverse_recurse['last_table_info'] ) ) {
3198
				$traverse['table_info'] = $traverse_recurse['last_table_info'];
3199
3200
				if ( ! empty( $traverse['table_info']['meta_table'] ) ) {
3201
					$meta_data_table = true;
3202
				}
3203
			} elseif ( ! in_array( $traverse['type'], $tableless_field_types, true ) && ! empty( $traverse_recurse['last_table_info'] ) && 0 === $traverse_recurse['depth'] ) {
3204
				$traverse['table_info'] = $traverse_recurse['last_table_info'];
3205
			} else {
3206
				if ( ! isset( $traverse['pod'] ) ) {
3207
					$traverse['pod'] = null;
3208
				}
3209
3210
				$traverse['table_info'] = $this->api->get_table_info( $traverse['pick_object'], $traverse['pick_val'], null, $traverse['pod'], $traverse );
3211
			}
3212
		}//end if
3213
3214
		if ( isset( $this->traversal[ $traverse_recurse['pod'] ][ $traverse['name'] ] ) ) {
3215
			$traverse = array_merge( $traverse, (array) $this->traversal[ $traverse_recurse['pod'] ][ $traverse['name'] ] );
3216
		}
3217
3218
		$traverse = apply_filters( 'pods_data_traverse', $traverse, compact( 'pod', 'fields', 'joined', 'depth', 'joined_id', 'params' ), $this );
3219
3220
		if ( empty( $traverse ) ) {
3221
			return $joins;
3222
		}
3223
3224
		$traverse['id'] = (int) $traverse['id'];
3225
3226
		if ( empty( $traverse['id'] ) ) {
3227
			$traverse['id'] = $field;
3228
		}
3229
3230
		$table_info = $traverse['table_info'];
3231
3232
		$this->traversal[ $traverse_recurse['pod'] ][ $field ] = $traverse;
3233
3234
		$field_joined = $field;
3235
3236
		if ( 0 < $traverse_recurse['depth'] && 't' !== $traverse_recurse['joined'] ) {
3237
			if ( $meta_data_table && ( 'pick' !== $traverse['type'] || ! in_array( pods_v( 'pick_object', $traverse, true ), $simple_tableless_objects ) ) ) {
3238
				$field_joined = $traverse_recurse['joined'] . '_d';
3239
			} else {
3240
				$field_joined = $traverse_recurse['joined'] . '_' . $field;
3241
			}
3242
		}
3243
3244
		$rel_alias = 'rel_' . $field_joined;
3245
3246
		if ( pods_v( 'search', $traverse_recurse['params'], false ) && empty( $traverse_recurse['params']->filters ) ) {
3247
			if ( 0 < strlen( pods_v( 'filter_' . $field_joined ) ) ) {
3248
				$val = absint( pods_v( 'filter_' . $field_joined ) );
3249
3250
				$search = "`{$field_joined}`.`{$table_info[ 'field_id' ]}` = {$val}";
3251
3252
				if ( 'text' === $this->search_mode ) {
3253
					$val = pods_v_sanitized( 'filter_' . $field_joined );
3254
3255
					$search = "`{$field_joined}`.`{$traverse[ 'name' ]}` = '{$val}'";
3256
				} elseif ( 'text_like' === $this->search_mode ) {
3257
					$val = pods_sanitize( pods_sanitize_like( pods_v( 'filter_' . $field_joined ) ) );
3258
3259
					$search = "`{$field_joined}`.`{$traverse[ 'name' ]}` LIKE '%{$val}%'";
3260
				}
3261
3262
				$this->search_where[] = " {$search} ";
3263
			}
3264
		}
3265
3266
		$the_join = null;
3267
3268
		$joined_id    = $table_info['field_id'];
3269
		$joined_index = $table_info['field_index'];
3270
3271
		if ( 'taxonomy' === $traverse['type'] ) {
3272
			$rel_tt_alias = 'rel_tt_' . $field_joined;
3273
3274
			if ( pods_tableless() && function_exists( 'get_term_meta' ) ) {
3275
				$the_join = "
3276
                    LEFT JOIN `{$table_info[ 'meta_table' ]}` AS `{$rel_alias}` ON
3277
                        `{$rel_alias}`.`{$table_info[ 'meta_field_index' ]}` = '{$traverse[ 'name' ]}'
3278
                        AND `{$rel_alias}`.`{$table_info[ 'meta_field_id' ]}` = `{$traverse_recurse[ 'joined' ]}`.`{$traverse_recurse[ 'joined_id' ]}`
3279
3280
                    LEFT JOIN `{$table_info[ 'meta_table' ]}` AS `{$field_joined}` ON
3281
                        `{$field_joined}`.`{$table_info[ 'meta_field_index' ]}` = '{$traverse[ 'name' ]}'
3282
                        AND `{$field_joined}`.`{$table_info[ 'meta_field_id' ]}` = CONVERT( `{$rel_alias}`.`{$table_info[ 'meta_field_value' ]}`, SIGNED )
3283
                ";
3284
3285
				$joined_id    = $table_info['meta_field_id'];
3286
				$joined_index = $table_info['meta_field_index'];
3287
			} elseif ( $meta_data_table ) {
3288
				$the_join = "
3289
                    LEFT JOIN `{$table_info[ 'pod_table' ]}` AS `{$field_joined}` ON
3290
                        `{$field_joined}`.`{$table_info[ 'pod_field_id' ]}` = `{$traverse_recurse[ 'rel_alias' ]}`.`{$traverse_recurse[ 'joined_id' ]}`
3291
                ";
3292
			} else {
3293
				$the_join = "
3294
                    LEFT JOIN `{$wpdb->term_relationships}` AS `{$rel_alias}` ON
3295
                        `{$rel_alias}`.`object_id` = `{$traverse_recurse[ 'joined' ]}`.`ID`
3296
3297
                    LEFT JOIN `{$wpdb->term_taxonomy}` AS `{$rel_tt_alias}` ON
3298
                        `{$rel_tt_alias}`.`taxonomy` = '{$traverse[ 'name' ]}'
3299
                        AND `{$rel_tt_alias}`.`term_taxonomy_id` = `{$rel_alias}`.`term_taxonomy_id`
3300
3301
                    LEFT JOIN `{$table_info[ 'table' ]}` AS `{$field_joined}` ON
3302
                        `{$field_joined}`.`{$table_info[ 'field_id' ]}` = `{$rel_tt_alias}`.`{$table_info[ 'field_id' ]}`
3303
                ";
3304
3305
				// Override $rel_alias.
3306
				$rel_alias = $field_joined;
3307
3308
				$joined_id    = $table_info['field_id'];
3309
				$joined_index = $table_info['field_index'];
3310
			}//end if
3311
		} elseif ( 'comment' === $traverse['type'] ) {
3312
			if ( pods_tableless() ) {
3313
				$the_join = "
3314
                    LEFT JOIN `{$table_info[ 'meta_table' ]}` AS `{$rel_alias}` ON
3315
                        `{$rel_alias}`.`{$table_info[ 'meta_field_index' ]}` = '{$traverse[ 'name' ]}'
3316
                        AND `{$rel_alias}`.`{$table_info[ 'meta_field_id' ]}` = `{$traverse_recurse[ 'joined' ]}`.`{$traverse_recurse[ 'joined_id' ]}`
3317
3318
                    LEFT JOIN `{$table_info[ 'meta_table' ]}` AS `{$field_joined}` ON
3319
                        `{$field_joined}`.`{$table_info[ 'meta_field_index' ]}` = '{$traverse[ 'name' ]}'
3320
                        AND `{$field_joined}`.`{$table_info[ 'meta_field_id' ]}` = CONVERT( `{$rel_alias}`.`{$table_info[ 'meta_field_value' ]}`, SIGNED )
3321
                ";
3322
3323
				$joined_id    = $table_info['meta_field_id'];
3324
				$joined_index = $table_info['meta_field_index'];
3325
			} elseif ( $meta_data_table ) {
3326
				$the_join = "
3327
                    LEFT JOIN `{$table_info[ 'pod_table' ]}` AS `{$field_joined}` ON
3328
                        `{$field_joined}`.`{$table_info[ 'pod_field_id' ]}` = `{$traverse_recurse[ 'rel_alias' ]}`.`{$traverse_recurse[ 'joined_id' ]}`
3329
                ";
3330
			} else {
3331
				$the_join = "
3332
                    LEFT JOIN `{$wpdb->comments}` AS `{$field_joined}` ON
3333
                        `{$field_joined}`.`comment_post_ID` = `{$traverse_recurse[ 'joined' ]}`.`ID`
3334
                ";
3335
3336
				// Override $rel_alias.
3337
				$rel_alias = $field_joined;
3338
3339
				$joined_id    = $table_info['field_id'];
3340
				$joined_index = $table_info['field_index'];
3341
			}//end if
3342
		} elseif ( in_array( $traverse['type'], $tableless_field_types, true ) && ( 'pick' !== $traverse['type'] || ! in_array( pods_v( 'pick_object', $traverse ), $simple_tableless_objects, true ) ) ) {
3343
			if ( pods_tableless() ) {
3344
				$the_join = "
3345
                    LEFT JOIN `{$table_info[ 'meta_table' ]}` AS `{$rel_alias}` ON
3346
                        `{$rel_alias}`.`{$table_info[ 'meta_field_index' ]}` = '{$traverse[ 'name' ]}'
3347
                        AND `{$rel_alias}`.`{$table_info[ 'meta_field_id' ]}` = `{$traverse_recurse[ 'joined' ]}`.`{$traverse_recurse[ 'joined_id' ]}`
3348
3349
                    LEFT JOIN `{$table_info[ 'meta_table' ]}` AS `{$field_joined}` ON
3350
                        `{$field_joined}`.`{$table_info[ 'meta_field_index' ]}` = '{$traverse[ 'name' ]}'
3351
                        AND `{$field_joined}`.`{$table_info[ 'meta_field_id' ]}` = CONVERT( `{$rel_alias}`.`{$table_info[ 'meta_field_value' ]}`, SIGNED )
3352
                ";
3353
3354
				$joined_id    = $table_info['meta_field_id'];
3355
				$joined_index = $table_info['meta_field_index'];
3356
			} elseif ( $meta_data_table ) {
3357
				if ( $traverse['id'] !== $traverse['pick_val'] ) {
3358
					// This must be a relationship.
3359
					$joined_id = 'related_item_id';
3360
				} else {
3361
					$joined_id = $traverse_recurse['joined_id'];
3362
				}
3363
3364
				$the_join = "
3365
                     LEFT JOIN `{$table_info['pod_table']}` AS `{$field_joined}` ON
3366
                        `{$field_joined}`.`{$table_info['pod_field_id']}` = `{$traverse_recurse['rel_alias']}`.`{$joined_id}`
3367
                 ";
3368
			} else {
3369
				if ( ( $traverse_recurse['depth'] + 2 ) === count( $traverse_recurse['fields'] ) && ( 'pick' !== $traverse['type'] || ! in_array( pods_v( 'pick_object', $traverse ), $simple_tableless_objects, true ) ) && 'post_author' === $traverse_recurse['fields'][ $traverse_recurse['depth'] + 1 ] ) {
3370
					$table_info['recurse'] = false;
3371
				}
3372
3373
				$the_join = "
3374
                    LEFT JOIN `@wp_podsrel` AS `{$rel_alias}` ON
3375
                        `{$rel_alias}`.`field_id` = {$traverse[ 'id' ]}
3376
                        AND `{$rel_alias}`.`item_id` = `{$traverse_recurse[ 'joined' ]}`.`{$traverse_recurse[ 'joined_id' ]}`
3377
3378
                    LEFT JOIN `{$table_info[ 'table' ]}` AS `{$field_joined}` ON
3379
                        `{$field_joined}`.`{$table_info[ 'field_id' ]}` = `{$rel_alias}`.`related_item_id`
3380
                ";
3381
			}//end if
3382
		} elseif ( 'meta' === $pod_data['storage'] ) {
3383
			if ( ( $traverse_recurse['depth'] + 2 ) === count( $traverse_recurse['fields'] ) && ( 'pick' !== $traverse['type'] || ! in_array( pods_v( 'pick_object', $traverse ), $simple_tableless_objects, true ) ) && $table_info['meta_field_value'] === $traverse_recurse['fields'][ $traverse_recurse['depth'] + 1 ] ) {
3384
				$the_join = "
3385
                    LEFT JOIN `{$table_info[ 'meta_table' ]}` AS `{$field_joined}` ON
3386
                        `{$field_joined}`.`{$table_info[ 'meta_field_index' ]}` = '{$traverse[ 'name' ]}'
3387
                        AND `{$field_joined}`.`{$table_info[ 'meta_field_id' ]}` = `{$traverse_recurse[ 'joined' ]}`.`{$traverse_recurse[ 'joined_id' ]}`
3388
                ";
3389
3390
				$table_info['recurse'] = false;
3391
			} else {
3392
				$the_join = "
3393
                    LEFT JOIN `{$table_info[ 'meta_table' ]}` AS `{$field_joined}` ON
3394
                        `{$field_joined}`.`{$table_info[ 'meta_field_index' ]}` = '{$traverse[ 'name' ]}'
3395
                        AND `{$field_joined}`.`{$table_info[ 'meta_field_id' ]}` = `{$traverse_recurse[ 'joined' ]}`.`{$traverse_recurse[ 'joined_id' ]}`
3396
                ";
3397
3398
				$joined_id    = $table_info['meta_field_id'];
3399
				$joined_index = $table_info['meta_field_index'];
3400
			}
3401
		}//end if
3402
3403
		$traverse_recursive = array(
3404
			'pod'             => pods_v( 'name', pods_v( 'pod', $table_info ) ),
3405
			'fields'          => $traverse_recurse['fields'],
3406
			'joined'          => $field_joined,
3407
			'depth'           => ( $traverse_recurse['depth'] + 1 ),
3408
			'joined_id'       => $joined_id,
3409
			'joined_index'    => $joined_index,
3410
			'params'          => $traverse_recurse['params'],
3411
			'rel_alias'       => $rel_alias,
3412
			'last_table_info' => $table_info,
3413
		);
3414
3415
		$the_join = apply_filters( 'pods_data_traverse_the_join', $the_join, $traverse_recurse, $traverse_recursive, $this );
3416
3417
		if ( empty( $the_join ) ) {
3418
			return $joins;
3419
		}
3420
3421
		$joins[ $traverse_recurse['pod'] . '_' . $traverse_recurse['depth'] . '_' . $traverse['id'] ] = $the_join;
3422
3423
		if ( ( $traverse_recurse['depth'] + 1 ) < count( $traverse_recurse['fields'] ) && ! empty( $traverse_recurse['pod'] ) && false !== $table_info['recurse'] ) {
3424
			$joins = array_merge( $joins, $this->traverse_recurse( $traverse_recursive ) );
3425
		}
3426
3427
		return $joins;
3428
	}
3429
3430
	/**
3431
	 * Recursively join tables based on fields
3432
	 *
3433
	 * @param array  $fields     Fields to recurse.
0 ignored issues
show
Should the type for parameter $fields not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

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. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

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

Loading history...
3434
	 * @param null   $all_fields (optional) If $fields is empty then traverse all fields, argument does not need to be.
3435
	 *                           passed
3436
	 * @param object $params     (optional) Parameters from build().
0 ignored issues
show
Should the type for parameter $params not be object|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...
3437
	 *
3438
	 * @return array Array of joins
3439
	 */
3440
	public function traverse( $fields = null, $all_fields = null, $params = null ) {
3441
3442
		$joins = array();
3443
3444
		if ( null === $fields ) {
3445
			$fields = $this->traverse_build( $all_fields, $params );
3446
		}
3447
3448
		foreach ( (array) $fields as $field_group ) {
3449
			$traverse_recurse = array(
3450
				'pod'             => $this->pod,
3451
				'fields'          => $fields,
3452
				'params'          => $params,
3453
				'last_table_info' => $this->pod_data,
3454
				'joined_id'       => $this->pod_data['field_id'],
3455
				'joined_index'    => $this->pod_data['field_index'],
3456
			);
3457
3458
			if ( is_array( $field_group ) ) {
3459
				$traverse_recurse['fields'] = $field_group;
3460
3461
				$joins = array_merge( $joins, $this->traverse_recurse( $traverse_recurse ) );
3462
			} else {
3463
				$joins = array_merge( $joins, $this->traverse_recurse( $traverse_recurse ) );
3464
				$joins = array_filter( $joins );
3465
3466
				return $joins;
3467
			}
3468
		}//end foreach
3469
3470
		$joins = array_filter( $joins );
3471
3472
		return $joins;
3473
	}
3474
3475
	/**
3476
	 * Handle filters / actions for the class
3477
	 *
3478
	 * @since 2.0
3479
	 */
3480
	private static function do_hook() {
3481
3482
		$args = func_get_args();
3483
3484
		if ( empty( $args ) ) {
3485
			return false;
3486
		}
3487
3488
		$name = array_shift( $args );
3489
3490
		return pods_do_hook( 'data', $name, $args );
3491
	}
3492
3493
	/**
3494
	 * Get the complete sql
3495
	 *
3496
	 * @since 2.0.5
3497
	 */
3498
	public function get_sql( $sql ) {
0 ignored issues
show
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
3499
3500
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
3501
3502
		if ( empty( $sql ) ) {
3503
			$sql = $this->sql;
3504
		}
3505
3506
		/**
3507
		 * Allow SQL query to be manipulated.
3508
		 *
3509
		 * @param string   $sql       SQL Query string.
3510
		 * @param PodsData $pods_data PodsData object.
3511
		 *
3512
		 * @since 2.7
3513
		 */
3514
		$sql = apply_filters( 'pods_data_get_sql', $sql, $this );
3515
3516
		$sql = str_replace( array( '@wp_users', '@wp_' ), array( $wpdb->users, $wpdb->prefix ), $sql );
0 ignored issues
show
Usage of users/usermeta tables is highly discouraged in VIP context, For storing user additional user metadata, you should look at User Attributes.
Loading history...
3517
3518
		$sql = str_replace( '{prefix}', '@wp_', $sql );
3519
		$sql = str_replace( '{/prefix/}', '{prefix}', $sql );
3520
3521
		return $sql;
3522
	}
3523
}
3524