Test Failed
Push — master ( 1d3b59...426730 )
by Devin
01:16
created

Give_Cache::get_key()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 21
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 7
nc 5
nop 3
dl 0
loc 21
rs 9.0534
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
	public static function get_key( $action, $query_args = null, $is_prefix = true ) {
96
		// Bailout.
97
		if ( empty( $action ) ) {
98
			return new WP_Error( 'give_invalid_cache_key_action', __( 'Do not pass empty action to generate cache key.', 'give' ) );
99
		}
100
101
		// Set cache key.
102
		$cache_key = $is_prefix ? "give_cache_{$action}" : $action;
103
104
		// Bailout.
105
		if ( ! empty( $query_args ) ) {
106
			$cache_key = "{$cache_key}_" . substr( md5( serialize( $query_args ) ), 0, 15 );
107
		}
108
109
		/**
110
		 * Filter the cache key name.
111
		 *
112
		 * @since 2.0
113
		 */
114
		return apply_filters( 'give_get_cache_key', $cache_key, $action, $query_args );
115
	}
116
117
	/**
118
	 * Get cache.
119
	 *
120
	 * @since  1.8.7
121
	 *
122
	 * @param  string $cache_key
123
	 * @param  bool   $custom_key
124
	 * @param  mixed  $query_args
125
	 *
126
	 * @return mixed
127
	 */
128
	public static function get( $cache_key, $custom_key = false, $query_args = array() ) {
129 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...
130
			if ( ! $custom_key ) {
131
				return new WP_Error( 'give_invalid_cache_key', __( 'Cache key format should be give_cache_*', 'give' ) );
132
			}
133
134
			$cache_key = self::get_key( $cache_key, $query_args );
135
		}
136
137
		$option = get_option( $cache_key );
138
139
		// Backward compatibility (<1.8.7).
140
		if ( ! is_array( $option ) || empty( $option ) || ! array_key_exists( 'expiration', $option ) ) {
141
			return $option;
142
		}
143
144
		// Get current time.
145
		$current_time = current_time( 'timestamp', 1 );
146
147
		if ( empty( $option['expiration'] ) || ( $current_time < $option['expiration'] ) ) {
148
			$option = $option['data'];
149
		} else {
150
			$option = false;
151
		}
152
153
		return $option;
154
	}
155
156
	/**
157
	 * Set cache.
158
	 *
159
	 * @since  1.8.7
160
	 *
161
	 * @param  string   $cache_key
162
	 * @param  mixed    $data
163
	 * @param  int|null $expiration Timestamp should be in GMT format.
164
	 * @param  bool     $custom_key
165
	 * @param  mixed    $query_args
166
	 *
167
	 * @return mixed
168
	 */
169
	public static function set( $cache_key, $data, $expiration = null, $custom_key = false, $query_args = array() ) {
170 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...
171
			if ( ! $custom_key ) {
172
				return new WP_Error( 'give_invalid_cache_key', __( 'Cache key format should be give_cache_*', 'give' ) );
173
			}
174
175
			$cache_key = self::get_key( $cache_key, $query_args );
176
		}
177
178
		$option_value = array(
179
			'data'       => $data,
180
			'expiration' => ! is_null( $expiration )
181
				? ( $expiration + current_time( 'timestamp', 1 ) )
182
				: null,
183
		);
184
185
		$result = update_option( $cache_key, $option_value, 'no' );
186
187
		return $result;
188
	}
189
190
	/**
191
	 * Delete cache.
192
	 *
193
	 * Note: only for internal use
194
	 *
195
	 * @since  1.8.7
196
	 *
197
	 * @param  string|array $cache_keys
198
	 *
199
	 * @return bool|WP_Error
200
	 */
201
	public static function delete( $cache_keys ) {
202
		$result       = true;
203
		$invalid_keys = array();
204
205
		if ( ! empty( $cache_keys ) ) {
206
			$cache_keys = is_array( $cache_keys ) ? $cache_keys : array( $cache_keys );
207
208
			foreach ( $cache_keys as $cache_key ) {
209
				if ( ! self::is_valid_cache_key( $cache_key ) ) {
210
					$invalid_keys[] = $cache_key;
211
					$result         = false;
212
				}
213
214
				delete_option( $cache_key );
215
			}
216
		}
217
218
		if ( ! $result ) {
219
			$result = new WP_Error(
220
				'give_invalid_cache_key',
221
				__( 'Cache key format should be give_cache_*', 'give' ),
222
				$invalid_keys
223
			);
224
		}
225
226
		return $result;
227
	}
228
229
	/**
230
	 * Delete all logging cache.
231
	 *
232
	 * Note: only for internal use
233
	 *
234
	 * @since  1.8.7
235
	 * @access public
236
	 * @global wpdb $wpdb
237
	 *
238
	 * @param bool  $force If set to true then all cached values will be delete instead of only expired
239
	 *
240
	 * @return bool
241
	 */
242
	public static function delete_all_expired( $force = false ) {
243
		global $wpdb;
244
		$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...
245
			$wpdb->prepare(
246
				"SELECT option_name, option_value
247
						FROM {$wpdb->options}
248
						Where option_name
249
						LIKE '%%%s%%'",
250
				'give_cache'
251
			),
252
			ARRAY_A
253
		);
254
255
		// Bailout.
256
		if ( empty( $options ) ) {
257
			return false;
258
		}
259
260
		$current_time = current_time( 'timestamp', 1 );
261
262
		// Delete log cache.
263
		foreach ( $options as $option ) {
264
			$option['option_value'] = maybe_unserialize( $option['option_value'] );
265
266
			if (
267
				(
268
					! self::is_valid_cache_key( $option['option_name'] )
269
					|| ! is_array( $option['option_value'] ) // Backward compatibility (<1.8.7).
270
					|| ! array_key_exists( 'expiration', $option['option_value'] ) // Backward compatibility (<1.8.7).
271
					|| empty( $option['option_value']['expiration'] )
272
					|| ( $current_time < $option['option_value']['expiration'] )
273
				)
274
				&& ! $force
275
			) {
276
				continue;
277
			}
278
279
			self::delete( $option['option_name'] );
280
		}
281
	}
282
283
284
	/**
285
	 * Get list of options like.
286
	 *
287
	 * Note: only for internal use
288
	 *
289
	 * @since  1.8.7
290
	 * @access public
291
	 *
292
	 * @param string $option_name
293
	 * @param bool   $fields
294
	 *
295
	 * @return array
296
	 */
297
	public static function get_options_like( $option_name, $fields = false ) {
298
		global $wpdb;
299
300
		$field_names = $fields ? 'option_name, option_value' : 'option_name';
301
302
		if ( $fields ) {
303
			$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...
304
				$wpdb->prepare(
305
					"SELECT {$field_names }
306
						FROM {$wpdb->options}
307
						Where option_name
308
						LIKE '%%%s%%'",
309
					"give_cache_{$option_name}"
310
				),
311
				ARRAY_A
312
			);
313
		} else {
314
			$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...
315
				$wpdb->prepare(
316
					"SELECT *
317
						FROM {$wpdb->options}
318
						Where option_name
319
						LIKE '%%%s%%'",
320
					"give_cache_{$option_name}"
321
				),
322
				1
323
			);
324
		}
325
326
		if ( ! empty( $options ) && $fields ) {
327
			foreach ( $options as $index => $option ) {
328
				$option['option_value'] = maybe_unserialize( $option['option_value'] );
329
				$options[ $index ]      = $option;
330
			}
331
		}
332
333
		return $options;
334
	}
335
336
	/**
337
	 * Check cache key validity.
338
	 *
339
	 * @since  1.8.7
340
	 * @access public
341
	 *
342
	 * @param $cache_key
343
	 *
344
	 * @return bool
345
	 */
346
	public static function is_valid_cache_key( $cache_key ) {
347
		$is_valid = ( false !== strpos( $cache_key, 'give_cache_' ) );
348
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
349
350
		/**
351
		 * Filter the flag which tell about cache key valid or not
352
		 *
353
		 * @since 2.0
354
		 */
355
		return apply_filters( 'give_is_valid_cache_key', $is_valid, $cache_key );
356
	}
357
358
359
	/**
360
	 * Get cache from group
361
	 *
362
	 * @since  2.0
363
	 * @access public
364
	 *
365
	 * @param int    $id
366
	 * @param string $group
367
	 *
368
	 * @return mixed
369
	 */
370
	public static function get_group( $id, $group = '' ) {
371
		$cached_data = null;
372
373
		// Bailout.
374
		if ( self::$instance->is_cache && ! empty( $id ) ) {
375
			$group = self::$instance->filter_group_name( $group );
376
377
			$cached_data = wp_cache_get( $id, $group );
378
			$cached_data = false !== $cached_data ? $cached_data : null;
379
		}
380
381
		return $cached_data;
382
	}
383
384
	/**
385
	 * Cache small chunks inside group
386
	 *
387
	 * @since  2.0
388
	 * @access public
389
	 *
390
	 * @param int    $id
391
	 * @param mixed  $data
392
	 * @param string $group
393
	 * @param int    $expire
394
	 *
395
	 * @return bool
396
	 */
397
	public static function set_group( $id, $data, $group = '', $expire = 0 ) {
398
		$status = false;
399
400
		// Bailout.
401
		if ( ! self::$instance->is_cache || empty( $id ) ) {
402
			return $status;
403
		}
404
405
		$group = self::$instance->filter_group_name( $group );
406
407
		$status = wp_cache_set( $id, $data, $group, $expire );
408
409
		return $status;
410
	}
411
412
	/**
413
	 * Cache small db query chunks inside group
414
	 *
415
	 * @since  2.0
416
	 * @access public
417
	 *
418
	 * @param int   $id
419
	 * @param mixed $data
420
	 *
421
	 * @return bool
422
	 */
423
	public static function set_db_query( $id, $data ) {
424
		$status = false;
425
426
		// Bailout.
427
		if ( ! self::$instance->is_cache || empty( $id ) ) {
428
			return $status;
429
		}
430
431
		return self::set_group( $id, $data, 'give-db-queries', 0 );
432
	}
433
434
	/**
435
	 * Get cache from group
436
	 *
437
	 * @since  2.0
438
	 * @access public
439
	 *
440
	 * @param int $id
441
	 *
442
	 * @return mixed
443
	 */
444
	public static function get_db_query( $id ) {
445
		return self::get_group( $id, 'give-db-queries' );
446
	}
447
448
	/**
449
	 * Delete group cache
450
	 *
451
	 * @since  2.0
452
	 * @access public
453
	 *
454
	 * @param int|array $ids
455
	 * @param string    $group
456
	 * @param int       $expire
457
	 *
458
	 * @return bool
459
	 */
460
	public static function delete_group( $ids, $group = '', $expire = 0 ) {
461
		$status = false;
462
463
		// Bailout.
464
		if ( ! self::$instance->is_cache || empty( $ids ) ) {
465
			return $status;
466
		}
467
468
		$group = self::$instance->filter_group_name( $group );
469
470
		// Delete single or multiple cache items from cache.
471
		if ( ! is_array( $ids ) ) {
472
			$status = wp_cache_delete( $ids, $group, $expire );
473
			self::$instance->get_incrementer( true );
474
475
			/**
476
			 * Fire action when cache deleted for specific id.
477
			 *
478
			 * @since 2.0
479
			 *
480
			 * @param string $ids
481
			 * @param string $group
482
			 * @param int    $expire
483
			 */
484
			do_action( "give_deleted_{$group}_cache", $ids, $group, $expire, $status );
485
486
		} else {
487
			foreach ( $ids as $id ) {
488
				$status = wp_cache_delete( $id, $group, $expire );
489
				self::$instance->get_incrementer( true );
490
491
				/**
492
				 * Fire action when cache deleted for specific id .
493
				 *
494
				 * @since 2.0
495
				 *
496
				 * @param string $ids
497
				 * @param string $group
498
				 * @param int    $expire
499
				 */
500
				do_action( "give_deleted_{$group}_cache", $id, $group, $expire, $status );
501
			}
502
		}
503
504
		return $status;
505
	}
506
507
508
	/**
509
	 * Delete form related cache
510
	 * Note: only use for internal purpose.
511
	 *
512
	 * @since  2.0
513
	 * @access public
514
	 *
515
	 * @param int $form_id
516
	 */
517
	public function delete_form_related_cache( $form_id ) {
518
		// If this is just a revision, don't send the email.
519
		if ( wp_is_post_revision( $form_id ) ) {
520
			return;
521
		}
522
523
		$donation_query = new Give_Payments_Query(
524
			array(
525
				'number'     => - 1,
526
				'give_forms' => $form_id,
527
			)
528
		);
529
530
		$donations = $donation_query->get_payments();
531
532
		if ( ! empty( $donations ) ) {
533
			/* @var Give_Payment $donation */
534
			foreach ( $donations as $donation ) {
535
				wp_cache_delete( $donation->ID, 'give-donations' );
536
				wp_cache_delete( $donation->donor_id, 'give-donors' );
537
			}
538
		}
539
540
		self::$instance->get_incrementer( true );
541
	}
542
543
	/**
544
	 * Delete payment related cache
545
	 * Note: only use for internal purpose.
546
	 *
547
	 * @since  2.0
548
	 * @access public
549
	 *
550
	 * @param int $donation_id
551
	 */
552
	public function delete_payment_related_cache( $donation_id ) {
553
		// If this is just a revision, don't send the email.
554
		if ( wp_is_post_revision( $donation_id ) ) {
555
			return;
556
		}
557
558
		/* @var Give_Payment $donation */
559
		$donation = new Give_Payment( $donation_id );
560
561
		if ( $donation && $donation->donor_id ) {
562
			wp_cache_delete( $donation->donor_id, 'give-donors' );
563
		}
564
565
		wp_cache_delete( $donation->ID, 'give-donations' );
566
567
		self::$instance->get_incrementer( true );
568
	}
569
570
	/**
571
	 * Delete donor 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_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...
582
		$donor        = new Give_Donor( $id );
583
		$donation_ids = array_map( 'trim', (array) explode( ',', trim( $donor->payment_ids ) ) );
584
585
		if ( ! empty( $donation_ids ) ) {
586
			foreach ( $donation_ids as $donation ) {
587
				wp_cache_delete( $donation, 'give-donations' );
588
			}
589
		}
590
591
		self::$instance->get_incrementer( true );
592
	}
593
594
	/**
595
	 * Delete donations related cache
596
	 * Note: only use for internal purpose.
597
	 *
598
	 * @since  2.0
599
	 * @access public
600
	 *
601
	 * @param string $id
602
	 * @param string $group
603
	 * @param int    $expire
604
	 */
605
	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...
606
		/* @var Give_Payment $donation */
607
		$donation = new Give_Payment( $id );
608
609
		if ( $donation && $donation->donor_id ) {
610
			wp_cache_delete( $donation->donor_id, 'give-donors' );
611
		}
612
613
		self::$instance->get_incrementer( true );
614
	}
615
616
617
	/**
618
	 * Get unique incrementer.
619
	 *
620
	 * @see    https://core.trac.wordpress.org/ticket/4476
621
	 * @see    https://www.tollmanz.com/invalidation-schemes/
622
	 *
623
	 * @since  2.0
624
	 * @access private
625
	 *
626
	 * @param bool   $refresh
627
	 * @param string $incrementer_key
628
	 *
629
	 * @return string
630
	 */
631
	private function get_incrementer( $refresh = false, $incrementer_key = 'give-cache-incrementer-db-queries' ) {
632
		$incrementer_value = wp_cache_get( $incrementer_key );
633
634
		if ( false === $incrementer_value || true === $refresh ) {
635
			$incrementer_value = microtime( true );
636
			wp_cache_set( $incrementer_key, $incrementer_value );
637
		}
638
639
		return $incrementer_value;
640
	}
641
642
643
	/**
644
	 * Flush cache on cache setting enable/disable
645
	 * Note: only for internal use
646
	 *
647
	 * @since  2.0
648
	 * @access public
649
	 */
650
	public function flush_cache() {
651
		if (
652
			Give_Admin_Settings::is_saving_settings() &&
653
			isset( $_POST['cache'] ) &&
0 ignored issues
show
introduced by
Detected access of super global var $_POST, probably need manual inspection.
Loading history...
654
			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...
655
		) {
656
			$this->get_incrementer( true );
657
			$this->get_incrementer( true, 'give-cache-incrementer' );
658
		}
659
	}
660
661
662
	/**
663
	 * Filter the group name
664
	 *
665
	 * @since  2.0
666
	 * @access private
667
	 *
668
	 * @param $group
669
	 *
670
	 * @return mixed
671
	 */
672
	private function filter_group_name( $group ) {
673
		if ( ! empty( $group ) ) {
674
			$incrementer = self::$instance->get_incrementer( false, 'give-cache-incrementer' );
675
676
			if ( 'give-db-queries' === $group ) {
677
				$incrementer = self::$instance->get_incrementer();
678
			}
679
680
			$group = "{$group}_{$incrementer}";
681
		}
682
683
		/**
684
		 * Filter the group name
685
		 *
686
		 * @since 2.0
687
		 */
688
		return $group;
689
	}
690
691
692
	/**
693
	 * Disable cache.
694
	 *
695
	 * @since  2.0
696
	 * @access public
697
	 */
698
	public static function disable() {
699
		self::get_instance()->is_cache = false;
700
	}
701
702
	/**
703
	 * Enable cache.
704
	 *
705
	 * @since  2.0
706
	 * @access public
707
	 */
708
	public static function enable() {
709
		self::get_instance()->is_cache = true;
710
	}
711
}
712
713
// Initialize
714
Give_Cache::get_instance()->setup();
715