Test Failed
Push — issues/1944 ( 111736...57386e )
by Ravinder
04:14
created

Give_Cache::get_unique_timestamp()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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