Completed
Push — master ( 8851bc...2533f0 )
by Jeremy
32:27 queued 21:03
created

VaultPress_Database::parse_create_table()   F

Complexity

Conditions 49
Paths > 20000

Size

Total Lines 149

Duplication

Lines 74
Ratio 49.66 %

Importance

Changes 0
Metric Value
cc 49
nc 748142
nop 1
dl 74
loc 149
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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() );
0 ignored issues
show
Bug introduced by
The property structure does not exist. Did you maybe forget to declare it?

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

class MyClass { }

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

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

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
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 );
0 ignored issues
show
Unused Code introduced by
$table is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
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 ) );
0 ignored issues
show
Coding Style Comprehensibility introduced by
$keys was never initialized. Although not strictly required by PHP, it is generally a good practice to add $keys = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
167
		foreach ( (array)$data as $key => $val ) {
168
			if ( null === $val ) {
169
				$vals[] = 'NULL';
0 ignored issues
show
Coding Style Comprehensibility introduced by
$vals was never initialized. Although not strictly required by PHP, it is generally a good practice to add $vals = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
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;
0 ignored issues
show
Bug introduced by
The variable $vals does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
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) )
0 ignored issues
show
Bug introduced by
The variable $keys does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
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 )
0 ignored issues
show
Bug Best Practice introduced by
The expression $columns of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
213
			return false;
214
215
		$table->name = preg_replace( '/(^[^`]+`|`[^`]+$)/', '', array_shift( preg_grep( '/^CREATE\s+TABLE\s+/', $sql ) ) );
0 ignored issues
show
Bug introduced by
preg_grep('/^CREATE\\s+TABLE\\s+/', $sql) cannot be passed to array_shift() as the parameter $array expects a reference.
Loading history...
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 );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
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 );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
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 );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
382
		return array( 'affected_rows' => $affected_rows, 'last_error' => $last_error, 'data_file' => $data_file );
383
	}
384
}
385