wpdb   D
last analyzed

Complexity

Total Complexity 437

Size/Duplication

Total Lines 3289
Duplicated Lines 3.56 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
dl 117
loc 3289
rs 4.4102
c 0
b 0
f 0
wmc 437
lcom 1
cbo 1

64 Methods

Rating   Name   Duplication   Size   Complexity  
D __construct() 0 34 9
A __destruct() 0 3 1
A __get() 0 6 2
A __set() 0 11 2
A __isset() 0 3 1
A __unset() 0 3 1
C init_charset() 0 24 7
C determine_charset() 0 30 13
C set_charset() 12 32 14
C set_sql_mode() 10 54 10
D set_prefix() 0 29 10
A set_blog_id() 0 17 4
B get_blog_prefix() 0 13 6
D tables() 10 48 16
B select() 0 50 5
A _weak_escape() 0 5 3
A _real_escape() 0 18 4
A _escape() 0 15 4
B escape() 0 16 6
A escape_by_ref() 0 4 2
B prepare() 2 21 5
A esc_like() 0 3 1
C print_error() 0 58 9
A show_errors() 0 5 1
A hide_errors() 0 5 1
A suppress_errors() 0 5 1
C flush() 0 24 7
F db_connect() 0 121 20
C check_connection() 0 74 14
F query() 0 135 28
B _do_query() 0 16 8
A insert() 0 3 1
A replace() 0 3 1
B _insert_replace_helper() 0 31 5
D update() 18 42 9
B delete() 9 28 5
B process_fields() 0 24 5
B process_field_formats() 0 23 5
B process_field_charsets() 18 20 5
B process_field_lengths() 18 20 5
B get_var() 0 19 7
C get_row() 0 29 13
B get_col() 0 16 5
C get_results() 0 47 14
D get_table_charset() 0 91 16
C get_col_charset() 7 52 8
C get_col_length() 7 77 18
A check_ascii() 0 11 4
C check_safe_collation() 0 48 11
F strip_invalid_text() 0 145 32
B strip_invalid_text_from_query() 0 36 6
B strip_invalid_text_for_column() 0 29 5
B get_table_from_query() 0 56 5
B load_col_info() 6 16 5
A get_col_info() 0 17 4
A timer_start() 0 4 1
A timer_stop() 0 3 1
A bail() 0 11 3
A check_database_version() 0 8 2
A supports_collation() 0 4 1
A get_charset_collate() 0 10 3
D has_cap() 0 36 10
A get_caller() 0 3 1
A db_version() 0 8 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like wpdb often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use wpdb, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * WordPress DB Class
4
 *
5
 * Original code from {@link http://php.justinvincent.com Justin Vincent ([email protected])}
6
 *
7
 * @package WordPress
8
 * @subpackage Database
9
 * @since 0.71
10
 */
11
12
/**
13
 * @since 0.71
14
 */
15
define( 'EZSQL_VERSION', 'WP1.25' );
16
17
/**
18
 * @since 0.71
19
 */
20
define( 'OBJECT', 'OBJECT' );
21
define( 'object', 'OBJECT' ); // Back compat.
22
23
/**
24
 * @since 2.5.0
25
 */
26
define( 'OBJECT_K', 'OBJECT_K' );
27
28
/**
29
 * @since 0.71
30
 */
31
define( 'ARRAY_A', 'ARRAY_A' );
32
33
/**
34
 * @since 0.71
35
 */
36
define( 'ARRAY_N', 'ARRAY_N' );
37
38
/**
39
 * WordPress Database Access Abstraction Object
40
 *
41
 * It is possible to replace this class with your own
42
 * by setting the $wpdb global variable in wp-content/db.php
43
 * file to your class. The wpdb class will still be included,
44
 * so you can extend it or simply use your own.
45
 *
46
 * @link https://codex.wordpress.org/Function_Reference/wpdb_Class
47
 *
48
 * @package WordPress
49
 * @subpackage Database
50
 * @since 0.71
51
 */
52
class wpdb {
53
54
	/**
55
	 * Whether to show SQL/DB errors.
56
	 *
57
	 * Default behavior is to show errors if both WP_DEBUG and WP_DEBUG_DISPLAY
58
	 * evaluated to true.
59
	 *
60
	 * @since 0.71
61
	 * @access private
62
	 * @var bool
63
	 */
64
	var $show_errors = false;
65
66
	/**
67
	 * Whether to suppress errors during the DB bootstrapping.
68
	 *
69
	 * @access private
70
	 * @since 2.5.0
71
	 * @var bool
72
	 */
73
	var $suppress_errors = false;
74
75
	/**
76
	 * The last error during query.
77
	 *
78
	 * @since 2.5.0
79
	 * @var string
80
	 */
81
	public $last_error = '';
82
83
	/**
84
	 * Amount of queries made
85
	 *
86
	 * @since 1.2.0
87
	 * @access public
88
	 * @var int
89
	 */
90
	public $num_queries = 0;
91
92
	/**
93
	 * Count of rows returned by previous query
94
	 *
95
	 * @since 0.71
96
	 * @access public
97
	 * @var int
98
	 */
99
	public $num_rows = 0;
100
101
	/**
102
	 * Count of affected rows by previous query
103
	 *
104
	 * @since 0.71
105
	 * @access private
106
	 * @var int
107
	 */
108
	var $rows_affected = 0;
109
110
	/**
111
	 * The ID generated for an AUTO_INCREMENT column by the previous query (usually INSERT).
112
	 *
113
	 * @since 0.71
114
	 * @access public
115
	 * @var int
116
	 */
117
	public $insert_id = 0;
118
119
	/**
120
	 * Last query made
121
	 *
122
	 * @since 0.71
123
	 * @access private
124
	 * @var array
125
	 */
126
	var $last_query;
127
128
	/**
129
	 * Results of the last query made
130
	 *
131
	 * @since 0.71
132
	 * @access private
133
	 * @var array|null
134
	 */
135
	var $last_result;
136
137
	/**
138
	 * MySQL result, which is either a resource or boolean.
139
	 *
140
	 * @since 0.71
141
	 * @access protected
142
	 * @var mixed
143
	 */
144
	protected $result;
145
146
	/**
147
	 * Cached column info, for sanity checking data before inserting
148
	 *
149
	 * @since 4.2.0
150
	 * @access protected
151
	 * @var array
152
	 */
153
	protected $col_meta = array();
154
155
	/**
156
	 * Calculated character sets on tables
157
	 *
158
	 * @since 4.2.0
159
	 * @access protected
160
	 * @var array
161
	 */
162
	protected $table_charset = array();
163
164
	/**
165
	 * Whether text fields in the current query need to be sanity checked.
166
	 *
167
	 * @since 4.2.0
168
	 * @access protected
169
	 * @var bool
170
	 */
171
	protected $check_current_query = true;
172
173
	/**
174
	 * Flag to ensure we don't run into recursion problems when checking the collation.
175
	 *
176
	 * @since 4.2.0
177
	 * @access private
178
	 * @see wpdb::check_safe_collation()
179
	 * @var bool
180
	 */
181
	private $checking_collation = false;
182
183
	/**
184
	 * Saved info on the table column
185
	 *
186
	 * @since 0.71
187
	 * @access protected
188
	 * @var array
189
	 */
190
	protected $col_info;
191
192
	/**
193
	 * Saved queries that were executed
194
	 *
195
	 * @since 1.5.0
196
	 * @access private
197
	 * @var array
198
	 */
199
	var $queries;
200
201
	/**
202
	 * The number of times to retry reconnecting before dying.
203
	 *
204
	 * @since 3.9.0
205
	 * @access protected
206
	 * @see wpdb::check_connection()
207
	 * @var int
208
	 */
209
	protected $reconnect_retries = 5;
210
211
	/**
212
	 * WordPress table prefix
213
	 *
214
	 * You can set this to have multiple WordPress installations
215
	 * in a single database. The second reason is for possible
216
	 * security precautions.
217
	 *
218
	 * @since 2.5.0
219
	 * @access public
220
	 * @var string
221
	 */
222
	public $prefix = '';
223
224
	/**
225
	 * WordPress base table prefix.
226
	 *
227
	 * @since 3.0.0
228
	 * @access public
229
	 * @var string
230
	 */
231
	 public $base_prefix;
232
233
	/**
234
	 * Whether the database queries are ready to start executing.
235
	 *
236
	 * @since 2.3.2
237
	 * @access private
238
	 * @var bool
239
	 */
240
	var $ready = false;
241
242
	/**
243
	 * Blog ID.
244
	 *
245
	 * @since 3.0.0
246
	 * @access public
247
	 * @var int
248
	 */
249
	public $blogid = 0;
250
251
	/**
252
	 * Site ID.
253
	 *
254
	 * @since 3.0.0
255
	 * @access public
256
	 * @var int
257
	 */
258
	public $siteid = 0;
259
260
	/**
261
	 * List of WordPress per-blog tables
262
	 *
263
	 * @since 2.5.0
264
	 * @access private
265
	 * @see wpdb::tables()
266
	 * @var array
267
	 */
268
	var $tables = array( 'posts', 'comments', 'links', 'options', 'postmeta',
269
		'terms', 'term_taxonomy', 'term_relationships', 'termmeta', 'commentmeta' );
270
271
	/**
272
	 * List of deprecated WordPress tables
273
	 *
274
	 * categories, post2cat, and link2cat were deprecated in 2.3.0, db version 5539
275
	 *
276
	 * @since 2.9.0
277
	 * @access private
278
	 * @see wpdb::tables()
279
	 * @var array
280
	 */
281
	var $old_tables = array( 'categories', 'post2cat', 'link2cat' );
282
283
	/**
284
	 * List of WordPress global tables
285
	 *
286
	 * @since 3.0.0
287
	 * @access private
288
	 * @see wpdb::tables()
289
	 * @var array
290
	 */
291
	var $global_tables = array( 'users', 'usermeta' );
292
293
	/**
294
	 * List of Multisite global tables
295
	 *
296
	 * @since 3.0.0
297
	 * @access private
298
	 * @see wpdb::tables()
299
	 * @var array
300
	 */
301
	var $ms_global_tables = array( 'blogs', 'signups', 'site', 'sitemeta',
302
		'sitecategories', 'registration_log', 'blog_versions' );
303
304
	/**
305
	 * WordPress Comments table
306
	 *
307
	 * @since 1.5.0
308
	 * @access public
309
	 * @var string
310
	 */
311
	public $comments;
312
313
	/**
314
	 * WordPress Comment Metadata table
315
	 *
316
	 * @since 2.9.0
317
	 * @access public
318
	 * @var string
319
	 */
320
	public $commentmeta;
321
322
	/**
323
	 * WordPress Links table
324
	 *
325
	 * @since 1.5.0
326
	 * @access public
327
	 * @var string
328
	 */
329
	public $links;
330
331
	/**
332
	 * WordPress Options table
333
	 *
334
	 * @since 1.5.0
335
	 * @access public
336
	 * @var string
337
	 */
338
	public $options;
339
340
	/**
341
	 * WordPress Post Metadata table
342
	 *
343
	 * @since 1.5.0
344
	 * @access public
345
	 * @var string
346
	 */
347
	public $postmeta;
348
349
	/**
350
	 * WordPress Posts table
351
	 *
352
	 * @since 1.5.0
353
	 * @access public
354
	 * @var string
355
	 */
356
	public $posts;
357
358
	/**
359
	 * WordPress Terms table
360
	 *
361
	 * @since 2.3.0
362
	 * @access public
363
	 * @var string
364
	 */
365
	public $terms;
366
367
	/**
368
	 * WordPress Term Relationships table
369
	 *
370
	 * @since 2.3.0
371
	 * @access public
372
	 * @var string
373
	 */
374
	public $term_relationships;
375
376
	/**
377
	 * WordPress Term Taxonomy table
378
	 *
379
	 * @since 2.3.0
380
	 * @access public
381
	 * @var string
382
	 */
383
	public $term_taxonomy;
384
385
	/**
386
	 * WordPress Term Meta table.
387
	 *
388
	 * @since 4.4.0
389
	 * @access public
390
	 * @var string
391
	 */
392
	public $termmeta;
393
394
	//
395
	// Global and Multisite tables
396
	//
397
398
	/**
399
	 * WordPress User Metadata table
400
	 *
401
	 * @since 2.3.0
402
	 * @access public
403
	 * @var string
404
	 */
405
	public $usermeta;
406
407
	/**
408
	 * WordPress Users table
409
	 *
410
	 * @since 1.5.0
411
	 * @access public
412
	 * @var string
413
	 */
414
	public $users;
415
416
	/**
417
	 * Multisite Blogs table
418
	 *
419
	 * @since 3.0.0
420
	 * @access public
421
	 * @var string
422
	 */
423
	public $blogs;
424
425
	/**
426
	 * Multisite Blog Versions table
427
	 *
428
	 * @since 3.0.0
429
	 * @access public
430
	 * @var string
431
	 */
432
	public $blog_versions;
433
434
	/**
435
	 * Multisite Registration Log table
436
	 *
437
	 * @since 3.0.0
438
	 * @access public
439
	 * @var string
440
	 */
441
	public $registration_log;
442
443
	/**
444
	 * Multisite Signups table
445
	 *
446
	 * @since 3.0.0
447
	 * @access public
448
	 * @var string
449
	 */
450
	public $signups;
451
452
	/**
453
	 * Multisite Sites table
454
	 *
455
	 * @since 3.0.0
456
	 * @access public
457
	 * @var string
458
	 */
459
	public $site;
460
461
	/**
462
	 * Multisite Sitewide Terms table
463
	 *
464
	 * @since 3.0.0
465
	 * @access public
466
	 * @var string
467
	 */
468
	public $sitecategories;
469
470
	/**
471
	 * Multisite Site Metadata table
472
	 *
473
	 * @since 3.0.0
474
	 * @access public
475
	 * @var string
476
	 */
477
	public $sitemeta;
478
479
	/**
480
	 * Format specifiers for DB columns. Columns not listed here default to %s. Initialized during WP load.
481
	 *
482
	 * Keys are column names, values are format types: 'ID' => '%d'
483
	 *
484
	 * @since 2.8.0
485
	 * @see wpdb::prepare()
486
	 * @see wpdb::insert()
487
	 * @see wpdb::update()
488
	 * @see wpdb::delete()
489
	 * @see wp_set_wpdb_vars()
490
	 * @access public
491
	 * @var array
492
	 */
493
	public $field_types = array();
494
495
	/**
496
	 * Database table columns charset
497
	 *
498
	 * @since 2.2.0
499
	 * @access public
500
	 * @var string
501
	 */
502
	public $charset;
503
504
	/**
505
	 * Database table columns collate
506
	 *
507
	 * @since 2.2.0
508
	 * @access public
509
	 * @var string
510
	 */
511
	public $collate;
512
513
	/**
514
	 * Database Username
515
	 *
516
	 * @since 2.9.0
517
	 * @access protected
518
	 * @var string
519
	 */
520
	protected $dbuser;
521
522
	/**
523
	 * Database Password
524
	 *
525
	 * @since 3.1.0
526
	 * @access protected
527
	 * @var string
528
	 */
529
	protected $dbpassword;
530
531
	/**
532
	 * Database Name
533
	 *
534
	 * @since 3.1.0
535
	 * @access protected
536
	 * @var string
537
	 */
538
	protected $dbname;
539
540
	/**
541
	 * Database Host
542
	 *
543
	 * @since 3.1.0
544
	 * @access protected
545
	 * @var string
546
	 */
547
	protected $dbhost;
548
549
	/**
550
	 * Database Handle
551
	 *
552
	 * @since 0.71
553
	 * @access protected
554
	 * @var string
555
	 */
556
	protected $dbh;
557
558
	/**
559
	 * A textual description of the last query/get_row/get_var call
560
	 *
561
	 * @since 3.0.0
562
	 * @access public
563
	 * @var string
564
	 */
565
	public $func_call;
566
567
	/**
568
	 * Whether MySQL is used as the database engine.
569
	 *
570
	 * Set in WPDB::db_connect() to true, by default. This is used when checking
571
	 * against the required MySQL version for WordPress. Normally, a replacement
572
	 * database drop-in (db.php) will skip these checks, but setting this to true
573
	 * will force the checks to occur.
574
	 *
575
	 * @since 3.3.0
576
	 * @access public
577
	 * @var bool
578
	 */
579
	public $is_mysql = null;
580
581
	/**
582
	 * A list of incompatible SQL modes.
583
	 *
584
	 * @since 3.9.0
585
	 * @access protected
586
	 * @var array
587
	 */
588
	protected $incompatible_modes = array( 'NO_ZERO_DATE', 'ONLY_FULL_GROUP_BY',
589
		'STRICT_TRANS_TABLES', 'STRICT_ALL_TABLES', 'TRADITIONAL' );
590
591
	/**
592
	 * Whether to use mysqli over mysql.
593
	 *
594
	 * @since 3.9.0
595
	 * @access private
596
	 * @var bool
597
	 */
598
	private $use_mysqli = false;
599
600
	/**
601
	 * Whether we've managed to successfully connect at some point
602
	 *
603
	 * @since 3.9.0
604
	 * @access private
605
	 * @var bool
606
	 */
607
	private $has_connected = false;
608
609
	/**
610
	 * Connects to the database server and selects a database
611
	 *
612
	 * PHP5 style constructor for compatibility with PHP5. Does
613
	 * the actual setting up of the class properties and connection
614
	 * to the database.
615
	 *
616
	 * @link https://core.trac.wordpress.org/ticket/3354
617
	 * @since 2.0.8
618
	 *
619
	 * @global string $wp_version
620
	 *
621
	 * @param string $dbuser     MySQL database user
622
	 * @param string $dbpassword MySQL database password
623
	 * @param string $dbname     MySQL database name
624
	 * @param string $dbhost     MySQL database host
625
	 */
626
	public function __construct( $dbuser, $dbpassword, $dbname, $dbhost ) {
627
		register_shutdown_function( array( $this, '__destruct' ) );
628
629
		if ( WP_DEBUG && WP_DEBUG_DISPLAY )
630
			$this->show_errors();
631
632
		/* Use ext/mysqli if it exists and:
633
		 *  - WP_USE_EXT_MYSQL is defined as false, or
634
		 *  - We are a development version of WordPress, or
635
		 *  - We are running PHP 5.5 or greater, or
636
		 *  - ext/mysql is not loaded.
637
		 */
638
		if ( function_exists( 'mysqli_connect' ) ) {
639
			if ( defined( 'WP_USE_EXT_MYSQL' ) ) {
640
				$this->use_mysqli = ! WP_USE_EXT_MYSQL;
641
			} elseif ( version_compare( phpversion(), '5.5', '>=' ) || ! function_exists( 'mysql_connect' ) ) {
642
				$this->use_mysqli = true;
643
			} elseif ( false !== strpos( $GLOBALS['wp_version'], '-' ) ) {
644
				$this->use_mysqli = true;
645
			}
646
		}
647
648
		$this->dbuser = $dbuser;
649
		$this->dbpassword = $dbpassword;
650
		$this->dbname = $dbname;
651
		$this->dbhost = $dbhost;
652
653
		// wp-config.php creation will manually connect when ready.
654
		if ( defined( 'WP_SETUP_CONFIG' ) ) {
655
			return;
656
		}
657
658
		$this->db_connect();
659
	}
660
661
	/**
662
	 * PHP5 style destructor and will run when database object is destroyed.
663
	 *
664
	 * @see wpdb::__construct()
665
	 * @since 2.0.8
666
	 * @return true
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
667
	 */
668
	public function __destruct() {
669
		return true;
670
	}
671
672
	/**
673
	 * Makes private properties readable for backward compatibility.
674
	 *
675
	 * @since 3.5.0
676
	 *
677
	 * @param string $name The private member to get, and optionally process
678
	 * @return mixed The private member
679
	 */
680
	public function __get( $name ) {
681
		if ( 'col_info' === $name )
682
			$this->load_col_info();
683
684
		return $this->$name;
685
	}
686
687
	/**
688
	 * Makes private properties settable for backward compatibility.
689
	 *
690
	 * @since 3.5.0
691
	 *
692
	 * @param string $name  The private member to set
693
	 * @param mixed  $value The value to set
694
	 */
695
	public function __set( $name, $value ) {
696
		$protected_members = array(
697
			'col_meta',
698
			'table_charset',
699
			'check_current_query',
700
		);
701
		if (  in_array( $name, $protected_members, true ) ) {
702
			return;
703
		}
704
		$this->$name = $value;
705
	}
706
707
	/**
708
	 * Makes private properties check-able for backward compatibility.
709
	 *
710
	 * @since 3.5.0
711
	 *
712
	 * @param string $name  The private member to check
713
	 *
714
	 * @return bool If the member is set or not
715
	 */
716
	public function __isset( $name ) {
717
		return isset( $this->$name );
718
	}
719
720
	/**
721
	 * Makes private properties un-settable for backward compatibility.
722
	 *
723
	 * @since 3.5.0
724
	 *
725
	 * @param string $name  The private member to unset
726
	 */
727
	public function __unset( $name ) {
728
		unset( $this->$name );
729
	}
730
731
	/**
732
	 * Set $this->charset and $this->collate
733
	 *
734
	 * @since 3.1.0
735
	 */
736
	public function init_charset() {
737
		$charset = '';
738
		$collate = '';
739
740
		if ( function_exists('is_multisite') && is_multisite() ) {
741
			$charset = 'utf8';
742
			if ( defined( 'DB_COLLATE' ) && DB_COLLATE ) {
743
				$collate = DB_COLLATE;
744
			} else {
745
				$collate = 'utf8_general_ci';
746
			}
747
		} elseif ( defined( 'DB_COLLATE' ) ) {
748
			$collate = DB_COLLATE;
749
		}
750
751
		if ( defined( 'DB_CHARSET' ) ) {
752
			$charset = DB_CHARSET;
753
		}
754
755
		$charset_collate = $this->determine_charset( $charset, $collate );
756
757
		$this->charset = $charset_collate['charset'];
758
		$this->collate = $charset_collate['collate'];
759
	}
760
761
	/**
762
	 * Determines the best charset and collation to use given a charset and collation.
763
	 *
764
	 * For example, when able, utf8mb4 should be used instead of utf8.
765
	 *
766
	 * @since 4.6.0
767
	 * @access public
768
	 *
769
	 * @param string $charset The character set to check.
770
	 * @param string $collate The collation to check.
771
	 * @return array The most appropriate character set and collation to use.
772
	 */
773
	public function determine_charset( $charset, $collate ) {
774
		if ( ( $this->use_mysqli && ! ( $this->dbh instanceof mysqli ) ) || empty( $this->dbh ) ) {
775
			return compact( 'charset', 'collate' );
776
		}
777
778
		if ( 'utf8' === $charset && $this->has_cap( 'utf8mb4' ) ) {
779
			$charset = 'utf8mb4';
780
		}
781
782
		if ( 'utf8mb4' === $charset && ! $this->has_cap( 'utf8mb4' ) ) {
783
			$charset = 'utf8';
784
			$collate = str_replace( 'utf8mb4_', 'utf8_', $collate );
785
		}
786
787
		if ( 'utf8mb4' === $charset ) {
788
			// _general_ is outdated, so we can upgrade it to _unicode_, instead.
789
			if ( ! $collate || 'utf8_general_ci' === $collate ) {
790
				$collate = 'utf8mb4_unicode_ci';
791
			} else {
792
				$collate = str_replace( 'utf8_', 'utf8mb4_', $collate );
793
			}
794
		}
795
796
		// _unicode_520_ is a better collation, we should use that when it's available.
797
		if ( $this->has_cap( 'utf8mb4_520' ) && 'utf8mb4_unicode_ci' === $collate ) {
798
			$collate = 'utf8mb4_unicode_520_ci';
799
		}
800
801
		return compact( 'charset', 'collate' );
802
	}
803
804
	/**
805
	 * Sets the connection's character set.
806
	 *
807
	 * @since 3.1.0
808
	 *
809
	 * @param resource $dbh     The resource given by mysql_connect
810
	 * @param string   $charset Optional. The character set. Default null.
811
	 * @param string   $collate Optional. The collation. Default null.
812
	 */
813
	public function set_charset( $dbh, $charset = null, $collate = null ) {
814
		if ( ! isset( $charset ) )
815
			$charset = $this->charset;
816
		if ( ! isset( $collate ) )
817
			$collate = $this->collate;
818
		if ( $this->has_cap( 'collation' ) && ! empty( $charset ) ) {
819
			$set_charset_succeeded = true;
820
821
			if ( $this->use_mysqli ) {
822
				if ( function_exists( 'mysqli_set_charset' ) && $this->has_cap( 'set_charset' ) ) {
823
					$set_charset_succeeded = mysqli_set_charset( $dbh, $charset );
824
				}
825
826 View Code Duplication
				if ( $set_charset_succeeded ) {
827
					$query = $this->prepare( 'SET NAMES %s', $charset );
828
					if ( ! empty( $collate ) )
829
						$query .= $this->prepare( ' COLLATE %s', $collate );
830
					mysqli_query( $dbh, $query );
831
				}
832
			} else {
833
				if ( function_exists( 'mysql_set_charset' ) && $this->has_cap( 'set_charset' ) ) {
834
					$set_charset_succeeded = mysql_set_charset( $charset, $dbh );
835
				}
836 View Code Duplication
				if ( $set_charset_succeeded ) {
837
					$query = $this->prepare( 'SET NAMES %s', $charset );
838
					if ( ! empty( $collate ) )
839
						$query .= $this->prepare( ' COLLATE %s', $collate );
840
					mysql_query( $query, $dbh );
841
				}
842
			}
843
		}
844
	}
845
846
	/**
847
	 * Change the current SQL mode, and ensure its WordPress compatibility.
848
	 *
849
	 * If no modes are passed, it will ensure the current MySQL server
850
	 * modes are compatible.
851
	 *
852
	 * @since 3.9.0
853
	 *
854
	 * @param array $modes Optional. A list of SQL modes to set.
855
	 */
856
	public function set_sql_mode( $modes = array() ) {
857
		if ( empty( $modes ) ) {
858 View Code Duplication
			if ( $this->use_mysqli ) {
859
				$res = mysqli_query( $this->dbh, 'SELECT @@SESSION.sql_mode' );
860
			} else {
861
				$res = mysql_query( 'SELECT @@SESSION.sql_mode', $this->dbh );
862
			}
863
864
			if ( empty( $res ) ) {
865
				return;
866
			}
867
868
			if ( $this->use_mysqli ) {
869
				$modes_array = mysqli_fetch_array( $res );
870
				if ( empty( $modes_array[0] ) ) {
871
					return;
872
				}
873
				$modes_str = $modes_array[0];
874
			} else {
875
				$modes_str = mysql_result( $res, 0 );
876
			}
877
878
			if ( empty( $modes_str ) ) {
879
				return;
880
			}
881
882
			$modes = explode( ',', $modes_str );
883
		}
884
885
		$modes = array_change_key_case( $modes, CASE_UPPER );
886
887
		/**
888
		 * Filters the list of incompatible SQL modes to exclude.
889
		 *
890
		 * @since 3.9.0
891
		 *
892
		 * @param array $incompatible_modes An array of incompatible modes.
893
		 */
894
		$incompatible_modes = (array) apply_filters( 'incompatible_sql_modes', $this->incompatible_modes );
895
896
		foreach ( $modes as $i => $mode ) {
897
			if ( in_array( $mode, $incompatible_modes ) ) {
898
				unset( $modes[ $i ] );
899
			}
900
		}
901
902
		$modes_str = implode( ',', $modes );
903
904 View Code Duplication
		if ( $this->use_mysqli ) {
905
			mysqli_query( $this->dbh, "SET SESSION sql_mode='$modes_str'" );
906
		} else {
907
			mysql_query( "SET SESSION sql_mode='$modes_str'", $this->dbh );
908
		}
909
	}
910
911
	/**
912
	 * Sets the table prefix for the WordPress tables.
913
	 *
914
	 * @since 2.5.0
915
	 *
916
	 * @param string $prefix          Alphanumeric name for the new prefix.
917
	 * @param bool   $set_table_names Optional. Whether the table names, e.g. wpdb::$posts, should be updated or not.
918
	 * @return string|WP_Error Old prefix or WP_Error on error
919
	 */
920
	public function set_prefix( $prefix, $set_table_names = true ) {
921
922
		if ( preg_match( '|[^a-z0-9_]|i', $prefix ) )
923
			return new WP_Error('invalid_db_prefix', 'Invalid database prefix' );
924
925
		$old_prefix = is_multisite() ? '' : $prefix;
926
927
		if ( isset( $this->base_prefix ) )
928
			$old_prefix = $this->base_prefix;
929
930
		$this->base_prefix = $prefix;
931
932
		if ( $set_table_names ) {
933
			foreach ( $this->tables( 'global' ) as $table => $prefixed_table )
934
				$this->$table = $prefixed_table;
935
936
			if ( is_multisite() && empty( $this->blogid ) )
937
				return $old_prefix;
938
939
			$this->prefix = $this->get_blog_prefix();
940
941
			foreach ( $this->tables( 'blog' ) as $table => $prefixed_table )
942
				$this->$table = $prefixed_table;
943
944
			foreach ( $this->tables( 'old' ) as $table => $prefixed_table )
945
				$this->$table = $prefixed_table;
946
		}
947
		return $old_prefix;
948
	}
949
950
	/**
951
	 * Sets blog id.
952
	 *
953
	 * @since 3.0.0
954
	 * @access public
955
	 *
956
	 * @param int $blog_id
957
	 * @param int $site_id Optional.
958
	 * @return int previous blog id
959
	 */
960
	public function set_blog_id( $blog_id, $site_id = 0 ) {
961
		if ( ! empty( $site_id ) )
962
			$this->siteid = $site_id;
963
964
		$old_blog_id  = $this->blogid;
965
		$this->blogid = $blog_id;
966
967
		$this->prefix = $this->get_blog_prefix();
968
969
		foreach ( $this->tables( 'blog' ) as $table => $prefixed_table )
970
			$this->$table = $prefixed_table;
971
972
		foreach ( $this->tables( 'old' ) as $table => $prefixed_table )
973
			$this->$table = $prefixed_table;
974
975
		return $old_blog_id;
976
	}
977
978
	/**
979
	 * Gets blog prefix.
980
	 *
981
	 * @since 3.0.0
982
	 * @param int $blog_id Optional.
983
	 * @return string Blog prefix.
984
	 */
985
	public function get_blog_prefix( $blog_id = null ) {
986
		if ( is_multisite() ) {
987
			if ( null === $blog_id )
988
				$blog_id = $this->blogid;
989
			$blog_id = (int) $blog_id;
990
			if ( defined( 'MULTISITE' ) && ( 0 == $blog_id || 1 == $blog_id ) )
991
				return $this->base_prefix;
992
			else
993
				return $this->base_prefix . $blog_id . '_';
994
		} else {
995
			return $this->base_prefix;
996
		}
997
	}
998
999
	/**
1000
	 * Returns an array of WordPress tables.
1001
	 *
1002
	 * Also allows for the CUSTOM_USER_TABLE and CUSTOM_USER_META_TABLE to
1003
	 * override the WordPress users and usermeta tables that would otherwise
1004
	 * be determined by the prefix.
1005
	 *
1006
	 * The scope argument can take one of the following:
1007
	 *
1008
	 * 'all' - returns 'all' and 'global' tables. No old tables are returned.
1009
	 * 'blog' - returns the blog-level tables for the queried blog.
1010
	 * 'global' - returns the global tables for the installation, returning multisite tables only if running multisite.
1011
	 * 'ms_global' - returns the multisite global tables, regardless if current installation is multisite.
1012
	 * 'old' - returns tables which are deprecated.
1013
	 *
1014
	 * @since 3.0.0
1015
	 * @uses wpdb::$tables
1016
	 * @uses wpdb::$old_tables
1017
	 * @uses wpdb::$global_tables
1018
	 * @uses wpdb::$ms_global_tables
1019
	 *
1020
	 * @param string $scope   Optional. Can be all, global, ms_global, blog, or old tables. Defaults to all.
1021
	 * @param bool   $prefix  Optional. Whether to include table prefixes. Default true. If blog
1022
	 *                        prefix is requested, then the custom users and usermeta tables will be mapped.
1023
	 * @param int    $blog_id Optional. The blog_id to prefix. Defaults to wpdb::$blogid. Used only when prefix is requested.
1024
	 * @return array Table names. When a prefix is requested, the key is the unprefixed table name.
1025
	 */
1026
	public function tables( $scope = 'all', $prefix = true, $blog_id = 0 ) {
1027
		switch ( $scope ) {
1028 View Code Duplication
			case 'all' :
1029
				$tables = array_merge( $this->global_tables, $this->tables );
1030
				if ( is_multisite() )
1031
					$tables = array_merge( $tables, $this->ms_global_tables );
1032
				break;
1033
			case 'blog' :
1034
				$tables = $this->tables;
1035
				break;
1036 View Code Duplication
			case 'global' :
1037
				$tables = $this->global_tables;
1038
				if ( is_multisite() )
1039
					$tables = array_merge( $tables, $this->ms_global_tables );
1040
				break;
1041
			case 'ms_global' :
1042
				$tables = $this->ms_global_tables;
1043
				break;
1044
			case 'old' :
1045
				$tables = $this->old_tables;
1046
				break;
1047
			default :
1048
				return array();
1049
		}
1050
1051
		if ( $prefix ) {
1052
			if ( ! $blog_id )
1053
				$blog_id = $this->blogid;
1054
			$blog_prefix = $this->get_blog_prefix( $blog_id );
1055
			$base_prefix = $this->base_prefix;
1056
			$global_tables = array_merge( $this->global_tables, $this->ms_global_tables );
1057
			foreach ( $tables as $k => $table ) {
1058
				if ( in_array( $table, $global_tables ) )
1059
					$tables[ $table ] = $base_prefix . $table;
1060
				else
1061
					$tables[ $table ] = $blog_prefix . $table;
1062
				unset( $tables[ $k ] );
1063
			}
1064
1065
			if ( isset( $tables['users'] ) && defined( 'CUSTOM_USER_TABLE' ) )
1066
				$tables['users'] = CUSTOM_USER_TABLE;
1067
1068
			if ( isset( $tables['usermeta'] ) && defined( 'CUSTOM_USER_META_TABLE' ) )
1069
				$tables['usermeta'] = CUSTOM_USER_META_TABLE;
1070
		}
1071
1072
		return $tables;
1073
	}
1074
1075
	/**
1076
	 * Selects a database using the current database connection.
1077
	 *
1078
	 * The database name will be changed based on the current database
1079
	 * connection. On failure, the execution will bail and display an DB error.
1080
	 *
1081
	 * @since 0.71
1082
	 *
1083
	 * @param string        $db  MySQL database name
1084
	 * @param resource|null $dbh Optional link identifier.
1085
	 */
1086
	public function select( $db, $dbh = null ) {
1087
		if ( is_null($dbh) )
1088
			$dbh = $this->dbh;
1089
1090
		if ( $this->use_mysqli ) {
1091
			$success = mysqli_select_db( $dbh, $db );
1092
		} else {
1093
			$success = mysql_select_db( $db, $dbh );
1094
		}
1095
		if ( ! $success ) {
1096
			$this->ready = false;
1097
			if ( ! did_action( 'template_redirect' ) ) {
1098
				wp_load_translations_early();
1099
1100
				$message = '<h1>' . __( 'Can&#8217;t select database' ) . "</h1>\n";
1101
1102
				$message .= '<p>' . sprintf(
1103
					/* translators: %s: database name */
1104
					__( 'We were able to connect to the database server (which means your username and password is okay) but not able to select the %s database.' ),
1105
					'<code>' . htmlspecialchars( $db, ENT_QUOTES ) . '</code>'
1106
				) . "</p>\n";
1107
1108
				$message .= "<ul>\n";
1109
				$message .= '<li>' . __( 'Are you sure it exists?' ) . "</li>\n";
1110
1111
				$message .= '<li>' . sprintf(
1112
					/* translators: 1: database user, 2: database name */
1113
					__( 'Does the user %1$s have permission to use the %2$s database?' ),
1114
					'<code>' . htmlspecialchars( $this->dbuser, ENT_QUOTES )  . '</code>',
1115
					'<code>' . htmlspecialchars( $db, ENT_QUOTES ) . '</code>'
1116
				) . "</li>\n";
1117
1118
				$message .= '<li>' . sprintf(
1119
					/* translators: %s: database name */
1120
					__( 'On some systems the name of your database is prefixed with your username, so it would be like <code>username_%1$s</code>. Could that be the problem?' ),
1121
					htmlspecialchars( $db, ENT_QUOTES )
1122
				). "</li>\n";
1123
1124
				$message .= "</ul>\n";
1125
1126
				$message .= '<p>' . sprintf(
1127
					/* translators: %s: support forums URL */
1128
					__( 'If you don&#8217;t know how to set up a database you should <strong>contact your host</strong>. If all else fails you may find help at the <a href="%s">WordPress Support Forums</a>.' ),
1129
					__( 'https://wordpress.org/support/' )
1130
				) . "</p>\n";
1131
1132
				$this->bail( $message, 'db_select_fail' );
1133
			}
1134
		}
1135
	}
1136
1137
	/**
1138
	 * Do not use, deprecated.
1139
	 *
1140
	 * Use esc_sql() or wpdb::prepare() instead.
1141
	 *
1142
	 * @since 2.8.0
1143
	 * @deprecated 3.6.0 Use wpdb::prepare()
1144
	 * @see wpdb::prepare
1145
	 * @see esc_sql()
1146
	 * @access private
1147
	 *
1148
	 * @param string $string
1149
	 * @return string
1150
	 */
1151
	function _weak_escape( $string ) {
1152
		if ( func_num_args() === 1 && function_exists( '_deprecated_function' ) )
1153
			_deprecated_function( __METHOD__, '3.6.0', 'wpdb::prepare() or esc_sql()' );
1154
		return addslashes( $string );
1155
	}
1156
1157
	/**
1158
	 * Real escape, using mysqli_real_escape_string() or mysql_real_escape_string()
1159
	 *
1160
	 * @see mysqli_real_escape_string()
1161
	 * @see mysql_real_escape_string()
1162
	 * @since 2.8.0
1163
	 * @access private
1164
	 *
1165
	 * @param  string $string to escape
1166
	 * @return string escaped
1167
	 */
1168
	function _real_escape( $string ) {
1169
		if ( $this->dbh ) {
1170
			if ( $this->use_mysqli ) {
1171
				return mysqli_real_escape_string( $this->dbh, $string );
1172
			} else {
1173
				return mysql_real_escape_string( $string, $this->dbh );
1174
			}
1175
		}
1176
1177
		$class = get_class( $this );
1178
		if ( function_exists( '__' ) ) {
1179
			/* translators: %s: database access abstraction class, usually wpdb or a class extending wpdb */
1180
			_doing_it_wrong( $class, sprintf( __( '%s must set a database connection for use with escaping.' ), $class ), '3.6.0' );
1181
		} else {
1182
			_doing_it_wrong( $class, sprintf( '%s must set a database connection for use with escaping.', $class ), '3.6.0' );
1183
		}
1184
		return addslashes( $string );
1185
	}
1186
1187
	/**
1188
	 * Escape data. Works on arrays.
1189
	 *
1190
	 * @uses wpdb::_real_escape()
1191
	 * @since  2.8.0
1192
	 * @access public
1193
	 *
1194
	 * @param  string|array $data
1195
	 * @return string|array escaped
1196
	 */
1197
	public function _escape( $data ) {
1198
		if ( is_array( $data ) ) {
1199
			foreach ( $data as $k => $v ) {
1200
				if ( is_array( $v ) ) {
1201
					$data[$k] = $this->_escape( $v );
1202
				} else {
1203
					$data[$k] = $this->_real_escape( $v );
1204
				}
1205
			}
1206
		} else {
1207
			$data = $this->_real_escape( $data );
1208
		}
1209
1210
		return $data;
1211
	}
1212
1213
	/**
1214
	 * Do not use, deprecated.
1215
	 *
1216
	 * Use esc_sql() or wpdb::prepare() instead.
1217
	 *
1218
	 * @since 0.71
1219
	 * @deprecated 3.6.0 Use wpdb::prepare()
1220
	 * @see wpdb::prepare()
1221
	 * @see esc_sql()
1222
	 *
1223
	 * @param mixed $data
1224
	 * @return mixed
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array|string.

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...
1225
	 */
1226
	public function escape( $data ) {
1227
		if ( func_num_args() === 1 && function_exists( '_deprecated_function' ) )
1228
			_deprecated_function( __METHOD__, '3.6.0', 'wpdb::prepare() or esc_sql()' );
1229
		if ( is_array( $data ) ) {
1230
			foreach ( $data as $k => $v ) {
1231
				if ( is_array( $v ) )
1232
					$data[$k] = $this->escape( $v, 'recursive' );
0 ignored issues
show
Deprecated Code introduced by
The method wpdb::escape() has been deprecated with message: 3.6.0 Use wpdb::prepare()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1233
				else
1234
					$data[$k] = $this->_weak_escape( $v, 'internal' );
0 ignored issues
show
Deprecated Code introduced by
The method wpdb::_weak_escape() has been deprecated with message: 3.6.0 Use wpdb::prepare()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1235
			}
1236
		} else {
1237
			$data = $this->_weak_escape( $data, 'internal' );
0 ignored issues
show
Deprecated Code introduced by
The method wpdb::_weak_escape() has been deprecated with message: 3.6.0 Use wpdb::prepare()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1238
		}
1239
1240
		return $data;
1241
	}
1242
1243
	/**
1244
	 * Escapes content by reference for insertion into the database, for security
1245
	 *
1246
	 * @uses wpdb::_real_escape()
1247
	 *
1248
	 * @since 2.3.0
1249
	 *
1250
	 * @param string $string to escape
1251
	 */
1252
	public function escape_by_ref( &$string ) {
1253
		if ( ! is_float( $string ) )
1254
			$string = $this->_real_escape( $string );
1255
	}
1256
1257
	/**
1258
	 * Prepares a SQL query for safe execution. Uses sprintf()-like syntax.
1259
	 *
1260
	 * The following directives can be used in the query format string:
1261
	 *   %d (integer)
1262
	 *   %f (float)
1263
	 *   %s (string)
1264
	 *   %% (literal percentage sign - no argument needed)
1265
	 *
1266
	 * All of %d, %f, and %s are to be left unquoted in the query string and they need an argument passed for them.
1267
	 * Literals (%) as parts of the query must be properly written as %%.
1268
	 *
1269
	 * This function only supports a small subset of the sprintf syntax; it only supports %d (integer), %f (float), and %s (string).
1270
	 * Does not support sign, padding, alignment, width or precision specifiers.
1271
	 * Does not support argument numbering/swapping.
1272
	 *
1273
	 * May be called like {@link https://secure.php.net/sprintf sprintf()} or like {@link https://secure.php.net/vsprintf vsprintf()}.
1274
	 *
1275
	 * Both %d and %s should be left unquoted in the query string.
1276
	 *
1277
	 *     $wpdb->prepare( "SELECT * FROM `table` WHERE `column` = %s AND `field` = %d", 'foo', 1337 );
1278
	 *     $wpdb->prepare( "SELECT DATE_FORMAT(`field`, '%%c') FROM `table` WHERE `column` = %s", 'foo' );
1279
	 *
1280
	 * @link https://secure.php.net/sprintf Description of syntax.
1281
	 * @since 2.3.0
1282
	 *
1283
	 * @param string      $query    Query statement with sprintf()-like placeholders
1284
	 * @param array|mixed $args     The array of variables to substitute into the query's placeholders if being called like
1285
	 *                              {@link https://secure.php.net/vsprintf vsprintf()}, or the first variable to substitute into the query's placeholders if
1286
	 *                              being called like {@link https://secure.php.net/sprintf sprintf()}.
1287
	 * @param mixed       $args,... further variables to substitute into the query's placeholders if being called like
0 ignored issues
show
Bug introduced by
There is no parameter named $args,.... Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1288
	 *                              {@link https://secure.php.net/sprintf sprintf()}.
1289
	 * @return string|void Sanitized query string, if there is a query to prepare.
1290
	 */
1291
	public function prepare( $query, $args ) {
0 ignored issues
show
Unused Code introduced by
The parameter $args is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1292
		if ( is_null( $query ) )
1293
			return;
1294
1295
		// This is not meant to be foolproof -- but it will catch obviously incorrect usage.
1296
		if ( strpos( $query, '%' ) === false ) {
1297
			_doing_it_wrong( 'wpdb::prepare', sprintf( __( 'The query argument of %s must have a placeholder.' ), 'wpdb::prepare()' ), '3.9.0' );
1298
		}
1299
1300
		$args = func_get_args();
1301
		array_shift( $args );
1302
		// If args were passed as an array (as in vsprintf), move them up
1303 View Code Duplication
		if ( isset( $args[0] ) && is_array($args[0]) )
1304
			$args = $args[0];
1305
		$query = str_replace( "'%s'", '%s', $query ); // in case someone mistakenly already singlequoted it
1306
		$query = str_replace( '"%s"', '%s', $query ); // doublequote unquoting
1307
		$query = preg_replace( '|(?<!%)%f|' , '%F', $query ); // Force floats to be locale unaware
1308
		$query = preg_replace( '|(?<!%)%s|', "'%s'", $query ); // quote the strings, avoiding escaped strings like %%s
1309
		array_walk( $args, array( $this, 'escape_by_ref' ) );
1310
		return @vsprintf( $query, $args );
1311
	}
1312
1313
	/**
1314
	 * First half of escaping for LIKE special characters % and _ before preparing for MySQL.
1315
	 *
1316
	 * Use this only before wpdb::prepare() or esc_sql().  Reversing the order is very bad for security.
1317
	 *
1318
	 * Example Prepared Statement:
1319
	 *
1320
	 *     $wild = '%';
1321
	 *     $find = 'only 43% of planets';
1322
	 *     $like = $wild . $wpdb->esc_like( $find ) . $wild;
1323
	 *     $sql  = $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE post_content LIKE '%s'", $like );
1324
	 *
1325
	 * Example Escape Chain:
1326
	 *
1327
	 *     $sql  = esc_sql( $wpdb->esc_like( $input ) );
1328
	 *
1329
	 * @since 4.0.0
1330
	 * @access public
1331
	 *
1332
	 * @param string $text The raw text to be escaped. The input typed by the user should have no
1333
	 *                     extra or deleted slashes.
1334
	 * @return string Text in the form of a LIKE phrase. The output is not SQL safe. Call $wpdb::prepare()
1335
	 *                or real_escape next.
1336
	 */
1337
	public function esc_like( $text ) {
1338
		return addcslashes( $text, '_%\\' );
1339
	}
1340
1341
	/**
1342
	 * Print SQL/DB error.
1343
	 *
1344
	 * @since 0.71
1345
	 * @global array $EZSQL_ERROR Stores error information of query and error string
1346
	 *
1347
	 * @param string $str The error to display
1348
	 * @return false|void False if the showing of errors is disabled.
1349
	 */
1350
	public function print_error( $str = '' ) {
1351
		global $EZSQL_ERROR;
1352
1353
		if ( !$str ) {
1354
			if ( $this->use_mysqli ) {
1355
				$str = mysqli_error( $this->dbh );
1356
			} else {
1357
				$str = mysql_error( $this->dbh );
1358
			}
1359
		}
1360
		$EZSQL_ERROR[] = array( 'query' => $this->last_query, 'error_str' => $str );
1361
1362
		if ( $this->suppress_errors )
1363
			return false;
1364
1365
		wp_load_translations_early();
1366
1367
		if ( $caller = $this->get_caller() ) {
1368
			/* translators: 1: Database error message, 2: SQL query, 3: Name of the calling function */
1369
			$error_str = sprintf( __( 'WordPress database error %1$s for query %2$s made by %3$s' ), $str, $this->last_query, $caller );
1370
		} else {
1371
			/* translators: 1: Database error message, 2: SQL query */
1372
			$error_str = sprintf( __( 'WordPress database error %1$s for query %2$s' ), $str, $this->last_query );
1373
		}
1374
1375
		error_log( $error_str );
1376
1377
		// Are we showing errors?
1378
		if ( ! $this->show_errors )
1379
			return false;
1380
1381
		// If there is an error then take note of it
1382
		if ( is_multisite() ) {
1383
			$msg = sprintf(
1384
				"%s [%s]\n%s\n",
1385
				__( 'WordPress database error:' ),
1386
				$str,
1387
				$this->last_query
1388
			);
1389
1390
			if ( defined( 'ERRORLOGFILE' ) ) {
1391
				error_log( $msg, 3, ERRORLOGFILE );
1392
			}
1393
			if ( defined( 'DIEONDBERROR' ) ) {
1394
				wp_die( $msg );
1395
			}
1396
		} else {
1397
			$str   = htmlspecialchars( $str, ENT_QUOTES );
1398
			$query = htmlspecialchars( $this->last_query, ENT_QUOTES );
1399
1400
			printf(
1401
				'<div id="error"><p class="wpdberror"><strong>%s</strong> [%s]<br /><code>%s</code></p></div>',
1402
				__( 'WordPress database error:' ),
1403
				$str,
1404
				$query
1405
			);
1406
		}
1407
	}
1408
1409
	/**
1410
	 * Enables showing of database errors.
1411
	 *
1412
	 * This function should be used only to enable showing of errors.
1413
	 * wpdb::hide_errors() should be used instead for hiding of errors. However,
1414
	 * this function can be used to enable and disable showing of database
1415
	 * errors.
1416
	 *
1417
	 * @since 0.71
1418
	 * @see wpdb::hide_errors()
1419
	 *
1420
	 * @param bool $show Whether to show or hide errors
1421
	 * @return bool Old value for showing errors.
1422
	 */
1423
	public function show_errors( $show = true ) {
1424
		$errors = $this->show_errors;
1425
		$this->show_errors = $show;
1426
		return $errors;
1427
	}
1428
1429
	/**
1430
	 * Disables showing of database errors.
1431
	 *
1432
	 * By default database errors are not shown.
1433
	 *
1434
	 * @since 0.71
1435
	 * @see wpdb::show_errors()
1436
	 *
1437
	 * @return bool Whether showing of errors was active
1438
	 */
1439
	public function hide_errors() {
1440
		$show = $this->show_errors;
1441
		$this->show_errors = false;
1442
		return $show;
1443
	}
1444
1445
	/**
1446
	 * Whether to suppress database errors.
1447
	 *
1448
	 * By default database errors are suppressed, with a simple
1449
	 * call to this function they can be enabled.
1450
	 *
1451
	 * @since 2.5.0
1452
	 * @see wpdb::hide_errors()
1453
	 * @param bool $suppress Optional. New value. Defaults to true.
1454
	 * @return bool Old value
1455
	 */
1456
	public function suppress_errors( $suppress = true ) {
1457
		$errors = $this->suppress_errors;
1458
		$this->suppress_errors = (bool) $suppress;
1459
		return $errors;
1460
	}
1461
1462
	/**
1463
	 * Kill cached query results.
1464
	 *
1465
	 * @since 0.71
1466
	 */
1467
	public function flush() {
1468
		$this->last_result = array();
1469
		$this->col_info    = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $col_info.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
1470
		$this->last_query  = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $last_query.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
1471
		$this->rows_affected = $this->num_rows = 0;
1472
		$this->last_error  = '';
1473
1474
		if ( $this->use_mysqli && $this->result instanceof mysqli_result ) {
1475
			mysqli_free_result( $this->result );
1476
			$this->result = null;
1477
1478
			// Sanity check before using the handle
1479
			if ( empty( $this->dbh ) || !( $this->dbh instanceof mysqli ) ) {
1480
				return;
1481
			}
1482
1483
			// Clear out any results from a multi-query
1484
			while ( mysqli_more_results( $this->dbh ) ) {
1485
				mysqli_next_result( $this->dbh );
1486
			}
1487
		} elseif ( is_resource( $this->result ) ) {
1488
			mysql_free_result( $this->result );
1489
		}
1490
	}
1491
1492
	/**
1493
	 * Connect to and select database.
1494
	 *
1495
	 * If $allow_bail is false, the lack of database connection will need
1496
	 * to be handled manually.
1497
	 *
1498
	 * @since 3.0.0
1499
	 * @since 3.9.0 $allow_bail parameter added.
1500
	 *
1501
	 * @param bool $allow_bail Optional. Allows the function to bail. Default true.
1502
	 * @return bool True with a successful connection, false on failure.
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1503
	 */
1504
	public function db_connect( $allow_bail = true ) {
1505
		$this->is_mysql = true;
1506
1507
		/*
1508
		 * Deprecated in 3.9+ when using MySQLi. No equivalent
1509
		 * $new_link parameter exists for mysqli_* functions.
1510
		 */
1511
		$new_link = defined( 'MYSQL_NEW_LINK' ) ? MYSQL_NEW_LINK : true;
1512
		$client_flags = defined( 'MYSQL_CLIENT_FLAGS' ) ? MYSQL_CLIENT_FLAGS : 0;
1513
1514
		if ( $this->use_mysqli ) {
1515
			$this->dbh = mysqli_init();
0 ignored issues
show
Documentation Bug introduced by
It seems like mysqli_init() of type object<mysql> is incompatible with the declared type string of property $dbh.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
1516
1517
			// mysqli_real_connect doesn't support the host param including a port or socket
1518
			// like mysql_connect does. This duplicates how mysql_connect detects a port and/or socket file.
1519
			$port = null;
1520
			$socket = null;
1521
			$host = $this->dbhost;
1522
			$port_or_socket = strstr( $host, ':' );
1523
			if ( ! empty( $port_or_socket ) ) {
1524
				$host = substr( $host, 0, strpos( $host, ':' ) );
1525
				$port_or_socket = substr( $port_or_socket, 1 );
1526
				if ( 0 !== strpos( $port_or_socket, '/' ) ) {
1527
					$port = intval( $port_or_socket );
1528
					$maybe_socket = strstr( $port_or_socket, ':' );
1529
					if ( ! empty( $maybe_socket ) ) {
1530
						$socket = substr( $maybe_socket, 1 );
1531
					}
1532
				} else {
1533
					$socket = $port_or_socket;
1534
				}
1535
			}
1536
1537
			if ( WP_DEBUG ) {
1538
				mysqli_real_connect( $this->dbh, $host, $this->dbuser, $this->dbpassword, null, $port, $socket, $client_flags );
1539
			} else {
1540
				@mysqli_real_connect( $this->dbh, $host, $this->dbuser, $this->dbpassword, null, $port, $socket, $client_flags );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1541
			}
1542
1543
			if ( $this->dbh->connect_errno ) {
1544
				$this->dbh = null;
1545
1546
				/* It's possible ext/mysqli is misconfigured. Fall back to ext/mysql if:
1547
		 		 *  - We haven't previously connected, and
1548
		 		 *  - WP_USE_EXT_MYSQL isn't set to false, and
1549
		 		 *  - ext/mysql is loaded.
1550
		 		 */
1551
				$attempt_fallback = true;
1552
1553
				if ( $this->has_connected ) {
1554
					$attempt_fallback = false;
1555
				} elseif ( defined( 'WP_USE_EXT_MYSQL' ) && ! WP_USE_EXT_MYSQL ) {
1556
					$attempt_fallback = false;
1557
				} elseif ( ! function_exists( 'mysql_connect' ) ) {
1558
					$attempt_fallback = false;
1559
				}
1560
1561
				if ( $attempt_fallback ) {
1562
					$this->use_mysqli = false;
1563
					return $this->db_connect( $allow_bail );
1564
				}
1565
			}
1566
		} else {
1567
			if ( WP_DEBUG ) {
1568
				$this->dbh = mysql_connect( $this->dbhost, $this->dbuser, $this->dbpassword, $new_link, $client_flags );
0 ignored issues
show
Documentation Bug introduced by
It seems like mysql_connect($this->dbh...ew_link, $client_flags) of type resource is incompatible with the declared type string of property $dbh.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
1569
			} else {
1570
				$this->dbh = @mysql_connect( $this->dbhost, $this->dbuser, $this->dbpassword, $new_link, $client_flags );
1571
			}
1572
		}
1573
1574
		if ( ! $this->dbh && $allow_bail ) {
1575
			wp_load_translations_early();
1576
1577
			// Load custom DB error template, if present.
1578
			if ( file_exists( WP_CONTENT_DIR . '/db-error.php' ) ) {
1579
				require_once( WP_CONTENT_DIR . '/db-error.php' );
1580
				die();
0 ignored issues
show
Coding Style Compatibility introduced by
The method db_connect() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1581
			}
1582
1583
			$message = '<h1>' . __( 'Error establishing a database connection' ) . "</h1>\n";
1584
1585
			$message .= '<p>' . sprintf(
1586
				/* translators: 1: wp-config.php. 2: database host */
1587
				__( 'This either means that the username and password information in your %1$s file is incorrect or we can&#8217;t contact the database server at %2$s. This could mean your host&#8217;s database server is down.' ),
1588
				'<code>wp-config.php</code>',
1589
				'<code>' . htmlspecialchars( $this->dbhost, ENT_QUOTES ) . '</code>'
1590
			) . "</p>\n";
1591
1592
			$message .= "<ul>\n";
1593
			$message .= '<li>' . __( 'Are you sure you have the correct username and password?' ) . "</li>\n";
1594
			$message .= '<li>' . __( 'Are you sure that you have typed the correct hostname?' ) . "</li>\n";
1595
			$message .= '<li>' . __( 'Are you sure that the database server is running?' ) . "</li>\n";
1596
			$message .= "</ul>\n";
1597
1598
			$message .= '<p>' . sprintf(
1599
				/* translators: %s: support forums URL */
1600
				__( 'If you&#8217;re unsure what these terms mean you should probably contact your host. If you still need help you can always visit the <a href="%s">WordPress Support Forums</a>.' ),
1601
				__( 'https://wordpress.org/support/' )
1602
			) . "</p>\n";
1603
1604
			$this->bail( $message, 'db_connect_fail' );
1605
1606
			return false;
1607
		} elseif ( $this->dbh ) {
1608
			if ( ! $this->has_connected ) {
1609
				$this->init_charset();
1610
			}
1611
1612
			$this->has_connected = true;
1613
1614
			$this->set_charset( $this->dbh );
1615
1616
			$this->ready = true;
1617
			$this->set_sql_mode();
1618
			$this->select( $this->dbname, $this->dbh );
1619
1620
			return true;
1621
		}
1622
1623
		return false;
1624
	}
1625
1626
	/**
1627
	 * Checks that the connection to the database is still up. If not, try to reconnect.
1628
	 *
1629
	 * If this function is unable to reconnect, it will forcibly die, or if after the
1630
	 * the {@see 'template_redirect'} hook has been fired, return false instead.
1631
	 *
1632
	 * If $allow_bail is false, the lack of database connection will need
1633
	 * to be handled manually.
1634
	 *
1635
	 * @since 3.9.0
1636
	 *
1637
	 * @param bool $allow_bail Optional. Allows the function to bail. Default true.
1638
	 * @return bool|void True if the connection is up.
1639
	 */
1640
	public function check_connection( $allow_bail = true ) {
1641
		if ( $this->use_mysqli ) {
1642
			if ( ! empty( $this->dbh ) && mysqli_ping( $this->dbh ) ) {
1643
				return true;
1644
			}
1645
		} else {
1646
			if ( ! empty( $this->dbh ) && mysql_ping( $this->dbh ) ) {
1647
				return true;
1648
			}
1649
		}
1650
1651
		$error_reporting = false;
1652
1653
		// Disable warnings, as we don't want to see a multitude of "unable to connect" messages
1654
		if ( WP_DEBUG ) {
1655
			$error_reporting = error_reporting();
1656
			error_reporting( $error_reporting & ~E_WARNING );
1657
		}
1658
1659
		for ( $tries = 1; $tries <= $this->reconnect_retries; $tries++ ) {
1660
			// On the last try, re-enable warnings. We want to see a single instance of the
1661
			// "unable to connect" message on the bail() screen, if it appears.
1662
			if ( $this->reconnect_retries === $tries && WP_DEBUG ) {
1663
				error_reporting( $error_reporting );
1664
			}
1665
1666
			if ( $this->db_connect( false ) ) {
1667
				if ( $error_reporting ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $error_reporting of type integer|false is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1668
					error_reporting( $error_reporting );
1669
				}
1670
1671
				return true;
1672
			}
1673
1674
			sleep( 1 );
1675
		}
1676
1677
		// If template_redirect has already happened, it's too late for wp_die()/dead_db().
1678
		// Let's just return and hope for the best.
1679
		if ( did_action( 'template_redirect' ) ) {
1680
			return false;
1681
		}
1682
1683
		if ( ! $allow_bail ) {
1684
			return false;
1685
		}
1686
1687
		wp_load_translations_early();
1688
1689
		$message = '<h1>' . __( 'Error reconnecting to the database' ) . "</h1>\n";
1690
1691
		$message .= '<p>' . sprintf(
1692
			/* translators: %s: database host */
1693
			__( 'This means that we lost contact with the database server at %s. This could mean your host&#8217;s database server is down.' ),
1694
			'<code>' . htmlspecialchars( $this->dbhost, ENT_QUOTES ) . '</code>'
1695
		) . "</p>\n";
1696
1697
		$message .= "<ul>\n";
1698
		$message .= '<li>' . __( 'Are you sure that the database server is running?' ) . "</li>\n";
1699
		$message .= '<li>' . __( 'Are you sure that the database server is not under particularly heavy load?' ) . "</li>\n";
1700
		$message .= "</ul>\n";
1701
1702
		$message .= '<p>' . sprintf(
1703
			/* translators: %s: support forums URL */
1704
			__( 'If you&#8217;re unsure what these terms mean you should probably contact your host. If you still need help you can always visit the <a href="%s">WordPress Support Forums</a>.' ),
1705
			__( 'https://wordpress.org/support/' )
1706
		) . "</p>\n";
1707
1708
		// We weren't able to reconnect, so we better bail.
1709
		$this->bail( $message, 'db_connect_fail' );
1710
1711
		// Call dead_db() if bail didn't die, because this database is no more. It has ceased to be (at least temporarily).
1712
		dead_db();
1713
	}
1714
1715
	/**
1716
	 * Perform a MySQL database query, using current database connection.
1717
	 *
1718
	 * More information can be found on the codex page.
1719
	 *
1720
	 * @since 0.71
1721
	 *
1722
	 * @param string $query Database query
1723
	 * @return int|false Number of rows affected/selected or false on error
1724
	 */
1725
	public function query( $query ) {
1726
		if ( ! $this->ready ) {
1727
			$this->check_current_query = true;
1728
			return false;
1729
		}
1730
1731
		/**
1732
		 * Filters the database query.
1733
		 *
1734
		 * Some queries are made before the plugins have been loaded,
1735
		 * and thus cannot be filtered with this method.
1736
		 *
1737
		 * @since 2.1.0
1738
		 *
1739
		 * @param string $query Database query.
1740
		 */
1741
		$query = apply_filters( 'query', $query );
1742
1743
		$this->flush();
1744
1745
		// Log how the function was called
1746
		$this->func_call = "\$db->query(\"$query\")";
1747
1748
		// If we're writing to the database, make sure the query will write safely.
1749
		if ( $this->check_current_query && ! $this->check_ascii( $query ) ) {
1750
			$stripped_query = $this->strip_invalid_text_from_query( $query );
1751
			// strip_invalid_text_from_query() can perform queries, so we need
1752
			// to flush again, just to make sure everything is clear.
1753
			$this->flush();
1754
			if ( $stripped_query !== $query ) {
1755
				$this->insert_id = 0;
1756
				return false;
1757
			}
1758
		}
1759
1760
		$this->check_current_query = true;
1761
1762
		// Keep track of the last query for debug.
1763
		$this->last_query = $query;
0 ignored issues
show
Documentation Bug introduced by
It seems like $query of type * is incompatible with the declared type array of property $last_query.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
1764
1765
		$this->_do_query( $query );
1766
1767
		// MySQL server has gone away, try to reconnect.
1768
		$mysql_errno = 0;
1769
		if ( ! empty( $this->dbh ) ) {
1770
			if ( $this->use_mysqli ) {
1771
				if ( $this->dbh instanceof mysqli ) {
1772
					$mysql_errno = mysqli_errno( $this->dbh );
1773
				} else {
1774
					// $dbh is defined, but isn't a real connection.
1775
					// Something has gone horribly wrong, let's try a reconnect.
1776
					$mysql_errno = 2006;
1777
				}
1778
			} else {
1779
				if ( is_resource( $this->dbh ) ) {
1780
					$mysql_errno = mysql_errno( $this->dbh );
1781
				} else {
1782
					$mysql_errno = 2006;
1783
				}
1784
			}
1785
		}
1786
1787
		if ( empty( $this->dbh ) || 2006 == $mysql_errno ) {
1788
			if ( $this->check_connection() ) {
1789
				$this->_do_query( $query );
1790
			} else {
1791
				$this->insert_id = 0;
1792
				return false;
1793
			}
1794
		}
1795
1796
		// If there is an error then take note of it.
1797
		if ( $this->use_mysqli ) {
1798
			if ( $this->dbh instanceof mysqli ) {
1799
				$this->last_error = mysqli_error( $this->dbh );
1800
			} else {
1801
				$this->last_error = __( 'Unable to retrieve the error message from MySQL' );
1802
			}
1803
		} else {
1804
			if ( is_resource( $this->dbh ) ) {
1805
				$this->last_error = mysql_error( $this->dbh );
1806
			} else {
1807
				$this->last_error = __( 'Unable to retrieve the error message from MySQL' );
1808
			}
1809
		}
1810
1811
		if ( $this->last_error ) {
1812
			// Clear insert_id on a subsequent failed insert.
1813
			if ( $this->insert_id && preg_match( '/^\s*(insert|replace)\s/i', $query ) )
1814
				$this->insert_id = 0;
1815
1816
			$this->print_error();
1817
			return false;
1818
		}
1819
1820
		if ( preg_match( '/^\s*(create|alter|truncate|drop)\s/i', $query ) ) {
1821
			$return_val = $this->result;
1822
		} elseif ( preg_match( '/^\s*(insert|delete|update|replace)\s/i', $query ) ) {
1823
			if ( $this->use_mysqli ) {
1824
				$this->rows_affected = mysqli_affected_rows( $this->dbh );
1825
			} else {
1826
				$this->rows_affected = mysql_affected_rows( $this->dbh );
1827
			}
1828
			// Take note of the insert_id
1829
			if ( preg_match( '/^\s*(insert|replace)\s/i', $query ) ) {
1830
				if ( $this->use_mysqli ) {
1831
					$this->insert_id = mysqli_insert_id( $this->dbh );
1832
				} else {
1833
					$this->insert_id = mysql_insert_id( $this->dbh );
1834
				}
1835
			}
1836
			// Return number of rows affected
1837
			$return_val = $this->rows_affected;
1838
		} else {
1839
			$num_rows = 0;
1840
			if ( $this->use_mysqli && $this->result instanceof mysqli_result ) {
1841
				while ( $row = mysqli_fetch_object( $this->result ) ) {
1842
					$this->last_result[$num_rows] = $row;
1843
					$num_rows++;
1844
				}
1845
			} elseif ( is_resource( $this->result ) ) {
1846
				while ( $row = mysql_fetch_object( $this->result ) ) {
1847
					$this->last_result[$num_rows] = $row;
1848
					$num_rows++;
1849
				}
1850
			}
1851
1852
			// Log number of rows the query returned
1853
			// and return number of rows selected
1854
			$this->num_rows = $num_rows;
1855
			$return_val     = $num_rows;
1856
		}
1857
1858
		return $return_val;
1859
	}
1860
1861
	/**
1862
	 * Internal function to perform the mysql_query() call.
1863
	 *
1864
	 * @since 3.9.0
1865
	 *
1866
	 * @access private
1867
	 * @see wpdb::query()
1868
	 *
1869
	 * @param string $query The query to run.
1870
	 */
1871
	private function _do_query( $query ) {
1872
		if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
1873
			$this->timer_start();
1874
		}
1875
1876
		if ( ! empty( $this->dbh ) && $this->use_mysqli ) {
1877
			$this->result = mysqli_query( $this->dbh, $query );
1878
		} elseif ( ! empty( $this->dbh ) ) {
1879
			$this->result = mysql_query( $query, $this->dbh );
1880
		}
1881
		$this->num_queries++;
1882
1883
		if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
1884
			$this->queries[] = array( $query, $this->timer_stop(), $this->get_caller() );
1885
		}
1886
	}
1887
1888
	/**
1889
	 * Insert a row into a table.
1890
	 *
1891
	 *     wpdb::insert( 'table', array( 'column' => 'foo', 'field' => 'bar' ) )
1892
	 *     wpdb::insert( 'table', array( 'column' => 'foo', 'field' => 1337 ), array( '%s', '%d' ) )
1893
	 *
1894
	 * @since 2.5.0
1895
	 * @see wpdb::prepare()
1896
	 * @see wpdb::$field_types
1897
	 * @see wp_set_wpdb_vars()
1898
	 *
1899
	 * @param string       $table  Table name
1900
	 * @param array        $data   Data to insert (in column => value pairs).
1901
	 *                             Both $data columns and $data values should be "raw" (neither should be SQL escaped).
1902
	 *                             Sending a null value will cause the column to be set to NULL - the corresponding format is ignored in this case.
1903
	 * @param array|string $format Optional. An array of formats to be mapped to each of the value in $data.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $format not be array|string|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...
1904
	 *                             If string, that format will be used for all of the values in $data.
1905
	 *                             A format is one of '%d', '%f', '%s' (integer, float, string).
1906
	 *                             If omitted, all values in $data will be treated as strings unless otherwise specified in wpdb::$field_types.
1907
	 * @return int|false The number of rows inserted, or false on error.
1908
	 */
1909
	public function insert( $table, $data, $format = null ) {
1910
		return $this->_insert_replace_helper( $table, $data, $format, 'INSERT' );
1911
	}
1912
1913
	/**
1914
	 * Replace a row into a table.
1915
	 *
1916
	 *     wpdb::replace( 'table', array( 'column' => 'foo', 'field' => 'bar' ) )
1917
	 *     wpdb::replace( 'table', array( 'column' => 'foo', 'field' => 1337 ), array( '%s', '%d' ) )
1918
	 *
1919
	 * @since 3.0.0
1920
	 * @see wpdb::prepare()
1921
	 * @see wpdb::$field_types
1922
	 * @see wp_set_wpdb_vars()
1923
	 *
1924
	 * @param string       $table  Table name
1925
	 * @param array        $data   Data to insert (in column => value pairs).
1926
	 *                             Both $data columns and $data values should be "raw" (neither should be SQL escaped).
1927
	 *                             Sending a null value will cause the column to be set to NULL - the corresponding format is ignored in this case.
1928
	 * @param array|string $format Optional. An array of formats to be mapped to each of the value in $data.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $format not be array|string|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...
1929
	 *                             If string, that format will be used for all of the values in $data.
1930
	 *                             A format is one of '%d', '%f', '%s' (integer, float, string).
1931
	 *                             If omitted, all values in $data will be treated as strings unless otherwise specified in wpdb::$field_types.
1932
	 * @return int|false The number of rows affected, or false on error.
1933
	 */
1934
	public function replace( $table, $data, $format = null ) {
1935
		return $this->_insert_replace_helper( $table, $data, $format, 'REPLACE' );
1936
	}
1937
1938
	/**
1939
	 * Helper function for insert and replace.
1940
	 *
1941
	 * Runs an insert or replace query based on $type argument.
1942
	 *
1943
	 * @access private
1944
	 * @since 3.0.0
1945
	 * @see wpdb::prepare()
1946
	 * @see wpdb::$field_types
1947
	 * @see wp_set_wpdb_vars()
1948
	 *
1949
	 * @param string       $table  Table name
1950
	 * @param array        $data   Data to insert (in column => value pairs).
1951
	 *                             Both $data columns and $data values should be "raw" (neither should be SQL escaped).
1952
	 *                             Sending a null value will cause the column to be set to NULL - the corresponding format is ignored in this case.
1953
	 * @param array|string $format Optional. An array of formats to be mapped to each of the value in $data.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $format not be array|string|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...
1954
	 *                             If string, that format will be used for all of the values in $data.
1955
	 *                             A format is one of '%d', '%f', '%s' (integer, float, string).
1956
	 *                             If omitted, all values in $data will be treated as strings unless otherwise specified in wpdb::$field_types.
1957
	 * @param string $type         Optional. What type of operation is this? INSERT or REPLACE. Defaults to INSERT.
1958
	 * @return int|false The number of rows affected, or false on error.
1959
	 */
1960
	function _insert_replace_helper( $table, $data, $format = null, $type = 'INSERT' ) {
1961
		$this->insert_id = 0;
1962
1963
		if ( ! in_array( strtoupper( $type ), array( 'REPLACE', 'INSERT' ) ) ) {
1964
			return false;
1965
		}
1966
1967
		$data = $this->process_fields( $table, $data, $format );
1968
		if ( false === $data ) {
1969
			return false;
1970
		}
1971
1972
		$formats = $values = array();
1973
		foreach ( $data as $value ) {
1974
			if ( is_null( $value['value'] ) ) {
1975
				$formats[] = 'NULL';
1976
				continue;
1977
			}
1978
1979
			$formats[] = $value['format'];
1980
			$values[]  = $value['value'];
1981
		}
1982
1983
		$fields  = '`' . implode( '`, `', array_keys( $data ) ) . '`';
1984
		$formats = implode( ', ', $formats );
1985
1986
		$sql = "$type INTO `$table` ($fields) VALUES ($formats)";
1987
1988
		$this->check_current_query = false;
1989
		return $this->query( $this->prepare( $sql, $values ) );
1990
	}
1991
1992
	/**
1993
	 * Update a row in the table
1994
	 *
1995
	 *     wpdb::update( 'table', array( 'column' => 'foo', 'field' => 'bar' ), array( 'ID' => 1 ) )
1996
	 *     wpdb::update( 'table', array( 'column' => 'foo', 'field' => 1337 ), array( 'ID' => 1 ), array( '%s', '%d' ), array( '%d' ) )
1997
	 *
1998
	 * @since 2.5.0
1999
	 * @see wpdb::prepare()
2000
	 * @see wpdb::$field_types
2001
	 * @see wp_set_wpdb_vars()
2002
	 *
2003
	 * @param string       $table        Table name
2004
	 * @param array        $data         Data to update (in column => value pairs).
2005
	 *                                   Both $data columns and $data values should be "raw" (neither should be SQL escaped).
2006
	 *                                   Sending a null value will cause the column to be set to NULL - the corresponding
2007
	 *                                   format is ignored in this case.
2008
	 * @param array        $where        A named array of WHERE clauses (in column => value pairs).
2009
	 *                                   Multiple clauses will be joined with ANDs.
2010
	 *                                   Both $where columns and $where values should be "raw".
2011
	 *                                   Sending a null value will create an IS NULL comparison - the corresponding format will be ignored in this case.
2012
	 * @param array|string $format       Optional. An array of formats to be mapped to each of the values in $data.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $format not be array|string|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...
2013
	 *                                   If string, that format will be used for all of the values in $data.
2014
	 *                                   A format is one of '%d', '%f', '%s' (integer, float, string).
2015
	 *                                   If omitted, all values in $data will be treated as strings unless otherwise specified in wpdb::$field_types.
2016
	 * @param array|string $where_format Optional. An array of formats to be mapped to each of the values in $where.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $where_format not be array|string|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...
2017
	 *                                   If string, that format will be used for all of the items in $where.
2018
	 *                                   A format is one of '%d', '%f', '%s' (integer, float, string).
2019
	 *                                   If omitted, all values in $where will be treated as strings.
2020
	 * @return int|false The number of rows updated, or false on error.
2021
	 */
2022
	public function update( $table, $data, $where, $format = null, $where_format = null ) {
2023
		if ( ! is_array( $data ) || ! is_array( $where ) ) {
2024
			return false;
2025
		}
2026
2027
		$data = $this->process_fields( $table, $data, $format );
2028
		if ( false === $data ) {
2029
			return false;
2030
		}
2031
		$where = $this->process_fields( $table, $where, $where_format );
2032
		if ( false === $where ) {
2033
			return false;
2034
		}
2035
2036
		$fields = $conditions = $values = array();
2037 View Code Duplication
		foreach ( $data as $field => $value ) {
2038
			if ( is_null( $value['value'] ) ) {
2039
				$fields[] = "`$field` = NULL";
2040
				continue;
2041
			}
2042
2043
			$fields[] = "`$field` = " . $value['format'];
2044
			$values[] = $value['value'];
2045
		}
2046 View Code Duplication
		foreach ( $where as $field => $value ) {
2047
			if ( is_null( $value['value'] ) ) {
2048
				$conditions[] = "`$field` IS NULL";
2049
				continue;
2050
			}
2051
2052
			$conditions[] = "`$field` = " . $value['format'];
2053
			$values[] = $value['value'];
2054
		}
2055
2056
		$fields = implode( ', ', $fields );
2057
		$conditions = implode( ' AND ', $conditions );
2058
2059
		$sql = "UPDATE `$table` SET $fields WHERE $conditions";
2060
2061
		$this->check_current_query = false;
2062
		return $this->query( $this->prepare( $sql, $values ) );
2063
	}
2064
2065
	/**
2066
	 * Delete a row in the table
2067
	 *
2068
	 *     wpdb::delete( 'table', array( 'ID' => 1 ) )
2069
	 *     wpdb::delete( 'table', array( 'ID' => 1 ), array( '%d' ) )
2070
	 *
2071
	 * @since 3.4.0
2072
	 * @see wpdb::prepare()
2073
	 * @see wpdb::$field_types
2074
	 * @see wp_set_wpdb_vars()
2075
	 *
2076
	 * @param string       $table        Table name
2077
	 * @param array        $where        A named array of WHERE clauses (in column => value pairs).
2078
	 *                                   Multiple clauses will be joined with ANDs.
2079
	 *                                   Both $where columns and $where values should be "raw".
2080
	 *                                   Sending a null value will create an IS NULL comparison - the corresponding format will be ignored in this case.
2081
	 * @param array|string $where_format Optional. An array of formats to be mapped to each of the values in $where.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $where_format not be array|string|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...
2082
	 *                                   If string, that format will be used for all of the items in $where.
2083
	 *                                   A format is one of '%d', '%f', '%s' (integer, float, string).
2084
	 *                                   If omitted, all values in $where will be treated as strings unless otherwise specified in wpdb::$field_types.
2085
	 * @return int|false The number of rows updated, or false on error.
2086
	 */
2087
	public function delete( $table, $where, $where_format = null ) {
2088
		if ( ! is_array( $where ) ) {
2089
			return false;
2090
		}
2091
2092
		$where = $this->process_fields( $table, $where, $where_format );
2093
		if ( false === $where ) {
2094
			return false;
2095
		}
2096
2097
		$conditions = $values = array();
2098 View Code Duplication
		foreach ( $where as $field => $value ) {
2099
			if ( is_null( $value['value'] ) ) {
2100
				$conditions[] = "`$field` IS NULL";
2101
				continue;
2102
			}
2103
2104
			$conditions[] = "`$field` = " . $value['format'];
2105
			$values[] = $value['value'];
2106
		}
2107
2108
		$conditions = implode( ' AND ', $conditions );
2109
2110
		$sql = "DELETE FROM `$table` WHERE $conditions";
2111
2112
		$this->check_current_query = false;
2113
		return $this->query( $this->prepare( $sql, $values ) );
2114
	}
2115
2116
	/**
2117
	 * Processes arrays of field/value pairs and field formats.
2118
	 *
2119
	 * This is a helper method for wpdb's CRUD methods, which take field/value
2120
	 * pairs for inserts, updates, and where clauses. This method first pairs
2121
	 * each value with a format. Then it determines the charset of that field,
2122
	 * using that to determine if any invalid text would be stripped. If text is
2123
	 * stripped, then field processing is rejected and the query fails.
2124
	 *
2125
	 * @since 4.2.0
2126
	 * @access protected
2127
	 *
2128
	 * @param string $table  Table name.
2129
	 * @param array  $data   Field/value pair.
2130
	 * @param mixed  $format Format for each field.
2131
	 * @return array|false Returns an array of fields that contain paired values
2132
	 *                    and formats. Returns false for invalid values.
2133
	 */
2134
	protected function process_fields( $table, $data, $format ) {
2135
		$data = $this->process_field_formats( $data, $format );
2136
		if ( false === $data ) {
2137
			return false;
2138
		}
2139
2140
		$data = $this->process_field_charsets( $data, $table );
2141
		if ( false === $data ) {
2142
			return false;
2143
		}
2144
2145
		$data = $this->process_field_lengths( $data, $table );
2146
		if ( false === $data ) {
2147
			return false;
2148
		}
2149
2150
		$converted_data = $this->strip_invalid_text( $data );
2151
2152
		if ( $data !== $converted_data ) {
2153
			return false;
2154
		}
2155
2156
		return $data;
2157
	}
2158
2159
	/**
2160
	 * Prepares arrays of value/format pairs as passed to wpdb CRUD methods.
2161
	 *
2162
	 * @since 4.2.0
2163
	 * @access protected
2164
	 *
2165
	 * @param array $data   Array of fields to values.
2166
	 * @param mixed $format Formats to be mapped to the values in $data.
2167
	 * @return array Array, keyed by field names with values being an array
2168
	 *               of 'value' and 'format' keys.
2169
	 */
2170
	protected function process_field_formats( $data, $format ) {
2171
		$formats = $original_formats = (array) $format;
2172
2173
		foreach ( $data as $field => $value ) {
2174
			$value = array(
2175
				'value'  => $value,
2176
				'format' => '%s',
2177
			);
2178
2179
			if ( ! empty( $format ) ) {
2180
				$value['format'] = array_shift( $formats );
2181
				if ( ! $value['format'] ) {
2182
					$value['format'] = reset( $original_formats );
2183
				}
2184
			} elseif ( isset( $this->field_types[ $field ] ) ) {
2185
				$value['format'] = $this->field_types[ $field ];
2186
			}
2187
2188
			$data[ $field ] = $value;
2189
		}
2190
2191
		return $data;
2192
	}
2193
2194
	/**
2195
	 * Adds field charsets to field/value/format arrays generated by
2196
	 * the wpdb::process_field_formats() method.
2197
	 *
2198
	 * @since 4.2.0
2199
	 * @access protected
2200
	 *
2201
	 * @param array  $data  As it comes from the wpdb::process_field_formats() method.
2202
	 * @param string $table Table name.
2203
	 * @return array|false The same array as $data with additional 'charset' keys.
2204
	 */
2205 View Code Duplication
	protected function process_field_charsets( $data, $table ) {
2206
		foreach ( $data as $field => $value ) {
2207
			if ( '%d' === $value['format'] || '%f' === $value['format'] ) {
2208
				/*
2209
				 * We can skip this field if we know it isn't a string.
2210
				 * This checks %d/%f versus ! %s because its sprintf() could take more.
2211
				 */
2212
				$value['charset'] = false;
2213
			} else {
2214
				$value['charset'] = $this->get_col_charset( $table, $field );
2215
				if ( is_wp_error( $value['charset'] ) ) {
2216
					return false;
2217
				}
2218
			}
2219
2220
			$data[ $field ] = $value;
2221
		}
2222
2223
		return $data;
2224
	}
2225
2226
	/**
2227
	 * For string fields, record the maximum string length that field can safely save.
2228
	 *
2229
	 * @since 4.2.1
2230
	 * @access protected
2231
	 *
2232
	 * @param array  $data  As it comes from the wpdb::process_field_charsets() method.
2233
	 * @param string $table Table name.
2234
	 * @return array|false The same array as $data with additional 'length' keys, or false if
2235
	 *                     any of the values were too long for their corresponding field.
2236
	 */
2237 View Code Duplication
	protected function process_field_lengths( $data, $table ) {
2238
		foreach ( $data as $field => $value ) {
2239
			if ( '%d' === $value['format'] || '%f' === $value['format'] ) {
2240
				/*
2241
				 * We can skip this field if we know it isn't a string.
2242
				 * This checks %d/%f versus ! %s because its sprintf() could take more.
2243
				 */
2244
				$value['length'] = false;
2245
			} else {
2246
				$value['length'] = $this->get_col_length( $table, $field );
2247
				if ( is_wp_error( $value['length'] ) ) {
2248
					return false;
2249
				}
2250
			}
2251
2252
			$data[ $field ] = $value;
2253
		}
2254
2255
		return $data;
2256
	}
2257
2258
	/**
2259
	 * Retrieve one variable from the database.
2260
	 *
2261
	 * Executes a SQL query and returns the value from the SQL result.
2262
	 * If the SQL result contains more than one column and/or more than one row, this function returns the value in the column and row specified.
2263
	 * If $query is null, this function returns the value in the specified column and row from the previous SQL result.
2264
	 *
2265
	 * @since 0.71
2266
	 *
2267
	 * @param string|null $query Optional. SQL query. Defaults to null, use the result from the previous query.
2268
	 * @param int         $x     Optional. Column of value to return. Indexed from 0.
2269
	 * @param int         $y     Optional. Row of value to return. Indexed from 0.
2270
	 * @return string|null Database query result (as string), or null on failure
2271
	 */
2272
	public function get_var( $query = null, $x = 0, $y = 0 ) {
2273
		$this->func_call = "\$db->get_var(\"$query\", $x, $y)";
2274
2275
		if ( $this->check_current_query && $this->check_safe_collation( $query ) ) {
2276
			$this->check_current_query = false;
2277
		}
2278
2279
		if ( $query ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $query of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2280
			$this->query( $query );
2281
		}
2282
2283
		// Extract var out of cached results based x,y vals
2284
		if ( !empty( $this->last_result[$y] ) ) {
2285
			$values = array_values( get_object_vars( $this->last_result[$y] ) );
2286
		}
2287
2288
		// If there is a value return it else return null
2289
		return ( isset( $values[$x] ) && $values[$x] !== '' ) ? $values[$x] : null;
2290
	}
2291
2292
	/**
2293
	 * Retrieve one row from the database.
2294
	 *
2295
	 * Executes a SQL query and returns the row from the SQL result.
2296
	 *
2297
	 * @since 0.71
2298
	 *
2299
	 * @param string|null $query  SQL query.
2300
	 * @param string      $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
2301
	 *                            an stdClass object, an associative array, or a numeric array, respectively. Default OBJECT.
2302
	 * @param int         $y      Optional. Row to return. Indexed from 0.
2303
	 * @return array|object|null|void Database query result in format specified by $output or null on failure
2304
	 */
2305
	public function get_row( $query = null, $output = OBJECT, $y = 0 ) {
2306
		$this->func_call = "\$db->get_row(\"$query\",$output,$y)";
2307
2308
		if ( $this->check_current_query && $this->check_safe_collation( $query ) ) {
2309
			$this->check_current_query = false;
2310
		}
2311
2312
		if ( $query ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $query of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2313
			$this->query( $query );
2314
		} else {
2315
			return null;
2316
		}
2317
2318
		if ( !isset( $this->last_result[$y] ) )
2319
			return null;
2320
2321
		if ( $output == OBJECT ) {
2322
			return $this->last_result[$y] ? $this->last_result[$y] : null;
2323
		} elseif ( $output == ARRAY_A ) {
2324
			return $this->last_result[$y] ? get_object_vars( $this->last_result[$y] ) : null;
2325
		} elseif ( $output == ARRAY_N ) {
2326
			return $this->last_result[$y] ? array_values( get_object_vars( $this->last_result[$y] ) ) : null;
2327
		} elseif ( strtoupper( $output ) === OBJECT ) {
2328
			// Back compat for OBJECT being previously case insensitive.
2329
			return $this->last_result[$y] ? $this->last_result[$y] : null;
2330
		} else {
2331
			$this->print_error( " \$db->get_row(string query, output type, int offset) -- Output type must be one of: OBJECT, ARRAY_A, ARRAY_N" );
2332
		}
2333
	}
2334
2335
	/**
2336
	 * Retrieve one column from the database.
2337
	 *
2338
	 * Executes a SQL query and returns the column from the SQL result.
2339
	 * If the SQL result contains more than one column, this function returns the column specified.
2340
	 * If $query is null, this function returns the specified column from the previous SQL result.
2341
	 *
2342
	 * @since 0.71
2343
	 *
2344
	 * @param string|null $query Optional. SQL query. Defaults to previous query.
2345
	 * @param int         $x     Optional. Column to return. Indexed from 0.
2346
	 * @return array Database query result. Array indexed from 0 by SQL result row number.
2347
	 */
2348
	public function get_col( $query = null , $x = 0 ) {
2349
		if ( $this->check_current_query && $this->check_safe_collation( $query ) ) {
2350
			$this->check_current_query = false;
2351
		}
2352
2353
		if ( $query ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $query of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2354
			$this->query( $query );
2355
		}
2356
2357
		$new_array = array();
2358
		// Extract the column values
2359
		for ( $i = 0, $j = count( $this->last_result ); $i < $j; $i++ ) {
2360
			$new_array[$i] = $this->get_var( null, $x, $i );
2361
		}
2362
		return $new_array;
2363
	}
2364
2365
	/**
2366
	 * Retrieve an entire SQL result set from the database (i.e., many rows)
2367
	 *
2368
	 * Executes a SQL query and returns the entire SQL result.
2369
	 *
2370
	 * @since 0.71
2371
	 *
2372
	 * @param string $query  SQL query.
2373
	 * @param string $output Optional. Any of ARRAY_A | ARRAY_N | OBJECT | OBJECT_K constants.
2374
	 *                       With one of the first three, return an array of rows indexed from 0 by SQL result row number.
2375
	 *                       Each row is an associative array (column => value, ...), a numerically indexed array (0 => value, ...), or an object. ( ->column = value ), respectively.
2376
	 *                       With OBJECT_K, return an associative array of row objects keyed by the value of each row's first column's value.
2377
	 *                       Duplicate keys are discarded.
2378
	 * @return array|object|null Database query results
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use null|array.

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...
2379
	 */
2380
	public function get_results( $query = null, $output = OBJECT ) {
2381
		$this->func_call = "\$db->get_results(\"$query\", $output)";
2382
2383
		if ( $this->check_current_query && $this->check_safe_collation( $query ) ) {
2384
			$this->check_current_query = false;
2385
		}
2386
2387
		if ( $query ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $query of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2388
			$this->query( $query );
2389
		} else {
2390
			return null;
2391
		}
2392
2393
		$new_array = array();
2394
		if ( $output == OBJECT ) {
2395
			// Return an integer-keyed array of row objects
2396
			return $this->last_result;
2397
		} elseif ( $output == OBJECT_K ) {
2398
			// Return an array of row objects with keys from column 1
2399
			// (Duplicates are discarded)
2400
			foreach ( $this->last_result as $row ) {
0 ignored issues
show
Bug introduced by
The expression $this->last_result of type array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
2401
				$var_by_ref = get_object_vars( $row );
2402
				$key = array_shift( $var_by_ref );
2403
				if ( ! isset( $new_array[ $key ] ) )
2404
					$new_array[ $key ] = $row;
2405
			}
2406
			return $new_array;
2407
		} elseif ( $output == ARRAY_A || $output == ARRAY_N ) {
2408
			// Return an integer-keyed array of...
2409
			if ( $this->last_result ) {
2410
				foreach ( (array) $this->last_result as $row ) {
2411
					if ( $output == ARRAY_N ) {
2412
						// ...integer-keyed row arrays
2413
						$new_array[] = array_values( get_object_vars( $row ) );
2414
					} else {
2415
						// ...column name-keyed row arrays
2416
						$new_array[] = get_object_vars( $row );
2417
					}
2418
				}
2419
			}
2420
			return $new_array;
2421
		} elseif ( strtoupper( $output ) === OBJECT ) {
2422
			// Back compat for OBJECT being previously case insensitive.
2423
			return $this->last_result;
2424
		}
2425
		return null;
2426
	}
2427
2428
	/**
2429
	 * Retrieves the character set for the given table.
2430
	 *
2431
	 * @since 4.2.0
2432
	 * @access protected
2433
	 *
2434
	 * @param string $table Table name.
2435
	 * @return string|WP_Error Table character set, WP_Error object if it couldn't be found.
2436
	 */
2437
	protected function get_table_charset( $table ) {
2438
		$tablekey = strtolower( $table );
2439
2440
		/**
2441
		 * Filters the table charset value before the DB is checked.
2442
		 *
2443
		 * Passing a non-null value to the filter will effectively short-circuit
2444
		 * checking the DB for the charset, returning that value instead.
2445
		 *
2446
		 * @since 4.2.0
2447
		 *
2448
		 * @param string $charset The character set to use. Default null.
2449
		 * @param string $table   The name of the table being checked.
2450
		 */
2451
		$charset = apply_filters( 'pre_get_table_charset', null, $table );
2452
		if ( null !== $charset ) {
2453
			return $charset;
2454
		}
2455
2456
		if ( isset( $this->table_charset[ $tablekey ] ) ) {
2457
			return $this->table_charset[ $tablekey ];
2458
		}
2459
2460
		$charsets = $columns = array();
2461
2462
		$table_parts = explode( '.', $table );
2463
		$table = '`' . implode( '`.`', $table_parts ) . '`';
2464
		$results = $this->get_results( "SHOW FULL COLUMNS FROM $table" );
2465
		if ( ! $results ) {
2466
			return new WP_Error( 'wpdb_get_table_charset_failure' );
2467
		}
2468
2469
		foreach ( $results as $column ) {
2470
			$columns[ strtolower( $column->Field ) ] = $column;
2471
		}
2472
2473
		$this->col_meta[ $tablekey ] = $columns;
2474
2475
		foreach ( $columns as $column ) {
2476
			if ( ! empty( $column->Collation ) ) {
2477
				list( $charset ) = explode( '_', $column->Collation );
2478
2479
				// If the current connection can't support utf8mb4 characters, let's only send 3-byte utf8 characters.
2480
				if ( 'utf8mb4' === $charset && ! $this->has_cap( 'utf8mb4' ) ) {
2481
					$charset = 'utf8';
2482
				}
2483
2484
				$charsets[ strtolower( $charset ) ] = true;
2485
			}
2486
2487
			list( $type ) = explode( '(', $column->Type );
2488
2489
			// A binary/blob means the whole query gets treated like this.
2490
			if ( in_array( strtoupper( $type ), array( 'BINARY', 'VARBINARY', 'TINYBLOB', 'MEDIUMBLOB', 'BLOB', 'LONGBLOB' ) ) ) {
2491
				$this->table_charset[ $tablekey ] = 'binary';
2492
				return 'binary';
2493
			}
2494
		}
2495
2496
		// utf8mb3 is an alias for utf8.
2497
		if ( isset( $charsets['utf8mb3'] ) ) {
2498
			$charsets['utf8'] = true;
2499
			unset( $charsets['utf8mb3'] );
2500
		}
2501
2502
		// Check if we have more than one charset in play.
2503
		$count = count( $charsets );
2504
		if ( 1 === $count ) {
2505
			$charset = key( $charsets );
2506
		} elseif ( 0 === $count ) {
2507
			// No charsets, assume this table can store whatever.
2508
			$charset = false;
2509
		} else {
2510
			// More than one charset. Remove latin1 if present and recalculate.
2511
			unset( $charsets['latin1'] );
2512
			$count = count( $charsets );
2513
			if ( 1 === $count ) {
2514
				// Only one charset (besides latin1).
2515
				$charset = key( $charsets );
2516
			} elseif ( 2 === $count && isset( $charsets['utf8'], $charsets['utf8mb4'] ) ) {
2517
				// Two charsets, but they're utf8 and utf8mb4, use utf8.
2518
				$charset = 'utf8';
2519
			} else {
2520
				// Two mixed character sets. ascii.
2521
				$charset = 'ascii';
2522
			}
2523
		}
2524
2525
		$this->table_charset[ $tablekey ] = $charset;
2526
		return $charset;
2527
	}
2528
2529
	/**
2530
	 * Retrieves the character set for the given column.
2531
	 *
2532
	 * @since 4.2.0
2533
	 * @access public
2534
	 *
2535
	 * @param string $table  Table name.
2536
	 * @param string $column Column name.
2537
	 * @return string|false|WP_Error Column character set as a string. False if the column has no
2538
	 *                               character set. WP_Error object if there was an error.
2539
	 */
2540
	public function get_col_charset( $table, $column ) {
2541
		$tablekey = strtolower( $table );
2542
		$columnkey = strtolower( $column );
2543
2544
		/**
2545
		 * Filters the column charset value before the DB is checked.
2546
		 *
2547
		 * Passing a non-null value to the filter will short-circuit
2548
		 * checking the DB for the charset, returning that value instead.
2549
		 *
2550
		 * @since 4.2.0
2551
		 *
2552
		 * @param string $charset The character set to use. Default null.
2553
		 * @param string $table   The name of the table being checked.
2554
		 * @param string $column  The name of the column being checked.
2555
		 */
2556
		$charset = apply_filters( 'pre_get_col_charset', null, $table, $column );
2557
		if ( null !== $charset ) {
2558
			return $charset;
2559
		}
2560
2561
		// Skip this entirely if this isn't a MySQL database.
2562
		if ( empty( $this->is_mysql ) ) {
2563
			return false;
2564
		}
2565
2566 View Code Duplication
		if ( empty( $this->table_charset[ $tablekey ] ) ) {
2567
			// This primes column information for us.
2568
			$table_charset = $this->get_table_charset( $table );
2569
			if ( is_wp_error( $table_charset ) ) {
2570
				return $table_charset;
2571
			}
2572
		}
2573
2574
		// If still no column information, return the table charset.
2575
		if ( empty( $this->col_meta[ $tablekey ] ) ) {
2576
			return $this->table_charset[ $tablekey ];
2577
		}
2578
2579
		// If this column doesn't exist, return the table charset.
2580
		if ( empty( $this->col_meta[ $tablekey ][ $columnkey ] ) ) {
2581
			return $this->table_charset[ $tablekey ];
2582
		}
2583
2584
		// Return false when it's not a string column.
2585
		if ( empty( $this->col_meta[ $tablekey ][ $columnkey ]->Collation ) ) {
2586
			return false;
2587
		}
2588
2589
		list( $charset ) = explode( '_', $this->col_meta[ $tablekey ][ $columnkey ]->Collation );
2590
		return $charset;
2591
	}
2592
2593
	/**
2594
	 * Retrieve the maximum string length allowed in a given column.
2595
	 * The length may either be specified as a byte length or a character length.
2596
	 *
2597
	 * @since 4.2.1
2598
	 * @access public
2599
	 *
2600
	 * @param string $table  Table name.
2601
	 * @param string $column Column name.
2602
	 * @return array|false|WP_Error array( 'length' => (int), 'type' => 'byte' | 'char' )
0 ignored issues
show
Documentation introduced by
Should the return type not be false|string|WP_Error|ar...<string,string|integer>?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
2603
	 *                              false if the column has no length (for example, numeric column)
2604
	 *                              WP_Error object if there was an error.
2605
	 */
2606
	public function get_col_length( $table, $column ) {
2607
		$tablekey = strtolower( $table );
2608
		$columnkey = strtolower( $column );
2609
2610
		// Skip this entirely if this isn't a MySQL database.
2611
		if ( empty( $this->is_mysql ) ) {
2612
			return false;
2613
		}
2614
2615 View Code Duplication
		if ( empty( $this->col_meta[ $tablekey ] ) ) {
2616
			// This primes column information for us.
2617
			$table_charset = $this->get_table_charset( $table );
2618
			if ( is_wp_error( $table_charset ) ) {
2619
				return $table_charset;
2620
			}
2621
		}
2622
2623
		if ( empty( $this->col_meta[ $tablekey ][ $columnkey ] ) ) {
2624
			return false;
2625
		}
2626
2627
		$typeinfo = explode( '(', $this->col_meta[ $tablekey ][ $columnkey ]->Type );
2628
2629
		$type = strtolower( $typeinfo[0] );
2630
		if ( ! empty( $typeinfo[1] ) ) {
2631
			$length = trim( $typeinfo[1], ')' );
2632
		} else {
2633
			$length = false;
2634
		}
2635
2636
		switch( $type ) {
2637
			case 'char':
2638
			case 'varchar':
2639
				return array(
2640
					'type'   => 'char',
2641
					'length' => (int) $length,
2642
				);
2643
2644
			case 'binary':
2645
			case 'varbinary':
2646
				return array(
2647
					'type'   => 'byte',
2648
					'length' => (int) $length,
2649
				);
2650
2651
			case 'tinyblob':
2652
			case 'tinytext':
2653
				return array(
2654
					'type'   => 'byte',
2655
					'length' => 255,        // 2^8 - 1
2656
				);
2657
2658
			case 'blob':
2659
			case 'text':
2660
				return array(
2661
					'type'   => 'byte',
2662
					'length' => 65535,      // 2^16 - 1
2663
				);
2664
2665
			case 'mediumblob':
2666
			case 'mediumtext':
2667
				return array(
2668
					'type'   => 'byte',
2669
					'length' => 16777215,   // 2^24 - 1
2670
				);
2671
2672
			case 'longblob':
2673
			case 'longtext':
2674
				return array(
2675
					'type'   => 'byte',
2676
					'length' => 4294967295, // 2^32 - 1
2677
				);
2678
2679
			default:
2680
				return false;
2681
		}
2682
	}
2683
2684
	/**
2685
	 * Check if a string is ASCII.
2686
	 *
2687
	 * The negative regex is faster for non-ASCII strings, as it allows
2688
	 * the search to finish as soon as it encounters a non-ASCII character.
2689
	 *
2690
	 * @since 4.2.0
2691
	 * @access protected
2692
	 *
2693
	 * @param string $string String to check.
2694
	 * @return bool True if ASCII, false if not.
2695
	 */
2696
	protected function check_ascii( $string ) {
2697
		if ( function_exists( 'mb_check_encoding' ) ) {
2698
			if ( mb_check_encoding( $string, 'ASCII' ) ) {
2699
				return true;
2700
			}
2701
		} elseif ( ! preg_match( '/[^\x00-\x7F]/', $string ) ) {
2702
			return true;
2703
		}
2704
2705
		return false;
2706
	}
2707
2708
	/**
2709
	 * Check if the query is accessing a collation considered safe on the current version of MySQL.
2710
	 *
2711
	 * @since 4.2.0
2712
	 * @access protected
2713
	 *
2714
	 * @param string $query The query to check.
2715
	 * @return bool True if the collation is safe, false if it isn't.
2716
	 */
2717
	protected function check_safe_collation( $query ) {
2718
		if ( $this->checking_collation ) {
2719
			return true;
2720
		}
2721
2722
		// We don't need to check the collation for queries that don't read data.
2723
		$query = ltrim( $query, "\r\n\t (" );
2724
		if ( preg_match( '/^(?:SHOW|DESCRIBE|DESC|EXPLAIN|CREATE)\s/i', $query ) ) {
2725
			return true;
2726
		}
2727
2728
		// All-ASCII queries don't need extra checking.
2729
		if ( $this->check_ascii( $query ) ) {
2730
			return true;
2731
		}
2732
2733
		$table = $this->get_table_from_query( $query );
2734
		if ( ! $table ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $table of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

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

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2735
			return false;
2736
		}
2737
2738
		$this->checking_collation = true;
2739
		$collation = $this->get_table_charset( $table );
2740
		$this->checking_collation = false;
2741
2742
		// Tables with no collation, or latin1 only, don't need extra checking.
2743
		if ( false === $collation || 'latin1' === $collation ) {
2744
			return true;
2745
		}
2746
2747
		$table = strtolower( $table );
2748
		if ( empty( $this->col_meta[ $table ] ) ) {
2749
			return false;
2750
		}
2751
2752
		// If any of the columns don't have one of these collations, it needs more sanity checking.
2753
		foreach ( $this->col_meta[ $table ] as $col ) {
2754
			if ( empty( $col->Collation ) ) {
2755
				continue;
2756
			}
2757
2758
			if ( ! in_array( $col->Collation, array( 'utf8_general_ci', 'utf8_bin', 'utf8mb4_general_ci', 'utf8mb4_bin' ), true ) ) {
2759
				return false;
2760
			}
2761
		}
2762
2763
		return true;
2764
	}
2765
2766
	/**
2767
	 * Strips any invalid characters based on value/charset pairs.
2768
	 *
2769
	 * @since 4.2.0
2770
	 * @access protected
2771
	 *
2772
	 * @param array $data Array of value arrays. Each value array has the keys
2773
	 *                    'value' and 'charset'. An optional 'ascii' key can be
2774
	 *                    set to false to avoid redundant ASCII checks.
2775
	 * @return array|WP_Error The $data parameter, with invalid characters removed from
2776
	 *                        each value. This works as a passthrough: any additional keys
2777
	 *                        such as 'field' are retained in each value array. If we cannot
2778
	 *                        remove invalid characters, a WP_Error object is returned.
2779
	 */
2780
	protected function strip_invalid_text( $data ) {
2781
		$db_check_string = false;
2782
2783
		foreach ( $data as &$value ) {
2784
			$charset = $value['charset'];
2785
2786
			if ( is_array( $value['length'] ) ) {
2787
				$length = $value['length']['length'];
2788
				$truncate_by_byte_length = 'byte' === $value['length']['type'];
2789
			} else {
2790
				$length = false;
2791
				// Since we have no length, we'll never truncate.
2792
				// Initialize the variable to false. true would take us
2793
				// through an unnecessary (for this case) codepath below.
2794
				$truncate_by_byte_length = false;
2795
			}
2796
2797
			// There's no charset to work with.
2798
			if ( false === $charset ) {
2799
				continue;
2800
			}
2801
2802
			// Column isn't a string.
2803
			if ( ! is_string( $value['value'] ) ) {
2804
				continue;
2805
			}
2806
2807
			$needs_validation = true;
2808
			if (
2809
				// latin1 can store any byte sequence
2810
				'latin1' === $charset
2811
			||
2812
				// ASCII is always OK.
2813
				( ! isset( $value['ascii'] ) && $this->check_ascii( $value['value'] ) )
2814
			) {
2815
				$truncate_by_byte_length = true;
2816
				$needs_validation = false;
2817
			}
2818
2819
			if ( $truncate_by_byte_length ) {
2820
				mbstring_binary_safe_encoding();
2821
				if ( false !== $length && strlen( $value['value'] ) > $length ) {
2822
					$value['value'] = substr( $value['value'], 0, $length );
2823
				}
2824
				reset_mbstring_encoding();
2825
2826
				if ( ! $needs_validation ) {
2827
					continue;
2828
				}
2829
			}
2830
2831
			// utf8 can be handled by regex, which is a bunch faster than a DB lookup.
2832
			if ( ( 'utf8' === $charset || 'utf8mb3' === $charset || 'utf8mb4' === $charset ) && function_exists( 'mb_strlen' ) ) {
2833
				$regex = '/
2834
					(
2835
						(?: [\x00-\x7F]                  # single-byte sequences   0xxxxxxx
2836
						|   [\xC2-\xDF][\x80-\xBF]       # double-byte sequences   110xxxxx 10xxxxxx
2837
						|   \xE0[\xA0-\xBF][\x80-\xBF]   # triple-byte sequences   1110xxxx 10xxxxxx * 2
2838
						|   [\xE1-\xEC][\x80-\xBF]{2}
2839
						|   \xED[\x80-\x9F][\x80-\xBF]
2840
						|   [\xEE-\xEF][\x80-\xBF]{2}';
2841
2842
				if ( 'utf8mb4' === $charset ) {
2843
					$regex .= '
2844
						|    \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences   11110xxx 10xxxxxx * 3
2845
						|    [\xF1-\xF3][\x80-\xBF]{3}
2846
						|    \xF4[\x80-\x8F][\x80-\xBF]{2}
2847
					';
2848
				}
2849
2850
				$regex .= '){1,40}                          # ...one or more times
2851
					)
2852
					| .                                  # anything else
2853
					/x';
2854
				$value['value'] = preg_replace( $regex, '$1', $value['value'] );
2855
2856
2857
				if ( false !== $length && mb_strlen( $value['value'], 'UTF-8' ) > $length ) {
2858
					$value['value'] = mb_substr( $value['value'], 0, $length, 'UTF-8' );
2859
				}
2860
				continue;
2861
			}
2862
2863
			// We couldn't use any local conversions, send it to the DB.
2864
			$value['db'] = $db_check_string = true;
2865
		}
2866
		unset( $value ); // Remove by reference.
2867
2868
		if ( $db_check_string ) {
2869
			$queries = array();
2870
			foreach ( $data as $col => $value ) {
2871
				if ( ! empty( $value['db'] ) ) {
2872
					// We're going to need to truncate by characters or bytes, depending on the length value we have.
2873
					if ( 'byte' === $value['length']['type'] ) {
2874
						// Using binary causes LEFT() to truncate by bytes.
2875
						$charset = 'binary';
2876
					} else {
2877
						$charset = $value['charset'];
2878
					}
2879
2880
					if ( $this->charset ) {
2881
						$connection_charset = $this->charset;
2882
					} else {
2883
						if ( $this->use_mysqli ) {
2884
							$connection_charset = mysqli_character_set_name( $this->dbh );
2885
						} else {
2886
							$connection_charset = mysql_client_encoding();
2887
						}
2888
					}
2889
2890
					if ( is_array( $value['length'] ) ) {
2891
						$queries[ $col ] = $this->prepare( "CONVERT( LEFT( CONVERT( %s USING $charset ), %.0f ) USING $connection_charset )", $value['value'], $value['length']['length'] );
2892
					} else if ( 'binary' !== $charset ) {
2893
						// If we don't have a length, there's no need to convert binary - it will always return the same result.
2894
						$queries[ $col ] = $this->prepare( "CONVERT( CONVERT( %s USING $charset ) USING $connection_charset )", $value['value'] );
2895
					}
2896
2897
					unset( $data[ $col ]['db'] );
2898
				}
2899
			}
2900
2901
			$sql = array();
2902
			foreach ( $queries as $column => $query ) {
2903
				if ( ! $query ) {
2904
					continue;
2905
				}
2906
2907
				$sql[] = $query . " AS x_$column";
2908
			}
2909
2910
			$this->check_current_query = false;
2911
			$row = $this->get_row( "SELECT " . implode( ', ', $sql ), ARRAY_A );
2912
			if ( ! $row ) {
2913
				return new WP_Error( 'wpdb_strip_invalid_text_failure' );
2914
			}
2915
2916
			foreach ( array_keys( $data ) as $column ) {
2917
				if ( isset( $row["x_$column"] ) ) {
2918
					$data[ $column ]['value'] = $row["x_$column"];
2919
				}
2920
			}
2921
		}
2922
2923
		return $data;
2924
	}
2925
2926
	/**
2927
	 * Strips any invalid characters from the query.
2928
	 *
2929
	 * @since 4.2.0
2930
	 * @access protected
2931
	 *
2932
	 * @param string $query Query to convert.
2933
	 * @return string|WP_Error The converted query, or a WP_Error object if the conversion fails.
2934
	 */
2935
	protected function strip_invalid_text_from_query( $query ) {
2936
		// We don't need to check the collation for queries that don't read data.
2937
		$trimmed_query = ltrim( $query, "\r\n\t (" );
2938
		if ( preg_match( '/^(?:SHOW|DESCRIBE|DESC|EXPLAIN|CREATE)\s/i', $trimmed_query ) ) {
2939
			return $query;
2940
		}
2941
2942
		$table = $this->get_table_from_query( $query );
2943
		if ( $table ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $table of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

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

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2944
			$charset = $this->get_table_charset( $table );
2945
			if ( is_wp_error( $charset ) ) {
2946
				return $charset;
2947
			}
2948
2949
			// We can't reliably strip text from tables containing binary/blob columns
2950
			if ( 'binary' === $charset ) {
2951
				return $query;
2952
			}
2953
		} else {
2954
			$charset = $this->charset;
2955
		}
2956
2957
		$data = array(
2958
			'value'   => $query,
2959
			'charset' => $charset,
2960
			'ascii'   => false,
2961
			'length'  => false,
2962
		);
2963
2964
		$data = $this->strip_invalid_text( array( $data ) );
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->strip_invalid_text(array($data)); of type WP_Error|array adds the type array to the return on line 2966 which is incompatible with the return type documented by wpdb::strip_invalid_text_from_query of type string|WP_Error.
Loading history...
2965
		if ( is_wp_error( $data ) ) {
2966
			return $data;
2967
		}
2968
2969
		return $data[0]['value'];
2970
	}
2971
2972
	/**
2973
	 * Strips any invalid characters from the string for a given table and column.
2974
	 *
2975
	 * @since 4.2.0
2976
	 * @access public
2977
	 *
2978
	 * @param string $table  Table name.
2979
	 * @param string $column Column name.
2980
	 * @param string $value  The text to check.
2981
	 * @return string|WP_Error The converted string, or a WP_Error object if the conversion fails.
2982
	 */
2983
	public function strip_invalid_text_for_column( $table, $column, $value ) {
2984
		if ( ! is_string( $value ) ) {
2985
			return $value;
2986
		}
2987
2988
		$charset = $this->get_col_charset( $table, $column );
2989
		if ( ! $charset ) {
2990
			// Not a string column.
2991
			return $value;
2992
		} elseif ( is_wp_error( $charset ) ) {
2993
			// Bail on real errors.
2994
			return $charset;
2995
		}
2996
2997
		$data = array(
2998
			$column => array(
2999
				'value'   => $value,
3000
				'charset' => $charset,
3001
				'length'  => $this->get_col_length( $table, $column ),
3002
			)
3003
		);
3004
3005
		$data = $this->strip_invalid_text( $data );
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->strip_invalid_text($data); of type WP_Error|array adds the type array to the return on line 3007 which is incompatible with the return type documented by wpdb::strip_invalid_text_for_column of type string|WP_Error.
Loading history...
3006
		if ( is_wp_error( $data ) ) {
3007
			return $data;
3008
		}
3009
3010
		return $data[ $column ]['value'];
3011
	}
3012
3013
	/**
3014
	 * Find the first table name referenced in a query.
3015
	 *
3016
	 * @since 4.2.0
3017
	 * @access protected
3018
	 *
3019
	 * @param string $query The query to search.
3020
	 * @return string|false $table The table name found, or false if a table couldn't be found.
3021
	 */
3022
	protected function get_table_from_query( $query ) {
3023
		// Remove characters that can legally trail the table name.
3024
		$query = rtrim( $query, ';/-#' );
3025
3026
		// Allow (select...) union [...] style queries. Use the first query's table name.
3027
		$query = ltrim( $query, "\r\n\t (" );
3028
3029
		// Strip everything between parentheses except nested selects.
3030
		$query = preg_replace( '/\((?!\s*select)[^(]*?\)/is', '()', $query );
3031
3032
		// Quickly match most common queries.
3033
		if ( preg_match( '/^\s*(?:'
3034
				. 'SELECT.*?\s+FROM'
3035
				. '|INSERT(?:\s+LOW_PRIORITY|\s+DELAYED|\s+HIGH_PRIORITY)?(?:\s+IGNORE)?(?:\s+INTO)?'
3036
				. '|REPLACE(?:\s+LOW_PRIORITY|\s+DELAYED)?(?:\s+INTO)?'
3037
				. '|UPDATE(?:\s+LOW_PRIORITY)?(?:\s+IGNORE)?'
3038
				. '|DELETE(?:\s+LOW_PRIORITY|\s+QUICK|\s+IGNORE)*(?:.+?FROM)?'
3039
				. ')\s+((?:[0-9a-zA-Z$_.`-]|[\xC2-\xDF][\x80-\xBF])+)/is', $query, $maybe ) ) {
3040
			return str_replace( '`', '', $maybe[1] );
3041
		}
3042
3043
		// SHOW TABLE STATUS and SHOW TABLES WHERE Name = 'wp_posts'
3044
		if ( preg_match( '/^\s*SHOW\s+(?:TABLE\s+STATUS|(?:FULL\s+)?TABLES).+WHERE\s+Name\s*=\s*("|\')((?:[0-9a-zA-Z$_.-]|[\xC2-\xDF][\x80-\xBF])+)\\1/is', $query, $maybe ) ) {
3045
			return $maybe[2];
3046
		}
3047
3048
		// SHOW TABLE STATUS LIKE and SHOW TABLES LIKE 'wp\_123\_%'
3049
		// This quoted LIKE operand seldom holds a full table name.
3050
		// It is usually a pattern for matching a prefix so we just
3051
		// strip the trailing % and unescape the _ to get 'wp_123_'
3052
		// which drop-ins can use for routing these SQL statements.
3053
		if ( preg_match( '/^\s*SHOW\s+(?:TABLE\s+STATUS|(?:FULL\s+)?TABLES)\s+(?:WHERE\s+Name\s+)?LIKE\s*("|\')((?:[\\\\0-9a-zA-Z$_.-]|[\xC2-\xDF][\x80-\xBF])+)%?\\1/is', $query, $maybe ) ) {
3054
			return str_replace( '\\_', '_', $maybe[2] );
3055
		}
3056
3057
		// Big pattern for the rest of the table-related queries.
3058
		if ( preg_match( '/^\s*(?:'
3059
				. '(?:EXPLAIN\s+(?:EXTENDED\s+)?)?SELECT.*?\s+FROM'
3060
				. '|DESCRIBE|DESC|EXPLAIN|HANDLER'
3061
				. '|(?:LOCK|UNLOCK)\s+TABLE(?:S)?'
3062
				. '|(?:RENAME|OPTIMIZE|BACKUP|RESTORE|CHECK|CHECKSUM|ANALYZE|REPAIR).*\s+TABLE'
3063
				. '|TRUNCATE(?:\s+TABLE)?'
3064
				. '|CREATE(?:\s+TEMPORARY)?\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?'
3065
				. '|ALTER(?:\s+IGNORE)?\s+TABLE'
3066
				. '|DROP\s+TABLE(?:\s+IF\s+EXISTS)?'
3067
				. '|CREATE(?:\s+\w+)?\s+INDEX.*\s+ON'
3068
				. '|DROP\s+INDEX.*\s+ON'
3069
				. '|LOAD\s+DATA.*INFILE.*INTO\s+TABLE'
3070
				. '|(?:GRANT|REVOKE).*ON\s+TABLE'
3071
				. '|SHOW\s+(?:.*FROM|.*TABLE)'
3072
				. ')\s+\(*\s*((?:[0-9a-zA-Z$_.`-]|[\xC2-\xDF][\x80-\xBF])+)\s*\)*/is', $query, $maybe ) ) {
3073
			return str_replace( '`', '', $maybe[1] );
3074
		}
3075
3076
		return false;
3077
	}
3078
3079
	/**
3080
	 * Load the column metadata from the last query.
3081
	 *
3082
	 * @since 3.5.0
3083
	 *
3084
	 * @access protected
3085
	 */
3086
	protected function load_col_info() {
3087
		if ( $this->col_info )
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->col_info of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
3088
			return;
3089
3090
		if ( $this->use_mysqli ) {
3091
			$num_fields = mysqli_num_fields( $this->result );
3092 View Code Duplication
			for ( $i = 0; $i < $num_fields; $i++ ) {
3093
				$this->col_info[ $i ] = mysqli_fetch_field( $this->result );
3094
			}
3095
		} else {
3096
			$num_fields = mysql_num_fields( $this->result );
3097 View Code Duplication
			for ( $i = 0; $i < $num_fields; $i++ ) {
3098
				$this->col_info[ $i ] = mysql_fetch_field( $this->result, $i );
3099
			}
3100
		}
3101
	}
3102
3103
	/**
3104
	 * Retrieve column metadata from the last query.
3105
	 *
3106
	 * @since 0.71
3107
	 *
3108
	 * @param string $info_type  Optional. Type one of name, table, def, max_length, not_null, primary_key, multiple_key, unique_key, numeric, blob, type, unsigned, zerofill
3109
	 * @param int    $col_offset Optional. 0: col name. 1: which table the col's in. 2: col's max length. 3: if the col is numeric. 4: col's type
3110
	 * @return mixed Column Results
3111
	 */
3112
	public function get_col_info( $info_type = 'name', $col_offset = -1 ) {
3113
		$this->load_col_info();
3114
3115
		if ( $this->col_info ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->col_info of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
3116
			if ( $col_offset == -1 ) {
3117
				$i = 0;
3118
				$new_array = array();
3119
				foreach ( (array) $this->col_info as $col ) {
3120
					$new_array[$i] = $col->{$info_type};
3121
					$i++;
3122
				}
3123
				return $new_array;
3124
			} else {
3125
				return $this->col_info[$col_offset]->{$info_type};
3126
			}
3127
		}
3128
	}
3129
3130
	/**
3131
	 * Starts the timer, for debugging purposes.
3132
	 *
3133
	 * @since 1.5.0
3134
	 *
3135
	 * @return true
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
3136
	 */
3137
	public function timer_start() {
3138
		$this->time_start = microtime( true );
0 ignored issues
show
Documentation introduced by
The property time_start does not exist on object<wpdb>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
3139
		return true;
3140
	}
3141
3142
	/**
3143
	 * Stops the debugging timer.
3144
	 *
3145
	 * @since 1.5.0
3146
	 *
3147
	 * @return float Total time spent on the query, in seconds
3148
	 */
3149
	public function timer_stop() {
3150
		return ( microtime( true ) - $this->time_start );
0 ignored issues
show
Documentation introduced by
The property time_start does not exist on object<wpdb>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
3151
	}
3152
3153
	/**
3154
	 * Wraps errors in a nice header and footer and dies.
3155
	 *
3156
	 * Will not die if wpdb::$show_errors is false.
3157
	 *
3158
	 * @since 1.5.0
3159
	 *
3160
	 * @param string $message    The Error message
3161
	 * @param string $error_code Optional. A Computer readable string to identify the error.
3162
	 * @return false|void
3163
	 */
3164
	public function bail( $message, $error_code = '500' ) {
3165
		if ( !$this->show_errors ) {
3166
			if ( class_exists( 'WP_Error', false ) ) {
3167
				$this->error = new WP_Error($error_code, $message);
0 ignored issues
show
Documentation introduced by
The property error does not exist on object<wpdb>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
3168
			} else {
3169
				$this->error = $message;
0 ignored issues
show
Documentation introduced by
The property error does not exist on object<wpdb>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
3170
			}
3171
			return false;
3172
		}
3173
		wp_die($message);
3174
	}
3175
3176
3177
	/**
3178
	 * Closes the current database connection.
3179
	 *
3180
	 * @since 4.5.0
3181
	 * @access public
3182
	 *
3183
	 * @return bool True if the connection was successfully closed, false if it wasn't,
3184
	 *              or the connection doesn't exist.
3185
	 */
3186
	public function close() {
3187
		if ( ! $this->dbh ) {
3188
			return false;
3189
		}
3190
3191
		if ( $this->use_mysqli ) {
3192
			$closed = mysqli_close( $this->dbh );
3193
		} else {
3194
			$closed = mysql_close( $this->dbh );
3195
		}
3196
3197
		if ( $closed ) {
3198
			$this->dbh = null;
3199
			$this->ready = false;
3200
			$this->has_connected = false;
3201
		}
3202
3203
		return $closed;
3204
	}
3205
3206
	/**
3207
	 * Whether MySQL database is at least the required minimum version.
3208
	 *
3209
	 * @since 2.5.0
3210
	 *
3211
	 * @global string $wp_version
3212
	 * @global string $required_mysql_version
3213
	 *
3214
	 * @return WP_Error|void
3215
	 */
3216
	public function check_database_version() {
3217
		global $wp_version, $required_mysql_version;
3218
		// Make sure the server has the required MySQL version
3219
		if ( version_compare($this->db_version(), $required_mysql_version, '<') ) {
3220
			/* translators: 1: WordPress version number, 2: Minimum required MySQL version number */
3221
			return new WP_Error('database_version', sprintf( __( '<strong>ERROR</strong>: WordPress %1$s requires MySQL %2$s or higher' ), $wp_version, $required_mysql_version ));
3222
		}
3223
	}
3224
3225
	/**
3226
	 * Whether the database supports collation.
3227
	 *
3228
	 * Called when WordPress is generating the table scheme.
3229
	 *
3230
	 * Use `wpdb::has_cap( 'collation' )`.
3231
	 *
3232
	 * @since 2.5.0
3233
	 * @deprecated 3.5.0 Use wpdb::has_cap()
3234
	 *
3235
	 * @return bool True if collation is supported, false if version does not
3236
	 */
3237
	public function supports_collation() {
3238
		_deprecated_function( __FUNCTION__, '3.5.0', 'wpdb::has_cap( \'collation\' )' );
3239
		return $this->has_cap( 'collation' );
3240
	}
3241
3242
	/**
3243
	 * The database character collate.
3244
	 *
3245
	 * @since 3.5.0
3246
	 *
3247
	 * @return string The database character collate.
3248
	 */
3249
	public function get_charset_collate() {
3250
		$charset_collate = '';
3251
3252
		if ( ! empty( $this->charset ) )
3253
			$charset_collate = "DEFAULT CHARACTER SET $this->charset";
3254
		if ( ! empty( $this->collate ) )
3255
			$charset_collate .= " COLLATE $this->collate";
3256
3257
		return $charset_collate;
3258
	}
3259
3260
	/**
3261
	 * Determine if a database supports a particular feature.
3262
	 *
3263
	 * @since 2.7.0
3264
	 * @since 4.1.0 Added support for the 'utf8mb4' feature.
3265
	 * @since 4.6.0 Added support for the 'utf8mb4_520' feature.
3266
	 *
3267
	 * @see wpdb::db_version()
3268
	 *
3269
	 * @param string $db_cap The feature to check for. Accepts 'collation',
3270
	 *                       'group_concat', 'subqueries', 'set_charset',
3271
	 *                       'utf8mb4', or 'utf8mb4_520'.
3272
	 * @return int|false Whether the database feature is supported, false otherwise.
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
3273
	 */
3274
	public function has_cap( $db_cap ) {
3275
		$version = $this->db_version();
3276
3277
		switch ( strtolower( $db_cap ) ) {
3278
			case 'collation' :    // @since 2.5.0
3279
			case 'group_concat' : // @since 2.7.0
3280
			case 'subqueries' :   // @since 2.7.0
3281
				return version_compare( $version, '4.1', '>=' );
3282
			case 'set_charset' :
3283
				return version_compare( $version, '5.0.7', '>=' );
3284
			case 'utf8mb4' :      // @since 4.1.0
3285
				if ( version_compare( $version, '5.5.3', '<' ) ) {
3286
					return false;
3287
				}
3288
				if ( $this->use_mysqli ) {
3289
					$client_version = mysqli_get_client_info();
3290
				} else {
3291
					$client_version = mysql_get_client_info();
3292
				}
3293
3294
				/*
3295
				 * libmysql has supported utf8mb4 since 5.5.3, same as the MySQL server.
3296
				 * mysqlnd has supported utf8mb4 since 5.0.9.
3297
				 */
3298
				if ( false !== strpos( $client_version, 'mysqlnd' ) ) {
3299
					$client_version = preg_replace( '/^\D+([\d.]+).*/', '$1', $client_version );
3300
					return version_compare( $client_version, '5.0.9', '>=' );
3301
				} else {
3302
					return version_compare( $client_version, '5.5.3', '>=' );
3303
				}
3304
			case 'utf8mb4_520' : // @since 4.6.0
3305
				return version_compare( $version, '5.6', '>=' );
3306
		}
3307
3308
		return false;
3309
	}
3310
3311
	/**
3312
	 * Retrieve the name of the function that called wpdb.
3313
	 *
3314
	 * Searches up the list of functions until it reaches
3315
	 * the one that would most logically had called this method.
3316
	 *
3317
	 * @since 2.5.0
3318
	 *
3319
	 * @return string|array The name of the calling function
3320
	 */
3321
	public function get_caller() {
3322
		return wp_debug_backtrace_summary( __CLASS__ );
3323
	}
3324
3325
	/**
3326
	 * Retrieves the MySQL server version.
3327
	 *
3328
	 * @since 2.7.0
3329
	 *
3330
	 * @return null|string Null on failure, version number on success.
3331
	 */
3332
	public function db_version() {
3333
		if ( $this->use_mysqli ) {
3334
			$server_info = mysqli_get_server_info( $this->dbh );
3335
		} else {
3336
			$server_info = mysql_get_server_info( $this->dbh );
3337
		}
3338
		return preg_replace( '/[^0-9.].*/', '', $server_info );
3339
	}
3340
}
3341