Test Failed
Push — issues/1944 ( 23166d...dc7b30 )
by Ravinder
05:28
created

Give_Cache::set()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 20
Code Lines 12

Duplication

Lines 7
Ratio 35 %

Importance

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