run:R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
23.8 KB
2026-03-11 16:18:51
R W Run
7.8 KB
2026-03-11 16:18:52
R W Run
36.1 KB
2026-03-11 16:18:51
R W Run
11.9 KB
2026-03-11 16:18:52
R W Run
18.94 KB
2026-03-11 16:18:52
R W Run
7.35 KB
2026-03-11 16:18:52
R W Run
28.6 KB
2026-03-11 16:18:51
R W Run
316 By
2026-03-11 16:18:51
R W Run
12.9 KB
2026-03-11 16:18:51
R W Run
61.02 KB
2026-03-11 16:18:52
R W Run
15 KB
2026-03-11 16:18:51
R W Run
112.05 KB
2026-03-11 16:18:51
R W Run
12.47 KB
2026-03-11 16:18:51
R W Run
15.07 KB
2026-03-11 16:18:52
R W Run
9.84 KB
2026-03-11 16:18:52
R W Run
13.17 KB
2026-03-11 16:18:52
R W Run
33.83 KB
2026-03-11 16:18:51
R W Run
42.63 KB
2026-03-11 16:18:51
R W Run
55.71 KB
2026-03-11 16:18:52
R W Run
12.53 KB
2026-03-11 16:18:51
R W Run
2.55 KB
2026-03-11 16:18:52
R W Run
28.92 KB
2026-03-11 16:18:52
R W Run
539 By
2026-03-11 16:18:51
R W Run
367 By
2026-03-11 16:18:52
R W Run
42.65 KB
2026-03-11 16:18:51
R W Run
401 By
2026-03-11 16:18:51
R W Run
6.61 KB
2026-03-11 16:18:51
R W Run
664 By
2026-03-11 16:18:52
R W Run
20.63 KB
2026-03-11 16:18:51
R W Run
2.18 KB
2026-03-11 16:18:52
R W Run
453 By
2026-03-11 16:18:52
R W Run
457 By
2026-03-11 16:18:51
R W Run
36.83 KB
2026-03-11 16:18:52
R W Run
2.41 KB
2026-03-11 16:18:52
R W Run
8.28 KB
2026-03-11 16:18:51
R W Run
13.89 KB
2026-03-11 16:18:51
R W Run
11.76 KB
2026-03-11 16:18:51
R W Run
2.65 KB
2026-03-11 16:18:51
R W Run
7.43 KB
2026-03-11 16:18:51
R W Run
17.46 KB
2026-03-11 16:18:51
R W Run
5.14 KB
2026-03-11 16:18:52
R W Run
16.7 KB
2026-03-11 16:18:51
R W Run
8.28 KB
2026-03-11 16:18:52
R W Run
2.92 KB
2026-03-11 16:18:52
R W Run
1.32 KB
2026-03-11 16:18:51
R W Run
4.6 KB
2026-03-11 16:18:52
R W Run
11.62 KB
2026-03-11 16:18:52
R W Run
2.5 KB
2026-03-11 16:18:51
R W Run
1.97 KB
2026-03-11 16:18:51
R W Run
11.25 KB
2026-03-11 16:18:52
R W Run
5.32 KB
2026-03-11 16:18:51
R W Run
10.99 KB
2026-03-11 16:18:52
R W Run
68.32 KB
2026-03-11 16:18:51
R W Run
6.34 KB
2026-03-11 16:18:51
R W Run
5.49 KB
2026-03-11 16:18:51
R W Run
1.99 KB
2026-03-11 16:18:52
R W Run
7.02 KB
2026-03-11 16:18:51
R W Run
4.91 KB
2026-03-11 16:18:52
R W Run
16.86 KB
2026-03-11 16:18:51
R W Run
24.23 KB
2026-03-11 16:18:51
R W Run
3.97 KB
2026-03-11 16:18:51
R W Run
47.66 KB
2026-03-11 16:18:51
R W Run
9.22 KB
2026-03-11 16:18:51
R W Run
25.51 KB
2026-03-11 16:18:51
R W Run
198.38 KB
2026-03-11 16:18:52
R W Run
56.65 KB
2026-03-11 16:18:51
R W Run
10.46 KB
2026-03-11 16:18:51
R W Run
10.95 KB
2026-03-11 16:18:52
R W Run
29.26 KB
2026-03-11 16:18:51
R W Run
70.91 KB
2026-03-11 16:18:52
R W Run
35.3 KB
2026-03-11 16:18:52
R W Run
16.61 KB
2026-03-11 16:18:52
R W Run
2.57 KB
2026-03-11 16:18:52
R W Run
39.83 KB
2026-03-11 16:18:51
R W Run
70.64 KB
2026-03-11 16:18:51
R W Run
15.56 KB
2026-03-11 16:18:52
R W Run
7.33 KB
2026-03-11 16:18:52
R W Run
253 By
2026-03-11 16:18:51
R W Run
7.96 KB
2026-03-11 16:18:52
R W Run
3.23 KB
2026-03-11 16:18:52
R W Run
969 By
2026-03-11 16:18:52
R W Run
16.28 KB
2026-03-11 16:18:51
R W Run
7.22 KB
2026-03-11 16:18:51
R W Run
12.95 KB
2026-03-11 16:18:51
R W Run
6.53 KB
2026-03-11 16:18:51
R W Run
3.42 KB
2026-03-11 16:18:52
R W Run
5.84 KB
2026-03-11 16:18:51
R W Run
1.97 KB
2026-03-11 16:18:51
R W Run
4.3 KB
2026-03-11 16:18:52
R W Run
2.91 KB
2026-03-11 16:18:51
R W Run
16.46 KB
2026-03-11 16:18:52
R W Run
40.6 KB
2026-03-11 16:18:51
R W Run
20.22 KB
2026-03-11 16:18:51
R W Run
36.11 KB
2026-03-11 16:18:52
R W Run
17.01 KB
2026-03-11 16:18:51
R W Run
7.27 KB
2026-03-11 16:18:52
R W Run
6.62 KB
2026-03-11 16:18:52
R W Run
16.49 KB
2026-03-11 16:18:52
R W Run
1.79 KB
2026-03-11 16:18:52
R W Run
29.82 KB
2026-03-11 16:18:51
R W Run
6.67 KB
2026-03-11 16:18:52
R W Run
8.98 KB
2026-03-11 16:18:52
R W Run
19.42 KB
2026-03-11 16:18:51
R W Run
12.01 KB
2026-03-11 16:18:51
R W Run
17.11 KB
2026-03-11 16:18:51
R W Run
6.74 KB
2026-03-11 16:18:52
R W Run
30.93 KB
2026-03-11 16:18:51
R W Run
4.99 KB
2026-03-11 16:18:51
R W Run
4.25 KB
2026-03-11 16:18:51
R W Run
24.72 KB
2026-03-11 16:18:51
R W Run
29.96 KB
2026-03-11 16:18:52
R W Run
6.41 KB
2026-03-11 16:18:51
R W Run
160 KB
2026-03-11 16:18:51
R W Run
6.72 KB
2026-03-11 16:18:52
R W Run
10.92 KB
2026-03-11 16:18:51
R W Run
4.77 KB
2026-03-11 16:18:51
R W Run
3.38 KB
2026-03-11 16:18:51
R W Run
11.18 KB
2026-03-11 16:18:51
R W Run
62.19 KB
2026-03-11 16:18:51
R W Run
2.46 KB
2026-03-11 16:18:51
R W Run
9.17 KB
2026-03-11 16:18:51
R W Run
32.15 KB
2026-03-11 16:18:51
R W Run
34.05 KB
2026-03-11 16:18:52
R W Run
7.15 KB
2026-03-11 16:18:51
R W Run
3.47 KB
2026-03-11 16:18:52
R W Run
1.87 KB
2026-03-11 16:18:52
R W Run
30.91 KB
2026-03-11 16:18:51
R W Run
7.29 KB
2026-03-11 16:18:52
R W Run
7.35 KB
2026-03-11 16:18:51
R W Run
12.54 KB
2026-03-11 16:18:51
R W Run
19.12 KB
2026-03-11 16:18:51
R W Run
18.12 KB
2026-03-11 16:18:52
R W Run
39.99 KB
2026-03-11 16:18:52
R W Run
5.17 KB
2026-03-11 16:18:52
R W Run
979 By
2026-03-11 16:18:51
R W Run
18.44 KB
2026-03-11 16:18:52
R W Run
10.24 KB
2026-03-11 16:18:51
R W Run
1.77 KB
2026-03-11 16:18:52
R W Run
34.9 KB
2026-03-11 16:18:51
R W Run
7.19 KB
2026-03-11 16:18:52
R W Run
160.5 KB
2026-03-11 16:18:51
R W Run
64.27 KB
2026-03-11 16:18:51
R W Run
27.95 KB
2026-03-11 16:18:51
R W Run
4.69 KB
2026-03-11 16:18:51
R W Run
2.94 KB
2026-03-11 16:18:51
R W Run
43.13 KB
2026-03-11 16:18:52
R W Run
2.25 KB
2026-03-11 16:18:52
R W Run
22.5 KB
2026-03-11 16:18:51
R W Run
13.01 KB
2026-03-11 16:18:52
R W Run
3.27 KB
2026-03-11 16:18:51
R W Run
18 KB
2026-03-11 16:18:51
R W Run
210.4 KB
2026-03-11 16:18:52
R W Run
25.86 KB
2026-03-11 16:18:52
R W Run
115.85 KB
2026-03-11 16:18:51
R W Run
373 By
2026-03-11 16:18:52
R W Run
343 By
2026-03-11 16:18:52
R W Run
338 By
2026-03-11 16:18:51
R W Run
100.73 KB
2026-03-11 16:18:52
R W Run
130.93 KB
2026-03-11 16:18:51
R W Run
19.1 KB
2026-03-11 16:18:51
R W Run
17.41 KB
2026-03-11 16:18:52
R W Run
41.98 KB
2026-03-11 16:18:52
R W Run
400 By
2026-03-11 16:18:52
R W Run
11.1 KB
2026-03-11 16:18:52
R W Run
37.02 KB
2026-03-11 16:18:51
R W Run
2.24 KB
2026-03-11 16:18:51
R W Run
188.13 KB
2026-03-11 16:18:51
R W Run
338 By
2026-03-11 16:18:51
R W Run
38 KB
2026-03-11 16:18:51
R W Run
4.02 KB
2026-03-11 16:18:52
R W Run
5.38 KB
2026-03-11 16:18:51
R W Run
3.05 KB
2026-03-11 16:18:52
R W Run
2.61 KB
2026-03-11 16:18:51
R W Run
1.16 KB
2026-03-11 16:18:52
R W Run
4.04 KB
2026-03-11 16:18:51
R W Run
3.71 KB
2026-03-11 16:18:51
R W Run
24.6 KB
2026-03-11 16:18:51
R W Run
9.56 KB
2026-03-11 16:18:51
R W Run
346.43 KB
2026-03-11 16:18:52
R W Run
281.84 KB
2026-03-11 16:18:52
R W Run
14.95 KB
2026-03-11 16:18:51
R W Run
8.44 KB
2026-03-11 16:18:52
R W Run
168.95 KB
2026-03-11 16:18:52
R W Run
20.71 KB
2026-03-11 16:18:52
R W Run
25.27 KB
2026-03-11 16:18:51
R W Run
5.72 KB
2026-03-11 16:18:51
R W Run
4.63 KB
2026-03-11 16:18:52
R W Run
81.73 KB
2026-03-11 16:18:51
R W Run
67.18 KB
2026-03-11 16:18:51
R W Run
156.36 KB
2026-03-11 16:18:52
R W Run
55.19 KB
2026-03-11 16:18:51
R W Run
162 By
2026-03-11 16:18:51
R W Run
61.72 KB
2026-03-11 16:18:51
R W Run
216.06 KB
2026-03-11 16:18:52
R W Run
65.09 KB
2026-03-11 16:18:51
R W Run
25.24 KB
2026-03-11 16:18:52
R W Run
4.81 KB
2026-03-11 16:18:51
R W Run
6.48 KB
2026-03-11 16:18:52
R W Run
21.25 KB
2026-03-11 16:18:51
R W Run
2.79 KB
2026-03-11 16:18:52
R W Run
89.69 KB
2026-03-11 16:18:52
R W Run
19.42 KB
2026-03-11 16:18:52
R W Run
3.69 KB
2026-03-11 16:18:52
R W Run
4.11 KB
2026-03-11 16:18:51
R W Run
40.74 KB
2026-03-11 16:18:51
R W Run
25.38 KB
2026-03-11 16:18:51
R W Run
43.31 KB
2026-03-11 16:18:52
R W Run
102.57 KB
2026-03-11 16:18:52
R W Run
6.18 KB
2026-03-11 16:18:51
R W Run
124.47 KB
2026-03-11 16:18:52
R W Run
35.65 KB
2026-03-11 16:18:52
R W Run
6.94 KB
2026-03-11 16:18:52
R W Run
67.04 KB
2026-03-11 16:18:52
R W Run
10.62 KB
2026-03-11 16:18:51
R W Run
289.35 KB
2026-03-11 16:18:52
R W Run
36.23 KB
2026-03-11 16:18:51
R W Run
200 By
2026-03-11 16:18:52
R W Run
200 By
2026-03-11 16:18:52
R W Run
98.29 KB
2026-03-11 16:18:52
R W Run
30.02 KB
2026-03-11 16:18:52
R W Run
19.03 KB
2026-03-11 16:18:52
R W Run
5.06 KB
2026-03-11 16:18:52
R W Run
255 By
2026-03-11 16:18:51
R W Run
22.66 KB
2026-03-11 16:18:52
R W Run
154.63 KB
2026-03-11 16:18:51
R W Run
9.68 KB
2026-03-11 16:18:51
R W Run
258 By
2026-03-11 16:18:51
R W Run
23.49 KB
2026-03-11 16:18:51
R W Run
3.16 KB
2026-03-11 16:18:51
R W Run
8.4 KB
2026-03-11 16:18:52
R W Run
441 By
2026-03-11 16:18:51
R W Run
7.39 KB
2026-03-11 16:18:51
R W Run
173 KB
2026-03-11 16:18:52
R W Run
544 By
2026-03-11 16:18:52
R W Run
4.17 KB
2026-03-11 16:18:51
R W Run
35.97 KB
2026-03-11 16:18:52
R W Run
1.69 KB
2026-03-11 16:18:51
R W Run
2.84 KB
2026-03-11 16:18:52
R W Run
6.09 KB
2026-03-11 16:18:51
R W Run
8.71 KB
2026-03-11 16:18:51
R W Run
131.84 KB
2026-03-11 16:18:51
R W Run
37.45 KB
2026-03-11 16:18:51
R W Run
173.89 KB
2026-03-11 16:18:51
R W Run
7.09 KB
2026-03-11 16:18:51
R W Run
6.41 KB
2026-03-11 16:18:51
R W Run
1.08 KB
2026-03-11 16:18:51
R W Run
69.46 KB
2026-03-11 16:18:52
R W Run
445 By
2026-03-11 16:18:51
R W Run
799 By
2026-03-11 16:18:52
R W Run
error_log
📄class-wpdb.php
1<?php
2/**
3 * WordPress database access abstraction class.
4 *
5 * Original code from {@link http://php.justinvincent.com Justin Vincent (justin@visunet.ie)}
6 *
7 * @package WordPress
8 * @subpackage Database
9 * @since 0.71
10 */
11
12/**
13 * @since 0.71
14 */
15define( 'EZSQL_VERSION', 'WP1.25' );
16
17/**
18 * @since 0.71
19 */
20define( 'OBJECT', 'OBJECT' );
21// phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ConstantNotUpperCase
22define( 'object', 'OBJECT' ); // Back compat.
23
24/**
25 * @since 2.5.0
26 */
27define( 'OBJECT_K', 'OBJECT_K' );
28
29/**
30 * @since 0.71
31 */
32define( 'ARRAY_A', 'ARRAY_A' );
33
34/**
35 * @since 0.71
36 */
37define( 'ARRAY_N', 'ARRAY_N' );
38
39/**
40 * WordPress database access abstraction class.
41 *
42 * This class is used to interact with a database without needing to use raw SQL statements.
43 * By default, WordPress uses this class to instantiate the global $wpdb object, providing
44 * access to the WordPress database.
45 *
46 * It is possible to replace the global instance with your own by setting the $wpdb global variable
47 * in wp-content/db.php file to your class. The wpdb class will still be included, so you can
48 * extend it or simply use your own.
49 *
50 * @link https://developer.wordpress.org/reference/classes/wpdb/
51 *
52 * @since 0.71
53 */
54#[AllowDynamicProperties]
55class wpdb {
56
57 /**
58 * Whether to show SQL/DB errors.
59 *
60 * Default is to show errors if both WP_DEBUG and WP_DEBUG_DISPLAY evaluate to true.
61 *
62 * @since 0.71
63 *
64 * @var bool
65 */
66 public $show_errors = false;
67
68 /**
69 * Whether to suppress errors during the DB bootstrapping. Default false.
70 *
71 * @since 2.5.0
72 *
73 * @var bool
74 */
75 public $suppress_errors = false;
76
77 /**
78 * The error encountered during the last query.
79 *
80 * @since 2.5.0
81 *
82 * @var string
83 */
84 public $last_error = '';
85
86 /**
87 * The number of queries made.
88 *
89 * @since 1.2.0
90 *
91 * @var int
92 */
93 public $num_queries = 0;
94
95 /**
96 * Count of rows returned by the last query.
97 *
98 * @since 0.71
99 *
100 * @var int
101 */
102 public $num_rows = 0;
103
104 /**
105 * Count of rows affected by the last query.
106 *
107 * @since 0.71
108 *
109 * @var int
110 */
111 public $rows_affected = 0;
112
113 /**
114 * The ID generated for an AUTO_INCREMENT column by the last query (usually INSERT).
115 *
116 * @since 0.71
117 *
118 * @var int
119 */
120 public $insert_id = 0;
121
122 /**
123 * The last query made.
124 *
125 * @since 0.71
126 *
127 * @var string
128 */
129 public $last_query;
130
131 /**
132 * Results of the last query.
133 *
134 * @since 0.71
135 *
136 * @var stdClass[]|null
137 */
138 public $last_result;
139
140 /**
141 * Database query result.
142 *
143 * Possible values:
144 *
145 * - `mysqli_result` instance for successful SELECT, SHOW, DESCRIBE, or EXPLAIN queries
146 * - `true` for other query types that were successful
147 * - `null` if a query is yet to be made or if the result has since been flushed
148 * - `false` if the query returned an error
149 *
150 * @since 0.71
151 *
152 * @var mysqli_result|bool|null
153 */
154 protected $result;
155
156 /**
157 * Cached column info, for confidence checking data before inserting.
158 *
159 * @since 4.2.0
160 *
161 * @var array
162 */
163 protected $col_meta = array();
164
165 /**
166 * Calculated character sets keyed by table name.
167 *
168 * @since 4.2.0
169 *
170 * @var string[]
171 */
172 protected $table_charset = array();
173
174 /**
175 * Whether text fields in the current query need to be confidence checked.
176 *
177 * @since 4.2.0
178 *
179 * @var bool
180 */
181 protected $check_current_query = true;
182
183 /**
184 * Flag to ensure we don't run into recursion problems when checking the collation.
185 *
186 * @since 4.2.0
187 *
188 * @see wpdb::check_safe_collation()
189 * @var bool
190 */
191 private $checking_collation = false;
192
193 /**
194 * Saved info on the table column.
195 *
196 * @since 0.71
197 *
198 * @var array
199 */
200 protected $col_info;
201
202 /**
203 * Log of queries that were executed, for debugging purposes.
204 *
205 * @since 1.5.0
206 * @since 2.5.0 The third element in each query log was added to record the calling functions.
207 * @since 5.1.0 The fourth element in each query log was added to record the start time.
208 * @since 5.3.0 The fifth element in each query log was added to record custom data.
209 *
210 * @var array[] {
211 * Array of arrays containing information about queries that were executed.
212 *
213 * @type array ...$0 {
214 * Data for each query.
215 *
216 * @type string $0 The query's SQL.
217 * @type float $1 Total time spent on the query, in seconds.
218 * @type string $2 Comma-separated list of the calling functions.
219 * @type float $3 Unix timestamp of the time at the start of the query.
220 * @type array $4 Custom query data.
221 * }
222 * }
223 */
224 public $queries;
225
226 /**
227 * The number of times to retry reconnecting before dying. Default 5.
228 *
229 * @since 3.9.0
230 *
231 * @see wpdb::check_connection()
232 * @var int
233 */
234 protected $reconnect_retries = 5;
235
236 /**
237 * WordPress table prefix.
238 *
239 * You can set this to have multiple WordPress installations in a single database.
240 *
241 * @since 2.5.0
242 *
243 * @var string
244 */
245 public $prefix = '';
246
247 /**
248 * WordPress base table prefix.
249 *
250 * @since 3.0.0
251 *
252 * @var string
253 */
254 public $base_prefix;
255
256 /**
257 * Whether the database queries are ready to start executing.
258 *
259 * @since 2.3.2
260 *
261 * @var bool
262 */
263 public $ready = false;
264
265 /**
266 * Blog ID.
267 *
268 * @since 3.0.0
269 *
270 * @var int
271 */
272 public $blogid = 0;
273
274 /**
275 * Site ID.
276 *
277 * @since 3.0.0
278 *
279 * @var int
280 */
281 public $siteid = 0;
282
283 /**
284 * List of WordPress per-site tables.
285 *
286 * @since 2.5.0
287 *
288 * @see wpdb::tables()
289 * @var string[]
290 */
291 public $tables = array(
292 'posts',
293 'comments',
294 'links',
295 'options',
296 'postmeta',
297 'terms',
298 'term_taxonomy',
299 'term_relationships',
300 'termmeta',
301 'commentmeta',
302 );
303
304 /**
305 * List of deprecated WordPress tables.
306 *
307 * 'categories', 'post2cat', and 'link2cat' were deprecated in 2.3.0, db version 5539.
308 *
309 * @since 2.9.0
310 *
311 * @see wpdb::tables()
312 * @var string[]
313 */
314 public $old_tables = array( 'categories', 'post2cat', 'link2cat' );
315
316 /**
317 * List of WordPress global tables.
318 *
319 * @since 3.0.0
320 *
321 * @see wpdb::tables()
322 * @var string[]
323 */
324 public $global_tables = array( 'users', 'usermeta' );
325
326 /**
327 * List of Multisite global tables.
328 *
329 * @since 3.0.0
330 *
331 * @see wpdb::tables()
332 * @var string[]
333 */
334 public $ms_global_tables = array(
335 'blogs',
336 'blogmeta',
337 'signups',
338 'site',
339 'sitemeta',
340 'registration_log',
341 );
342
343 /**
344 * List of deprecated WordPress Multisite global tables.
345 *
346 * @since 6.1.0
347 *
348 * @see wpdb::tables()
349 * @var string[]
350 */
351 public $old_ms_global_tables = array( 'sitecategories' );
352
353 /**
354 * WordPress Comments table.
355 *
356 * @since 1.5.0
357 *
358 * @var string
359 */
360 public $comments;
361
362 /**
363 * WordPress Comment Metadata table.
364 *
365 * @since 2.9.0
366 *
367 * @var string
368 */
369 public $commentmeta;
370
371 /**
372 * WordPress Links table.
373 *
374 * @since 1.5.0
375 *
376 * @var string
377 */
378 public $links;
379
380 /**
381 * WordPress Options table.
382 *
383 * @since 1.5.0
384 *
385 * @var string
386 */
387 public $options;
388
389 /**
390 * WordPress Post Metadata table.
391 *
392 * @since 1.5.0
393 *
394 * @var string
395 */
396 public $postmeta;
397
398 /**
399 * WordPress Posts table.
400 *
401 * @since 1.5.0
402 *
403 * @var string
404 */
405 public $posts;
406
407 /**
408 * WordPress Terms table.
409 *
410 * @since 2.3.0
411 *
412 * @var string
413 */
414 public $terms;
415
416 /**
417 * WordPress Term Relationships table.
418 *
419 * @since 2.3.0
420 *
421 * @var string
422 */
423 public $term_relationships;
424
425 /**
426 * WordPress Term Taxonomy table.
427 *
428 * @since 2.3.0
429 *
430 * @var string
431 */
432 public $term_taxonomy;
433
434 /**
435 * WordPress Term Meta table.
436 *
437 * @since 4.4.0
438 *
439 * @var string
440 */
441 public $termmeta;
442
443 //
444 // Global and Multisite tables
445 //
446
447 /**
448 * WordPress User Metadata table.
449 *
450 * @since 2.3.0
451 *
452 * @var string
453 */
454 public $usermeta;
455
456 /**
457 * WordPress Users table.
458 *
459 * @since 1.5.0
460 *
461 * @var string
462 */
463 public $users;
464
465 /**
466 * Multisite Blogs table.
467 *
468 * @since 3.0.0
469 *
470 * @var string|null
471 */
472 public $blogs;
473
474 /**
475 * Multisite Blog Metadata table.
476 *
477 * @since 5.1.0
478 *
479 * @var string|null
480 */
481 public $blogmeta;
482
483 /**
484 * Multisite Registration Log table.
485 *
486 * @since 3.0.0
487 *
488 * @var string|null
489 */
490 public $registration_log;
491
492 /**
493 * Multisite Signups table.
494 *
495 * @since 3.0.0
496 *
497 * @var string|null
498 */
499 public $signups;
500
501 /**
502 * Multisite Sites table.
503 *
504 * @since 3.0.0
505 *
506 * @var string|null
507 */
508 public $site;
509
510 /**
511 * Multisite Sitewide Terms table.
512 *
513 * @since 3.0.0
514 *
515 * @var string|null
516 */
517 public $sitecategories;
518
519 /**
520 * Multisite Site Metadata table.
521 *
522 * @since 3.0.0
523 *
524 * @var string|null
525 */
526 public $sitemeta;
527
528 /**
529 * Format specifiers for DB columns.
530 *
531 * Columns not listed here default to %s. Initialized during WP load.
532 * Keys are column names, values are format types: 'ID' => '%d'.
533 *
534 * @since 2.8.0
535 *
536 * @see wpdb::prepare()
537 * @see wpdb::insert()
538 * @see wpdb::update()
539 * @see wpdb::delete()
540 * @see wp_set_wpdb_vars()
541 * @var array
542 */
543 public $field_types = array();
544
545 /**
546 * Database table columns charset.
547 *
548 * @since 2.2.0
549 *
550 * @var string
551 */
552 public $charset;
553
554 /**
555 * Database table columns collate.
556 *
557 * @since 2.2.0
558 *
559 * @var string
560 */
561 public $collate;
562
563 /**
564 * Database Username.
565 *
566 * @since 2.9.0
567 *
568 * @var string
569 */
570 protected $dbuser;
571
572 /**
573 * Database Password.
574 *
575 * @since 3.1.0
576 *
577 * @var string
578 */
579 protected $dbpassword;
580
581 /**
582 * Database Name.
583 *
584 * @since 3.1.0
585 *
586 * @var string
587 */
588 protected $dbname;
589
590 /**
591 * Database Host.
592 *
593 * @since 3.1.0
594 *
595 * @var string
596 */
597 protected $dbhost;
598
599 /**
600 * Database handle.
601 *
602 * Possible values:
603 *
604 * - `mysqli` instance during normal operation
605 * - `null` if the connection is yet to be made or has been closed
606 * - `false` if the connection has failed
607 *
608 * @since 0.71
609 *
610 * @var mysqli|false|null
611 */
612 protected $dbh;
613
614 /**
615 * A textual description of the last query/get_row/get_var call.
616 *
617 * @since 3.0.0
618 *
619 * @var string
620 */
621 public $func_call;
622
623 /**
624 * Whether MySQL is used as the database engine.
625 *
626 * Set in wpdb::db_connect() to true, by default. This is used when checking
627 * against the required MySQL version for WordPress. Normally, a replacement
628 * database drop-in (db.php) will skip these checks, but setting this to true
629 * will force the checks to occur.
630 *
631 * @since 3.3.0
632 *
633 * @var bool
634 */
635 public $is_mysql = null;
636
637 /**
638 * A list of incompatible SQL modes.
639 *
640 * @since 3.9.0
641 *
642 * @var string[]
643 */
644 protected $incompatible_modes = array(
645 'NO_ZERO_DATE',
646 'ONLY_FULL_GROUP_BY',
647 'STRICT_TRANS_TABLES',
648 'STRICT_ALL_TABLES',
649 'TRADITIONAL',
650 'ANSI',
651 );
652
653 /**
654 * Backward compatibility, where wpdb::prepare() has not quoted formatted/argnum placeholders.
655 *
656 * This is often used for table/field names (before %i was supported), and sometimes string formatting, e.g.
657 *
658 * $wpdb->prepare( 'WHERE `%1$s` = "%2$s something %3$s" OR %1$s = "%4$-10s"', 'field_1', 'a', 'b', 'c' );
659 *
660 * But it's risky, e.g. forgetting to add quotes, resulting in SQL Injection vulnerabilities:
661 *
662 * $wpdb->prepare( 'WHERE (id = %1s) OR (id = %2$s)', $_GET['id'], $_GET['id'] ); // ?id=id
663 *
664 * This feature is preserved while plugin authors update their code to use safer approaches:
665 *
666 * $_GET['key'] = 'a`b';
667 *
668 * $wpdb->prepare( 'WHERE %1s = %s', $_GET['key'], $_GET['value'] ); // WHERE a`b = 'value'
669 * $wpdb->prepare( 'WHERE `%1$s` = "%2$s"', $_GET['key'], $_GET['value'] ); // WHERE `a`b` = "value"
670 *
671 * $wpdb->prepare( 'WHERE %i = %s', $_GET['key'], $_GET['value'] ); // WHERE `a``b` = 'value'
672 *
673 * While changing to false will be fine for queries not using formatted/argnum placeholders,
674 * any remaining cases are most likely going to result in SQL errors (good, in a way):
675 *
676 * $wpdb->prepare( 'WHERE %1$s = "%2$-10s"', 'my_field', 'my_value' );
677 * true = WHERE my_field = "my_value "
678 * false = WHERE 'my_field' = "'my_value '"
679 *
680 * But there may be some queries that result in an SQL Injection vulnerability:
681 *
682 * $wpdb->prepare( 'WHERE id = %1$s', $_GET['id'] ); // ?id=id
683 *
684 * So there may need to be a `_doing_it_wrong()` phase, after we know everyone can use
685 * identifier placeholders (%i), but before this feature is disabled or removed.
686 *
687 * @since 6.2.0
688 * @var bool
689 */
690 private $allow_unsafe_unquoted_parameters = true;
691
692 /**
693 * Whether to use the mysqli extension over mysql. This is no longer used as the mysql
694 * extension is no longer supported.
695 *
696 * Default true.
697 *
698 * @since 3.9.0
699 * @since 6.4.0 This property was removed.
700 * @since 6.4.1 This property was reinstated and its default value was changed to true.
701 * The property is no longer used in core but may be accessed externally.
702 *
703 * @var bool
704 */
705 private $use_mysqli = true;
706
707 /**
708 * Whether we've managed to successfully connect at some point.
709 *
710 * @since 3.9.0
711 *
712 * @var bool
713 */
714 private $has_connected = false;
715
716 /**
717 * Time when the last query was performed.
718 *
719 * Only set when `SAVEQUERIES` is defined and truthy.
720 *
721 * @since 1.5.0
722 *
723 * @var float
724 */
725 public $time_start = null;
726
727 /**
728 * The last SQL error that was encountered.
729 *
730 * @since 2.5.0
731 *
732 * @var WP_Error|string
733 */
734 public $error = null;
735
736 /**
737 * Connects to the database server and selects a database.
738 *
739 * Does the actual setting up
740 * of the class properties and connection to the database.
741 *
742 * @since 2.0.8
743 *
744 * @link https://core.trac.wordpress.org/ticket/3354
745 *
746 * @param string $dbuser Database user.
747 * @param string $dbpassword Database password.
748 * @param string $dbname Database name.
749 * @param string $dbhost Database host.
750 */
751 public function __construct(
752 $dbuser,
753 #[\SensitiveParameter]
754 $dbpassword,
755 $dbname,
756 $dbhost
757 ) {
758 if ( WP_DEBUG && WP_DEBUG_DISPLAY ) {
759 $this->show_errors();
760 }
761
762 $this->dbuser = $dbuser;
763 $this->dbpassword = $dbpassword;
764 $this->dbname = $dbname;
765 $this->dbhost = $dbhost;
766
767 // wp-config.php creation will manually connect when ready.
768 if ( defined( 'WP_SETUP_CONFIG' ) ) {
769 return;
770 }
771
772 $this->db_connect();
773 }
774
775 /**
776 * Makes private properties readable for backward compatibility.
777 *
778 * @since 3.5.0
779 *
780 * @param string $name The private member to get, and optionally process.
781 * @return mixed The private member.
782 */
783 public function __get( $name ) {
784 if ( 'col_info' === $name ) {
785 $this->load_col_info();
786 }
787
788 return $this->$name;
789 }
790
791 /**
792 * Makes private properties settable for backward compatibility.
793 *
794 * @since 3.5.0
795 *
796 * @param string $name The private member to set.
797 * @param mixed $value The value to set.
798 */
799 public function __set( $name, $value ) {
800 $protected_members = array(
801 'col_meta',
802 'table_charset',
803 'check_current_query',
804 'allow_unsafe_unquoted_parameters',
805 );
806 if ( in_array( $name, $protected_members, true ) ) {
807 return;
808 }
809 $this->$name = $value;
810 }
811
812 /**
813 * Makes private properties check-able for backward compatibility.
814 *
815 * @since 3.5.0
816 *
817 * @param string $name The private member to check.
818 * @return bool If the member is set or not.
819 */
820 public function __isset( $name ) {
821 return isset( $this->$name );
822 }
823
824 /**
825 * Makes private properties un-settable for backward compatibility.
826 *
827 * @since 3.5.0
828 *
829 * @param string $name The private member to unset.
830 */
831 public function __unset( $name ) {
832 unset( $this->$name );
833 }
834
835 /**
836 * Sets $this->charset and $this->collate.
837 *
838 * @since 3.1.0
839 */
840 public function init_charset() {
841 $charset = '';
842 $collate = '';
843
844 if ( function_exists( 'is_multisite' ) && is_multisite() ) {
845 $charset = 'utf8';
846 if ( defined( 'DB_COLLATE' ) && DB_COLLATE ) {
847 $collate = DB_COLLATE;
848 } else {
849 $collate = 'utf8_general_ci';
850 }
851 } elseif ( defined( 'DB_COLLATE' ) ) {
852 $collate = DB_COLLATE;
853 }
854
855 if ( defined( 'DB_CHARSET' ) ) {
856 $charset = DB_CHARSET;
857 }
858
859 $charset_collate = $this->determine_charset( $charset, $collate );
860
861 $this->charset = $charset_collate['charset'];
862 $this->collate = $charset_collate['collate'];
863 }
864
865 /**
866 * Determines the best charset and collation to use given a charset and collation.
867 *
868 * For example, when able, utf8mb4 should be used instead of utf8.
869 *
870 * @since 4.6.0
871 *
872 * @param string $charset The character set to check.
873 * @param string $collate The collation to check.
874 * @return array {
875 * The most appropriate character set and collation to use.
876 *
877 * @type string $charset Character set.
878 * @type string $collate Collation.
879 * }
880 */
881 public function determine_charset( $charset, $collate ) {
882 if ( ( ! ( $this->dbh instanceof mysqli ) ) || empty( $this->dbh ) ) {
883 return compact( 'charset', 'collate' );
884 }
885
886 if ( 'utf8' === $charset ) {
887 $charset = 'utf8mb4';
888 }
889
890 if ( 'utf8mb4' === $charset ) {
891 // _general_ is outdated, so we can upgrade it to _unicode_, instead.
892 if ( ! $collate || 'utf8_general_ci' === $collate ) {
893 $collate = 'utf8mb4_unicode_ci';
894 } else {
895 $collate = str_replace( 'utf8_', 'utf8mb4_', $collate );
896 }
897 }
898
899 // _unicode_520_ is a better collation, we should use that when it's available.
900 if ( $this->has_cap( 'utf8mb4_520' ) && 'utf8mb4_unicode_ci' === $collate ) {
901 $collate = 'utf8mb4_unicode_520_ci';
902 }
903
904 return compact( 'charset', 'collate' );
905 }
906
907 /**
908 * Sets the connection's character set.
909 *
910 * @since 3.1.0
911 *
912 * @param mysqli $dbh The connection returned by `mysqli_connect()`.
913 * @param string $charset Optional. The character set. Default null.
914 * @param string $collate Optional. The collation. Default null.
915 */
916 public function set_charset( $dbh, $charset = null, $collate = null ) {
917 if ( ! isset( $charset ) ) {
918 $charset = $this->charset;
919 }
920 if ( ! isset( $collate ) ) {
921 $collate = $this->collate;
922 }
923 if ( $this->has_cap( 'collation' ) && ! empty( $charset ) ) {
924 $set_charset_succeeded = true;
925
926 if ( function_exists( 'mysqli_set_charset' ) && $this->has_cap( 'set_charset' ) ) {
927 $set_charset_succeeded = mysqli_set_charset( $dbh, $charset );
928 }
929
930 if ( $set_charset_succeeded ) {
931 $query = $this->prepare( 'SET NAMES %s', $charset );
932 if ( ! empty( $collate ) ) {
933 $query .= $this->prepare( ' COLLATE %s', $collate );
934 }
935 mysqli_query( $dbh, $query );
936 }
937 }
938 }
939
940 /**
941 * Changes the current SQL mode, and ensures its WordPress compatibility.
942 *
943 * If no modes are passed, it will ensure the current SQL server modes are compatible.
944 *
945 * @since 3.9.0
946 *
947 * @param array $modes Optional. A list of SQL modes to set. Default empty array.
948 */
949 public function set_sql_mode( $modes = array() ) {
950 if ( empty( $modes ) ) {
951 $res = mysqli_query( $this->dbh, 'SELECT @@SESSION.sql_mode' );
952
953 if ( empty( $res ) ) {
954 return;
955 }
956
957 $modes_array = mysqli_fetch_array( $res );
958
959 if ( empty( $modes_array[0] ) ) {
960 return;
961 }
962
963 $modes_str = $modes_array[0];
964
965 if ( empty( $modes_str ) ) {
966 return;
967 }
968
969 $modes = explode( ',', $modes_str );
970 }
971
972 $modes = array_change_key_case( $modes, CASE_UPPER );
973
974 /**
975 * Filters the list of incompatible SQL modes to exclude.
976 *
977 * @since 3.9.0
978 *
979 * @param array $incompatible_modes An array of incompatible modes.
980 */
981 $incompatible_modes = (array) apply_filters( 'incompatible_sql_modes', $this->incompatible_modes );
982
983 foreach ( $modes as $i => $mode ) {
984 if ( in_array( $mode, $incompatible_modes, true ) ) {
985 unset( $modes[ $i ] );
986 }
987 }
988
989 $modes_str = implode( ',', $modes );
990
991 mysqli_query( $this->dbh, "SET SESSION sql_mode='$modes_str'" );
992 }
993
994 /**
995 * Sets the table prefix for the WordPress tables.
996 *
997 * @since 2.5.0
998 *
999 * @param string $prefix Alphanumeric name for the new prefix.
1000 * @param bool $set_table_names Optional. Whether the table names, e.g. wpdb::$posts,
1001 * should be updated or not. Default true.
1002 * @return string|WP_Error Old prefix or WP_Error on error.
1003 */
1004 public function set_prefix( $prefix, $set_table_names = true ) {
1005
1006 if ( preg_match( '|[^a-z0-9_]|i', $prefix ) ) {
1007 return new WP_Error( 'invalid_db_prefix', 'Invalid database prefix' );
1008 }
1009
1010 $old_prefix = is_multisite() ? '' : $prefix;
1011
1012 if ( isset( $this->base_prefix ) ) {
1013 $old_prefix = $this->base_prefix;
1014 }
1015
1016 $this->base_prefix = $prefix;
1017
1018 if ( $set_table_names ) {
1019 foreach ( $this->tables( 'global' ) as $table => $prefixed_table ) {
1020 $this->$table = $prefixed_table;
1021 }
1022
1023 if ( is_multisite() && empty( $this->blogid ) ) {
1024 return $old_prefix;
1025 }
1026
1027 $this->prefix = $this->get_blog_prefix();
1028
1029 foreach ( $this->tables( 'blog' ) as $table => $prefixed_table ) {
1030 $this->$table = $prefixed_table;
1031 }
1032
1033 foreach ( $this->tables( 'old' ) as $table => $prefixed_table ) {
1034 $this->$table = $prefixed_table;
1035 }
1036 }
1037 return $old_prefix;
1038 }
1039
1040 /**
1041 * Sets blog ID.
1042 *
1043 * @since 3.0.0
1044 *
1045 * @param int $blog_id
1046 * @param int $network_id Optional. Network ID. Default 0.
1047 * @return int Previous blog ID.
1048 */
1049 public function set_blog_id( $blog_id, $network_id = 0 ) {
1050 if ( ! empty( $network_id ) ) {
1051 $this->siteid = $network_id;
1052 }
1053
1054 $old_blog_id = $this->blogid;
1055 $this->blogid = $blog_id;
1056
1057 $this->prefix = $this->get_blog_prefix();
1058
1059 foreach ( $this->tables( 'blog' ) as $table => $prefixed_table ) {
1060 $this->$table = $prefixed_table;
1061 }
1062
1063 foreach ( $this->tables( 'old' ) as $table => $prefixed_table ) {
1064 $this->$table = $prefixed_table;
1065 }
1066
1067 return $old_blog_id;
1068 }
1069
1070 /**
1071 * Gets blog prefix.
1072 *
1073 * @since 3.0.0
1074 *
1075 * @param int $blog_id Optional. Blog ID to retrieve the table prefix for.
1076 * Defaults to the current blog ID.
1077 * @return string Blog prefix.
1078 */
1079 public function get_blog_prefix( $blog_id = null ) {
1080 if ( is_multisite() ) {
1081 if ( null === $blog_id ) {
1082 $blog_id = $this->blogid;
1083 }
1084
1085 $blog_id = (int) $blog_id;
1086
1087 if ( defined( 'MULTISITE' ) && ( 0 === $blog_id || 1 === $blog_id ) ) {
1088 return $this->base_prefix;
1089 } else {
1090 return $this->base_prefix . $blog_id . '_';
1091 }
1092 } else {
1093 return $this->base_prefix;
1094 }
1095 }
1096
1097 /**
1098 * Returns an array of WordPress tables.
1099 *
1100 * Also allows for the `CUSTOM_USER_TABLE` and `CUSTOM_USER_META_TABLE` to override the WordPress users
1101 * and usermeta tables that would otherwise be determined by the prefix.
1102 *
1103 * The `$scope` argument can take one of the following:
1104 *
1105 * - 'all' - returns 'all' and 'global' tables. No old tables are returned.
1106 * - 'blog' - returns the blog-level tables for the queried blog.
1107 * - 'global' - returns the global tables for the installation, returning multisite tables only on multisite.
1108 * - 'ms_global' - returns the multisite global tables, regardless if current installation is multisite.
1109 * - 'old' - returns tables which are deprecated.
1110 *
1111 * @since 3.0.0
1112 * @since 6.1.0 `old` now includes deprecated multisite global tables only on multisite.
1113 *
1114 * @uses wpdb::$tables
1115 * @uses wpdb::$old_tables
1116 * @uses wpdb::$global_tables
1117 * @uses wpdb::$ms_global_tables
1118 * @uses wpdb::$old_ms_global_tables
1119 *
1120 * @param string $scope Optional. Possible values include 'all', 'global', 'ms_global', 'blog',
1121 * or 'old' tables. Default 'all'.
1122 * @param bool $prefix Optional. Whether to include table prefixes. If blog prefix is requested,
1123 * then the custom users and usermeta tables will be mapped. Default true.
1124 * @param int $blog_id Optional. The blog_id to prefix. Used only when prefix is requested.
1125 * Defaults to `wpdb::$blogid`.
1126 * @return string[] Table names. When a prefix is requested, the key is the unprefixed table name.
1127 */
1128 public function tables( $scope = 'all', $prefix = true, $blog_id = 0 ) {
1129 switch ( $scope ) {
1130 case 'all':
1131 $tables = array_merge( $this->global_tables, $this->tables );
1132 if ( is_multisite() ) {
1133 $tables = array_merge( $tables, $this->ms_global_tables );
1134 }
1135 break;
1136 case 'blog':
1137 $tables = $this->tables;
1138 break;
1139 case 'global':
1140 $tables = $this->global_tables;
1141 if ( is_multisite() ) {
1142 $tables = array_merge( $tables, $this->ms_global_tables );
1143 }
1144 break;
1145 case 'ms_global':
1146 $tables = $this->ms_global_tables;
1147 break;
1148 case 'old':
1149 $tables = $this->old_tables;
1150 if ( is_multisite() ) {
1151 $tables = array_merge( $tables, $this->old_ms_global_tables );
1152 }
1153 break;
1154 default:
1155 return array();
1156 }
1157
1158 if ( $prefix ) {
1159 if ( ! $blog_id ) {
1160 $blog_id = $this->blogid;
1161 }
1162 $blog_prefix = $this->get_blog_prefix( $blog_id );
1163 $base_prefix = $this->base_prefix;
1164 $global_tables = array_merge( $this->global_tables, $this->ms_global_tables );
1165 foreach ( $tables as $k => $table ) {
1166 if ( in_array( $table, $global_tables, true ) ) {
1167 $tables[ $table ] = $base_prefix . $table;
1168 } else {
1169 $tables[ $table ] = $blog_prefix . $table;
1170 }
1171 unset( $tables[ $k ] );
1172 }
1173
1174 if ( isset( $tables['users'] ) && defined( 'CUSTOM_USER_TABLE' ) ) {
1175 $tables['users'] = CUSTOM_USER_TABLE;
1176 }
1177
1178 if ( isset( $tables['usermeta'] ) && defined( 'CUSTOM_USER_META_TABLE' ) ) {
1179 $tables['usermeta'] = CUSTOM_USER_META_TABLE;
1180 }
1181 }
1182
1183 return $tables;
1184 }
1185
1186 /**
1187 * Selects a database using the current or provided database connection.
1188 *
1189 * The database name will be changed based on the current database connection.
1190 * On failure, the execution will bail and display a DB error.
1191 *
1192 * @since 0.71
1193 *
1194 * @param string $db Database name.
1195 * @param mysqli $dbh Optional. Database connection.
1196 * Defaults to the current database handle.
1197 */
1198 public function select( $db, $dbh = null ) {
1199 if ( is_null( $dbh ) ) {
1200 $dbh = $this->dbh;
1201 }
1202
1203 $success = mysqli_select_db( $dbh, $db );
1204
1205 if ( ! $success ) {
1206 $this->ready = false;
1207 if ( ! did_action( 'template_redirect' ) ) {
1208 wp_load_translations_early();
1209
1210 $message = '<h1>' . __( 'Cannot select database' ) . "</h1>\n";
1211
1212 $message .= '<p>' . sprintf(
1213 /* translators: %s: Database name. */
1214 __( 'The database server could be connected to (which means your username and password is okay) but the %s database could not be selected.' ),
1215 '<code>' . htmlspecialchars( $db, ENT_QUOTES ) . '</code>'
1216 ) . "</p>\n";
1217
1218 $message .= "<ul>\n";
1219 $message .= '<li>' . __( 'Are you sure it exists?' ) . "</li>\n";
1220
1221 $message .= '<li>' . sprintf(
1222 /* translators: 1: Database user, 2: Database name. */
1223 __( 'Does the user %1$s have permission to use the %2$s database?' ),
1224 '<code>' . htmlspecialchars( $this->dbuser, ENT_QUOTES ) . '</code>',
1225 '<code>' . htmlspecialchars( $db, ENT_QUOTES ) . '</code>'
1226 ) . "</li>\n";
1227
1228 $message .= '<li>' . sprintf(
1229 /* translators: %s: Database name. */
1230 __( '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?' ),
1231 htmlspecialchars( $db, ENT_QUOTES )
1232 ) . "</li>\n";
1233
1234 $message .= "</ul>\n";
1235
1236 $message .= '<p>' . sprintf(
1237 /* translators: %s: Support forums URL. */
1238 __( 'If you do not 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>.' ),
1239 __( 'https://wordpress.org/support/forums/' )
1240 ) . "</p>\n";
1241
1242 $this->bail( $message, 'db_select_fail' );
1243 }
1244 }
1245 }
1246
1247 /**
1248 * Do not use, deprecated.
1249 *
1250 * Use esc_sql() or wpdb::prepare() instead.
1251 *
1252 * @since 2.8.0
1253 * @deprecated 3.6.0 Use wpdb::prepare()
1254 * @see wpdb::prepare()
1255 * @see esc_sql()
1256 *
1257 * @param string $data
1258 * @return string
1259 */
1260 public function _weak_escape( $data ) {
1261 if ( func_num_args() === 1 && function_exists( '_deprecated_function' ) ) {
1262 _deprecated_function( __METHOD__, '3.6.0', 'wpdb::prepare() or esc_sql()' );
1263 }
1264 return addslashes( $data );
1265 }
1266
1267 /**
1268 * Real escape using mysqli_real_escape_string().
1269 *
1270 * @since 2.8.0
1271 *
1272 * @see mysqli_real_escape_string()
1273 *
1274 * @param string $data String to escape.
1275 * @return string Escaped string.
1276 */
1277 public function _real_escape( $data ) {
1278 if ( ! is_scalar( $data ) ) {
1279 return '';
1280 }
1281
1282 if ( $this->dbh ) {
1283 $escaped = mysqli_real_escape_string( $this->dbh, $data );
1284 } else {
1285 $class = get_class( $this );
1286
1287 wp_load_translations_early();
1288 /* translators: %s: Database access abstraction class, usually wpdb or a class extending wpdb. */
1289 _doing_it_wrong( $class, sprintf( __( '%s must set a database connection for use with escaping.' ), $class ), '3.6.0' );
1290
1291 $escaped = addslashes( $data );
1292 }
1293
1294 return $this->add_placeholder_escape( $escaped );
1295 }
1296
1297 /**
1298 * Escapes data. Works on arrays.
1299 *
1300 * @since 2.8.0
1301 *
1302 * @uses wpdb::_real_escape()
1303 *
1304 * @param string|array $data Data to escape.
1305 * @return string|array Escaped data, in the same type as supplied.
1306 */
1307 public function _escape( $data ) {
1308 if ( is_array( $data ) ) {
1309 foreach ( $data as $k => $v ) {
1310 if ( is_array( $v ) ) {
1311 $data[ $k ] = $this->_escape( $v );
1312 } else {
1313 $data[ $k ] = $this->_real_escape( $v );
1314 }
1315 }
1316 } else {
1317 $data = $this->_real_escape( $data );
1318 }
1319
1320 return $data;
1321 }
1322
1323 /**
1324 * Do not use, deprecated.
1325 *
1326 * Use esc_sql() or wpdb::prepare() instead.
1327 *
1328 * @since 0.71
1329 * @deprecated 3.6.0 Use wpdb::prepare()
1330 * @see wpdb::prepare()
1331 * @see esc_sql()
1332 *
1333 * @param string|array $data Data to escape.
1334 * @return string|array Escaped data, in the same type as supplied.
1335 */
1336 public function escape( $data ) {
1337 if ( func_num_args() === 1 && function_exists( '_deprecated_function' ) ) {
1338 _deprecated_function( __METHOD__, '3.6.0', 'wpdb::prepare() or esc_sql()' );
1339 }
1340 if ( is_array( $data ) ) {
1341 foreach ( $data as $k => $v ) {
1342 if ( is_array( $v ) ) {
1343 $data[ $k ] = $this->escape( $v, 'recursive' );
1344 } else {
1345 $data[ $k ] = $this->_weak_escape( $v, 'internal' );
1346 }
1347 }
1348 } else {
1349 $data = $this->_weak_escape( $data, 'internal' );
1350 }
1351
1352 return $data;
1353 }
1354
1355 /**
1356 * Escapes content by reference for insertion into the database, for security.
1357 *
1358 * @uses wpdb::_real_escape()
1359 *
1360 * @since 2.3.0
1361 *
1362 * @param string $data String to escape.
1363 */
1364 public function escape_by_ref( &$data ) {
1365 if ( ! is_float( $data ) ) {
1366 $data = $this->_real_escape( $data );
1367 }
1368 }
1369
1370 /**
1371 * Quotes an identifier such as a table or field name.
1372 *
1373 * @since 6.2.0
1374 *
1375 * @param string $identifier Identifier to escape.
1376 * @return string Escaped identifier.
1377 */
1378 public function quote_identifier( $identifier ) {
1379 return '`' . $this->_escape_identifier_value( $identifier ) . '`';
1380 }
1381
1382 /**
1383 * Escapes an identifier value without adding the surrounding quotes.
1384 *
1385 * - Permitted characters in quoted identifiers include the full Unicode
1386 * Basic Multilingual Plane (BMP), except U+0000.
1387 * - To quote the identifier itself, you need to double the character, e.g. `a``b`.
1388 *
1389 * @since 6.2.0
1390 *
1391 * @link https://dev.mysql.com/doc/refman/8.0/en/identifiers.html
1392 *
1393 * @param string $identifier Identifier to escape.
1394 * @return string Escaped identifier.
1395 */
1396 private function _escape_identifier_value( $identifier ) {
1397 return str_replace( '`', '``', $identifier );
1398 }
1399
1400 /**
1401 * Prepares a SQL query for safe execution.
1402 *
1403 * Uses `sprintf()`-like syntax. The following placeholders can be used in the query string:
1404 *
1405 * - `%d` (integer)
1406 * - `%f` (float)
1407 * - `%s` (string)
1408 * - `%i` (identifier, e.g. table/field names)
1409 *
1410 * All placeholders MUST be left unquoted in the query string. A corresponding argument
1411 * MUST be passed for each placeholder.
1412 *
1413 * Note: There is one exception to the above: for compatibility with old behavior,
1414 * numbered or formatted string placeholders (eg, `%1$s`, `%5s`) will not have quotes
1415 * added by this function, so should be passed with appropriate quotes around them.
1416 *
1417 * Literal percentage signs (`%`) in the query string must be written as `%%`. Percentage wildcards
1418 * (for example, to use in LIKE syntax) must be passed via a substitution argument containing
1419 * the complete LIKE string, these cannot be inserted directly in the query string.
1420 * Also see wpdb::esc_like().
1421 *
1422 * Arguments may be passed as individual arguments to the method, or as a single array
1423 * containing all arguments. A combination of the two is not supported.
1424 *
1425 * Examples:
1426 *
1427 * $wpdb->prepare(
1428 * "SELECT * FROM `table` WHERE `column` = %s AND `field` = %d OR `other_field` LIKE %s",
1429 * array( 'foo', 1337, '%bar' )
1430 * );
1431 *
1432 * $wpdb->prepare(
1433 * "SELECT DATE_FORMAT(`field`, '%%c') FROM `table` WHERE `column` = %s",
1434 * 'foo'
1435 * );
1436 *
1437 * $wpdb->prepare(
1438 * "SELECT * FROM %i WHERE %i = %s",
1439 * $table,
1440 * $field,
1441 * $value
1442 * );
1443 *
1444 * @since 2.3.0
1445 * @since 5.3.0 Formalized the existing and already documented `...$args` parameter
1446 * by updating the function signature. The second parameter was changed
1447 * from `$args` to `...$args`.
1448 * @since 6.2.0 Added `%i` for identifiers, e.g. table or field names.
1449 * Check support via `wpdb::has_cap( 'identifier_placeholders' )`.
1450 * This preserves compatibility with `sprintf()`, as the C version uses
1451 * `%d` and `$i` as a signed integer, whereas PHP only supports `%d`.
1452 *
1453 * @link https://www.php.net/sprintf Description of syntax.
1454 *
1455 * @param string $query Query statement with `sprintf()`-like placeholders.
1456 * @param array|mixed $args The array of variables to substitute into the query's placeholders
1457 * if being called with an array of arguments, or the first variable
1458 * to substitute into the query's placeholders if being called with
1459 * individual arguments.
1460 * @param mixed ...$args Further variables to substitute into the query's placeholders
1461 * if being called with individual arguments.
1462 * @return string|void Sanitized query string, if there is a query to prepare.
1463 */
1464 public function prepare( $query, ...$args ) {
1465 if ( is_null( $query ) ) {
1466 return;
1467 }
1468
1469 /*
1470 * This is not meant to be foolproof -- but it will catch obviously incorrect usage.
1471 *
1472 * Note: str_contains() is not used here, as this file can be included
1473 * directly outside of WordPress core, e.g. by HyperDB, in which case
1474 * the polyfills from wp-includes/compat.php are not loaded.
1475 */
1476 if ( false === strpos( $query, '%' ) ) {
1477 wp_load_translations_early();
1478 _doing_it_wrong(
1479 'wpdb::prepare',
1480 sprintf(
1481 /* translators: %s: wpdb::prepare() */
1482 __( 'The query argument of %s must have a placeholder.' ),
1483 'wpdb::prepare()'
1484 ),
1485 '3.9.0'
1486 );
1487 }
1488
1489 /*
1490 * Specify the formatting allowed in a placeholder. The following are allowed:
1491 *
1492 * - Sign specifier, e.g. $+d
1493 * - Numbered placeholders, e.g. %1$s
1494 * - Padding specifier, including custom padding characters, e.g. %05s, %'#5s
1495 * - Alignment specifier, e.g. %05-s
1496 * - Precision specifier, e.g. %.2f
1497 */
1498 $allowed_format = '(?:[1-9][0-9]*[$])?[-+0-9]*(?: |0|\'.)?[-+0-9]*(?:\.[0-9]+)?';
1499
1500 /*
1501 * If a %s placeholder already has quotes around it, removing the existing quotes
1502 * and re-inserting them ensures the quotes are consistent.
1503 *
1504 * For backward compatibility, this is only applied to %s, and not to placeholders like %1$s,
1505 * which are frequently used in the middle of longer strings, or as table name placeholders.
1506 */
1507 $query = str_replace( "'%s'", '%s', $query ); // Strip any existing single quotes.
1508 $query = str_replace( '"%s"', '%s', $query ); // Strip any existing double quotes.
1509
1510 // Escape any unescaped percents (i.e. anything unrecognised).
1511 $query = preg_replace( "/%(?:%|$|(?!($allowed_format)?[sdfFi]))/", '%%\\1', $query );
1512
1513 // Extract placeholders from the query.
1514 $split_query = preg_split( "/(^|[^%]|(?:%%)+)(%(?:$allowed_format)?[sdfFi])/", $query, -1, PREG_SPLIT_DELIM_CAPTURE );
1515
1516 $split_query_count = count( $split_query );
1517
1518 /*
1519 * Split always returns with 1 value before the first placeholder (even with $query = "%s"),
1520 * then 3 additional values per placeholder.
1521 */
1522 $placeholder_count = ( ( $split_query_count - 1 ) / 3 );
1523
1524 // If args were passed as an array, as in vsprintf(), move them up.
1525 $passed_as_array = ( isset( $args[0] ) && is_array( $args[0] ) && 1 === count( $args ) );
1526 if ( $passed_as_array ) {
1527 $args = $args[0];
1528 }
1529
1530 $new_query = '';
1531 $key = 2; // Keys 0 and 1 in $split_query contain values before the first placeholder.
1532 $arg_id = 0;
1533 $arg_identifiers = array();
1534 $arg_strings = array();
1535
1536 while ( $key < $split_query_count ) {
1537 $placeholder = $split_query[ $key ];
1538
1539 $format = substr( $placeholder, 1, -1 );
1540 $type = substr( $placeholder, -1 );
1541
1542 if ( 'f' === $type && true === $this->allow_unsafe_unquoted_parameters
1543 /*
1544 * Note: str_ends_with() is not used here, as this file can be included
1545 * directly outside of WordPress core, e.g. by HyperDB, in which case
1546 * the polyfills from wp-includes/compat.php are not loaded.
1547 */
1548 && '%' === substr( $split_query[ $key - 1 ], -1, 1 )
1549 ) {
1550
1551 /*
1552 * Before WP 6.2 the "force floats to be locale-unaware" RegEx didn't
1553 * convert "%%%f" to "%%%F" (note the uppercase F).
1554 * This was because it didn't check to see if the leading "%" was escaped.
1555 * And because the "Escape any unescaped percents" RegEx used "[sdF]" in its
1556 * negative lookahead assertion, when there was an odd number of "%", it added
1557 * an extra "%", to give the fully escaped "%%%%f" (not a placeholder).
1558 */
1559
1560 $s = $split_query[ $key - 2 ] . $split_query[ $key - 1 ];
1561 $k = 1;
1562 $l = strlen( $s );
1563 while ( $k <= $l && '%' === $s[ $l - $k ] ) {
1564 ++$k;
1565 }
1566
1567 $placeholder = '%' . ( $k % 2 ? '%' : '' ) . $format . $type;
1568
1569 --$placeholder_count;
1570
1571 } else {
1572
1573 // Force floats to be locale-unaware.
1574 if ( 'f' === $type ) {
1575 $type = 'F';
1576 $placeholder = '%' . $format . $type;
1577 }
1578
1579 if ( 'i' === $type ) {
1580 $placeholder = '`%' . $format . 's`';
1581 // Using a simple strpos() due to previous checking (e.g. $allowed_format).
1582 $argnum_pos = strpos( $format, '$' );
1583
1584 if ( false !== $argnum_pos ) {
1585 // sprintf() argnum starts at 1, $arg_id from 0.
1586 $arg_identifiers[] = ( ( (int) substr( $format, 0, $argnum_pos ) ) - 1 );
1587 } else {
1588 $arg_identifiers[] = $arg_id;
1589 }
1590 } elseif ( 'd' !== $type && 'F' !== $type ) {
1591 /*
1592 * i.e. ( 's' === $type ), where 'd' and 'F' keeps $placeholder unchanged,
1593 * and we ensure string escaping is used as a safe default (e.g. even if 'x').
1594 */
1595 $argnum_pos = strpos( $format, '$' );
1596
1597 if ( false !== $argnum_pos ) {
1598 $arg_strings[] = ( ( (int) substr( $format, 0, $argnum_pos ) ) - 1 );
1599 } else {
1600 $arg_strings[] = $arg_id;
1601 }
1602
1603 /*
1604 * Unquoted strings for backward compatibility (dangerous).
1605 * First, "numbered or formatted string placeholders (eg, %1$s, %5s)".
1606 * Second, if "%s" has a "%" before it, even if it's unrelated (e.g. "LIKE '%%%s%%'").
1607 */
1608 if ( true !== $this->allow_unsafe_unquoted_parameters
1609 /*
1610 * Note: str_ends_with() is not used here, as this file can be included
1611 * directly outside of WordPress core, e.g. by HyperDB, in which case
1612 * the polyfills from wp-includes/compat.php are not loaded.
1613 */
1614 || ( '' === $format && '%' !== substr( $split_query[ $key - 1 ], -1, 1 ) )
1615 ) {
1616 $placeholder = "'%" . $format . "s'";
1617 }
1618 }
1619 }
1620
1621 // Glue (-2), any leading characters (-1), then the new $placeholder.
1622 $new_query .= $split_query[ $key - 2 ] . $split_query[ $key - 1 ] . $placeholder;
1623
1624 $key += 3;
1625 ++$arg_id;
1626 }
1627
1628 // Replace $query; and add remaining $query characters, or index 0 if there were no placeholders.
1629 $query = $new_query . $split_query[ $key - 2 ];
1630
1631 $dual_use = array_intersect( $arg_identifiers, $arg_strings );
1632
1633 if ( count( $dual_use ) > 0 ) {
1634 wp_load_translations_early();
1635
1636 $used_placeholders = array();
1637
1638 $key = 2;
1639 $arg_id = 0;
1640 // Parse again (only used when there is an error).
1641 while ( $key < $split_query_count ) {
1642 $placeholder = $split_query[ $key ];
1643
1644 $format = substr( $placeholder, 1, -1 );
1645
1646 $argnum_pos = strpos( $format, '$' );
1647
1648 if ( false !== $argnum_pos ) {
1649 $arg_pos = ( ( (int) substr( $format, 0, $argnum_pos ) ) - 1 );
1650 } else {
1651 $arg_pos = $arg_id;
1652 }
1653
1654 $used_placeholders[ $arg_pos ][] = $placeholder;
1655
1656 $key += 3;
1657 ++$arg_id;
1658 }
1659
1660 $conflicts = array();
1661 foreach ( $dual_use as $arg_pos ) {
1662 $conflicts[] = implode( ' and ', $used_placeholders[ $arg_pos ] );
1663 }
1664
1665 _doing_it_wrong(
1666 'wpdb::prepare',
1667 sprintf(
1668 /* translators: %s: A list of placeholders found to be a problem. */
1669 __( 'Arguments cannot be prepared as both an Identifier and Value. Found the following conflicts: %s' ),
1670 implode( ', ', $conflicts )
1671 ),
1672 '6.2.0'
1673 );
1674
1675 return;
1676 }
1677
1678 $args_count = count( $args );
1679
1680 if ( $args_count !== $placeholder_count ) {
1681 if ( 1 === $placeholder_count && $passed_as_array ) {
1682 /*
1683 * If the passed query only expected one argument,
1684 * but the wrong number of arguments was sent as an array, bail.
1685 */
1686 wp_load_translations_early();
1687 _doing_it_wrong(
1688 'wpdb::prepare',
1689 __( 'The query only expected one placeholder, but an array of multiple placeholders was sent.' ),
1690 '4.9.0'
1691 );
1692
1693 return;
1694 } else {
1695 /*
1696 * If we don't have the right number of placeholders,
1697 * but they were passed as individual arguments,
1698 * or we were expecting multiple arguments in an array, throw a warning.
1699 */
1700 wp_load_translations_early();
1701 _doing_it_wrong(
1702 'wpdb::prepare',
1703 sprintf(
1704 /* translators: 1: Number of placeholders, 2: Number of arguments passed. */
1705 __( 'The query does not contain the correct number of placeholders (%1$d) for the number of arguments passed (%2$d).' ),
1706 $placeholder_count,
1707 $args_count
1708 ),
1709 '4.8.3'
1710 );
1711
1712 /*
1713 * If we don't have enough arguments to match the placeholders,
1714 * return an empty string to avoid a fatal error on PHP 8.
1715 */
1716 if ( $args_count < $placeholder_count ) {
1717 $max_numbered_placeholder = 0;
1718
1719 for ( $i = 2, $l = $split_query_count; $i < $l; $i += 3 ) {
1720 // Assume a leading number is for a numbered placeholder, e.g. '%3$s'.
1721 $argnum = (int) substr( $split_query[ $i ], 1 );
1722
1723 if ( $max_numbered_placeholder < $argnum ) {
1724 $max_numbered_placeholder = $argnum;
1725 }
1726 }
1727
1728 if ( ! $max_numbered_placeholder || $args_count < $max_numbered_placeholder ) {
1729 return '';
1730 }
1731 }
1732 }
1733 }
1734
1735 $args_escaped = array();
1736
1737 foreach ( $args as $i => $value ) {
1738 if ( in_array( $i, $arg_identifiers, true ) ) {
1739 $args_escaped[] = $this->_escape_identifier_value( $value );
1740 } elseif ( is_int( $value ) || is_float( $value ) ) {
1741 $args_escaped[] = $value;
1742 } else {
1743 if ( ! is_scalar( $value ) && ! is_null( $value ) ) {
1744 wp_load_translations_early();
1745 _doing_it_wrong(
1746 'wpdb::prepare',
1747 sprintf(
1748 /* translators: %s: Value type. */
1749 __( 'Unsupported value type (%s).' ),
1750 gettype( $value )
1751 ),
1752 '4.8.2'
1753 );
1754
1755 // Preserving old behavior, where values are escaped as strings.
1756 $value = '';
1757 }
1758
1759 $args_escaped[] = $this->_real_escape( $value );
1760 }
1761 }
1762
1763 $query = vsprintf( $query, $args_escaped );
1764
1765 return $this->add_placeholder_escape( $query );
1766 }
1767
1768 /**
1769 * First half of escaping for `LIKE` special characters `%` and `_` before preparing for SQL.
1770 *
1771 * Use this only before wpdb::prepare() or esc_sql(). Reversing the order is very bad for security.
1772 *
1773 * Example Prepared Statement:
1774 *
1775 * $wild = '%';
1776 * $find = 'only 43% of planets';
1777 * $like = $wild . $wpdb->esc_like( $find ) . $wild;
1778 * $sql = $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE post_content LIKE %s", $like );
1779 *
1780 * Example Escape Chain:
1781 *
1782 * $sql = esc_sql( $wpdb->esc_like( $input ) );
1783 *
1784 * @since 4.0.0
1785 *
1786 * @param string $text The raw text to be escaped. The input typed by the user
1787 * should have no extra or deleted slashes.
1788 * @return string Text in the form of a LIKE phrase. The output is not SQL safe.
1789 * Call wpdb::prepare() or wpdb::_real_escape() next.
1790 */
1791 public function esc_like( $text ) {
1792 return addcslashes( $text, '_%\\' );
1793 }
1794
1795 /**
1796 * Prints SQL/DB error.
1797 *
1798 * @since 0.71
1799 *
1800 * @global array $EZSQL_ERROR Stores error information of query and error string.
1801 *
1802 * @param string $str The error to display.
1803 * @return void|false Void if the showing of errors is enabled, false if disabled.
1804 */
1805 public function print_error( $str = '' ) {
1806 global $EZSQL_ERROR;
1807
1808 if ( ! $str ) {
1809 $str = mysqli_error( $this->dbh );
1810 }
1811
1812 $EZSQL_ERROR[] = array(
1813 'query' => $this->last_query,
1814 'error_str' => $str,
1815 );
1816
1817 if ( $this->suppress_errors ) {
1818 return false;
1819 }
1820
1821 $caller = $this->get_caller();
1822 if ( $caller ) {
1823 // Not translated, as this will only appear in the error log.
1824 $error_str = sprintf( 'WordPress database error %1$s for query %2$s made by %3$s', $str, $this->last_query, $caller );
1825 } else {
1826 $error_str = sprintf( 'WordPress database error %1$s for query %2$s', $str, $this->last_query );
1827 }
1828
1829 error_log( $error_str );
1830
1831 // Are we showing errors?
1832 if ( ! $this->show_errors ) {
1833 return false;
1834 }
1835
1836 wp_load_translations_early();
1837
1838 // If there is an error then take note of it.
1839 if ( is_multisite() ) {
1840 $msg = sprintf(
1841 "%s [%s]\n%s\n",
1842 __( 'WordPress database error:' ),
1843 $str,
1844 $this->last_query
1845 );
1846
1847 if ( defined( 'ERRORLOGFILE' ) ) {
1848 error_log( $msg, 3, ERRORLOGFILE );
1849 }
1850 if ( defined( 'DIEONDBERROR' ) ) {
1851 wp_die( $msg );
1852 }
1853 } else {
1854 $str = htmlspecialchars( $str, ENT_QUOTES );
1855 $query = htmlspecialchars( $this->last_query, ENT_QUOTES );
1856
1857 printf(
1858 '<div id="error"><p class="wpdberror"><strong>%s</strong> [%s]<br /><code>%s</code></p></div>',
1859 __( 'WordPress database error:' ),
1860 $str,
1861 $query
1862 );
1863 }
1864 }
1865
1866 /**
1867 * Enables showing of database errors.
1868 *
1869 * This function should be used only to enable showing of errors.
1870 * wpdb::hide_errors() should be used instead for hiding errors.
1871 *
1872 * @since 0.71
1873 *
1874 * @see wpdb::hide_errors()
1875 *
1876 * @param bool $show Optional. Whether to show errors. Default true.
1877 * @return bool Whether showing of errors was previously active.
1878 */
1879 public function show_errors( $show = true ) {
1880 $errors = $this->show_errors;
1881 $this->show_errors = $show;
1882 return $errors;
1883 }
1884
1885 /**
1886 * Disables showing of database errors.
1887 *
1888 * By default database errors are not shown.
1889 *
1890 * @since 0.71
1891 *
1892 * @see wpdb::show_errors()
1893 *
1894 * @return bool Whether showing of errors was previously active.
1895 */
1896 public function hide_errors() {
1897 $show = $this->show_errors;
1898 $this->show_errors = false;
1899 return $show;
1900 }
1901
1902 /**
1903 * Enables or disables suppressing of database errors.
1904 *
1905 * By default database errors are suppressed.
1906 *
1907 * @since 2.5.0
1908 *
1909 * @see wpdb::hide_errors()
1910 *
1911 * @param bool $suppress Optional. Whether to suppress errors. Default true.
1912 * @return bool Whether suppressing of errors was previously active.
1913 */
1914 public function suppress_errors( $suppress = true ) {
1915 $errors = $this->suppress_errors;
1916 $this->suppress_errors = (bool) $suppress;
1917 return $errors;
1918 }
1919
1920 /**
1921 * Kills cached query results.
1922 *
1923 * @since 0.71
1924 */
1925 public function flush() {
1926 $this->last_result = array();
1927 $this->col_info = null;
1928 $this->last_query = null;
1929 $this->rows_affected = 0;
1930 $this->num_rows = 0;
1931 $this->last_error = '';
1932
1933 if ( $this->result instanceof mysqli_result ) {
1934 mysqli_free_result( $this->result );
1935 $this->result = null;
1936
1937 // Confidence check before using the handle.
1938 if ( empty( $this->dbh ) || ! ( $this->dbh instanceof mysqli ) ) {
1939 return;
1940 }
1941
1942 // Clear out any results from a multi-query.
1943 while ( mysqli_more_results( $this->dbh ) ) {
1944 mysqli_next_result( $this->dbh );
1945 }
1946 }
1947 }
1948
1949 /**
1950 * Connects to and selects database.
1951 *
1952 * If `$allow_bail` is false, the lack of database connection will need to be handled manually.
1953 *
1954 * @since 3.0.0
1955 * @since 3.9.0 $allow_bail parameter added.
1956 *
1957 * @param bool $allow_bail Optional. Allows the function to bail. Default true.
1958 * @return bool True with a successful connection, false on failure.
1959 */
1960 public function db_connect( $allow_bail = true ) {
1961 $this->is_mysql = true;
1962
1963 $client_flags = defined( 'MYSQL_CLIENT_FLAGS' ) ? MYSQL_CLIENT_FLAGS : 0;
1964
1965 /*
1966 * Switch error reporting off because WordPress handles its own.
1967 * This is due to the default value change from `MYSQLI_REPORT_OFF`
1968 * to `MYSQLI_REPORT_ERROR|MYSQLI_REPORT_STRICT` in PHP 8.1.
1969 */
1970 mysqli_report( MYSQLI_REPORT_OFF );
1971
1972 $this->dbh = mysqli_init();
1973
1974 $host = $this->dbhost;
1975 $port = null;
1976 $socket = null;
1977 $is_ipv6 = false;
1978
1979 $host_data = $this->parse_db_host( $this->dbhost );
1980 if ( $host_data ) {
1981 list( $host, $port, $socket, $is_ipv6 ) = $host_data;
1982 }
1983
1984 /*
1985 * If using the `mysqlnd` library, the IPv6 address needs to be enclosed
1986 * in square brackets, whereas it doesn't while using the `libmysqlclient` library.
1987 * @see https://bugs.php.net/bug.php?id=67563
1988 */
1989 if ( $is_ipv6 && extension_loaded( 'mysqlnd' ) ) {
1990 $host = "[$host]";
1991 }
1992
1993 if ( WP_DEBUG ) {
1994 mysqli_real_connect( $this->dbh, $host, $this->dbuser, $this->dbpassword, null, $port, $socket, $client_flags );
1995 } else {
1996 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
1997 @mysqli_real_connect( $this->dbh, $host, $this->dbuser, $this->dbpassword, null, $port, $socket, $client_flags );
1998 }
1999
2000 if ( $this->dbh->connect_errno ) {
2001 $this->dbh = null;
2002 }
2003
2004 if ( ! $this->dbh && $allow_bail ) {
2005 wp_load_translations_early();
2006
2007 // Load custom DB error template, if present.
2008 if ( file_exists( WP_CONTENT_DIR . '/db-error.php' ) ) {
2009 require_once WP_CONTENT_DIR . '/db-error.php';
2010 die();
2011 }
2012
2013 $message = '<h1>' . __( 'Error establishing a database connection' ) . "</h1>\n";
2014
2015 $message .= '<p>' . sprintf(
2016 /* translators: 1: wp-config.php, 2: Database host. */
2017 __( 'This either means that the username and password information in your %1$s file is incorrect or that contact with the database server at %2$s could not be established. This could mean your host&#8217;s database server is down.' ),
2018 '<code>wp-config.php</code>',
2019 '<code>' . htmlspecialchars( $this->dbhost, ENT_QUOTES ) . '</code>'
2020 ) . "</p>\n";
2021
2022 $message .= "<ul>\n";
2023 $message .= '<li>' . __( 'Are you sure you have the correct username and password?' ) . "</li>\n";
2024 $message .= '<li>' . __( 'Are you sure you have typed the correct hostname?' ) . "</li>\n";
2025 $message .= '<li>' . __( 'Are you sure the database server is running?' ) . "</li>\n";
2026 $message .= "</ul>\n";
2027
2028 $message .= '<p>' . sprintf(
2029 /* translators: %s: Support forums URL. */
2030 __( 'If you are 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>.' ),
2031 __( 'https://wordpress.org/support/forums/' )
2032 ) . "</p>\n";
2033
2034 $this->bail( $message, 'db_connect_fail' );
2035
2036 return false;
2037 } elseif ( $this->dbh ) {
2038 if ( ! $this->has_connected ) {
2039 $this->init_charset();
2040 }
2041
2042 $this->has_connected = true;
2043
2044 $this->set_charset( $this->dbh );
2045
2046 $this->ready = true;
2047 $this->set_sql_mode();
2048 $this->select( $this->dbname, $this->dbh );
2049
2050 return true;
2051 }
2052
2053 return false;
2054 }
2055
2056 /**
2057 * Parses the DB_HOST setting to interpret it for mysqli_real_connect().
2058 *
2059 * mysqli_real_connect() doesn't support the host param including a port or socket
2060 * like mysql_connect() does. This duplicates how mysql_connect() detects a port
2061 * and/or socket file.
2062 *
2063 * @since 4.9.0
2064 *
2065 * @param string $host The DB_HOST setting to parse.
2066 * @return array|false {
2067 * Array containing the host, the port, the socket and
2068 * whether it is an IPv6 address, in that order.
2069 * False if the host couldn't be parsed.
2070 *
2071 * @type string $0 Host name.
2072 * @type string|null $1 Port.
2073 * @type string|null $2 Socket.
2074 * @type bool $3 Whether it is an IPv6 address.
2075 * }
2076 */
2077 public function parse_db_host( $host ) {
2078 $socket = null;
2079 $is_ipv6 = false;
2080
2081 // First peel off the socket parameter from the right, if it exists.
2082 $socket_pos = strpos( $host, ':/' );
2083 if ( false !== $socket_pos ) {
2084 $socket = substr( $host, $socket_pos + 1 );
2085 $host = substr( $host, 0, $socket_pos );
2086 }
2087
2088 /*
2089 * We need to check for an IPv6 address first.
2090 * An IPv6 address will always contain at least two colons.
2091 */
2092 if ( substr_count( $host, ':' ) > 1 ) {
2093 $pattern = '#^(?:\[)?(?P<host>[0-9a-fA-F:]+)(?:\]:(?P<port>[\d]+))?#';
2094 $is_ipv6 = true;
2095 } else {
2096 // We seem to be dealing with an IPv4 address.
2097 $pattern = '#^(?P<host>[^:/]*)(?::(?P<port>[\d]+))?#';
2098 }
2099
2100 $matches = array();
2101 $result = preg_match( $pattern, $host, $matches );
2102
2103 if ( 1 !== $result ) {
2104 // Couldn't parse the address, bail.
2105 return false;
2106 }
2107
2108 $host = ! empty( $matches['host'] ) ? $matches['host'] : '';
2109 // Port cannot be a string; must be null or an integer.
2110 $port = ! empty( $matches['port'] ) ? absint( $matches['port'] ) : null;
2111
2112 return array( $host, $port, $socket, $is_ipv6 );
2113 }
2114
2115 /**
2116 * Checks that the connection to the database is still up. If not, try to reconnect.
2117 *
2118 * If this function is unable to reconnect, it will forcibly die, or if called
2119 * after the {@see 'template_redirect'} hook has been fired, return false instead.
2120 *
2121 * If `$allow_bail` is false, the lack of database connection will need to be handled manually.
2122 *
2123 * @since 3.9.0
2124 *
2125 * @param bool $allow_bail Optional. Allows the function to bail. Default true.
2126 * @return bool|void True if the connection is up.
2127 */
2128 public function check_connection( $allow_bail = true ) {
2129 // Check if the connection is alive.
2130 if ( ! empty( $this->dbh ) && mysqli_query( $this->dbh, 'DO 1' ) !== false ) {
2131 return true;
2132 }
2133
2134 $error_reporting = false;
2135
2136 // Disable warnings, as we don't want to see a multitude of "unable to connect" messages.
2137 if ( WP_DEBUG ) {
2138 $error_reporting = error_reporting();
2139 error_reporting( $error_reporting & ~E_WARNING );
2140 }
2141
2142 for ( $tries = 1; $tries <= $this->reconnect_retries; $tries++ ) {
2143 /*
2144 * On the last try, re-enable warnings. We want to see a single instance
2145 * of the "unable to connect" message on the bail() screen, if it appears.
2146 */
2147 if ( $this->reconnect_retries === $tries && WP_DEBUG ) {
2148 error_reporting( $error_reporting );
2149 }
2150
2151 if ( $this->db_connect( false ) ) {
2152 if ( $error_reporting ) {
2153 error_reporting( $error_reporting );
2154 }
2155
2156 return true;
2157 }
2158
2159 sleep( 1 );
2160 }
2161
2162 /*
2163 * If template_redirect has already happened, it's too late for wp_die()/dead_db().
2164 * Let's just return and hope for the best.
2165 */
2166 if ( did_action( 'template_redirect' ) ) {
2167 return false;
2168 }
2169
2170 if ( ! $allow_bail ) {
2171 return false;
2172 }
2173
2174 wp_load_translations_early();
2175
2176 $message = '<h1>' . __( 'Error reconnecting to the database' ) . "</h1>\n";
2177
2178 $message .= '<p>' . sprintf(
2179 /* translators: %s: Database host. */
2180 __( 'This means that the contact with the database server at %s was lost. This could mean your host&#8217;s database server is down.' ),
2181 '<code>' . htmlspecialchars( $this->dbhost, ENT_QUOTES ) . '</code>'
2182 ) . "</p>\n";
2183
2184 $message .= "<ul>\n";
2185 $message .= '<li>' . __( 'Are you sure the database server is running?' ) . "</li>\n";
2186 $message .= '<li>' . __( 'Are you sure the database server is not under particularly heavy load?' ) . "</li>\n";
2187 $message .= "</ul>\n";
2188
2189 $message .= '<p>' . sprintf(
2190 /* translators: %s: Support forums URL. */
2191 __( 'If you are 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>.' ),
2192 __( 'https://wordpress.org/support/forums/' )
2193 ) . "</p>\n";
2194
2195 // We weren't able to reconnect, so we better bail.
2196 $this->bail( $message, 'db_connect_fail' );
2197
2198 /*
2199 * Call dead_db() if bail didn't die, because this database is no more.
2200 * It has ceased to be (at least temporarily).
2201 */
2202 dead_db();
2203 }
2204
2205 /**
2206 * Performs a database query, using current database connection.
2207 *
2208 * More information can be found on the documentation page.
2209 *
2210 * @since 0.71
2211 *
2212 * @link https://developer.wordpress.org/reference/classes/wpdb/
2213 *
2214 * @param string $query Database query.
2215 * @return int|bool Boolean true for CREATE, ALTER, TRUNCATE and DROP queries. Number of rows
2216 * affected/selected for all other queries. Boolean false on error.
2217 */
2218 public function query( $query ) {
2219 if ( ! $this->ready ) {
2220 $this->check_current_query = true;
2221 return false;
2222 }
2223
2224 /**
2225 * Filters the database query.
2226 *
2227 * Some queries are made before the plugins have been loaded,
2228 * and thus cannot be filtered with this method.
2229 *
2230 * @since 2.1.0
2231 *
2232 * @param string $query Database query.
2233 */
2234 $query = apply_filters( 'query', $query );
2235
2236 if ( ! $query ) {
2237 $this->insert_id = 0;
2238 return false;
2239 }
2240
2241 $this->flush();
2242
2243 // Log how the function was called.
2244 $this->func_call = "\$db->query(\"$query\")";
2245
2246 // If we're writing to the database, make sure the query will write safely.
2247 if ( $this->check_current_query && ! $this->check_ascii( $query ) ) {
2248 $stripped_query = $this->strip_invalid_text_from_query( $query );
2249 /*
2250 * strip_invalid_text_from_query() can perform queries, so we need
2251 * to flush again, just to make sure everything is clear.
2252 */
2253 $this->flush();
2254 if ( $stripped_query !== $query ) {
2255 $this->insert_id = 0;
2256 $this->last_query = $query;
2257
2258 wp_load_translations_early();
2259
2260 $this->last_error = __( 'WordPress database error: Could not perform query because it contains invalid data.' );
2261
2262 return false;
2263 }
2264 }
2265
2266 $this->check_current_query = true;
2267
2268 // Keep track of the last query for debug.
2269 $this->last_query = $query;
2270
2271 $this->_do_query( $query );
2272
2273 // Database server has gone away, try to reconnect.
2274 $mysql_errno = 0;
2275
2276 if ( $this->dbh instanceof mysqli ) {
2277 $mysql_errno = mysqli_errno( $this->dbh );
2278 } else {
2279 /*
2280 * $dbh is defined, but isn't a real connection.
2281 * Something has gone horribly wrong, let's try a reconnect.
2282 */
2283 $mysql_errno = 2006;
2284 }
2285
2286 if ( empty( $this->dbh ) || 2006 === $mysql_errno ) {
2287 if ( $this->check_connection() ) {
2288 $this->_do_query( $query );
2289 } else {
2290 $this->insert_id = 0;
2291 return false;
2292 }
2293 }
2294
2295 // If there is an error then take note of it.
2296 if ( $this->dbh instanceof mysqli ) {
2297 $this->last_error = mysqli_error( $this->dbh );
2298 } else {
2299 $this->last_error = __( 'Unable to retrieve the error message from the database server' );
2300 }
2301
2302 if ( $this->last_error ) {
2303 // Clear insert_id on a subsequent failed insert.
2304 if ( $this->insert_id && preg_match( '/^\s*(insert|replace)\s/i', $query ) ) {
2305 $this->insert_id = 0;
2306 }
2307
2308 $this->print_error();
2309 return false;
2310 }
2311
2312 if ( preg_match( '/^\s*(create|alter|truncate|drop)\s/i', $query ) ) {
2313 $return_val = $this->result;
2314 } elseif ( preg_match( '/^\s*(insert|delete|update|replace)\s/i', $query ) ) {
2315 $this->rows_affected = mysqli_affected_rows( $this->dbh );
2316
2317 // Take note of the insert_id.
2318 if ( preg_match( '/^\s*(insert|replace)\s/i', $query ) ) {
2319 $this->insert_id = mysqli_insert_id( $this->dbh );
2320 }
2321
2322 // Return number of rows affected.
2323 $return_val = $this->rows_affected;
2324 } else {
2325 $num_rows = 0;
2326
2327 if ( $this->result instanceof mysqli_result ) {
2328 while ( $row = mysqli_fetch_object( $this->result ) ) {
2329 $this->last_result[ $num_rows ] = $row;
2330 ++$num_rows;
2331 }
2332 }
2333
2334 // Log and return the number of rows selected.
2335 $this->num_rows = $num_rows;
2336 $return_val = $num_rows;
2337 }
2338
2339 return $return_val;
2340 }
2341
2342 /**
2343 * Internal function to perform the mysqli_query() call.
2344 *
2345 * @since 3.9.0
2346 *
2347 * @see wpdb::query()
2348 *
2349 * @param string $query The query to run.
2350 */
2351 private function _do_query( $query ) {
2352 if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
2353 $this->timer_start();
2354 }
2355
2356 if ( ! empty( $this->dbh ) ) {
2357 $this->result = mysqli_query( $this->dbh, $query );
2358 }
2359
2360 ++$this->num_queries;
2361
2362 if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
2363 $this->log_query(
2364 $query,
2365 $this->timer_stop(),
2366 $this->get_caller(),
2367 $this->time_start,
2368 array()
2369 );
2370 }
2371 }
2372
2373 /**
2374 * Logs query data.
2375 *
2376 * @since 5.3.0
2377 *
2378 * @param string $query The query's SQL.
2379 * @param float $query_time Total time spent on the query, in seconds.
2380 * @param string $query_callstack Comma-separated list of the calling functions.
2381 * @param float $query_start Unix timestamp of the time at the start of the query.
2382 * @param array $query_data Custom query data.
2383 */
2384 public function log_query( $query, $query_time, $query_callstack, $query_start, $query_data ) {
2385 /**
2386 * Filters the custom data to log alongside a query.
2387 *
2388 * Caution should be used when modifying any of this data, it is recommended that any additional
2389 * information you need to store about a query be added as a new associative array element.
2390 *
2391 * @since 5.3.0
2392 *
2393 * @param array $query_data Custom query data.
2394 * @param string $query The query's SQL.
2395 * @param float $query_time Total time spent on the query, in seconds.
2396 * @param string $query_callstack Comma-separated list of the calling functions.
2397 * @param float $query_start Unix timestamp of the time at the start of the query.
2398 */
2399 $query_data = apply_filters( 'log_query_custom_data', $query_data, $query, $query_time, $query_callstack, $query_start );
2400
2401 $this->queries[] = array(
2402 $query,
2403 $query_time,
2404 $query_callstack,
2405 $query_start,
2406 $query_data,
2407 );
2408 }
2409
2410 /**
2411 * Generates and returns a placeholder escape string for use in queries returned by ::prepare().
2412 *
2413 * @since 4.8.3
2414 *
2415 * @return string String to escape placeholders.
2416 */
2417 public function placeholder_escape() {
2418 static $placeholder;
2419
2420 if ( ! $placeholder ) {
2421 // Old WP installs may not have AUTH_SALT defined.
2422 $salt = defined( 'AUTH_SALT' ) && AUTH_SALT ? AUTH_SALT : (string) rand();
2423
2424 $placeholder = '{' . hash_hmac( 'sha256', uniqid( $salt, true ), $salt ) . '}';
2425 }
2426
2427 /*
2428 * Add the filter to remove the placeholder escaper. Uses priority 0, so that anything
2429 * else attached to this filter will receive the query with the placeholder string removed.
2430 */
2431 if ( false === has_filter( 'query', array( $this, 'remove_placeholder_escape' ) ) ) {
2432 add_filter( 'query', array( $this, 'remove_placeholder_escape' ), 0 );
2433 }
2434
2435 return $placeholder;
2436 }
2437
2438 /**
2439 * Adds a placeholder escape string, to escape anything that resembles a printf() placeholder.
2440 *
2441 * @since 4.8.3
2442 *
2443 * @param string $query The query to escape.
2444 * @return string The query with the placeholder escape string inserted where necessary.
2445 */
2446 public function add_placeholder_escape( $query ) {
2447 /*
2448 * To prevent returning anything that even vaguely resembles a placeholder,
2449 * we clobber every % we can find.
2450 */
2451 return str_replace( '%', $this->placeholder_escape(), $query );
2452 }
2453
2454 /**
2455 * Removes the placeholder escape strings from a query.
2456 *
2457 * @since 4.8.3
2458 *
2459 * @param string $query The query from which the placeholder will be removed.
2460 * @return string The query with the placeholder removed.
2461 */
2462 public function remove_placeholder_escape( $query ) {
2463 return str_replace( $this->placeholder_escape(), '%', $query );
2464 }
2465
2466 /**
2467 * Inserts a row into the table.
2468 *
2469 * Examples:
2470 *
2471 * $wpdb->insert(
2472 * 'table',
2473 * array(
2474 * 'column1' => 'foo',
2475 * 'column2' => 'bar',
2476 * )
2477 * );
2478 * $wpdb->insert(
2479 * 'table',
2480 * array(
2481 * 'column1' => 'foo',
2482 * 'column2' => 1337,
2483 * ),
2484 * array(
2485 * '%s',
2486 * '%d',
2487 * )
2488 * );
2489 *
2490 * @since 2.5.0
2491 *
2492 * @see wpdb::prepare()
2493 * @see wpdb::$field_types
2494 * @see wp_set_wpdb_vars()
2495 *
2496 * @param string $table Table name.
2497 * @param array $data Data to insert (in column => value pairs).
2498 * Both `$data` columns and `$data` values should be "raw" (neither should be SQL escaped).
2499 * Sending a null value will cause the column to be set to NULL - the corresponding
2500 * format is ignored in this case.
2501 * @param string[]|string $format Optional. An array of formats to be mapped to each of the value in `$data`.
2502 * If string, that format will be used for all of the values in `$data`.
2503 * A format is one of '%d', '%f', '%s' (integer, float, string).
2504 * If omitted, all values in `$data` will be treated as strings unless otherwise
2505 * specified in wpdb::$field_types. Default null.
2506 * @return int|false The number of rows inserted, or false on error.
2507 */
2508 public function insert( $table, $data, $format = null ) {
2509 return $this->_insert_replace_helper( $table, $data, $format, 'INSERT' );
2510 }
2511
2512 /**
2513 * Replaces a row in the table or inserts it if it does not exist, based on a PRIMARY KEY or a UNIQUE index.
2514 *
2515 * A REPLACE works exactly like an INSERT, except that if an old row in the table has the same value as a new row
2516 * for a PRIMARY KEY or a UNIQUE index, the old row is deleted before the new row is inserted.
2517 *
2518 * Examples:
2519 *
2520 * $wpdb->replace(
2521 * 'table',
2522 * array(
2523 * 'ID' => 123,
2524 * 'column1' => 'foo',
2525 * 'column2' => 'bar',
2526 * )
2527 * );
2528 * $wpdb->replace(
2529 * 'table',
2530 * array(
2531 * 'ID' => 456,
2532 * 'column1' => 'foo',
2533 * 'column2' => 1337,
2534 * ),
2535 * array(
2536 * '%d',
2537 * '%s',
2538 * '%d',
2539 * )
2540 * );
2541 *
2542 * @since 3.0.0
2543 *
2544 * @see wpdb::prepare()
2545 * @see wpdb::$field_types
2546 * @see wp_set_wpdb_vars()
2547 *
2548 * @param string $table Table name.
2549 * @param array $data Data to insert (in column => value pairs).
2550 * Both `$data` columns and `$data` values should be "raw" (neither should be SQL escaped).
2551 * A primary key or unique index is required to perform a replace operation.
2552 * Sending a null value will cause the column to be set to NULL - the corresponding
2553 * format is ignored in this case.
2554 * @param string[]|string $format Optional. An array of formats to be mapped to each of the value in `$data`.
2555 * If string, that format will be used for all of the values in `$data`.
2556 * A format is one of '%d', '%f', '%s' (integer, float, string).
2557 * If omitted, all values in `$data` will be treated as strings unless otherwise
2558 * specified in wpdb::$field_types. Default null.
2559 * @return int|false The number of rows affected, or false on error.
2560 */
2561 public function replace( $table, $data, $format = null ) {
2562 return $this->_insert_replace_helper( $table, $data, $format, 'REPLACE' );
2563 }
2564
2565 /**
2566 * Helper function for insert and replace.
2567 *
2568 * Runs an insert or replace query based on `$type` argument.
2569 *
2570 * @since 3.0.0
2571 *
2572 * @see wpdb::prepare()
2573 * @see wpdb::$field_types
2574 * @see wp_set_wpdb_vars()
2575 *
2576 * @param string $table Table name.
2577 * @param array $data Data to insert (in column => value pairs).
2578 * Both `$data` columns and `$data` values should be "raw" (neither should be SQL escaped).
2579 * Sending a null value will cause the column to be set to NULL - the corresponding
2580 * format is ignored in this case.
2581 * @param string[]|string $format Optional. An array of formats to be mapped to each of the value in `$data`.
2582 * If string, that format will be used for all of the values in `$data`.
2583 * A format is one of '%d', '%f', '%s' (integer, float, string).
2584 * If omitted, all values in `$data` will be treated as strings unless otherwise
2585 * specified in wpdb::$field_types. Default null.
2586 * @param string $type Optional. Type of operation. Either 'INSERT' or 'REPLACE'.
2587 * Default 'INSERT'.
2588 * @return int|false The number of rows affected, or false on error.
2589 */
2590 public function _insert_replace_helper( $table, $data, $format = null, $type = 'INSERT' ) {
2591 $this->insert_id = 0;
2592
2593 if ( ! in_array( strtoupper( $type ), array( 'REPLACE', 'INSERT' ), true ) ) {
2594 return false;
2595 }
2596
2597 $data = $this->process_fields( $table, $data, $format );
2598 if ( false === $data ) {
2599 return false;
2600 }
2601
2602 $formats = array();
2603 $values = array();
2604 foreach ( $data as $value ) {
2605 if ( is_null( $value['value'] ) ) {
2606 $formats[] = 'NULL';
2607 continue;
2608 }
2609
2610 $formats[] = $value['format'];
2611 $values[] = $value['value'];
2612 }
2613
2614 $fields = '`' . implode( '`, `', array_keys( $data ) ) . '`';
2615 $formats = implode( ', ', $formats );
2616
2617 $sql = "$type INTO `$table` ($fields) VALUES ($formats)";
2618
2619 $this->check_current_query = false;
2620 return $this->query( $this->prepare( $sql, $values ) );
2621 }
2622
2623 /**
2624 * Updates a row in the table.
2625 *
2626 * Examples:
2627 *
2628 * $wpdb->update(
2629 * 'table',
2630 * array(
2631 * 'column1' => 'foo',
2632 * 'column2' => 'bar',
2633 * ),
2634 * array(
2635 * 'ID' => 1,
2636 * )
2637 * );
2638 * $wpdb->update(
2639 * 'table',
2640 * array(
2641 * 'column1' => 'foo',
2642 * 'column2' => 1337,
2643 * ),
2644 * array(
2645 * 'ID' => 1,
2646 * ),
2647 * array(
2648 * '%s',
2649 * '%d',
2650 * ),
2651 * array(
2652 * '%d',
2653 * )
2654 * );
2655 *
2656 * @since 2.5.0
2657 *
2658 * @see wpdb::prepare()
2659 * @see wpdb::$field_types
2660 * @see wp_set_wpdb_vars()
2661 *
2662 * @param string $table Table name.
2663 * @param array $data Data to update (in column => value pairs).
2664 * Both $data columns and $data values should be "raw" (neither should be SQL escaped).
2665 * Sending a null value will cause the column to be set to NULL - the corresponding
2666 * format is ignored in this case.
2667 * @param array $where A named array of WHERE clauses (in column => value pairs).
2668 * Multiple clauses will be joined with ANDs.
2669 * Both $where columns and $where values should be "raw".
2670 * Sending a null value will create an IS NULL comparison - the corresponding
2671 * format will be ignored in this case.
2672 * @param string[]|string $format Optional. An array of formats to be mapped to each of the values in $data.
2673 * If string, that format will be used for all of the values in $data.
2674 * A format is one of '%d', '%f', '%s' (integer, float, string).
2675 * If omitted, all values in $data will be treated as strings unless otherwise
2676 * specified in wpdb::$field_types. Default null.
2677 * @param string[]|string $where_format Optional. An array of formats to be mapped to each of the values in $where.
2678 * If string, that format will be used for all of the items in $where.
2679 * A format is one of '%d', '%f', '%s' (integer, float, string).
2680 * If omitted, all values in $where will be treated as strings unless otherwise
2681 * specified in wpdb::$field_types. Default null.
2682 * @return int|false The number of rows updated, or false on error.
2683 */
2684 public function update( $table, $data, $where, $format = null, $where_format = null ) {
2685 if ( ! is_array( $data ) || ! is_array( $where ) ) {
2686 return false;
2687 }
2688
2689 $data = $this->process_fields( $table, $data, $format );
2690 if ( false === $data ) {
2691 return false;
2692 }
2693 $where = $this->process_fields( $table, $where, $where_format );
2694 if ( false === $where ) {
2695 return false;
2696 }
2697
2698 $fields = array();
2699 $conditions = array();
2700 $values = array();
2701 foreach ( $data as $field => $value ) {
2702 if ( is_null( $value['value'] ) ) {
2703 $fields[] = "`$field` = NULL";
2704 continue;
2705 }
2706
2707 $fields[] = "`$field` = " . $value['format'];
2708 $values[] = $value['value'];
2709 }
2710 foreach ( $where as $field => $value ) {
2711 if ( is_null( $value['value'] ) ) {
2712 $conditions[] = "`$field` IS NULL";
2713 continue;
2714 }
2715
2716 $conditions[] = "`$field` = " . $value['format'];
2717 $values[] = $value['value'];
2718 }
2719
2720 $fields = implode( ', ', $fields );
2721 $conditions = implode( ' AND ', $conditions );
2722
2723 $sql = "UPDATE `$table` SET $fields WHERE $conditions";
2724
2725 $this->check_current_query = false;
2726 return $this->query( $this->prepare( $sql, $values ) );
2727 }
2728
2729 /**
2730 * Deletes a row in the table.
2731 *
2732 * Examples:
2733 *
2734 * $wpdb->delete(
2735 * 'table',
2736 * array(
2737 * 'ID' => 1,
2738 * )
2739 * );
2740 * $wpdb->delete(
2741 * 'table',
2742 * array(
2743 * 'ID' => 1,
2744 * ),
2745 * array(
2746 * '%d',
2747 * )
2748 * );
2749 *
2750 * @since 3.4.0
2751 *
2752 * @see wpdb::prepare()
2753 * @see wpdb::$field_types
2754 * @see wp_set_wpdb_vars()
2755 *
2756 * @param string $table Table name.
2757 * @param array $where A named array of WHERE clauses (in column => value pairs).
2758 * Multiple clauses will be joined with ANDs.
2759 * Both $where columns and $where values should be "raw".
2760 * Sending a null value will create an IS NULL comparison - the corresponding
2761 * format will be ignored in this case.
2762 * @param string[]|string $where_format Optional. An array of formats to be mapped to each of the values in $where.
2763 * If string, that format will be used for all of the items in $where.
2764 * A format is one of '%d', '%f', '%s' (integer, float, string).
2765 * If omitted, all values in $data will be treated as strings unless otherwise
2766 * specified in wpdb::$field_types. Default null.
2767 * @return int|false The number of rows deleted, or false on error.
2768 */
2769 public function delete( $table, $where, $where_format = null ) {
2770 if ( ! is_array( $where ) ) {
2771 return false;
2772 }
2773
2774 $where = $this->process_fields( $table, $where, $where_format );
2775 if ( false === $where ) {
2776 return false;
2777 }
2778
2779 $conditions = array();
2780 $values = array();
2781 foreach ( $where as $field => $value ) {
2782 if ( is_null( $value['value'] ) ) {
2783 $conditions[] = "`$field` IS NULL";
2784 continue;
2785 }
2786
2787 $conditions[] = "`$field` = " . $value['format'];
2788 $values[] = $value['value'];
2789 }
2790
2791 $conditions = implode( ' AND ', $conditions );
2792
2793 $sql = "DELETE FROM `$table` WHERE $conditions";
2794
2795 $this->check_current_query = false;
2796 return $this->query( $this->prepare( $sql, $values ) );
2797 }
2798
2799 /**
2800 * Processes arrays of field/value pairs and field formats.
2801 *
2802 * This is a helper method for wpdb's CRUD methods, which take field/value pairs
2803 * for inserts, updates, and where clauses. This method first pairs each value
2804 * with a format. Then it determines the charset of that field, using that
2805 * to determine if any invalid text would be stripped. If text is stripped,
2806 * then field processing is rejected and the query fails.
2807 *
2808 * @since 4.2.0
2809 *
2810 * @param string $table Table name.
2811 * @param array $data Array of values keyed by their field names.
2812 * @param string[]|string $format Formats or format to be mapped to the values in the data.
2813 * @return array|false An array of fields that contain paired value and formats.
2814 * False for invalid values.
2815 */
2816 protected function process_fields( $table, $data, $format ) {
2817 $data = $this->process_field_formats( $data, $format );
2818 if ( false === $data ) {
2819 return false;
2820 }
2821
2822 $data = $this->process_field_charsets( $data, $table );
2823 if ( false === $data ) {
2824 return false;
2825 }
2826
2827 $data = $this->process_field_lengths( $data, $table );
2828 if ( false === $data ) {
2829 return false;
2830 }
2831
2832 $converted_data = $this->strip_invalid_text( $data );
2833
2834 if ( $data !== $converted_data ) {
2835
2836 $problem_fields = array();
2837 foreach ( $data as $field => $value ) {
2838 if ( $value !== $converted_data[ $field ] ) {
2839 $problem_fields[] = $field;
2840 }
2841 }
2842
2843 wp_load_translations_early();
2844
2845 if ( 1 === count( $problem_fields ) ) {
2846 $this->last_error = sprintf(
2847 /* translators: %s: Database field where the error occurred. */
2848 __( 'WordPress database error: Processing the value for the following field failed: %s. The supplied value may be too long or contains invalid data.' ),
2849 reset( $problem_fields )
2850 );
2851 } else {
2852 $this->last_error = sprintf(
2853 /* translators: %s: Database fields where the error occurred. */
2854 __( 'WordPress database error: Processing the values for the following fields failed: %s. The supplied values may be too long or contain invalid data.' ),
2855 implode( ', ', $problem_fields )
2856 );
2857 }
2858
2859 return false;
2860 }
2861
2862 return $data;
2863 }
2864
2865 /**
2866 * Prepares arrays of value/format pairs as passed to wpdb CRUD methods.
2867 *
2868 * @since 4.2.0
2869 *
2870 * @param array $data Array of values keyed by their field names.
2871 * @param string[]|string $format Formats or format to be mapped to the values in the data.
2872 * @return array {
2873 * Array of values and formats keyed by their field names.
2874 *
2875 * @type array ...$0 {
2876 * Value and format for this field.
2877 *
2878 * @type mixed $value The value to be formatted.
2879 * @type string $format The format to be mapped to the value.
2880 * }
2881 * }
2882 */
2883 protected function process_field_formats( $data, $format ) {
2884 $formats = (array) $format;
2885 $original_formats = $formats;
2886
2887 foreach ( $data as $field => $value ) {
2888 $value = array(
2889 'value' => $value,
2890 'format' => '%s',
2891 );
2892
2893 if ( ! empty( $format ) ) {
2894 $value['format'] = array_shift( $formats );
2895 if ( ! $value['format'] ) {
2896 $value['format'] = reset( $original_formats );
2897 }
2898 } elseif ( isset( $this->field_types[ $field ] ) ) {
2899 $value['format'] = $this->field_types[ $field ];
2900 }
2901
2902 $data[ $field ] = $value;
2903 }
2904
2905 return $data;
2906 }
2907
2908 /**
2909 * Adds field charsets to field/value/format arrays generated by wpdb::process_field_formats().
2910 *
2911 * @since 4.2.0
2912 *
2913 * @param array $data {
2914 * Array of values and formats keyed by their field names,
2915 * as it comes from the wpdb::process_field_formats() method.
2916 *
2917 * @type array ...$0 {
2918 * Value and format for this field.
2919 *
2920 * @type mixed $value The value to be formatted.
2921 * @type string $format The format to be mapped to the value.
2922 * }
2923 * }
2924 * @param string $table Table name.
2925 * @return array|false {
2926 * The same array of data with additional 'charset' keys, or false if
2927 * the charset for the table cannot be found.
2928 *
2929 * @type array ...$0 {
2930 * Value, format, and charset for this field.
2931 *
2932 * @type mixed $value The value to be formatted.
2933 * @type string $format The format to be mapped to the value.
2934 * @type string|false $charset The charset to be used for the value.
2935 * }
2936 * }
2937 */
2938 protected function process_field_charsets( $data, $table ) {
2939 foreach ( $data as $field => $value ) {
2940 if ( '%d' === $value['format'] || '%f' === $value['format'] ) {
2941 /*
2942 * We can skip this field if we know it isn't a string.
2943 * This checks %d/%f versus ! %s because its sprintf() could take more.
2944 */
2945 $value['charset'] = false;
2946 } else {
2947 $value['charset'] = $this->get_col_charset( $table, $field );
2948 if ( is_wp_error( $value['charset'] ) ) {
2949 return false;
2950 }
2951 }
2952
2953 $data[ $field ] = $value;
2954 }
2955
2956 return $data;
2957 }
2958
2959 /**
2960 * For string fields, records the maximum string length that field can safely save.
2961 *
2962 * @since 4.2.1
2963 *
2964 * @param array $data {
2965 * Array of values, formats, and charsets keyed by their field names,
2966 * as it comes from the wpdb::process_field_charsets() method.
2967 *
2968 * @type array ...$0 {
2969 * Value, format, and charset for this field.
2970 *
2971 * @type mixed $value The value to be formatted.
2972 * @type string $format The format to be mapped to the value.
2973 * @type string|false $charset The charset to be used for the value.
2974 * }
2975 * }
2976 * @param string $table Table name.
2977 * @return array|false {
2978 * The same array of data with additional 'length' keys, or false if
2979 * information for the table cannot be found.
2980 *
2981 * @type array ...$0 {
2982 * Value, format, charset, and length for this field.
2983 *
2984 * @type mixed $value The value to be formatted.
2985 * @type string $format The format to be mapped to the value.
2986 * @type string|false $charset The charset to be used for the value.
2987 * @type array|false $length {
2988 * Information about the maximum length of the value.
2989 * False if the column has no length.
2990 *
2991 * @type string $type One of 'byte' or 'char'.
2992 * @type int $length The column length.
2993 * }
2994 * }
2995 * }
2996 */
2997 protected function process_field_lengths( $data, $table ) {
2998 foreach ( $data as $field => $value ) {
2999 if ( '%d' === $value['format'] || '%f' === $value['format'] ) {
3000 /*
3001 * We can skip this field if we know it isn't a string.
3002 * This checks %d/%f versus ! %s because its sprintf() could take more.
3003 */
3004 $value['length'] = false;
3005 } else {
3006 $value['length'] = $this->get_col_length( $table, $field );
3007 if ( is_wp_error( $value['length'] ) ) {
3008 return false;
3009 }
3010 }
3011
3012 $data[ $field ] = $value;
3013 }
3014
3015 return $data;
3016 }
3017
3018 /**
3019 * Retrieves one value from the database.
3020 *
3021 * Executes a SQL query and returns the value from the SQL result.
3022 * If the SQL result contains more than one column and/or more than one row,
3023 * the value in the column and row specified is returned. If $query is null,
3024 * the value in the specified column and row from the previous SQL result is returned.
3025 *
3026 * @since 0.71
3027 *
3028 * @param string|null $query Optional. SQL query. Defaults to null, use the result from the previous query.
3029 * @param int $x Optional. Column of value to return. Indexed from 0. Default 0.
3030 * @param int $y Optional. Row of value to return. Indexed from 0. Default 0.
3031 * @return string|null Database query result (as string), or null on failure.
3032 */
3033 public function get_var( $query = null, $x = 0, $y = 0 ) {
3034 $this->func_call = "\$db->get_var(\"$query\", $x, $y)";
3035
3036 if ( $query ) {
3037 if ( $this->check_current_query && $this->check_safe_collation( $query ) ) {
3038 $this->check_current_query = false;
3039 }
3040
3041 $this->query( $query );
3042 }
3043
3044 // Extract var out of cached results based on x,y vals.
3045 if ( ! empty( $this->last_result[ $y ] ) ) {
3046 $values = array_values( get_object_vars( $this->last_result[ $y ] ) );
3047 }
3048
3049 // If there is a value return it, else return null.
3050 return ( isset( $values[ $x ] ) && '' !== $values[ $x ] ) ? $values[ $x ] : null;
3051 }
3052
3053 /**
3054 * Retrieves one row from the database.
3055 *
3056 * Executes a SQL query and returns the row from the SQL result.
3057 *
3058 * @since 0.71
3059 *
3060 * @param string|null $query SQL query.
3061 * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which
3062 * correspond to an stdClass object, an associative array, or a numeric array,
3063 * respectively. Default OBJECT.
3064 * @param int $y Optional. Row to return. Indexed from 0. Default 0.
3065 * @return array|object|null|void Database query result in format specified by $output or null on failure.
3066 */
3067 public function get_row( $query = null, $output = OBJECT, $y = 0 ) {
3068 $this->func_call = "\$db->get_row(\"$query\",$output,$y)";
3069
3070 if ( $query ) {
3071 if ( $this->check_current_query && $this->check_safe_collation( $query ) ) {
3072 $this->check_current_query = false;
3073 }
3074
3075 $this->query( $query );
3076 } else {
3077 return null;
3078 }
3079
3080 if ( ! isset( $this->last_result[ $y ] ) ) {
3081 return null;
3082 }
3083
3084 if ( OBJECT === $output ) {
3085 return $this->last_result[ $y ] ? $this->last_result[ $y ] : null;
3086 } elseif ( ARRAY_A === $output ) {
3087 return $this->last_result[ $y ] ? get_object_vars( $this->last_result[ $y ] ) : null;
3088 } elseif ( ARRAY_N === $output ) {
3089 return $this->last_result[ $y ] ? array_values( get_object_vars( $this->last_result[ $y ] ) ) : null;
3090 } elseif ( OBJECT === strtoupper( $output ) ) {
3091 // Back compat for OBJECT being previously case-insensitive.
3092 return $this->last_result[ $y ] ? $this->last_result[ $y ] : null;
3093 } else {
3094 $this->print_error( ' $db->get_row(string query, output type, int offset) -- Output type must be one of: OBJECT, ARRAY_A, ARRAY_N' );
3095 }
3096 }
3097
3098 /**
3099 * Retrieves one column from the database.
3100 *
3101 * Executes a SQL query and returns the column from the SQL result.
3102 * If the SQL result contains more than one column, the column specified is returned.
3103 * If $query is null, the specified column from the previous SQL result is returned.
3104 *
3105 * @since 0.71
3106 *
3107 * @param string|null $query Optional. SQL query. Defaults to previous query.
3108 * @param int $x Optional. Column to return. Indexed from 0. Default 0.
3109 * @return array Database query result. Array indexed from 0 by SQL result row number.
3110 */
3111 public function get_col( $query = null, $x = 0 ) {
3112 if ( $query ) {
3113 if ( $this->check_current_query && $this->check_safe_collation( $query ) ) {
3114 $this->check_current_query = false;
3115 }
3116
3117 $this->query( $query );
3118 }
3119
3120 $new_array = array();
3121 // Extract the column values.
3122 if ( $this->last_result ) {
3123 for ( $i = 0, $j = count( $this->last_result ); $i < $j; $i++ ) {
3124 $new_array[ $i ] = $this->get_var( null, $x, $i );
3125 }
3126 }
3127 return $new_array;
3128 }
3129
3130 /**
3131 * Retrieves an entire SQL result set from the database (i.e., many rows).
3132 *
3133 * Executes a SQL query and returns the entire SQL result.
3134 *
3135 * @since 0.71
3136 *
3137 * @param string $query SQL query.
3138 * @param string $output Optional. Any of ARRAY_A | ARRAY_N | OBJECT | OBJECT_K constants.
3139 * With one of the first three, return an array of rows indexed
3140 * from 0 by SQL result row number. Each row is an associative array
3141 * (column => value, ...), a numerically indexed array (0 => value, ...),
3142 * or an object ( ->column = value ), respectively. With OBJECT_K,
3143 * return an associative array of row objects keyed by the value
3144 * of each row's first column's value. Duplicate keys are discarded.
3145 * Default OBJECT.
3146 * @return array|object|null Database query results.
3147 */
3148 public function get_results( $query = null, $output = OBJECT ) {
3149 $this->func_call = "\$db->get_results(\"$query\", $output)";
3150
3151 if ( $query ) {
3152 if ( $this->check_current_query && $this->check_safe_collation( $query ) ) {
3153 $this->check_current_query = false;
3154 }
3155
3156 $this->query( $query );
3157 } else {
3158 return null;
3159 }
3160
3161 $new_array = array();
3162 if ( OBJECT === $output ) {
3163 // Return an integer-keyed array of row objects.
3164 return $this->last_result;
3165 } elseif ( OBJECT_K === $output ) {
3166 /*
3167 * Return an array of row objects with keys from column 1.
3168 * (Duplicates are discarded.)
3169 */
3170 if ( $this->last_result ) {
3171 foreach ( $this->last_result as $row ) {
3172 $var_by_ref = get_object_vars( $row );
3173 $key = array_shift( $var_by_ref );
3174 if ( ! isset( $new_array[ $key ] ) ) {
3175 $new_array[ $key ] = $row;
3176 }
3177 }
3178 }
3179 return $new_array;
3180 } elseif ( ARRAY_A === $output || ARRAY_N === $output ) {
3181 // Return an integer-keyed array of...
3182 if ( $this->last_result ) {
3183 if ( ARRAY_N === $output ) {
3184 foreach ( (array) $this->last_result as $row ) {
3185 // ...integer-keyed row arrays.
3186 $new_array[] = array_values( get_object_vars( $row ) );
3187 }
3188 } else {
3189 foreach ( (array) $this->last_result as $row ) {
3190 // ...column name-keyed row arrays.
3191 $new_array[] = get_object_vars( $row );
3192 }
3193 }
3194 }
3195 return $new_array;
3196 } elseif ( strtoupper( $output ) === OBJECT ) {
3197 // Back compat for OBJECT being previously case-insensitive.
3198 return $this->last_result;
3199 }
3200 return null;
3201 }
3202
3203 /**
3204 * Retrieves the character set for the given table.
3205 *
3206 * @since 4.2.0
3207 *
3208 * @param string $table Table name.
3209 * @return string|WP_Error Table character set, WP_Error object if it couldn't be found.
3210 */
3211 protected function get_table_charset( $table ) {
3212 $tablekey = strtolower( $table );
3213
3214 /**
3215 * Filters the table charset value before the DB is checked.
3216 *
3217 * Returning a non-null value from the filter will effectively short-circuit
3218 * checking the DB for the charset, returning that value instead.
3219 *
3220 * @since 4.2.0
3221 *
3222 * @param string|WP_Error|null $charset The character set to use, WP_Error object
3223 * if it couldn't be found. Default null.
3224 * @param string $table The name of the table being checked.
3225 */
3226 $charset = apply_filters( 'pre_get_table_charset', null, $table );
3227 if ( null !== $charset ) {
3228 return $charset;
3229 }
3230
3231 if ( isset( $this->table_charset[ $tablekey ] ) ) {
3232 return $this->table_charset[ $tablekey ];
3233 }
3234
3235 $charsets = array();
3236 $columns = array();
3237
3238 $table_parts = explode( '.', $table );
3239 $table = '`' . implode( '`.`', $table_parts ) . '`';
3240 $results = $this->get_results( "SHOW FULL COLUMNS FROM $table" );
3241 if ( ! $results ) {
3242 return new WP_Error( 'wpdb_get_table_charset_failure', __( 'Could not retrieve table charset.' ) );
3243 }
3244
3245 foreach ( $results as $column ) {
3246 $columns[ strtolower( $column->Field ) ] = $column;
3247 }
3248
3249 $this->col_meta[ $tablekey ] = $columns;
3250
3251 foreach ( $columns as $column ) {
3252 if ( ! empty( $column->Collation ) ) {
3253 list( $charset ) = explode( '_', $column->Collation );
3254
3255 $charsets[ strtolower( $charset ) ] = true;
3256 }
3257
3258 list( $type ) = explode( '(', $column->Type );
3259
3260 // A binary/blob means the whole query gets treated like this.
3261 if ( in_array( strtoupper( $type ), array( 'BINARY', 'VARBINARY', 'TINYBLOB', 'MEDIUMBLOB', 'BLOB', 'LONGBLOB' ), true ) ) {
3262 $this->table_charset[ $tablekey ] = 'binary';
3263 return 'binary';
3264 }
3265 }
3266
3267 // utf8mb3 is an alias for utf8.
3268 if ( isset( $charsets['utf8mb3'] ) ) {
3269 $charsets['utf8'] = true;
3270 unset( $charsets['utf8mb3'] );
3271 }
3272
3273 // Check if we have more than one charset in play.
3274 $count = count( $charsets );
3275 if ( 1 === $count ) {
3276 $charset = key( $charsets );
3277 } elseif ( 0 === $count ) {
3278 // No charsets, assume this table can store whatever.
3279 $charset = false;
3280 } else {
3281 // More than one charset. Remove latin1 if present and recalculate.
3282 unset( $charsets['latin1'] );
3283 $count = count( $charsets );
3284 if ( 1 === $count ) {
3285 // Only one charset (besides latin1).
3286 $charset = key( $charsets );
3287 } elseif ( 2 === $count && isset( $charsets['utf8'], $charsets['utf8mb4'] ) ) {
3288 // Two charsets, but they're utf8 and utf8mb4, use utf8.
3289 $charset = 'utf8';
3290 } else {
3291 // Two mixed character sets. ascii.
3292 $charset = 'ascii';
3293 }
3294 }
3295
3296 $this->table_charset[ $tablekey ] = $charset;
3297 return $charset;
3298 }
3299
3300 /**
3301 * Retrieves the character set for the given column.
3302 *
3303 * @since 4.2.0
3304 *
3305 * @param string $table Table name.
3306 * @param string $column Column name.
3307 * @return string|false|WP_Error Column character set as a string. False if the column has
3308 * no character set. WP_Error object if there was an error.
3309 */
3310 public function get_col_charset( $table, $column ) {
3311 $tablekey = strtolower( $table );
3312 $columnkey = strtolower( $column );
3313
3314 /**
3315 * Filters the column charset value before the DB is checked.
3316 *
3317 * Passing a non-null value to the filter will short-circuit
3318 * checking the DB for the charset, returning that value instead.
3319 *
3320 * @since 4.2.0
3321 *
3322 * @param string|null|false|WP_Error $charset The character set to use. Default null.
3323 * @param string $table The name of the table being checked.
3324 * @param string $column The name of the column being checked.
3325 */
3326 $charset = apply_filters( 'pre_get_col_charset', null, $table, $column );
3327 if ( null !== $charset ) {
3328 return $charset;
3329 }
3330
3331 // Skip this entirely if this isn't a MySQL database.
3332 if ( empty( $this->is_mysql ) ) {
3333 return false;
3334 }
3335
3336 if ( empty( $this->table_charset[ $tablekey ] ) ) {
3337 // This primes column information for us.
3338 $table_charset = $this->get_table_charset( $table );
3339 if ( is_wp_error( $table_charset ) ) {
3340 return $table_charset;
3341 }
3342 }
3343
3344 // If still no column information, return the table charset.
3345 if ( empty( $this->col_meta[ $tablekey ] ) ) {
3346 return $this->table_charset[ $tablekey ];
3347 }
3348
3349 // If this column doesn't exist, return the table charset.
3350 if ( empty( $this->col_meta[ $tablekey ][ $columnkey ] ) ) {
3351 return $this->table_charset[ $tablekey ];
3352 }
3353
3354 // Return false when it's not a string column.
3355 if ( empty( $this->col_meta[ $tablekey ][ $columnkey ]->Collation ) ) {
3356 return false;
3357 }
3358
3359 list( $charset ) = explode( '_', $this->col_meta[ $tablekey ][ $columnkey ]->Collation );
3360 return $charset;
3361 }
3362
3363 /**
3364 * Retrieves the maximum string length allowed in a given column.
3365 *
3366 * The length may either be specified as a byte length or a character length.
3367 *
3368 * @since 4.2.1
3369 *
3370 * @param string $table Table name.
3371 * @param string $column Column name.
3372 * @return array|false|WP_Error {
3373 * Array of column length information, false if the column has no length (for
3374 * example, numeric column), WP_Error object if there was an error.
3375 *
3376 * @type string $type One of 'byte' or 'char'.
3377 * @type int $length The column length.
3378 * }
3379 */
3380 public function get_col_length( $table, $column ) {
3381 $tablekey = strtolower( $table );
3382 $columnkey = strtolower( $column );
3383
3384 // Skip this entirely if this isn't a MySQL database.
3385 if ( empty( $this->is_mysql ) ) {
3386 return false;
3387 }
3388
3389 if ( empty( $this->col_meta[ $tablekey ] ) ) {
3390 // This primes column information for us.
3391 $table_charset = $this->get_table_charset( $table );
3392 if ( is_wp_error( $table_charset ) ) {
3393 return $table_charset;
3394 }
3395 }
3396
3397 if ( empty( $this->col_meta[ $tablekey ][ $columnkey ] ) ) {
3398 return false;
3399 }
3400
3401 $typeinfo = explode( '(', $this->col_meta[ $tablekey ][ $columnkey ]->Type );
3402
3403 $type = strtolower( $typeinfo[0] );
3404 if ( ! empty( $typeinfo[1] ) ) {
3405 $length = trim( $typeinfo[1], ')' );
3406 } else {
3407 $length = false;
3408 }
3409
3410 switch ( $type ) {
3411 case 'char':
3412 case 'varchar':
3413 return array(
3414 'type' => 'char',
3415 'length' => (int) $length,
3416 );
3417
3418 case 'binary':
3419 case 'varbinary':
3420 return array(
3421 'type' => 'byte',
3422 'length' => (int) $length,
3423 );
3424
3425 case 'tinyblob':
3426 case 'tinytext':
3427 return array(
3428 'type' => 'byte',
3429 'length' => 255, // 2^8 - 1
3430 );
3431
3432 case 'blob':
3433 case 'text':
3434 return array(
3435 'type' => 'byte',
3436 'length' => 65535, // 2^16 - 1
3437 );
3438
3439 case 'mediumblob':
3440 case 'mediumtext':
3441 return array(
3442 'type' => 'byte',
3443 'length' => 16777215, // 2^24 - 1
3444 );
3445
3446 case 'longblob':
3447 case 'longtext':
3448 return array(
3449 'type' => 'byte',
3450 'length' => 4294967295, // 2^32 - 1
3451 );
3452
3453 default:
3454 return false;
3455 }
3456 }
3457
3458 /**
3459 * Checks if a string is ASCII.
3460 *
3461 * The negative regex is faster for non-ASCII strings, as it allows
3462 * the search to finish as soon as it encounters a non-ASCII character.
3463 *
3464 * @since 4.2.0
3465 *
3466 * @param string $input_string String to check.
3467 * @return bool True if ASCII, false if not.
3468 */
3469 protected function check_ascii( $input_string ) {
3470 if ( function_exists( 'mb_check_encoding' ) ) {
3471 if ( mb_check_encoding( $input_string, 'ASCII' ) ) {
3472 return true;
3473 }
3474 } elseif ( ! preg_match( '/[^\x00-\x7F]/', $input_string ) ) {
3475 return true;
3476 }
3477
3478 return false;
3479 }
3480
3481 /**
3482 * Checks if the query is accessing a collation considered safe.
3483 *
3484 * @since 4.2.0
3485 *
3486 * @param string $query The query to check.
3487 * @return bool True if the collation is safe, false if it isn't.
3488 */
3489 protected function check_safe_collation( $query ) {
3490 if ( $this->checking_collation ) {
3491 return true;
3492 }
3493
3494 // We don't need to check the collation for queries that don't read data.
3495 $query = ltrim( $query, "\r\n\t (" );
3496 if ( preg_match( '/^(?:SHOW|DESCRIBE|DESC|EXPLAIN|CREATE)\s/i', $query ) ) {
3497 return true;
3498 }
3499
3500 // All-ASCII queries don't need extra checking.
3501 if ( $this->check_ascii( $query ) ) {
3502 return true;
3503 }
3504
3505 $table = $this->get_table_from_query( $query );
3506 if ( ! $table ) {
3507 return false;
3508 }
3509
3510 $this->checking_collation = true;
3511 $collation = $this->get_table_charset( $table );
3512 $this->checking_collation = false;
3513
3514 // Tables with no collation, or latin1 only, don't need extra checking.
3515 if ( false === $collation || 'latin1' === $collation ) {
3516 return true;
3517 }
3518
3519 $table = strtolower( $table );
3520 if ( empty( $this->col_meta[ $table ] ) ) {
3521 return false;
3522 }
3523
3524 // If any of the columns don't have one of these collations, it needs more confidence checking.
3525 $safe_collations = array(
3526 'utf8_bin',
3527 'utf8_general_ci',
3528 'utf8mb3_bin',
3529 'utf8mb3_general_ci',
3530 'utf8mb4_bin',
3531 'utf8mb4_general_ci',
3532 );
3533
3534 foreach ( $this->col_meta[ $table ] as $col ) {
3535 if ( empty( $col->Collation ) ) {
3536 continue;
3537 }
3538
3539 if ( ! in_array( $col->Collation, $safe_collations, true ) ) {
3540 return false;
3541 }
3542 }
3543
3544 return true;
3545 }
3546
3547 /**
3548 * Strips any invalid characters based on value/charset pairs.
3549 *
3550 * @since 4.2.0
3551 *
3552 * @param array $data Array of value arrays. Each value array has the keys 'value', 'charset', and 'length'.
3553 * An optional 'ascii' key can be set to false to avoid redundant ASCII checks.
3554 * @return array|WP_Error The $data parameter, with invalid characters removed from each value.
3555 * This works as a passthrough: any additional keys such as 'field' are
3556 * retained in each value array. If we cannot remove invalid characters,
3557 * a WP_Error object is returned.
3558 */
3559 protected function strip_invalid_text( $data ) {
3560 $db_check_string = false;
3561
3562 foreach ( $data as &$value ) {
3563 $charset = $value['charset'];
3564
3565 if ( is_array( $value['length'] ) ) {
3566 $length = $value['length']['length'];
3567 $truncate_by_byte_length = 'byte' === $value['length']['type'];
3568 } else {
3569 $length = false;
3570 /*
3571 * Since we have no length, we'll never truncate. Initialize the variable to false.
3572 * True would take us through an unnecessary (for this case) codepath below.
3573 */
3574 $truncate_by_byte_length = false;
3575 }
3576
3577 // There's no charset to work with.
3578 if ( false === $charset ) {
3579 continue;
3580 }
3581
3582 // Column isn't a string.
3583 if ( ! is_string( $value['value'] ) ) {
3584 continue;
3585 }
3586
3587 $needs_validation = true;
3588 if (
3589 // latin1 can store any byte sequence.
3590 'latin1' === $charset
3591 ||
3592 // ASCII is always OK.
3593 ( ! isset( $value['ascii'] ) && $this->check_ascii( $value['value'] ) )
3594 ) {
3595 $truncate_by_byte_length = true;
3596 $needs_validation = false;
3597 }
3598
3599 if ( $truncate_by_byte_length ) {
3600 mbstring_binary_safe_encoding();
3601 if ( false !== $length && strlen( $value['value'] ) > $length ) {
3602 $value['value'] = substr( $value['value'], 0, $length );
3603 }
3604 reset_mbstring_encoding();
3605
3606 if ( ! $needs_validation ) {
3607 continue;
3608 }
3609 }
3610
3611 // utf8 can be handled by regex, which is a bunch faster than a DB lookup.
3612 if ( ( 'utf8' === $charset || 'utf8mb3' === $charset || 'utf8mb4' === $charset ) && function_exists( 'mb_strlen' ) ) {
3613 $regex = '/
3614 (
3615 (?: [\x00-\x7F] # single-byte sequences 0xxxxxxx
3616 | [\xC2-\xDF][\x80-\xBF] # double-byte sequences 110xxxxx 10xxxxxx
3617 | \xE0[\xA0-\xBF][\x80-\xBF] # triple-byte sequences 1110xxxx 10xxxxxx * 2
3618 | [\xE1-\xEC][\x80-\xBF]{2}
3619 | \xED[\x80-\x9F][\x80-\xBF]
3620 | [\xEE-\xEF][\x80-\xBF]{2}';
3621
3622 if ( 'utf8mb4' === $charset ) {
3623 $regex .= '
3624 | \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences 11110xxx 10xxxxxx * 3
3625 | [\xF1-\xF3][\x80-\xBF]{3}
3626 | \xF4[\x80-\x8F][\x80-\xBF]{2}
3627 ';
3628 }
3629
3630 $regex .= '){1,40} # ...one or more times
3631 )
3632 | . # anything else
3633 /x';
3634 $value['value'] = preg_replace( $regex, '$1', $value['value'] );
3635
3636 if ( false !== $length && mb_strlen( $value['value'], 'UTF-8' ) > $length ) {
3637 $value['value'] = mb_substr( $value['value'], 0, $length, 'UTF-8' );
3638 }
3639 continue;
3640 }
3641
3642 // We couldn't use any local conversions, send it to the DB.
3643 $value['db'] = true;
3644 $db_check_string = true;
3645 }
3646 unset( $value ); // Remove by reference.
3647
3648 if ( $db_check_string ) {
3649 $queries = array();
3650 foreach ( $data as $col => $value ) {
3651 if ( ! empty( $value['db'] ) ) {
3652 // We're going to need to truncate by characters or bytes, depending on the length value we have.
3653 if ( isset( $value['length']['type'] ) && 'byte' === $value['length']['type'] ) {
3654 // Using binary causes LEFT() to truncate by bytes.
3655 $charset = 'binary';
3656 } else {
3657 $charset = $value['charset'];
3658 }
3659
3660 if ( $this->charset ) {
3661 $connection_charset = $this->charset;
3662 } else {
3663 $connection_charset = mysqli_character_set_name( $this->dbh );
3664 }
3665
3666 if ( is_array( $value['length'] ) ) {
3667 $length = sprintf( '%.0f', $value['length']['length'] );
3668 $queries[ $col ] = $this->prepare( "CONVERT( LEFT( CONVERT( %s USING $charset ), $length ) USING $connection_charset )", $value['value'] );
3669 } elseif ( 'binary' !== $charset ) {
3670 // If we don't have a length, there's no need to convert binary - it will always return the same result.
3671 $queries[ $col ] = $this->prepare( "CONVERT( CONVERT( %s USING $charset ) USING $connection_charset )", $value['value'] );
3672 }
3673
3674 unset( $data[ $col ]['db'] );
3675 }
3676 }
3677
3678 $sql = array();
3679 foreach ( $queries as $column => $query ) {
3680 if ( ! $query ) {
3681 continue;
3682 }
3683
3684 $sql[] = $query . " AS x_$column";
3685 }
3686
3687 $this->check_current_query = false;
3688 $row = $this->get_row( 'SELECT ' . implode( ', ', $sql ), ARRAY_A );
3689 if ( ! $row ) {
3690 return new WP_Error( 'wpdb_strip_invalid_text_failure', __( 'Could not strip invalid text.' ) );
3691 }
3692
3693 foreach ( array_keys( $data ) as $column ) {
3694 if ( isset( $row[ "x_$column" ] ) ) {
3695 $data[ $column ]['value'] = $row[ "x_$column" ];
3696 }
3697 }
3698 }
3699
3700 return $data;
3701 }
3702
3703 /**
3704 * Strips any invalid characters from the query.
3705 *
3706 * @since 4.2.0
3707 *
3708 * @param string $query Query to convert.
3709 * @return string|WP_Error The converted query, or a WP_Error object if the conversion fails.
3710 */
3711 protected function strip_invalid_text_from_query( $query ) {
3712 // We don't need to check the collation for queries that don't read data.
3713 $trimmed_query = ltrim( $query, "\r\n\t (" );
3714 if ( preg_match( '/^(?:SHOW|DESCRIBE|DESC|EXPLAIN|CREATE)\s/i', $trimmed_query ) ) {
3715 return $query;
3716 }
3717
3718 $table = $this->get_table_from_query( $query );
3719 if ( $table ) {
3720 $charset = $this->get_table_charset( $table );
3721 if ( is_wp_error( $charset ) ) {
3722 return $charset;
3723 }
3724
3725 // We can't reliably strip text from tables containing binary/blob columns.
3726 if ( 'binary' === $charset ) {
3727 return $query;
3728 }
3729 } else {
3730 $charset = $this->charset;
3731 }
3732
3733 $data = array(
3734 'value' => $query,
3735 'charset' => $charset,
3736 'ascii' => false,
3737 'length' => false,
3738 );
3739
3740 $data = $this->strip_invalid_text( array( $data ) );
3741 if ( is_wp_error( $data ) ) {
3742 return $data;
3743 }
3744
3745 return $data[0]['value'];
3746 }
3747
3748 /**
3749 * Strips any invalid characters from the string for a given table and column.
3750 *
3751 * @since 4.2.0
3752 *
3753 * @param string $table Table name.
3754 * @param string $column Column name.
3755 * @param string $value The text to check.
3756 * @return string|WP_Error The converted string, or a WP_Error object if the conversion fails.
3757 */
3758 public function strip_invalid_text_for_column( $table, $column, $value ) {
3759 if ( ! is_string( $value ) ) {
3760 return $value;
3761 }
3762
3763 $charset = $this->get_col_charset( $table, $column );
3764 if ( ! $charset ) {
3765 // Not a string column.
3766 return $value;
3767 } elseif ( is_wp_error( $charset ) ) {
3768 // Bail on real errors.
3769 return $charset;
3770 }
3771
3772 $data = array(
3773 $column => array(
3774 'value' => $value,
3775 'charset' => $charset,
3776 'length' => $this->get_col_length( $table, $column ),
3777 ),
3778 );
3779
3780 $data = $this->strip_invalid_text( $data );
3781 if ( is_wp_error( $data ) ) {
3782 return $data;
3783 }
3784
3785 return $data[ $column ]['value'];
3786 }
3787
3788 /**
3789 * Finds the first table name referenced in a query.
3790 *
3791 * @since 4.2.0
3792 *
3793 * @param string $query The query to search.
3794 * @return string|false The table name found, or false if a table couldn't be found.
3795 */
3796 protected function get_table_from_query( $query ) {
3797 // Remove characters that can legally trail the table name.
3798 $query = rtrim( $query, ';/-#' );
3799
3800 // Allow (select...) union [...] style queries. Use the first query's table name.
3801 $query = ltrim( $query, "\r\n\t (" );
3802
3803 // Strip everything between parentheses except nested selects.
3804 $query = preg_replace( '/\((?!\s*select)[^(]*?\)/is', '()', $query );
3805
3806 // Strip any leading SET STATEMENT statements.
3807 $query = preg_replace( '/^SET STATEMENT.+?\sFOR\s+/is', '', $query );
3808
3809 // Quickly match most common queries.
3810 if ( preg_match(
3811 '/^\s*(?:'
3812 . 'SELECT.*?\s+FROM'
3813 . '|INSERT(?:\s+LOW_PRIORITY|\s+DELAYED|\s+HIGH_PRIORITY)?(?:\s+IGNORE)?(?:\s+INTO)?'
3814 . '|REPLACE(?:\s+LOW_PRIORITY|\s+DELAYED)?(?:\s+INTO)?'
3815 . '|UPDATE(?:\s+LOW_PRIORITY)?(?:\s+IGNORE)?'
3816 . '|DELETE(?:\s+LOW_PRIORITY|\s+QUICK|\s+IGNORE)*(?:.+?FROM)?'
3817 . ')\s+((?:[0-9a-zA-Z$_.`-]|[\xC2-\xDF][\x80-\xBF])+)/is',
3818 $query,
3819 $maybe
3820 ) ) {
3821 return str_replace( '`', '', $maybe[1] );
3822 }
3823
3824 // SHOW TABLE STATUS and SHOW TABLES WHERE Name = 'wp_posts'
3825 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 ) ) {
3826 return $maybe[2];
3827 }
3828
3829 /*
3830 * SHOW TABLE STATUS LIKE and SHOW TABLES LIKE 'wp\_123\_%'
3831 * This quoted LIKE operand seldom holds a full table name.
3832 * It is usually a pattern for matching a prefix so we just
3833 * strip the trailing % and unescape the _ to get 'wp_123_'
3834 * which drop-ins can use for routing these SQL statements.
3835 */
3836 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 ) ) {
3837 return str_replace( '\\_', '_', $maybe[2] );
3838 }
3839
3840 // Big pattern for the rest of the table-related queries.
3841 if ( preg_match(
3842 '/^\s*(?:'
3843 . '(?:EXPLAIN\s+(?:EXTENDED\s+)?)?SELECT.*?\s+FROM'
3844 . '|DESCRIBE|DESC|EXPLAIN|HANDLER'
3845 . '|(?:LOCK|UNLOCK)\s+TABLE(?:S)?'
3846 . '|(?:RENAME|OPTIMIZE|BACKUP|RESTORE|CHECK|CHECKSUM|ANALYZE|REPAIR).*\s+TABLE'
3847 . '|TRUNCATE(?:\s+TABLE)?'
3848 . '|CREATE(?:\s+TEMPORARY)?\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?'
3849 . '|ALTER(?:\s+IGNORE)?\s+TABLE'
3850 . '|DROP\s+TABLE(?:\s+IF\s+EXISTS)?'
3851 . '|CREATE(?:\s+\w+)?\s+INDEX.*\s+ON'
3852 . '|DROP\s+INDEX.*\s+ON'
3853 . '|LOAD\s+DATA.*INFILE.*INTO\s+TABLE'
3854 . '|(?:GRANT|REVOKE).*ON\s+TABLE'
3855 . '|SHOW\s+(?:.*FROM|.*TABLE)'
3856 . ')\s+\(*\s*((?:[0-9a-zA-Z$_.`-]|[\xC2-\xDF][\x80-\xBF])+)\s*\)*/is',
3857 $query,
3858 $maybe
3859 ) ) {
3860 return str_replace( '`', '', $maybe[1] );
3861 }
3862
3863 return false;
3864 }
3865
3866 /**
3867 * Loads the column metadata from the last query.
3868 *
3869 * @since 3.5.0
3870 */
3871 protected function load_col_info() {
3872 if ( $this->col_info ) {
3873 return;
3874 }
3875
3876 $num_fields = mysqli_num_fields( $this->result );
3877
3878 for ( $i = 0; $i < $num_fields; $i++ ) {
3879 $this->col_info[ $i ] = mysqli_fetch_field( $this->result );
3880 }
3881 }
3882
3883 /**
3884 * Retrieves column metadata from the last query.
3885 *
3886 * @since 0.71
3887 *
3888 * @param string $info_type Optional. Possible values include 'name', 'table', 'def', 'max_length',
3889 * 'not_null', 'primary_key', 'multiple_key', 'unique_key', 'numeric',
3890 * 'blob', 'type', 'unsigned', 'zerofill'. Default 'name'.
3891 * @param int $col_offset Optional. 0: col name. 1: which table the col's in. 2: col's max length.
3892 * 3: if the col is numeric. 4: col's type. Default -1.
3893 * @return mixed Column results.
3894 */
3895 public function get_col_info( $info_type = 'name', $col_offset = -1 ) {
3896 $this->load_col_info();
3897
3898 if ( $this->col_info ) {
3899 if ( -1 === $col_offset ) {
3900 $i = 0;
3901 $new_array = array();
3902 foreach ( (array) $this->col_info as $col ) {
3903 $new_array[ $i ] = $col->{$info_type};
3904 ++$i;
3905 }
3906 return $new_array;
3907 } else {
3908 return $this->col_info[ $col_offset ]->{$info_type};
3909 }
3910 }
3911 }
3912
3913 /**
3914 * Starts the timer, for debugging purposes.
3915 *
3916 * @since 1.5.0
3917 *
3918 * @return true
3919 */
3920 public function timer_start() {
3921 $this->time_start = microtime( true );
3922 return true;
3923 }
3924
3925 /**
3926 * Stops the debugging timer.
3927 *
3928 * @since 1.5.0
3929 *
3930 * @return float Total time spent on the query, in seconds.
3931 */
3932 public function timer_stop() {
3933 return ( microtime( true ) - $this->time_start );
3934 }
3935
3936 /**
3937 * Wraps errors in a nice header and footer and dies.
3938 *
3939 * Will not die if wpdb::$show_errors is false.
3940 *
3941 * @since 1.5.0
3942 *
3943 * @param string $message The error message.
3944 * @param string $error_code Optional. A computer-readable string to identify the error.
3945 * Default '500'.
3946 * @return void|false Void if the showing of errors is enabled, false if disabled.
3947 */
3948 public function bail( $message, $error_code = '500' ) {
3949 if ( $this->show_errors ) {
3950 $error = '';
3951
3952 if ( $this->dbh instanceof mysqli ) {
3953 $error = mysqli_error( $this->dbh );
3954 } elseif ( mysqli_connect_errno() ) {
3955 $error = mysqli_connect_error();
3956 }
3957
3958 if ( $error ) {
3959 $message = '<p><code>' . $error . "</code></p>\n" . $message;
3960 }
3961
3962 wp_die( $message );
3963 } else {
3964 if ( class_exists( 'WP_Error', false ) ) {
3965 $this->error = new WP_Error( $error_code, $message );
3966 } else {
3967 $this->error = $message;
3968 }
3969
3970 return false;
3971 }
3972 }
3973
3974 /**
3975 * Closes the current database connection.
3976 *
3977 * @since 4.5.0
3978 *
3979 * @return bool True if the connection was successfully closed,
3980 * false if it wasn't, or if the connection doesn't exist.
3981 */
3982 public function close() {
3983 if ( ! $this->dbh ) {
3984 return false;
3985 }
3986
3987 $closed = mysqli_close( $this->dbh );
3988
3989 if ( $closed ) {
3990 $this->dbh = null;
3991 $this->ready = false;
3992 $this->has_connected = false;
3993 }
3994
3995 return $closed;
3996 }
3997
3998 /**
3999 * Determines whether the database server is at least the required minimum version.
4000 *
4001 * @since 2.5.0
4002 *
4003 * @global string $required_mysql_version The minimum required MySQL version string.
4004 * @return void|WP_Error
4005 */
4006 public function check_database_version() {
4007 global $required_mysql_version;
4008 $wp_version = wp_get_wp_version();
4009
4010 // Make sure the server has the required MySQL version.
4011 if ( version_compare( $this->db_version(), $required_mysql_version, '<' ) ) {
4012 /* translators: 1: WordPress version number, 2: Minimum required MySQL version number. */
4013 return new WP_Error( 'database_version', sprintf( __( '<strong>Error:</strong> WordPress %1$s requires MySQL %2$s or higher' ), $wp_version, $required_mysql_version ) );
4014 }
4015 }
4016
4017 /**
4018 * Determines whether the database supports collation.
4019 *
4020 * Called when WordPress is generating the table scheme.
4021 *
4022 * Use `wpdb::has_cap( 'collation' )`.
4023 *
4024 * @since 2.5.0
4025 * @deprecated 3.5.0 Use wpdb::has_cap()
4026 *
4027 * @return bool True if collation is supported, false if not.
4028 */
4029 public function supports_collation() {
4030 _deprecated_function( __FUNCTION__, '3.5.0', 'wpdb::has_cap( \'collation\' )' );
4031 return $this->has_cap( 'collation' );
4032 }
4033
4034 /**
4035 * Retrieves the database character collate.
4036 *
4037 * @since 3.5.0
4038 *
4039 * @return string The database character collate.
4040 */
4041 public function get_charset_collate() {
4042 $charset_collate = '';
4043
4044 if ( ! empty( $this->charset ) ) {
4045 $charset_collate = "DEFAULT CHARACTER SET $this->charset";
4046 }
4047 if ( ! empty( $this->collate ) ) {
4048 $charset_collate .= " COLLATE $this->collate";
4049 }
4050
4051 return $charset_collate;
4052 }
4053
4054 /**
4055 * Determines whether the database or WPDB supports a particular feature.
4056 *
4057 * Capability sniffs for the database server and current version of WPDB.
4058 *
4059 * Database sniffs are based on the version of the database server in use.
4060 *
4061 * WPDB sniffs are added as new features are introduced to allow theme and plugin
4062 * developers to determine feature support. This is to account for drop-ins which may
4063 * introduce feature support at a different time to WordPress.
4064 *
4065 * @since 2.7.0
4066 * @since 4.1.0 Added support for the 'utf8mb4' feature.
4067 * @since 4.6.0 Added support for the 'utf8mb4_520' feature.
4068 * @since 6.2.0 Added support for the 'identifier_placeholders' feature.
4069 * @since 6.6.0 The `utf8mb4` feature now always returns true.
4070 *
4071 * @see wpdb::db_version()
4072 *
4073 * @param string $db_cap The feature to check for. Accepts 'collation', 'group_concat',
4074 * 'subqueries', 'set_charset', 'utf8mb4', 'utf8mb4_520',
4075 * or 'identifier_placeholders'.
4076 * @return bool True when the database feature is supported, false otherwise.
4077 */
4078 public function has_cap( $db_cap ) {
4079 $db_version = $this->db_version();
4080 $db_server_info = $this->db_server_info();
4081
4082 /*
4083 * Account for MariaDB version being prefixed with '5.5.5-' on older PHP versions.
4084 *
4085 * Note: str_contains() is not used here, as this file can be included
4086 * directly outside of WordPress core, e.g. by HyperDB, in which case
4087 * the polyfills from wp-includes/compat.php are not loaded.
4088 */
4089 if ( '5.5.5' === $db_version && false !== strpos( $db_server_info, 'MariaDB' )
4090 && PHP_VERSION_ID < 80016 // PHP 8.0.15 or older.
4091 ) {
4092 // Strip the '5.5.5-' prefix and set the version to the correct value.
4093 $db_server_info = preg_replace( '/^5\.5\.5-(.*)/', '$1', $db_server_info );
4094 $db_version = preg_replace( '/[^0-9.].*/', '', $db_server_info );
4095 }
4096
4097 switch ( strtolower( $db_cap ) ) {
4098 case 'collation': // @since 2.5.0
4099 case 'group_concat': // @since 2.7.0
4100 case 'subqueries': // @since 2.7.0
4101 return version_compare( $db_version, '4.1', '>=' );
4102 case 'set_charset':
4103 return version_compare( $db_version, '5.0.7', '>=' );
4104 case 'utf8mb4': // @since 4.1.0
4105 return true;
4106 case 'utf8mb4_520': // @since 4.6.0
4107 return version_compare( $db_version, '5.6', '>=' );
4108 case 'identifier_placeholders': // @since 6.2.0
4109 /*
4110 * As of WordPress 6.2, wpdb::prepare() supports identifiers via '%i',
4111 * e.g. table/field names.
4112 */
4113 return true;
4114 }
4115
4116 return false;
4117 }
4118
4119 /**
4120 * Retrieves a comma-separated list of the names of the functions that called wpdb.
4121 *
4122 * @since 2.5.0
4123 *
4124 * @return string Comma-separated list of the calling functions.
4125 */
4126 public function get_caller() {
4127 return wp_debug_backtrace_summary( __CLASS__ );
4128 }
4129
4130 /**
4131 * Retrieves the database server version number.
4132 *
4133 * @since 2.7.0
4134 *
4135 * @return string|null Version number on success, null on failure.
4136 */
4137 public function db_version() {
4138 return preg_replace( '/[^0-9.].*/', '', $this->db_server_info() );
4139 }
4140
4141 /**
4142 * Returns the raw version string of the database server.
4143 *
4144 * @since 5.5.0
4145 *
4146 * @return string Database server version as a string.
4147 */
4148 public function db_server_info() {
4149 return mysqli_get_server_info( $this->dbh );
4150 }
4151}
4152
Ui Ux Design – Teachers Night Out https://cardgames4educators.com Wed, 16 Oct 2024 22:24:18 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.4 https://cardgames4educators.com/wp-content/uploads/2024/06/cropped-Card-4-Educators-logo-32x32.png Ui Ux Design – Teachers Night Out https://cardgames4educators.com 32 32 Masters In English How English Speaker https://cardgames4educators.com/masters-in-english-how-english-speaker/ https://cardgames4educators.com/masters-in-english-how-english-speaker/#comments Mon, 27 May 2024 08:54:45 +0000 https://themexriver.com/wp/kadu/?p=1

Erat himenaeos neque id sagittis massa. Hac suscipit pulvinar dignissim platea magnis eu. Don tellus a pharetra inceptos efficitur dui pulvinar. Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent pulvinar odio volutpat parturient. Quisque risus finibus suspendisse mus purus magnis facilisi condimentum consectetur dui. Curae elit suspendisse cursus vehicula.

Turpis taciti class non vel pretium quis pulvinar tempor lobortis nunc. Libero phasellus parturient sapien volutpat malesuada ornare. Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae. Porta est tempor ex eget feugiat vulputate ipsum. Justo nec iaculis habitant diam arcu fermentum.

We offer comprehen sive emplo ment services such as assistance wit employer compliance.Our company is your strategic HR partner as instead of HR. john smithson

Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae.

Exploring Learning Landscapes in Academic

Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent.

]]>
https://cardgames4educators.com/masters-in-english-how-english-speaker/feed/ 1