Test Failed
Pull Request — master (#2823)
by Devin
05:06
created

Give_DB::get_columns()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 0
cp 0
crap 2
rs 10
1
<?php
2
/**
3
 * Give DB
4
 *
5
 * @package     Give
6
 * @subpackage  Classes/Give_DB
7
 * @copyright   Copyright (c) 2016, WordImpress
8
 * @license     https://opensource.org/licenses/gpl-license GNU Public License
9
 * @since       1.0
10
 */
11
12
// Exit if accessed directly.
13
if ( ! defined( 'ABSPATH' ) ) {
14
	exit;
15
}
16
17
/**
18
 * Give_DB Class
19
 *
20
 * This class is for interacting with the database table.
21
 *
22
 * @since 1.0
23
 */
24
abstract class Give_DB {
25
26
	/**
27
	 * The name of our database table
28
	 *
29
	 * @since  1.0
30
	 * @access public
31
	 *
32
	 * @var    string
33
	 */
34
	public $table_name;
35
36
	/**
37
	 * Set Minimum Index Length
38
	 *
39
	 * @since  2.0.1
40
	 * @access public
41
	 *
42
	 * @var int
43
	 */
44
	public $min_index_length = 191;
45
46
	/**
47
	 * The version of our database table
48
	 *
49
	 * @since  1.0
50
	 * @access public
51
	 *
52
	 * @var    string
53
	 */
54
	public $version;
55
56
	/**
57
	 * The name of the primary column
58
	 *
59
	 * @since  1.0
60
	 * @access public
61
	 *
62
	 * @var    string
63
	 */
64
	public $primary_key;
65
66
	/**
67
	 * Class Constructor
68
	 *
69
	 * Set up the Give DB Class.
70
	 *
71
	 * @since  1.0
72
	 * @access public
73
	 */
74
	public function __construct() {
75
		if( is_multisite() ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
76
			add_action( 'switch_blog', array( $this, 'handle_switch_blog' ), 10, 2 );
77
		}
78
	}
79
80
	/**
81
	 * Whitelist of columns
82
	 *
83
	 * @since  1.0
84
	 * @access public
85
	 *
86
	 * @return array  Columns and formats.
87
	 */
88
	public function get_columns() {
89
		return array();
90
	}
91
92
	/**
93
	 * Default column values
94
	 *
95
	 * @since  1.0
96
	 * @access public
97
	 *
98
	 * @return array  Default column values.
99
	 */
100
	public function get_column_defaults() {
101
		return array();
102
	}
103
104
	/**
105
	 * Retrieve a row by the primary key
106
	 *
107
	 * @since  1.0
108
	 * @access public
109 52
	 *
110 52
	 * @param  int $row_id Row ID.
111 52
	 *
112 52
	 * @return object
113
	 */
114
	public function get( $row_id ) {
115
		/* @var WPDB $wpdb */
116
		global $wpdb;
117
118
		// Bailout.
119
		if ( empty( $row_id ) ) {
120
			return null;
121
		}
122 2
123 2
		return $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $this->table_name WHERE $this->primary_key = %s LIMIT 1;", $row_id ) );
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
124 2
	}
125 2
126 2
	/**
127
	 * Retrieve a row by a specific column / value
128
	 *
129
	 * @since  1.0
130
	 * @access public
131
	 *
132
	 * @param  int $column Column ID.
133
	 * @param  int $row_id Row ID.
134
	 *
135
	 * @return object
136 52
	 */
137 52 View Code Duplication
	public function get_by( $column, $row_id ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
138
		/* @var WPDB $wpdb */
139
		global $wpdb;
140 52
141
		// Bailout.
142 52
		if ( empty( $column ) || empty( $row_id ) ) {
143
			return null;
144
		}
145 52
146
		$column = esc_sql( $column );
147
148 52
		return $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $this->table_name WHERE $column = %s LIMIT 1;", $row_id ) );
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
149
	}
150
151 52
	/**
152
	 * Retrieve a specific column's value by the primary key
153
	 *
154 52
	 * @since  1.0
155 52
	 * @access public
156
	 *
157 52
	 * @param  int $column Column ID.
158
	 * @param  int $row_id Row ID.
159 52
	 *
160
	 * @return string      Column value.
161 52
	 */
162 View Code Duplication
	public function get_column( $column, $row_id ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
163
		/* @var WPDB $wpdb */
164
		global $wpdb;
165
166
		// Bailout.
167
		if ( empty( $column ) || empty( $row_id ) ) {
168
			return null;
169
		}
170
171 52
		$column = esc_sql( $column );
172
173 52
		return $wpdb->get_var( $wpdb->prepare( "SELECT $column FROM $this->table_name WHERE $this->primary_key = %s LIMIT 1;", $row_id ) );
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
174
	}
175
176 52
	/**
177
	 * Retrieve a specific column's value by the the specified column / value
178 52
	 *
179 1
	 * @since  1.0
180
	 * @access public
181
	 *
182 52
	 * @param  int    $column       Column ID.
183 52
	 * @param  string $column_where Column name.
184 52
	 * @param  string $column_value Column value.
185
	 *
186
	 * @return string
187 52
	 */
188
	public function get_column_by( $column, $column_where, $column_value ) {
189
		/* @var WPDB $wpdb */
190 52
		global $wpdb;
191
192
		// Bailout.
193 52
		if ( empty( $column ) || empty( $column_where ) || empty( $column_value ) ) {
194
			return null;
195
		}
196 52
197 52
		$column_where = esc_sql( $column_where );
198
		$column       = esc_sql( $column );
199 52
200
		return $wpdb->get_var( $wpdb->prepare( "SELECT $column FROM $this->table_name WHERE $column_where = %s LIMIT 1;", $column_value ) );
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
201
	}
202
203 52
	/**
204
	 * Insert a new row
205
	 *
206
	 * @since  1.0
207
	 * @access public
208
	 *
209
	 * @param  array  $data
210
	 * @param  string $type
211
	 *
212
	 * @return int
213
	 */
214
	public function insert( $data, $type = '' ) {
215
		/* @var WPDB $wpdb */
216
		global $wpdb;
217
218
		// Set default values.
219
		$data = wp_parse_args( $data, $this->get_column_defaults() );
220
221
		/**
222
		 * Fires before inserting data to the database.
223
		 *
224
		 * @since 1.0
225
		 *
226
		 * @param array $data
227
		 */
228
		do_action( "give_pre_insert_{$type}", $data );
229
230
		// Initialise column format array
231
		$column_formats = $this->get_columns();
232
233
		// Force fields to lower case
234
		// $data = array_change_key_case( $data );
235
236
		// White list columns
237
		$data = array_intersect_key( $data, $column_formats );
238 2
239 2
		// Reorder $column_formats to match the order of columns given in $data
240 2
		$data_keys      = array_keys( $data );
241
		$column_formats = array_merge( array_flip( $data_keys ), $column_formats );
242 2
243
		$wpdb->insert( $this->table_name, $data, $column_formats );
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
244
245
		/**
246
		 * Fires after inserting data to the database.
247
		 *
248
		 * @since 1.0
249
		 *
250
		 * @param int   $insert_id
251
		 * @param array $data
252
		 */
253
		do_action( "give_post_insert_{$type}", $wpdb->insert_id, $data );
254
255
		return $wpdb->insert_id;
256
	}
257
258
	/**
259
	 * Update a row
260
	 *
261
	 * @since  1.0
262
	 * @access public
263
	 *
264
	 * @param  int    $row_id Column ID
265
	 * @param  array  $data
266
	 * @param  string $where  Column value
267
	 *
268
	 * @return bool
269
	 */
270
	public function update( $row_id, $data = array(), $where = '' ) {
271
		/* @var WPDB $wpdb */
272
		global $wpdb;
273
274
		// Row ID must be positive integer
275
		$row_id = absint( $row_id );
276
277
		if ( empty( $row_id ) ) {
278
			return false;
279
		}
280
281
		if ( empty( $where ) ) {
282
			$where = $this->primary_key;
283
		}
284
285
		// Initialise column format array
286
		$column_formats = $this->get_columns();
287
288
		// Force fields to lower case
289
		$data = array_change_key_case( $data );
290
291
		// White list columns
292
		$data = array_intersect_key( $data, $column_formats );
293
294
		// Reorder $column_formats to match the order of columns given in $data
295
		$data_keys      = array_keys( $data );
296
		$column_formats = array_merge( array_flip( $data_keys ), $column_formats );
297
298
		if ( false === $wpdb->update( $this->table_name, $data, array( $where => $row_id ), $column_formats ) ) {
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
299
			return false;
300
		}
301
302
		return true;
303
	}
304
305
	/**
306
	 * Delete a row identified by the primary key
307
	 *
308
	 * @since  1.0
309
	 * @access public
310
	 *
311
	 * @param  int $row_id Column ID.
312
	 *
313
	 * @return bool
314
	 */
315 View Code Duplication
	public function delete( $row_id = 0 ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
316
		/* @var WPDB $wpdb */
317
		global $wpdb;
318
319
		// Row ID must be positive integer
320
		$row_id = absint( $row_id );
321
322
		if ( empty( $row_id ) ) {
323
			return false;
324
		}
325
326
		if ( false === $wpdb->query( $wpdb->prepare( "DELETE FROM $this->table_name WHERE $this->primary_key = %d", $row_id ) ) ) {
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
327
			return false;
328
		}
329
330
		return true;
331
	}
332
333
	/**
334
	 * Check if the given table exists
335
	 *
336
	 * @since  1.3.2
337
	 * @access public
338
	 *
339
	 * @param  string $table The table name.
340
	 *
341
	 * @return bool          If the table name exists.
342
	 */
343
	public function table_exists( $table ) {
344
		/* @var WPDB $wpdb */
345
		global $wpdb;
346
347
		$table = sanitize_text_field( $table );
348
349
		return $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE '%s'", $table ) ) === $table;
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
350
	}
351
352
	/**
353
	 * Checks whether column exists in a table or not.
354
	 *
355
	 * @param string $column_name Name of the Column in Database Table.
356
	 *
357
	 * @since 1.8.18
358
	 *
359
	 * @see https://gist.github.com/datafeedr/54e89e07f87232fb055121bb766743fe
360
	 *
361
	 * @return bool
362
	 */
363
	public function does_column_exist( $column_name ) {
364
365
		global $wpdb;
366
367
		$column = $wpdb->get_results( $wpdb->prepare(
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
368
			"SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = %s ",
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal SELECT * FROM INFORMATIO...s AND COLUMN_NAME = %s does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
369
			DB_NAME, $this->table_name, $column_name
370
		) );
371
372
		if ( ! empty( $column ) ) {
373
			return true;
374
		}
375
376
		return false;
377
	}
378
379
	/**
380
	 * Check if the table was ever installed
381
	 *
382
	 * @since  1.6
383
	 * @access public
384
	 *
385
	 * @return bool Returns if the customers table was installed and upgrade routine run.
386
	 */
387
	public function installed() {
388
		return $this->table_exists( $this->table_name );
389
	}
390
391
	/**
392
	 * Register tables
393
	 *
394
	 * @since  1.8.9
395
	 * @access public
396
	 */
397
	public function register_table() {
398
		$current_version = get_option( $this->table_name . '_db_version' );
399
		if ( ! $current_version || version_compare( $current_version, $this->version, '<' ) ) {
400
			$this->create_table();
401
		}
402
	}
403
404
	/**
405
	 * Create table
406
	 *
407
	 * @since  1.8.9
408
	 * @access public
409
	 */
410
	public function create_table() {
411
	}
412
413
414
	/**
415
	 * Given a ID, make sure it's a positive number, greater than zero before inserting or adding.
416
	 *
417
	 * @access private
418
	 * @since  2.0
419
	 *
420
	 * @param  int $id A passed ID.
421
	 *
422
	 * @return int|bool                The normalized log ID or false if it's found to not be valid.
423
	 */
424
	public function sanitize_id( $id ) {
425
		if ( ! is_numeric( $id ) ) {
426
			return false;
427
		}
428
429
		$id = (int) $id;
430
431
		// We were given a non positive number.
432
		if ( absint( $id ) !== $id ) {
433
			return false;
434
		}
435
436
		if ( empty( $id ) ) {
437
			return false;
438
		}
439
440
		return absint( $id );
441
442
	}
443
444
	/**
445
	 * Handle switch blog on multi-site
446
	 *
447
	 * @since  2.0.4
448
	 *
449
	 * @access public
450
	 *
451
	 * @param $new_blog_id
452
	 * @param $prev_blog_id
453
	 */
454
	public function handle_switch_blog( $new_blog_id, $prev_blog_id ) {
455
		global $wpdb;
456
457
		// Bailout.
458
		if ( $new_blog_id === $prev_blog_id ) {
459
			return;
460
		}
461
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
462
463
		$this->table_name = str_replace(
464
			1 != $prev_blog_id ? $wpdb->get_blog_prefix( $prev_blog_id ) : $wpdb->base_prefix,
465
			1 != $new_blog_id ? $wpdb->get_blog_prefix( $new_blog_id ) : $wpdb->base_prefix,
466
			$this->table_name
467
		);
468
469
		if ( $this instanceof Give_DB_Meta ) {
470
			$wpdb->{$this->get_meta_type() . 'meta'} = $this->table_name;
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Give_DB as the method get_meta_type() does only exist in the following sub-classes of Give_DB: Give_DB_Donor_Meta, Give_DB_Form_Meta, Give_DB_Log_Meta, Give_DB_Meta, Give_DB_Payment_Meta. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
471
		}
472
473
	}
474
}
475