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: