|
1
|
|
|
<?php |
|
2
|
|
|
// don't call the file directly |
|
3
|
|
|
defined( 'ABSPATH' ) or die(); |
|
4
|
|
|
|
|
5
|
|
|
class VaultPress_Database { |
|
6
|
|
|
|
|
7
|
|
|
var $table = null; |
|
8
|
|
|
var $pks = null; |
|
9
|
|
|
|
|
10
|
|
|
function __construct() { |
|
11
|
|
|
} |
|
12
|
|
|
|
|
13
|
|
|
function attach( $table, $parse_create_table = false ) { |
|
14
|
|
|
$this->table=$table; |
|
15
|
|
|
if ( $parse_create_table ) { |
|
16
|
|
|
$this->structure = $this->parse_create_table( $this->show_create() ); |
|
|
|
|
|
|
17
|
|
|
} |
|
18
|
|
|
} |
|
19
|
|
|
|
|
20
|
|
|
function get_tables( $filter=null ) { |
|
21
|
|
|
global $wpdb; |
|
22
|
|
|
$rval = $wpdb->get_col( 'SHOW TABLES' ); |
|
23
|
|
|
if ( $filter ) |
|
24
|
|
|
$rval = preg_grep( $filter, $rval ); |
|
25
|
|
|
return $rval; |
|
26
|
|
|
} |
|
27
|
|
|
|
|
28
|
|
|
function show_create() { |
|
29
|
|
|
global $wpdb; |
|
30
|
|
|
if ( !$this->table ) |
|
31
|
|
|
return false; |
|
32
|
|
|
$table = esc_sql( $this->table ); |
|
33
|
|
|
$results = $wpdb->get_row( "SHOW CREATE TABLE `$table`" ); |
|
34
|
|
|
$want = 'Create Table'; |
|
35
|
|
|
if ( $results ) { |
|
36
|
|
|
if ( isset( $results->$want ) ) { |
|
37
|
|
|
$results = $results->$want; |
|
38
|
|
|
} else { |
|
39
|
|
|
$results = false; |
|
40
|
|
|
} |
|
41
|
|
|
} |
|
42
|
|
|
return $results; |
|
43
|
|
|
} |
|
44
|
|
|
|
|
45
|
|
|
function explain() { |
|
46
|
|
|
global $wpdb; |
|
47
|
|
|
if ( !$this->table ) |
|
48
|
|
|
return false; |
|
49
|
|
|
$table = esc_sql( $this->table ); |
|
50
|
|
|
return $wpdb->get_results( "EXPLAIN `$table`" ); |
|
51
|
|
|
} |
|
52
|
|
|
|
|
53
|
|
|
function diff( $signatures ) { |
|
54
|
|
|
global $wpdb; |
|
55
|
|
|
if ( !is_array( $signatures ) || !count( $signatures ) ) |
|
56
|
|
|
return false; |
|
57
|
|
|
if ( !$this->table ) |
|
58
|
|
|
return false; |
|
59
|
|
|
$table = esc_sql( $this->table ); |
|
60
|
|
|
$diff = array(); |
|
61
|
|
|
foreach ( $signatures as $where => $signature ) { |
|
62
|
|
|
$pksig = md5( $where ); |
|
63
|
|
|
unset( $wpdb->queries ); |
|
64
|
|
|
$row = $wpdb->get_row( "SELECT * FROM `$table` WHERE $where" ); |
|
65
|
|
|
if ( !$row ) { |
|
66
|
|
|
$diff[$pksig] = array ( 'change' => 'deleted', 'where' => $where ); |
|
67
|
|
|
continue; |
|
68
|
|
|
} |
|
69
|
|
|
$row = serialize( $row ); |
|
70
|
|
|
$hash = md5( $row ); |
|
71
|
|
|
if ( $hash != $signature ) |
|
72
|
|
|
$diff[$pksig] = array( 'change' => 'modified', 'where' => $where, 'signature' => $hash, 'row' => $row ); |
|
73
|
|
|
} |
|
74
|
|
|
return $diff; |
|
75
|
|
|
} |
|
76
|
|
|
|
|
77
|
|
|
function count( $columns ) { |
|
78
|
|
|
global $wpdb; |
|
79
|
|
|
if ( !is_array( $columns ) || !count( $columns ) ) |
|
80
|
|
|
return false; |
|
81
|
|
|
if ( !$this->table ) |
|
82
|
|
|
return false; |
|
83
|
|
|
$table = esc_sql( $this->table ); |
|
84
|
|
|
$column = esc_sql( array_shift( $columns ) ); |
|
85
|
|
|
return $wpdb->get_var( "SELECT COUNT( $column ) FROM `$table`" ); |
|
86
|
|
|
} |
|
87
|
|
|
|
|
88
|
|
|
function wpdb( $query, $function='get_results' ) { |
|
89
|
|
|
global $wpdb; |
|
90
|
|
|
|
|
91
|
|
|
if ( !is_callable( array( $wpdb, $function ) ) ) |
|
92
|
|
|
return false; |
|
93
|
|
|
|
|
94
|
|
|
$res = $wpdb->$function( $query ); |
|
95
|
|
|
if ( !$res ) |
|
96
|
|
|
return $res; |
|
97
|
|
|
switch ( $function ) { |
|
98
|
|
|
case 'get_results': |
|
99
|
|
|
foreach ( $res as $idx => $row ) { |
|
100
|
|
|
if ( isset( $row->option_name ) && $row->option_name == 'cron' ) |
|
101
|
|
|
$res[$idx]->option_value = serialize( array() ); |
|
102
|
|
|
} |
|
103
|
|
|
break; |
|
104
|
|
|
case 'get_row': |
|
105
|
|
|
if ( isset( $res->option_name ) && $res->option_name == 'cron' ) |
|
106
|
|
|
$res->option_value = serialize( array() ); |
|
107
|
|
|
break; |
|
108
|
|
|
} |
|
109
|
|
|
return $res; |
|
110
|
|
|
} |
|
111
|
|
|
|
|
112
|
|
|
function get_cols( $columns, $limit=false, $offset=false, $where=false ) { |
|
113
|
|
|
global $wpdb; |
|
114
|
|
|
if ( !is_array( $columns ) || !count( $columns ) ) |
|
115
|
|
|
return false; |
|
116
|
|
|
if ( !$this->table ) |
|
117
|
|
|
return false; |
|
118
|
|
|
$table = esc_sql( $this->table ); |
|
|
|
|
|
|
119
|
|
|
$limitsql = ''; |
|
120
|
|
|
$offsetsql = ''; |
|
121
|
|
|
$wheresql = ''; |
|
122
|
|
|
if ( $limit ) |
|
123
|
|
|
$limitsql = ' LIMIT ' . intval( $limit ); |
|
124
|
|
|
if ( $offset ) |
|
125
|
|
|
$offsetsql = ' OFFSET ' . intval( $offset ); |
|
126
|
|
|
if ( $where ) |
|
127
|
|
|
$wheresql = ' WHERE ' . base64_decode($where); |
|
128
|
|
|
$rval = array(); |
|
129
|
|
|
foreach ( $wpdb->get_results( "SELECT * FROM `$this->table` $wheresql $limitsql $offsetsql" ) as $row ) { |
|
130
|
|
|
// We don't need to actually record a real cron option value, just an empty array |
|
131
|
|
|
if ( isset( $row->option_name ) && $row->option_name == 'cron' ) |
|
132
|
|
|
$row->option_value = serialize( array() ); |
|
133
|
|
|
if ( !empty( $this->structure ) ) { |
|
134
|
|
|
$hash = md5( $this->convert_to_sql_string( $row, $this->structure->columns ) ); |
|
135
|
|
|
foreach ( get_object_vars( $row ) as $i => $v ) { |
|
136
|
|
|
if ( !in_array( $i, $columns ) ) |
|
137
|
|
|
unset( $row->$i ); |
|
138
|
|
|
} |
|
139
|
|
|
|
|
140
|
|
|
$row->hash = $hash; |
|
141
|
|
|
} else { |
|
142
|
|
|
$keys = array(); |
|
143
|
|
|
$vals = array(); |
|
144
|
|
|
foreach ( get_object_vars( $row ) as $i => $v ) { |
|
145
|
|
|
$keys[] = sprintf( "`%s`", esc_sql( $i ) ); |
|
146
|
|
|
$vals[] = sprintf( "'%s'", esc_sql( $v ) ); |
|
147
|
|
|
if ( !in_array( $i, $columns ) ) |
|
148
|
|
|
unset( $row->$i ); |
|
149
|
|
|
} |
|
150
|
|
|
$row->hash = md5( sprintf( "(%s) VALUES(%s)", implode( ',',$keys ), implode( ',',$vals ) ) ); |
|
151
|
|
|
} |
|
152
|
|
|
$rval[]=$row; |
|
153
|
|
|
} |
|
154
|
|
|
return $rval; |
|
155
|
|
|
} |
|
156
|
|
|
|
|
157
|
|
|
/** |
|
158
|
|
|
* Convert a PHP object to a mysqldump compatible string, using the provided data type information. |
|
159
|
|
|
**/ |
|
160
|
|
|
function convert_to_sql_string( $data, $datatypes ) { |
|
161
|
|
|
global $wpdb; |
|
162
|
|
|
if ( !is_object( $data ) || !is_object( $datatypes ) ) |
|
163
|
|
|
return false; |
|
164
|
|
|
|
|
165
|
|
|
foreach ( array_keys( (array)$data ) as $key ) |
|
166
|
|
|
$keys[] = sprintf( "`%s`", esc_sql( $key ) ); |
|
|
|
|
|
|
167
|
|
|
foreach ( (array)$data as $key => $val ) { |
|
168
|
|
|
if ( null === $val ) { |
|
169
|
|
|
$vals[] = 'NULL'; |
|
|
|
|
|
|
170
|
|
|
continue; |
|
171
|
|
|
} |
|
172
|
|
|
$type = 'text'; |
|
173
|
|
|
if ( isset( $datatypes->$key->type ) ) |
|
174
|
|
|
$type= strtolower( $datatypes->$key->type ); |
|
175
|
|
|
if ( preg_match( '/int|double|float|decimal|bool/i', $type ) ) |
|
176
|
|
|
$type = 'number'; |
|
177
|
|
|
|
|
178
|
|
|
if ( 'number' === $type ) { |
|
179
|
|
|
// do not add quotes to numeric types. |
|
180
|
|
|
$vals[] = $val; |
|
|
|
|
|
|
181
|
|
|
} else { |
|
182
|
|
|
$val = esc_sql( $val ); |
|
183
|
|
|
// Escape characters that aren't escaped by esc_sql(): \n, \r, etc. |
|
184
|
|
|
$val = str_replace( array( "\x0a", "\x0d", "\x1a" ), array( '\n', '\r', '\Z' ), $val ); |
|
185
|
|
|
$vals[] = sprintf( "'%s'", $val ); |
|
186
|
|
|
} |
|
187
|
|
|
} |
|
188
|
|
|
if ( !count($keys) ) |
|
|
|
|
|
|
189
|
|
|
return false; |
|
190
|
|
|
// format the row as a mysqldump line: (`column1`, `column2`) VALUES (numeric_value1,'text value 2') |
|
191
|
|
|
return sprintf( "(%s) VALUES (%s)", implode( ', ',$keys ), implode( ',',$vals ) ); |
|
192
|
|
|
} |
|
193
|
|
|
|
|
194
|
|
|
|
|
195
|
|
|
|
|
196
|
|
|
function parse_create_table( $sql ) { |
|
197
|
|
|
$table = new stdClass(); |
|
198
|
|
|
|
|
199
|
|
|
$table->raw = $sql; |
|
200
|
|
|
$table->columns = new stdClass(); |
|
201
|
|
|
$table->primary = null; |
|
202
|
|
|
$table->uniques = new stdClass(); |
|
203
|
|
|
$table->keys = new stdClass(); |
|
204
|
|
|
$sql = explode( "\n", trim( $sql ) ); |
|
205
|
|
|
$table->engine = preg_replace( '/^.+ ENGINE=(\S+) .+$/i', "$1", $sql[(count($sql)-1)] ); |
|
206
|
|
|
$table->charset = preg_replace( '/^.+ DEFAULT CHARSET=(\S+)( .+)?$/i', "$1", $sql[(count($sql)-1)] ); |
|
207
|
|
|
$table->single_int_paging_column = null; |
|
208
|
|
|
|
|
209
|
|
|
foreach ( $sql as $idx => $val ) |
|
210
|
|
|
$sql[$idx] = trim($val); |
|
211
|
|
|
$columns = preg_grep( '/^\s*`[^`]+`\s*\S*/', $sql ); |
|
212
|
|
|
if ( !$columns ) |
|
|
|
|
|
|
213
|
|
|
return false; |
|
214
|
|
|
|
|
215
|
|
|
$table->name = preg_replace( '/(^[^`]+`|`[^`]+$)/', '', array_shift( preg_grep( '/^CREATE\s+TABLE\s+/', $sql ) ) ); |
|
|
|
|
|
|
216
|
|
|
|
|
217
|
|
|
foreach ( $columns as $line ) { |
|
218
|
|
|
preg_match( '/^`([^`]+)`\s+([a-z]+)(\(\d+\))?\s*/', $line, $m ); |
|
219
|
|
|
$name = $m[1]; |
|
220
|
|
|
$table->columns->$name = new stdClass(); |
|
221
|
|
|
$table->columns->$name->null = (bool)stripos( $line, ' NOT NULL ' ); |
|
222
|
|
|
$table->columns->$name->type = $m[2]; |
|
223
|
|
View Code Duplication |
if ( isset($m[3]) ) { |
|
224
|
|
|
if ( substr( $m[3], 0, 1 ) == '(' ) |
|
225
|
|
|
$table->columns->$name->length = substr( $m[3], 1, -1 ); |
|
226
|
|
|
else |
|
227
|
|
|
$table->columns->$name->length = $m[3]; |
|
228
|
|
|
} else { |
|
229
|
|
|
$table->columns->$name->length = null; |
|
230
|
|
|
} |
|
231
|
|
View Code Duplication |
if ( preg_match( '/ character set (\S+)/i', $line, $m ) ) { |
|
232
|
|
|
$table->columns->$name->charset = $m[1]; |
|
233
|
|
|
} else { |
|
234
|
|
|
$table->columns->$name->charset = ''; |
|
235
|
|
|
} |
|
236
|
|
View Code Duplication |
if ( preg_match( '/ collate (\S+)/i', $line, $m ) ) { |
|
237
|
|
|
$table->columns->$name->collate = $m[1]; |
|
238
|
|
|
} else { |
|
239
|
|
|
$table->columns->$name->collate = ''; |
|
240
|
|
|
} |
|
241
|
|
View Code Duplication |
if ( preg_match( '/ DEFAULT (.+),$/i', $line, $m ) ) { |
|
242
|
|
|
if ( substr( $m[1], 0, 1 ) == "'" ) |
|
243
|
|
|
$table->columns->$name->default = substr( $m[1], 1, -1 ); |
|
244
|
|
|
else |
|
245
|
|
|
$table->columns->$name->default = $m[1]; |
|
246
|
|
|
} else { |
|
247
|
|
|
$table->columns->$name->default = null; |
|
248
|
|
|
} |
|
249
|
|
|
$table->columns->$name->line = $line; |
|
250
|
|
|
} |
|
251
|
|
|
$pk = preg_grep( '/^PRIMARY\s+KEY\s+/i', $sql ); |
|
252
|
|
|
if ( count( $pk ) ) { |
|
253
|
|
|
$pk = array_pop( $pk ); |
|
254
|
|
|
$pk = preg_replace( '/(^[^\(]+\(`|`\),?$)/', '', $pk ); |
|
255
|
|
|
$pk = preg_replace( '/\([0-9]+\)/', '', $pk ); |
|
256
|
|
|
$pk = explode( '`,`', $pk ); |
|
257
|
|
|
$table->primary = $pk; |
|
258
|
|
|
} |
|
259
|
|
View Code Duplication |
if ( is_array( $table->primary ) && count( $table->primary ) == 1 ) { |
|
260
|
|
|
$pk_column_name = $table->primary[0]; |
|
261
|
|
|
switch( strtolower( $table->columns->$pk_column_name->type ) ) { |
|
262
|
|
|
// Integers, exact value |
|
263
|
|
|
case 'tinyint': |
|
264
|
|
|
case 'smallint': |
|
265
|
|
|
case 'int': |
|
266
|
|
|
case 'integer': |
|
267
|
|
|
case 'bigint': |
|
268
|
|
|
// Fixed point, exact value |
|
269
|
|
|
case 'decimal': |
|
270
|
|
|
case 'numeric': |
|
271
|
|
|
// Floating point, approximate value |
|
272
|
|
|
case 'float': |
|
273
|
|
|
case 'double': |
|
274
|
|
|
case 'real': |
|
275
|
|
|
// Date and Time |
|
276
|
|
|
case 'date': |
|
277
|
|
|
case 'datetime': |
|
278
|
|
|
case 'timestamp': |
|
279
|
|
|
$table->single_int_paging_column = $pk_column_name; |
|
280
|
|
|
break; |
|
281
|
|
|
} |
|
282
|
|
|
} |
|
283
|
|
|
$keys = preg_grep( '/^((?:UNIQUE )?INDEX|(?:UNIQUE )?KEY)\s+/i', $sql ); |
|
284
|
|
|
if ( !count( $keys ) ) |
|
285
|
|
|
return $table; |
|
286
|
|
|
foreach ( $keys as $idx => $key ) { |
|
287
|
|
|
if ( 0 === strpos( $key, 'UNIQUE' ) ) |
|
288
|
|
|
$is_unique = false; |
|
289
|
|
|
else |
|
290
|
|
|
$is_unique = true; |
|
291
|
|
|
|
|
292
|
|
|
// for KEY `refresh` (`ip`,`time_last`) USING BTREE, |
|
293
|
|
|
$key = preg_replace( '/ USING \S+ ?(,?)$/', '$1', $key ); |
|
294
|
|
|
|
|
295
|
|
|
// for KEY `id` USING BTREE (`id`), |
|
296
|
|
|
$key = preg_replace( '/` USING \S+ \(/i', '` (', $key ); |
|
297
|
|
|
|
|
298
|
|
|
$key = preg_replace( '/^((?:UNIQUE )?INDEX|(?:UNIQUE )?KEY)\s+/i', '', $key ); |
|
299
|
|
|
$key = preg_replace( '/\([0-9]+\)/', '', $key ); |
|
300
|
|
|
preg_match( '/^`([^`]+)`\s+\(`(.+)`\),?$/', $key, $m ); |
|
301
|
|
|
$key = $m[1]; //preg_replace( '/\([^)]+\)/', '', $m[1]); |
|
302
|
|
|
if ( !$key ) |
|
303
|
|
|
continue; |
|
304
|
|
|
if ( $is_unique ) |
|
305
|
|
|
$table->keys->$key = explode( '`,`', $m[2] ); |
|
306
|
|
|
else |
|
307
|
|
|
$table->uniques->$key = explode( '`,`', $m[2] ); |
|
308
|
|
|
} |
|
309
|
|
|
|
|
310
|
|
|
$uniques = get_object_vars( $table->uniques ); |
|
311
|
|
|
foreach( $uniques as $idx => $val ) { |
|
312
|
|
View Code Duplication |
if ( is_array( $val ) && count( $val ) == 1 ) { |
|
313
|
|
|
$pk_column_name = $val[0]; |
|
314
|
|
|
switch( strtolower( $table->columns->$pk_column_name->type ) ) { |
|
315
|
|
|
// Integers, exact value |
|
316
|
|
|
case 'tinyint': |
|
317
|
|
|
case 'smallint': |
|
318
|
|
|
case 'int': |
|
319
|
|
|
case 'integer': |
|
320
|
|
|
case 'bigint': |
|
321
|
|
|
// Fixed point, exact value |
|
322
|
|
|
case 'decimal': |
|
323
|
|
|
case 'numeric': |
|
324
|
|
|
// Floating point, approximate value |
|
325
|
|
|
case 'float': |
|
326
|
|
|
case 'double': |
|
327
|
|
|
case 'real': |
|
328
|
|
|
// Date and Time |
|
329
|
|
|
case 'date': |
|
330
|
|
|
case 'datetime': |
|
331
|
|
|
case 'timestamp': |
|
332
|
|
|
$table->single_int_paging_column = $pk_column_name; |
|
333
|
|
|
break; |
|
334
|
|
|
} |
|
335
|
|
|
} |
|
336
|
|
|
} |
|
337
|
|
|
|
|
338
|
|
|
if ( empty( $table->primary ) ) { |
|
339
|
|
|
if ( !empty( $uniques ) ) |
|
340
|
|
|
$table->primary = array_shift( $uniques ); |
|
341
|
|
|
} |
|
342
|
|
|
|
|
343
|
|
|
return $table; |
|
344
|
|
|
} |
|
345
|
|
|
|
|
346
|
|
|
function restore( $data_file, $md5_sum, $delete = true ) { |
|
347
|
|
|
global $wpdb; |
|
348
|
|
|
if ( !file_exists( $data_file ) || !is_readable( $data_file ) || !filesize( $data_file ) ) |
|
349
|
|
|
return array( 'last_error' => 'File does not exist', 'data_file' => $data_file ); |
|
350
|
|
|
if ( $md5_sum && md5_file( $data_file ) !== $md5_sum ) |
|
351
|
|
|
return array( 'last_error' => 'Checksum mistmatch', 'data_file' => $data_file ); |
|
352
|
|
|
if ( function_exists( 'exec' ) && ( $mysql = exec( 'which mysql' ) ) ) { |
|
353
|
|
|
$details = explode( ':', DB_HOST, 2 ); |
|
354
|
|
|
$params = array( defined( 'DB_CHARSET' ) && DB_CHARSET ? DB_CHARSET : 'utf8', DB_USER, DB_PASSWORD, $details[0], isset( $details[1] ) ? $details[1] : 3306, DB_NAME, $data_file ); |
|
355
|
|
|
exec( sprintf( '%s %s', escapeshellcmd( $mysql ), vsprintf( '-A --default-character-set=%s -u%s -p%s -h%s -P%s %s < %s', array_map( 'escapeshellarg', $params ) ) ), $output, $r ); |
|
356
|
|
|
if ( 0 === $r ) { |
|
357
|
|
|
if ( $delete ) |
|
358
|
|
|
@unlink( $data_file ); |
|
|
|
|
|
|
359
|
|
|
return array( 'affected_rows' => 1, 'data_file' => $data_file, 'mysql_cli' => true ); |
|
360
|
|
|
} |
|
361
|
|
|
} |
|
362
|
|
|
$size = filesize( $data_file ); |
|
363
|
|
|
$fh = fopen( $data_file, 'r' ); |
|
364
|
|
|
$last_error = false; |
|
365
|
|
|
$affected_rows = 0; |
|
366
|
|
|
if ( $size == 0 || !is_resource( $fh ) ) { |
|
367
|
|
|
if ( $delete ) |
|
368
|
|
|
@unlink( $data_file ); |
|
|
|
|
|
|
369
|
|
|
return array( 'last_error' => 'Empty file or not readable', 'data_file' => $data_file ); |
|
370
|
|
|
} else { |
|
371
|
|
|
while( !feof( $fh ) ) { |
|
372
|
|
|
$query = trim( stream_get_line( $fh, $size, ";\n" ) ); |
|
373
|
|
|
if ( !empty( $query ) ) { |
|
374
|
|
|
$affected_rows += $wpdb->query( $query ); |
|
375
|
|
|
$last_error = $wpdb->last_error; |
|
376
|
|
|
} |
|
377
|
|
|
} |
|
378
|
|
|
fclose( $fh ); |
|
379
|
|
|
} |
|
380
|
|
|
if ( $delete ) |
|
381
|
|
|
@unlink( $data_file ); |
|
|
|
|
|
|
382
|
|
|
return array( 'affected_rows' => $affected_rows, 'last_error' => $last_error, 'data_file' => $data_file ); |
|
383
|
|
|
} |
|
384
|
|
|
} |
|
385
|
|
|
|
In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:
Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion: