Test Failed
Push — issues/1944 ( ed954b...e0f33c )
by Ravinder
04:12
created

Give_Cache   C

Complexity

Total Complexity 73

Size/Duplication

Total Lines 600
Duplicated Lines 6.33 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
dl 38
loc 600
rs 5.5447
c 0
b 0
f 0
wmc 73
lcom 1
cbo 4

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 2 1
A get_instance() 0 7 3
A setup() 0 12 2
B get_key() 0 27 4
C get() 7 27 8
A set() 7 20 4
B delete() 0 27 6
D delete_all_expired() 0 40 9
C get_options_like() 0 42 7
A is_valid_cache_key() 0 11 1
A get_group() 12 12 3
A set_group() 12 12 3
B delete_group() 0 44 5
B delete_form_related_cache() 0 28 4
A delete_payment_related_cache() 0 17 4
A delete_donor_related_cache() 0 15 3
A delete_donations_related_cache() 0 10 3
A get_incrementor() 0 11 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Give_Cache often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Give_Cache, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Class for managing cache
4
 * Note: only use for internal purpose.
5
 *
6
 * @package     Give
7
 * @subpackage  Classes/Give_Cache
8
 * @copyright   Copyright (c) 2017, WordImpress
9
 * @license     https://opensource.org/licenses/gpl-license GNU Public License
10
 * @since       1.8.7
11
 */
12
13
// Exit if accessed directly.
14
if ( ! defined( 'ABSPATH' ) ) {
15
	exit;
16
}
17
18
class Give_Cache {
0 ignored issues
show
Coding Style introduced by
Since you have declared the constructor as private, maybe you should also declare the class as final.
Loading history...
19
	/**
20
	 * Instance.
21
	 *
22
	 * @since  1.8.7
23
	 * @access private
24
	 * @var Give_Cache
25
	 */
26
	static private $instance;
27
28
	/**
29
	 * Flag to check if caching enabled or not.
30
	 *
31
	 * @since  2.0
32
	 * @access private
33
	 * @var
34
	 */
35
	private $is_cache;
36
37
	/**
38
	 * Singleton pattern.
39
	 *
40
	 * @since  1.8.7
41
	 * @access private
42
	 * Give_Cache constructor.
43
	 */
44
	private function __construct() {
45
	}
46
47
48
	/**
49
	 * Get instance.
50
	 *
51
	 * @since  1.8.7
52
	 * @access public
53
	 * @return static
54
	 */
55
	public static function get_instance() {
56
		if ( ! isset( self::$instance ) && ! ( self::$instance instanceof Give_Cache ) ) {
57
			self::$instance = new Give_Cache();
58
		}
59
60
		return self::$instance;
61
	}
62
63
	/**
64
	 * Setup hooks.
65
	 *
66
	 * @since  1.8.7
67
	 * @access public
68
	 */
69
	public function setup() {
70
		// Currently enable cache only for backend.
71
		self::$instance->is_cache = give_is_setting_enabled( give_get_option( 'cache', 'enabled' ) ) && is_admin();
72
73
		// weekly delete all expired cache.
74
		Give_Cron::add_weekly_event( array( $this, 'delete_all_expired' ) );
75
76
		add_action( 'save_post_give_forms', array( $this, 'delete_form_related_cache' ) );
77
		add_action( 'save_post_give_payment', array( $this, 'delete_payment_related_cache' ) );
78
		add_action( 'give_deleted_give-donors_cache', array( $this, 'delete_donor_related_cache' ), 10, 3 );
79
		add_action( 'give_deleted_give-donations_cache', array( $this, 'delete_donations_related_cache' ), 10, 3 );
80
	}
81
82
	/**
83
	 * Get cache key.
84
	 *
85
	 * @since  1.8.7
86
	 *
87
	 * @param  string $action     Cache key prefix.
88
	 * @param  array  $query_args (optional) Query array.
89
	 *
90
	 * @return string
91
	 */
92
93
	public static function get_key( $action, $query_args = null ) {
94
		// Bailout.
95
		if ( empty( $action ) ) {
96
			return new WP_Error( 'give_invalid_cache_key_action', __( 'Do not pass empty action to generate cache key.', 'give' ) );
97
		}
98
99
		// Handle specific cache key prefix.
100
		if ( 'give-db-queries' === $action ) {
101
			return 'give-db-queries-' . self::get_instance()->get_incrementor();
102
		}
103
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
104
105
		// Set cache key.
106
		$cache_key = "give_cache_{$action}";
107
108
		// Bailout.
109
		if ( ! empty( $query_args ) ) {
110
			$cache_key = "{$cache_key}_" . substr( md5( serialize( $query_args ) ), 0, 15 );
111
		}
112
113
		/**
114
		 * Filter the cache key name.
115
		 *
116
		 * @since 2.0
117
		 */
118
		return apply_filters( 'give_get_cache_key', $cache_key, $action, $query_args );
119
	}
120
121
	/**
122
	 * Get cache.
123
	 *
124
	 * @since  1.8.7
125
	 *
126
	 * @param  string $cache_key
127
	 * @param  bool   $custom_key
128
	 * @param  mixed  $query_args
129
	 *
130
	 * @return mixed
131
	 */
132
133
	public static function get( $cache_key, $custom_key = false, $query_args = array() ) {
134 View Code Duplication
		if ( ! self::is_valid_cache_key( $cache_key ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
135
			if ( ! $custom_key ) {
136
				return new WP_Error( 'give_invalid_cache_key', __( 'Cache key format should be give_cache_*', 'give' ) );
137
			}
138
139
			$cache_key = self::get_key( $cache_key, $query_args );
140
		}
141
142
		$option = get_option( $cache_key );
143
144
		// Backward compatibility (<1.8.7).
145
		if ( ! is_array( $option ) || empty( $option ) || ! array_key_exists( 'expiration', $option ) ) {
146
			return $option;
147
		}
148
149
		// Get current time.
150
		$current_time = current_time( 'timestamp', 1 );
151
152
		if ( empty( $option['expiration'] ) || ( $current_time < $option['expiration'] ) ) {
153
			$option = $option['data'];
154
		} else {
155
			$option = false;
156
		}
157
158
		return $option;
159
	}
160
161
	/**
162
	 * Set cache.
163
	 *
164
	 * @since  1.8.7
165
	 *
166
	 * @param  string   $cache_key
167
	 * @param  mixed    $data
168
	 * @param  int|null $expiration Timestamp should be in GMT format.
169
	 * @param  bool     $custom_key
170
	 * @param  mixed    $query_args
171
	 *
172
	 * @return mixed
173
	 */
174
175
	public static function set( $cache_key, $data, $expiration = null, $custom_key = false, $query_args = array() ) {
176 View Code Duplication
		if ( ! self::is_valid_cache_key( $cache_key ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
177
			if ( ! $custom_key ) {
178
				return new WP_Error( 'give_invalid_cache_key', __( 'Cache key format should be give_cache_*', 'give' ) );
179
			}
180
181
			$cache_key = self::get_key( $cache_key, $query_args );
182
		}
183
184
		$option_value = array(
185
			'data'       => $data,
186
			'expiration' => ! is_null( $expiration )
187
				? ( $expiration + current_time( 'timestamp', 1 ) )
188
				: null,
189
		);
190
191
		$result = update_option( $cache_key, $option_value, 'no' );
192
193
		return $result;
194
	}
195
196
	/**
197
	 * Delete cache.
198
	 *
199
	 * Note: only for internal use
200
	 *
201
	 * @since  1.8.7
202
	 *
203
	 * @param  string|array $cache_keys
204
	 *
205
	 * @return bool|WP_Error
206
	 */
207
208
	public static function delete( $cache_keys ) {
209
		$result       = true;
210
		$invalid_keys = array();
211
212
		if ( ! empty( $cache_keys ) ) {
213
			$cache_keys = is_array( $cache_keys ) ? $cache_keys : array( $cache_keys );
214
215
			foreach ( $cache_keys as $cache_key ) {
216
				if ( ! self::is_valid_cache_key( $cache_key ) ) {
217
					$invalid_keys[] = $cache_key;
218
					$result         = false;
219
				}
220
221
				delete_option( $cache_key );
222
			}
223
		}
224
225
		if ( ! $result ) {
226
			$result = new WP_Error(
227
				'give_invalid_cache_key',
228
				__( 'Cache key format should be give_cache_*', 'give' ),
229
				$invalid_keys
230
			);
231
		}
232
233
		return $result;
234
	}
235
236
	/**
237
	 * Delete all logging cache.
238
	 *
239
	 * Note: only for internal use
240
	 *
241
	 * @since  1.8.7
242
	 * @access public
243
	 * @global wpdb $wpdb
244
	 *
245
	 * @param bool  $force If set to true then all cached values will be delete instead of only expired
246
	 *
247
	 * @return bool
248
	 */
249
	public static function delete_all_expired( $force = false ) {
250
		global $wpdb;
251
		$options = $wpdb->get_results(
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...
252
			$wpdb->prepare(
253
				"SELECT option_name, option_value
254
						FROM {$wpdb->options}
255
						Where option_name
256
						LIKE '%%%s%%'",
257
				'give_cache'
258
			),
259
			ARRAY_A
260
		);
261
262
		// Bailout.
263
		if ( empty( $options ) ) {
264
			return false;
265
		}
266
267
		$current_time = current_time( 'timestamp', 1 );
268
269
		// Delete log cache.
270
		foreach ( $options as $option ) {
271
			$option['option_value'] = maybe_unserialize( $option['option_value'] );
272
273
			if (
274
				(
275
					! self::is_valid_cache_key( $option['option_name'] )
276
					|| ! is_array( $option['option_value'] ) // Backward compatibility (<1.8.7).
277
					|| ! array_key_exists( 'expiration', $option['option_value'] ) // Backward compatibility (<1.8.7).
278
					|| empty( $option['option_value']['expiration'] )
279
					|| ( $current_time < $option['option_value']['expiration'] )
280
				)
281
				&& ! $force
282
			) {
283
				continue;
284
			}
285
286
			self::delete( $option['option_name'] );
287
		}
288
	}
289
290
291
	/**
292
	 * Get list of options like.
293
	 *
294
	 * Note: only for internal use
295
	 *
296
	 * @since  1.8.7
297
	 * @access public
298
	 *
299
	 * @param string $option_name
300
	 * @param bool   $fields
301
	 *
302
	 * @return array
303
	 */
304
	public static function get_options_like( $option_name, $fields = false ) {
305
		global $wpdb;
306
307
		if ( empty( $option_name ) ) {
308
			return array();
309
		}
310
311
		$field_names = $fields ? 'option_name, option_value' : 'option_name';
312
313
		if ( $fields ) {
314
			$options = $wpdb->get_results(
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...
315
				$wpdb->prepare(
316
					"SELECT {$field_names }
317
						FROM {$wpdb->options}
318
						Where option_name
319
						LIKE '%%%s%%'",
320
					"give_cache_{$option_name}"
321
				),
322
				ARRAY_A
323
			);
324
		} else {
325
			$options = $wpdb->get_col(
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...
326
				$wpdb->prepare(
327
					"SELECT *
328
						FROM {$wpdb->options}
329
						Where option_name
330
						LIKE '%%%s%%'",
331
					"give_cache_{$option_name}"
332
				),
333
				1
334
			);
335
		}
336
337
		if ( ! empty( $options ) && $fields ) {
338
			foreach ( $options as $index => $option ) {
339
				$option['option_value'] = maybe_unserialize( $option['option_value'] );
340
				$options[ $index ]      = $option;
341
			}
342
		}
343
344
		return $options;
345
	}
346
347
	/**
348
	 * Check cache key validity.
349
	 *
350
	 * @since  1.8.7
351
	 * @access public
352
	 *
353
	 * @param $cache_key
354
	 *
355
	 * @return bool
356
	 */
357
	public static function is_valid_cache_key( $cache_key ) {
358
		$is_valid = ( false !== strpos( $cache_key, 'give_cache_' ) );
359
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
360
361
		/**
362
		 * Filter the flag which tell about cache key valid or not
363
		 *
364
		 * @since 2.0
365
		 */
366
		return apply_filters( 'give_is_valid_cache_key', $is_valid, $cache_key );
367
	}
368
369
370
	/**
371
	 * Get cache from group
372
	 *
373
	 * @since  2.0
374
	 * @access public
375
	 *
376
	 * @param int    $id
377
	 * @param string $group
378
	 *
379
	 * @return mixed
380
	 */
381 View Code Duplication
	public static function get_group( $id, $group = '' ) {
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...
382
		$cached_data = false;
383
384
		// Bailout.
385
		if ( ! self::$instance->is_cache || empty( $id ) ) {
386
			return $cached_data;
387
		}
388
389
		$cached_data = wp_cache_get( $id, $group );
390
391
		return $cached_data;
392
	}
393
394
	/**
395
	 * Cache small chunks inside group
396
	 *
397
	 * @since  2.0
398
	 * @access public
399
	 *
400
	 * @param int    $id
401
	 * @param mixed  $data
402
	 * @param string $group
403
	 * @param int    $expire
404
	 *
405
	 * @return bool
406
	 */
407 View Code Duplication
	public static function set_group( $id, $data, $group = '', $expire = 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...
408
		$status = false;
409
410
		// Bailout.
411
		if ( ! self::$instance->is_cache || empty( $id ) ) {
412
			return $status;
413
		}
414
415
		$status = wp_cache_set( $id, $data, $group, $expire );
416
417
		return $status;
418
	}
419
420
	/**
421
	 * Delete group cache
422
	 *
423
	 * @since  2.0
424
	 * @access public
425
	 *
426
	 * @param int|array $ids
427
	 * @param string    $group
428
	 * @param int       $expire
429
	 *
430
	 * @return bool
431
	 */
432
	public static function delete_group( $ids, $group = '', $expire = 0 ) {
433
		$status = false;
434
435
		// Bailout.
436
		if ( ! self::$instance->is_cache || empty( $ids ) ) {
437
			return $status;
438
		}
439
440
		// Delete single or multiple cache items from cache.
441
		if ( ! is_array( $ids ) ) {
442
			$status = wp_cache_delete( $ids, $group, $expire );
443
			self::get_instance()->get_incrementor( true );
444
445
			/**
446
			 * Fire action when cache deleted for specific id.
447
			 *
448
			 * @since 2.0
449
			 *
450
			 * @param string $ids
451
			 * @param string $group
452
			 * @param int    $expire
453
			 */
454
			do_action( "give_deleted_{$group}_cache", $ids, $group, $expire, $status );
455
456
		} else {
457
			foreach ( $ids as $id ) {
458
				$status = wp_cache_delete( $id, $group, $expire );
459
				self::get_instance()->get_incrementor( true );
460
461
				/**
462
				 * Fire action when cache deleted for specific id .
463
				 *
464
				 * @since 2.0
465
				 *
466
				 * @param string $ids
467
				 * @param string $group
468
				 * @param int    $expire
469
				 */
470
				do_action( "give_deleted_{$group}_cache", $id, $group, $expire, $status );
471
			}
472
		}
473
474
		return $status;
475
	}
476
477
478
	/**
479
	 * Delete form related cache
480
	 * Note: only use for internal purpose.
481
	 *
482
	 * @since  2.0
483
	 * @access public
484
	 *
485
	 * @param int $form_id
486
	 */
487
	public function delete_form_related_cache( $form_id ) {
488
		// If this is just a revision, don't send the email.
489
		if ( wp_is_post_revision( $form_id ) ) {
490
			return;
491
		}
492
493
		$donation_query = new Give_Payments_Query(
494
			array(
495
				'number'     => - 1,
496
				'give_forms' => $form_id
0 ignored issues
show
introduced by
Each line in an array declaration must end in a comma
Loading history...
497
			)
498
		);
499
500
		$donations = $donation_query->get_payments();
501
502
		// Bailout.
503
		if ( empty( $donations ) ) {
504
			return;
505
		}
506
507
		/* @var Give_Payment $donation */
508
		foreach ( $donations as $donation ) {
509
			wp_cache_delete( $donation->ID, 'give-donations' );
510
			wp_cache_delete( $donation->donor_id, 'give-donors' );
511
		}
512
513
		self::get_instance()->get_incrementor( true );
514
	}
515
516
	/**
517
	 * Delete payment related cache
518
	 * Note: only use for internal purpose.
519
	 *
520
	 * @since  2.0
521
	 * @access public
522
	 *
523
	 * @param int $donation_id
524
	 */
525
	public function delete_payment_related_cache( $donation_id ) {
526
		// If this is just a revision, don't send the email.
527
		if ( wp_is_post_revision( $donation_id ) ) {
528
			return;
529
		}
530
531
		/* @var Give_Payment $donation */
532
		$donation = new Give_Payment( $donation_id );
533
534
		if ( $donation && $donation->donor_id ) {
535
			wp_cache_delete( $donation->donor_id, 'give-donors' );
536
		}
537
538
		wp_cache_delete( $donation->ID, 'give-donations' );
539
540
		self::get_instance()->get_incrementor( true );
541
	}
542
543
	/**
544
	 * Delete donor related cache
545
	 * Note: only use for internal purpose.
546
	 *
547
	 * @since  2.0
548
	 * @access public
549
	 *
550
	 * @param string $id
551
	 * @param string $group
552
	 * @param int    $expire
553
	 */
554
	public function delete_donor_related_cache( $id, $group, $expire ) {
0 ignored issues
show
Unused Code introduced by
The parameter $group is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $expire is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
555
		$donor        = new Give_Donor( $id );
0 ignored issues
show
Documentation introduced by
$id is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
556
		$donation_ids = array_map( 'trim', (array) explode( ',', trim( $donor->payment_ids ) ) );
557
558
		// bailout.
559
		if ( empty( $donation_ids ) ) {
560
			return;
561
		}
562
563
		foreach ( $donation_ids as $donation ) {
564
			wp_cache_delete( $donation, 'give-donations' );
565
		}
566
567
		self::get_instance()->get_incrementor( true );
568
	}
569
570
	/**
571
	 * Delete donations related cache
572
	 * Note: only use for internal purpose.
573
	 *
574
	 * @since  2.0
575
	 * @access public
576
	 *
577
	 * @param string $id
578
	 * @param string $group
579
	 * @param int    $expire
580
	 */
581
	public function delete_donations_related_cache( $id, $group, $expire ) {
0 ignored issues
show
Unused Code introduced by
The parameter $group is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $expire is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
582
		/* @var Give_Payment $donation */
583
		$donation = new Give_Payment( $id );
584
585
		if ( $donation && $donation->donor_id ) {
586
			wp_cache_delete( $donation->donor_id, 'give-donors' );
587
		}
588
589
		self::get_instance()->get_incrementor( true );
590
	}
591
592
593
	/**
594
	 * Get unique incrementor.
595
	 *
596
	 * @see    https://core.trac.wordpress.org/ticket/4476
597
	 * @see    https://www.tollmanz.com/invalidation-schemes/
598
	 *
599
	 * @since  2.0
600
	 * @access private
601
	 *
602
	 * @param bool $refresh
603
	 *
604
	 * @return string
605
	 */
606
	private function get_incrementor( $refresh = false ) {
607
		$incrementor_key   = 'give-cache-incrementor';
608
		$incrementor_value = wp_cache_get( $incrementor_key );
609
610
		if ( false === $incrementor_value || true === $refresh ) {
611
			$incrementor_value = microtime( true );
612
			wp_cache_set( $incrementor_key, $incrementor_value );
613
		}
614
615
		return $incrementor_value;
616
	}
617
}
618
619
// Initialize
620
Give_Cache::get_instance()->setup();
621