Test Failed
Push — issues/1944 ( c837e6...093a4f )
by Ravinder
04:16
created

Give_Cache::get_incrementer()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 2
nop 2
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
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
		add_action( 'give_save_settings_give_settings', array( $this, 'flush_cache' ) );
82
	}
83
84
	/**
85
	 * Get cache key.
86
	 *
87
	 * @since  1.8.7
88
	 *
89
	 * @param  string $action     Cache key prefix.
90
	 * @param  array  $query_args (optional) Query array.
91
	 * @param  bool   $is_prefix
92
	 *
93
	 * @return string
94
	 */
95
96
	public static function get_key( $action, $query_args = null, $is_prefix = true ) {
97
		// Bailout.
98
		if ( empty( $action ) ) {
99
			return new WP_Error( 'give_invalid_cache_key_action', __( 'Do not pass empty action to generate cache key.', 'give' ) );
100
		}
101
102
		// Set cache key.
103
		$cache_key = $is_prefix ? "give_cache_{$action}" : $action;
104
105
		// Bailout.
106
		if ( ! empty( $query_args ) ) {
107
			$cache_key = "{$cache_key}_" . substr( md5( serialize( $query_args ) ), 0, 15 );
108
		}
109
110
		/**
111
		 * Filter the cache key name.
112
		 *
113
		 * @since 2.0
114
		 */
115
		return apply_filters( 'give_get_cache_key', $cache_key, $action, $query_args );
116
	}
117
118
	/**
119
	 * Get cache.
120
	 *
121
	 * @since  1.8.7
122
	 *
123
	 * @param  string $cache_key
124
	 * @param  bool   $custom_key
125
	 * @param  mixed  $query_args
126
	 *
127
	 * @return mixed
128
	 */
129
130
	public static function get( $cache_key, $custom_key = false, $query_args = array() ) {
131 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...
132
			if ( ! $custom_key ) {
133
				return new WP_Error( 'give_invalid_cache_key', __( 'Cache key format should be give_cache_*', 'give' ) );
134
			}
135
136
			$cache_key = self::get_key( $cache_key, $query_args );
137
		}
138
139
		$option = get_option( $cache_key );
140
141
		// Backward compatibility (<1.8.7).
142
		if ( ! is_array( $option ) || empty( $option ) || ! array_key_exists( 'expiration', $option ) ) {
143
			return $option;
144
		}
145
146
		// Get current time.
147
		$current_time = current_time( 'timestamp', 1 );
148
149
		if ( empty( $option['expiration'] ) || ( $current_time < $option['expiration'] ) ) {
150
			$option = $option['data'];
151
		} else {
152
			$option = false;
153
		}
154
155
		return $option;
156
	}
157
158
	/**
159
	 * Set cache.
160
	 *
161
	 * @since  1.8.7
162
	 *
163
	 * @param  string   $cache_key
164
	 * @param  mixed    $data
165
	 * @param  int|null $expiration Timestamp should be in GMT format.
166
	 * @param  bool     $custom_key
167
	 * @param  mixed    $query_args
168
	 *
169
	 * @return mixed
170
	 */
171
172
	public static function set( $cache_key, $data, $expiration = null, $custom_key = false, $query_args = array() ) {
173 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...
174
			if ( ! $custom_key ) {
175
				return new WP_Error( 'give_invalid_cache_key', __( 'Cache key format should be give_cache_*', 'give' ) );
176
			}
177
178
			$cache_key = self::get_key( $cache_key, $query_args );
179
		}
180
181
		$option_value = array(
182
			'data'       => $data,
183
			'expiration' => ! is_null( $expiration )
184
				? ( $expiration + current_time( 'timestamp', 1 ) )
185
				: null,
186
		);
187
188
		$result = update_option( $cache_key, $option_value, 'no' );
189
190
		return $result;
191
	}
192
193
	/**
194
	 * Delete cache.
195
	 *
196
	 * Note: only for internal use
197
	 *
198
	 * @since  1.8.7
199
	 *
200
	 * @param  string|array $cache_keys
201
	 *
202
	 * @return bool|WP_Error
203
	 */
204
205
	public static function delete( $cache_keys ) {
206
		$result       = true;
207
		$invalid_keys = array();
208
209
		if ( ! empty( $cache_keys ) ) {
210
			$cache_keys = is_array( $cache_keys ) ? $cache_keys : array( $cache_keys );
211
212
			foreach ( $cache_keys as $cache_key ) {
213
				if ( ! self::is_valid_cache_key( $cache_key ) ) {
214
					$invalid_keys[] = $cache_key;
215
					$result         = false;
216
				}
217
218
				delete_option( $cache_key );
219
			}
220
		}
221
222
		if ( ! $result ) {
223
			$result = new WP_Error(
224
				'give_invalid_cache_key',
225
				__( 'Cache key format should be give_cache_*', 'give' ),
226
				$invalid_keys
227
			);
228
		}
229
230
		return $result;
231
	}
232
233
	/**
234
	 * Delete all logging cache.
235
	 *
236
	 * Note: only for internal use
237
	 *
238
	 * @since  1.8.7
239
	 * @access public
240
	 * @global wpdb $wpdb
241
	 *
242
	 * @param bool  $force If set to true then all cached values will be delete instead of only expired
243
	 *
244
	 * @return bool
245
	 */
246
	public static function delete_all_expired( $force = false ) {
247
		global $wpdb;
248
		$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...
249
			$wpdb->prepare(
250
				"SELECT option_name, option_value
251
						FROM {$wpdb->options}
252
						Where option_name
253
						LIKE '%%%s%%'",
254
				'give_cache'
255
			),
256
			ARRAY_A
257
		);
258
259
		// Bailout.
260
		if ( empty( $options ) ) {
261
			return false;
262
		}
263
264
		$current_time = current_time( 'timestamp', 1 );
265
266
		// Delete log cache.
267
		foreach ( $options as $option ) {
268
			$option['option_value'] = maybe_unserialize( $option['option_value'] );
269
270
			if (
271
				(
272
					! self::is_valid_cache_key( $option['option_name'] )
273
					|| ! is_array( $option['option_value'] ) // Backward compatibility (<1.8.7).
274
					|| ! array_key_exists( 'expiration', $option['option_value'] ) // Backward compatibility (<1.8.7).
275
					|| empty( $option['option_value']['expiration'] )
276
					|| ( $current_time < $option['option_value']['expiration'] )
277
				)
278
				&& ! $force
279
			) {
280
				continue;
281
			}
282
283
			self::delete( $option['option_name'] );
284
		}
285
	}
286
287
288
	/**
289
	 * Get list of options like.
290
	 *
291
	 * Note: only for internal use
292
	 *
293
	 * @since  1.8.7
294
	 * @access public
295
	 *
296
	 * @param string $option_name
297
	 * @param bool   $fields
298
	 *
299
	 * @return array
300
	 */
301
	public static function get_options_like( $option_name, $fields = false ) {
302
		global $wpdb;
303
304
		if ( empty( $option_name ) ) {
305
			return array();
306
		}
307
308
		$field_names = $fields ? 'option_name, option_value' : 'option_name';
309
310
		if ( $fields ) {
311
			$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...
312
				$wpdb->prepare(
313
					"SELECT {$field_names }
314
						FROM {$wpdb->options}
315
						Where option_name
316
						LIKE '%%%s%%'",
317
					"give_cache_{$option_name}"
318
				),
319
				ARRAY_A
320
			);
321
		} else {
322
			$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...
323
				$wpdb->prepare(
324
					"SELECT *
325
						FROM {$wpdb->options}
326
						Where option_name
327
						LIKE '%%%s%%'",
328
					"give_cache_{$option_name}"
329
				),
330
				1
331
			);
332
		}
333
334
		if ( ! empty( $options ) && $fields ) {
335
			foreach ( $options as $index => $option ) {
336
				$option['option_value'] = maybe_unserialize( $option['option_value'] );
337
				$options[ $index ]      = $option;
338
			}
339
		}
340
341
		return $options;
342
	}
343
344
	/**
345
	 * Check cache key validity.
346
	 *
347
	 * @since  1.8.7
348
	 * @access public
349
	 *
350
	 * @param $cache_key
351
	 *
352
	 * @return bool
353
	 */
354
	public static function is_valid_cache_key( $cache_key ) {
355
		$is_valid = ( false !== strpos( $cache_key, 'give_cache_' ) );
356
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
357
358
		/**
359
		 * Filter the flag which tell about cache key valid or not
360
		 *
361
		 * @since 2.0
362
		 */
363
		return apply_filters( 'give_is_valid_cache_key', $is_valid, $cache_key );
364
	}
365
366
367
	/**
368
	 * Get cache from group
369
	 *
370
	 * @since  2.0
371
	 * @access public
372
	 *
373
	 * @param int    $id
374
	 * @param string $group
375
	 *
376
	 * @return mixed
377
	 */
378 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...
379
		$cached_data = false;
380
381
		// Bailout.
382
		if ( ! self::$instance->is_cache || empty( $id ) ) {
383
			return $cached_data;
384
		}
385
386
		$group = self::$instance->filter_group_name( $group );
387
388
		$cached_data = wp_cache_get( $id, $group );
389
390
		return $cached_data;
391
	}
392
393
	/**
394
	 * Cache small chunks inside group
395
	 *
396
	 * @since  2.0
397
	 * @access public
398
	 *
399
	 * @param int    $id
400
	 * @param mixed  $data
401
	 * @param string $group
402
	 * @param int    $expire
403
	 *
404
	 * @return bool
405
	 */
406 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...
407
		$status = false;
408
409
		// Bailout.
410
		if ( ! self::$instance->is_cache || empty( $id ) ) {
411
			return $status;
412
		}
413
414
		$group = self::$instance->filter_group_name( $group );
415
416
		$status = wp_cache_set( $id, $data, $group, $expire );
417
418
		return $status;
419
	}
420
421
	/**
422
	 * Delete group cache
423
	 *
424
	 * @since  2.0
425
	 * @access public
426
	 *
427
	 * @param int|array $ids
428
	 * @param string    $group
429
	 * @param int       $expire
430
	 *
431
	 * @return bool
432
	 */
433
	public static function delete_group( $ids, $group = '', $expire = 0 ) {
434
		$status = false;
435
436
		// Bailout.
437
		if ( ! self::$instance->is_cache || empty( $ids ) ) {
438
			return $status;
439
		}
440
441
		$group = self::$instance->filter_group_name( $group );
442
443
		// Delete single or multiple cache items from cache.
444
		if ( ! is_array( $ids ) ) {
445
			$status = wp_cache_delete( $ids, $group, $expire );
446
			self::$instance->get_incrementer( true );
447
448
			/**
449
			 * Fire action when cache deleted for specific id.
450
			 *
451
			 * @since 2.0
452
			 *
453
			 * @param string $ids
454
			 * @param string $group
455
			 * @param int    $expire
456
			 */
457
			do_action( "give_deleted_{$group}_cache", $ids, $group, $expire, $status );
458
459
		} else {
460
			foreach ( $ids as $id ) {
461
				$status = wp_cache_delete( $id, $group, $expire );
462
				self::$instance->get_incrementer( true );
463
464
				/**
465
				 * Fire action when cache deleted for specific id .
466
				 *
467
				 * @since 2.0
468
				 *
469
				 * @param string $ids
470
				 * @param string $group
471
				 * @param int    $expire
472
				 */
473
				do_action( "give_deleted_{$group}_cache", $id, $group, $expire, $status );
474
			}
475
		}
476
477
		return $status;
478
	}
479
480
481
	/**
482
	 * Delete form related cache
483
	 * Note: only use for internal purpose.
484
	 *
485
	 * @since  2.0
486
	 * @access public
487
	 *
488
	 * @param int $form_id
489
	 */
490
	public function delete_form_related_cache( $form_id ) {
491
		// If this is just a revision, don't send the email.
492
		if ( wp_is_post_revision( $form_id ) ) {
493
			return;
494
		}
495
496
		$donation_query = new Give_Payments_Query(
497
			array(
498
				'number'     => - 1,
499
				'give_forms' => $form_id
0 ignored issues
show
introduced by
Each line in an array declaration must end in a comma
Loading history...
500
			)
501
		);
502
503
		$donations = $donation_query->get_payments();
504
505
		if ( ! empty( $donations ) ) {
506
			/* @var Give_Payment $donation */
507
			foreach ( $donations as $donation ) {
508
				wp_cache_delete( $donation->ID, 'give-donations' );
509
				wp_cache_delete( $donation->donor_id, 'give-donors' );
510
			}
511
		}
512
513
		self::$instance->get_incrementer( 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::$instance->get_incrementer( 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
		if ( ! empty( $donation_ids ) ) {
559
			foreach ( $donation_ids as $donation ) {
560
				wp_cache_delete( $donation, 'give-donations' );
561
			}
562
		}
563
564
		self::$instance->get_incrementer( true );
565
	}
566
567
	/**
568
	 * Delete donations related cache
569
	 * Note: only use for internal purpose.
570
	 *
571
	 * @since  2.0
572
	 * @access public
573
	 *
574
	 * @param string $id
575
	 * @param string $group
576
	 * @param int    $expire
577
	 */
578
	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...
579
		/* @var Give_Payment $donation */
580
		$donation = new Give_Payment( $id );
581
582
		if ( $donation && $donation->donor_id ) {
583
			wp_cache_delete( $donation->donor_id, 'give-donors' );
584
		}
585
586
		self::$instance->get_incrementer( true );
587
	}
588
589
590
	/**
591
	 * Get unique incrementer.
592
	 *
593
	 * @see    https://core.trac.wordpress.org/ticket/4476
594
	 * @see    https://www.tollmanz.com/invalidation-schemes/
595
	 *
596
	 * @since  2.0
597
	 * @access private
598
	 *
599
	 * @param bool   $refresh
600
	 * @param string $incrementer_key
601
	 *
602
	 * @return string
603
	 */
604
	private function get_incrementer( $refresh = false, $incrementer_key = 'give-cahce-incrementer-db-queries' ) {
605
		$incrementer_value = wp_cache_get( $incrementer_key );
606
607
		if ( false === $incrementer_value || true === $refresh ) {
608
			$incrementer_value = microtime( true );
609
			wp_cache_set( $incrementer_key, $incrementer_value );
610
		}
611
612
		return $incrementer_value;
613
	}
614
615
616
	/**
617
	 * Flush cache on cache setting enable/disable
618
	 * Note: only for internal use
619
	 *
620
	 * @since  2.0
621
	 * @access public
622
	 */
623
	public function flush_cache() {
624
		if (
625
			Give_Admin_Settings::is_saving_settings() &&
626
			isset( $_POST['cache'] ) &&
0 ignored issues
show
introduced by
Detected access of super global var $_POST, probably need manual inspection.
Loading history...
627
			give_is_setting_enabled( give_clean( $_POST['cache'] ) )
0 ignored issues
show
Bug introduced by
It seems like give_clean($_POST['cache']) targeting give_clean() can also be of type array; however, give_is_setting_enabled() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
introduced by
Detected access of super global var $_POST, probably need manual inspection.
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
628
		) {
629
			$this->get_incrementer( true );
630
			$this->get_incrementer( true, 'give-cahce-incrementer' );
631
		}
632
	}
633
634
635
	/**
636
	 * Filter the group name
637
	 *
638
	 * @since  2.0
639
	 * @access private
640
	 *
641
	 * @param $group
642
	 *
643
	 * @return mixed
644
	 */
645
	private function filter_group_name( $group ) {
646
		if ( ! empty( $group ) ) {
647
			$incrementer = self::$instance->get_incrementer( false, 'give-cahce-incrementer' );
648
649
			if ( 'give-db-queries' === $group ) {
650
				$incrementer = self::$instance->get_incrementer();
651
			}
652
653
			$group = "{$group}_{$incrementer}";
654
		}
655
656
		/**
657
		 * Filter the group name
658
		 *
659
		 * @since 2.0
660
		 */
661
		return $group;
662
	}
663
}
664
665
// Initialize
666
Give_Cache::get_instance()->setup();
667