Test Failed
Push — issue/3627 ( fa91e5...bf57b1 )
by Ravinder
10:14
created

Give_DB::handle_switch_blog()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
nc 3
nop 2
dl 0
loc 20
rs 9.2888
c 0
b 0
f 0
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
	 *
110
	 * @param  int $row_id Row ID.
111
	 *
112
	 * @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
123
		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
	}
125
126
	/**
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
	 */
137 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
141
		// Bailout.
142
		if ( empty( $column ) || empty( $row_id ) ) {
143
			return null;
144
		}
145
146
		$column = esc_sql( $column );
147
148
		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
	/**
152
	 * Retrieve all rows by a specific column / value
153
	 *
154
	 * @since  2.2.4
155
	 * @access public
156
	 *
157
	 * @param  int $column Column ID.
158
	 * @param  int $row_id Row ID.
159
	 *
160
	 * @return array
161
	 */
162 View Code Duplication
	public function get_results_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...
163
		/* @var WPDB $wpdb */
164
		global $wpdb;
165
166
		// Bailout.
167
		if ( empty( $column ) || empty( $row_id ) ) {
168
			return null;
169
		}
170
171
		$column = esc_sql( $column );
172
173
		return $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $this->table_name WHERE $column = %s;", $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
	/**
177
	 * Retrieve a specific column's value by the primary key
178
	 *
179
	 * @since  1.0
180
	 * @access public
181
	 *
182
	 * @param  int $column Column ID.
183
	 * @param  int $row_id Row ID.
184
	 *
185
	 * @return string      Column value.
186
	 */
187 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...
188
		/* @var WPDB $wpdb */
189
		global $wpdb;
190
191
		// Bailout.
192
		if ( empty( $column ) || empty( $row_id ) ) {
193
			return null;
194
		}
195
196
		$column = esc_sql( $column );
197
198
		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...
199
	}
200
201
	/**
202
	 * Retrieve a specific column's value by the the specified column / value
203
	 *
204
	 * @since  1.0
205
	 * @access public
206
	 *
207
	 * @param  int    $column       Column ID.
208
	 * @param  string $column_where Column name.
209
	 * @param  string $column_value Column value.
210
	 *
211
	 * @return string
212
	 */
213
	public function get_column_by( $column, $column_where, $column_value ) {
214
		/* @var WPDB $wpdb */
215
		global $wpdb;
216
217
		// Bailout.
218
		if ( empty( $column ) || empty( $column_where ) || empty( $column_value ) ) {
219
			return null;
220
		}
221
222
		$column_where = esc_sql( $column_where );
223
		$column       = esc_sql( $column );
224
225
		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...
226
	}
227
228
	/**
229
	 * Insert a new row
230
	 *
231
	 * @since  1.0
232
	 * @access public
233
	 *
234
	 * @param  array  $data
235
	 * @param  string $type
236
	 *
237
	 * @return int
238
	 */
239
	public function insert( $data, $type = '' ) {
240
		/* @var WPDB $wpdb */
241
		global $wpdb;
242
243
		// Set default values.
244
		$data = wp_parse_args( $data, $this->get_column_defaults() );
245
246
		/**
247
		 * Fires before inserting data to the database.
248
		 *
249
		 * @since 1.0
250
		 *
251
		 * @param array $data
252
		 */
253
		do_action( "give_pre_insert_{$type}", $data );
254
255
		// Initialise column format array
256
		$column_formats = $this->get_columns();
257
258
		// Force fields to lower case
259
		// $data = array_change_key_case( $data );
260
261
		// White list columns
262
		$data = array_intersect_key( $data, $column_formats );
263
264
		// Reorder $column_formats to match the order of columns given in $data
265
		$data_keys      = array_keys( $data );
266
		$column_formats = array_merge( array_flip( $data_keys ), $column_formats );
267
268
		$wpdb->insert( $this->table_name, $data, $column_formats );
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
269
270
		/**
271
		 * Fires after inserting data to the database.
272
		 *
273
		 * @since 1.0
274
		 *
275
		 * @param int   $insert_id
276
		 * @param array $data
277
		 */
278
		do_action( "give_post_insert_{$type}", $wpdb->insert_id, $data );
279
280
		return $wpdb->insert_id;
281
	}
282
283
	/**
284
	 * Update a row
285
	 *
286
	 * @since  1.0
287
	 * @access public
288
	 *
289
	 * @param  int    $row_id Column ID
290
	 * @param  array  $data
291
	 * @param  string $where  Column value
292
	 *
293
	 * @return bool
294
	 */
295
	public function update( $row_id, $data = array(), $where = '' ) {
296
		/* @var WPDB $wpdb */
297
		global $wpdb;
298
299
		// Row ID must be positive integer
300
		$row_id = absint( $row_id );
301
302
		if ( empty( $row_id ) ) {
303
			return false;
304
		}
305
306
		if ( empty( $where ) ) {
307
			$where = $this->primary_key;
308
		}
309
310
		// Initialise column format array
311
		$column_formats = $this->get_columns();
312
313
		// Force fields to lower case
314
		$data = array_change_key_case( $data );
315
316
		// White list columns
317
		$data = array_intersect_key( $data, $column_formats );
318
319
		// Reorder $column_formats to match the order of columns given in $data
320
		$data_keys      = array_keys( $data );
321
		$column_formats = array_merge( array_flip( $data_keys ), $column_formats );
322
323
		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...
324
			return false;
325
		}
326
327
		return true;
328
	}
329
330
	/**
331
	 * Delete a row identified by the primary key
332
	 *
333
	 * @since  1.0
334
	 * @access public
335
	 *
336
	 * @param  int $row_id Column ID.
337
	 *
338
	 * @return bool
339
	 */
340
	public function delete( $row_id = 0 ) {
341
		/* @var WPDB $wpdb */
342
		global $wpdb;
343
344
		// Row ID must be positive integer
345
		$row_id = absint( $row_id );
346
347
		if ( empty( $row_id ) ) {
348
			return false;
349
		}
350
351
		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...
352
			return false;
353
		}
354
355
		return true;
356
	}
357
358
	/**
359
	 * Check if the given table exists
360
	 *
361
	 * @since  1.3.2
362
	 * @access public
363
	 *
364
	 * @param  string $table The table name.
365
	 *
366
	 * @return bool          If the table name exists.
367
	 */
368
	public function table_exists( $table ) {
369
		/* @var WPDB $wpdb */
370
		global $wpdb;
371
372
		$table = sanitize_text_field( $table );
373
374
		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...
375
	}
376
377
	/**
378
	 * Checks whether column exists in a table or not.
379
	 *
380
	 * @param string $column_name Name of the Column in Database Table.
381
	 *
382
	 * @since 1.8.18
383
	 *
384
	 * @see https://gist.github.com/datafeedr/54e89e07f87232fb055121bb766743fe
385
	 *
386
	 * @return bool
387
	 */
388
	public function does_column_exist( $column_name ) {
389
390
		global $wpdb;
391
392
		$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...
393
			"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...
394
			DB_NAME, $this->table_name, $column_name
395
		) );
396
397
		if ( ! empty( $column ) ) {
398
			return true;
399
		}
400
401
		return false;
402
	}
403
404
	/**
405
	 * Check if the table was ever installed
406
	 *
407
	 * @since  1.6
408
	 * @access public
409
	 *
410
	 * @return bool Returns if the customers table was installed and upgrade routine run.
411
	 */
412
	public function installed() {
413
		return $this->table_exists( $this->table_name );
414
	}
415
416
	/**
417
	 * Register tables
418
	 *
419
	 * @since  1.8.9
420
	 * @access public
421
	 */
422
	public function register_table() {
423
		$current_version = get_option( $this->table_name . '_db_version' );
424
		if ( ! $current_version || version_compare( $current_version, $this->version, '<' ) ) {
425
			$this->create_table();
426
		}
427
	}
428
429
	/**
430
	 * Create table
431
	 *
432
	 * @since  1.8.9
433
	 * @access public
434
	 */
435
	public function create_table() {
436
	}
437
438
439
	/**
440
	 * Given a ID, make sure it's a positive number, greater than zero before inserting or adding.
441
	 *
442
	 * @access private
443
	 * @since  2.0
444
	 *
445
	 * @param  int $id A passed ID.
446
	 *
447
	 * @return int|bool                The normalized log ID or false if it's found to not be valid.
448
	 */
449
	public function sanitize_id( $id ) {
450
		if ( ! is_numeric( $id ) ) {
451
			return false;
452
		}
453
454
		$id = (int) $id;
455
456
		// We were given a non positive number.
457
		if ( absint( $id ) !== $id ) {
458
			return false;
459
		}
460
461
		if ( empty( $id ) ) {
462
			return false;
463
		}
464
465
		return absint( $id );
466
467
	}
468
469
	/**
470
	 * Handle switch blog on multi-site
471
	 *
472
	 * @since  2.0.4
473
	 *
474
	 * @access public
475
	 *
476
	 * @param $new_blog_id
477
	 * @param $prev_blog_id
478
	 */
479
	public function handle_switch_blog( $new_blog_id, $prev_blog_id ) {
480
		global $wpdb;
481
482
		// Bailout.
483
		if ( $new_blog_id === $prev_blog_id ) {
484
			return;
485
		}
486
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
487
488
		$this->table_name = str_replace(
489
			1 != $prev_blog_id ? $wpdb->get_blog_prefix( $prev_blog_id ) : $wpdb->base_prefix,
490
			1 != $new_blog_id ? $wpdb->get_blog_prefix( $new_blog_id ) : $wpdb->base_prefix,
491
			$this->table_name
492
		);
493
494
		if ( $this instanceof Give_DB_Meta ) {
495
			$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_Comment_Meta, 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...
496
		}
497
498
	}
499
}
500