Test Failed
Push — issues/1944 ( 3accab...d3ad41 )
by Ravinder
04:09
created

Give_Cache::get_db_query()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 3
rs 10
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
		if ( empty( $option_name ) ) {
301
			return array();
302
		}
303
304
		$field_names = $fields ? 'option_name, option_value' : 'option_name';
305
306
		if ( $fields ) {
307
			$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...
308
				$wpdb->prepare(
309
					"SELECT {$field_names }
310
						FROM {$wpdb->options}
311
						Where option_name
312
						LIKE '%%%s%%'",
313
					"give_cache_{$option_name}"
314
				),
315
				ARRAY_A
316
			);
317
		} else {
318
			$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...
319
				$wpdb->prepare(
320
					"SELECT *
321
						FROM {$wpdb->options}
322
						Where option_name
323
						LIKE '%%%s%%'",
324
					"give_cache_{$option_name}"
325
				),
326
				1
327
			);
328
		}
329
330
		if ( ! empty( $options ) && $fields ) {
331
			foreach ( $options as $index => $option ) {
332
				$option['option_value'] = maybe_unserialize( $option['option_value'] );
333
				$options[ $index ]      = $option;
334
			}
335
		}
336
337
		return $options;
338
	}
339
340
	/**
341
	 * Check cache key validity.
342
	 *
343
	 * @since  1.8.7
344
	 * @access public
345
	 *
346
	 * @param $cache_key
347
	 *
348
	 * @return bool
349
	 */
350
	public static function is_valid_cache_key( $cache_key ) {
351
		$is_valid = ( false !== strpos( $cache_key, 'give_cache_' ) );
352
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
353
354
		/**
355
		 * Filter the flag which tell about cache key valid or not
356
		 *
357
		 * @since 2.0
358
		 */
359
		return apply_filters( 'give_is_valid_cache_key', $is_valid, $cache_key );
360
	}
361
362
363
	/**
364
	 * Get cache from group
365
	 *
366
	 * @since  2.0
367
	 * @access public
368
	 *
369
	 * @param int    $id
370
	 * @param string $group
371
	 *
372
	 * @return mixed
373
	 */
374
	public static function get_group( $id, $group = '' ) {
375
		$cached_data = null;
376
377
		// Bailout.
378
		if ( self::$instance->is_cache && ! empty( $id ) ) {
379
			$group = self::$instance->filter_group_name( $group );
380
381
			$cached_data = wp_cache_get( $id, $group );
382
			$cached_data = false !== $cached_data ? $cached_data : null;
383
		}
384
385
		return $cached_data;
386
	}
387
388
	/**
389
	 * Cache small chunks inside group
390
	 *
391
	 * @since  2.0
392
	 * @access public
393
	 *
394
	 * @param int    $id
395
	 * @param mixed  $data
396
	 * @param string $group
397
	 * @param int    $expire
398
	 *
399
	 * @return bool
400
	 */
401
	public static function set_group( $id, $data, $group = '', $expire = 0 ) {
402
		$status = false;
403
404
		// Bailout.
405
		if ( ! self::$instance->is_cache || empty( $id ) ) {
406
			return $status;
407
		}
408
409
		$group = self::$instance->filter_group_name( $group );
410
411
		$status = wp_cache_set( $id, $data, $group, $expire );
412
413
		return $status;
414
	}
415
416
	/**
417
	 * Cache small db query chunks inside group
418
	 *
419
	 * @since  2.0
420
	 * @access public
421
	 *
422
	 * @param int   $id
423
	 * @param mixed $data
424
	 *
425
	 * @return bool
426
	 */
427
	public static function set_db_query( $id, $data ) {
428
		$status = false;
429
430
		// Bailout.
431
		if ( ! self::$instance->is_cache || empty( $id ) ) {
432
			return $status;
433
		}
434
435
		return self::set_group( $id, $data, 'give-db-queries', 0 );
436
	}
437
438
	/**
439
	 * Get cache from group
440
	 *
441
	 * @since  2.0
442
	 * @access public
443
	 *
444
	 * @param int $id
445
	 *
446
	 * @return mixed
447
	 */
448
	public static function get_db_query( $id ) {
449
		return self::get_group( $id, 'give-db-queries' );
450
	}
451
452
	/**
453
	 * Delete group cache
454
	 *
455
	 * @since  2.0
456
	 * @access public
457
	 *
458
	 * @param int|array $ids
459
	 * @param string    $group
460
	 * @param int       $expire
461
	 *
462
	 * @return bool
463
	 */
464
	public static function delete_group( $ids, $group = '', $expire = 0 ) {
465
		$status = false;
466
467
		// Bailout.
468
		if ( ! self::$instance->is_cache || empty( $ids ) ) {
469
			return $status;
470
		}
471
472
		$group = self::$instance->filter_group_name( $group );
473
474
		// Delete single or multiple cache items from cache.
475
		if ( ! is_array( $ids ) ) {
476
			$status = wp_cache_delete( $ids, $group, $expire );
477
			self::$instance->get_incrementer( true );
478
479
			/**
480
			 * Fire action when cache deleted for specific id.
481
			 *
482
			 * @since 2.0
483
			 *
484
			 * @param string $ids
485
			 * @param string $group
486
			 * @param int    $expire
487
			 */
488
			do_action( "give_deleted_{$group}_cache", $ids, $group, $expire, $status );
489
490
		} else {
491
			foreach ( $ids as $id ) {
492
				$status = wp_cache_delete( $id, $group, $expire );
493
				self::$instance->get_incrementer( true );
494
495
				/**
496
				 * Fire action when cache deleted for specific id .
497
				 *
498
				 * @since 2.0
499
				 *
500
				 * @param string $ids
501
				 * @param string $group
502
				 * @param int    $expire
503
				 */
504
				do_action( "give_deleted_{$group}_cache", $id, $group, $expire, $status );
505
			}
506
		}
507
508
		return $status;
509
	}
510
511
512
	/**
513
	 * Delete form related cache
514
	 * Note: only use for internal purpose.
515
	 *
516
	 * @since  2.0
517
	 * @access public
518
	 *
519
	 * @param int $form_id
520
	 */
521
	public function delete_form_related_cache( $form_id ) {
522
		// If this is just a revision, don't send the email.
523
		if ( wp_is_post_revision( $form_id ) ) {
524
			return;
525
		}
526
527
		$donation_query = new Give_Payments_Query(
528
			array(
529
				'number'     => - 1,
530
				'give_forms' => $form_id,
531
			)
532
		);
533
534
		$donations = $donation_query->get_payments();
535
536
		if ( ! empty( $donations ) ) {
537
			/* @var Give_Payment $donation */
538
			foreach ( $donations as $donation ) {
539
				wp_cache_delete( $donation->ID, 'give-donations' );
540
				wp_cache_delete( $donation->donor_id, 'give-donors' );
541
			}
542
		}
543
544
		self::$instance->get_incrementer( true );
545
	}
546
547
	/**
548
	 * Delete payment related cache
549
	 * Note: only use for internal purpose.
550
	 *
551
	 * @since  2.0
552
	 * @access public
553
	 *
554
	 * @param int $donation_id
555
	 */
556
	public function delete_payment_related_cache( $donation_id ) {
557
		// If this is just a revision, don't send the email.
558
		if ( wp_is_post_revision( $donation_id ) ) {
559
			return;
560
		}
561
562
		/* @var Give_Payment $donation */
563
		$donation = new Give_Payment( $donation_id );
564
565
		if ( $donation && $donation->donor_id ) {
566
			wp_cache_delete( $donation->donor_id, 'give-donors' );
567
		}
568
569
		wp_cache_delete( $donation->ID, 'give-donations' );
570
571
		self::$instance->get_incrementer( true );
572
	}
573
574
	/**
575
	 * Delete donor related cache
576
	 * Note: only use for internal purpose.
577
	 *
578
	 * @since  2.0
579
	 * @access public
580
	 *
581
	 * @param string $id
582
	 * @param string $group
583
	 * @param int    $expire
584
	 */
585
	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...
586
		$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...
587
		$donation_ids = array_map( 'trim', (array) explode( ',', trim( $donor->payment_ids ) ) );
588
589
		if ( ! empty( $donation_ids ) ) {
590
			foreach ( $donation_ids as $donation ) {
591
				wp_cache_delete( $donation, 'give-donations' );
592
			}
593
		}
594
595
		self::$instance->get_incrementer( true );
596
	}
597
598
	/**
599
	 * Delete donations related cache
600
	 * Note: only use for internal purpose.
601
	 *
602
	 * @since  2.0
603
	 * @access public
604
	 *
605
	 * @param string $id
606
	 * @param string $group
607
	 * @param int    $expire
608
	 */
609
	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...
610
		/* @var Give_Payment $donation */
611
		$donation = new Give_Payment( $id );
612
613
		if ( $donation && $donation->donor_id ) {
614
			wp_cache_delete( $donation->donor_id, 'give-donors' );
615
		}
616
617
		self::$instance->get_incrementer( true );
618
	}
619
620
621
	/**
622
	 * Get unique incrementer.
623
	 *
624
	 * @see    https://core.trac.wordpress.org/ticket/4476
625
	 * @see    https://www.tollmanz.com/invalidation-schemes/
626
	 *
627
	 * @since  2.0
628
	 * @access private
629
	 *
630
	 * @param bool   $refresh
631
	 * @param string $incrementer_key
632
	 *
633
	 * @return string
634
	 */
635
	private function get_incrementer( $refresh = false, $incrementer_key = 'give-cahce-incrementer-db-queries' ) {
636
		$incrementer_value = wp_cache_get( $incrementer_key );
637
638
		if ( false === $incrementer_value || true === $refresh ) {
639
			$incrementer_value = microtime( true );
640
			wp_cache_set( $incrementer_key, $incrementer_value );
641
		}
642
643
		return $incrementer_value;
644
	}
645
646
647
	/**
648
	 * Flush cache on cache setting enable/disable
649
	 * Note: only for internal use
650
	 *
651
	 * @since  2.0
652
	 * @access public
653
	 */
654
	public function flush_cache() {
655
		if (
656
			Give_Admin_Settings::is_saving_settings() &&
657
			isset( $_POST['cache'] ) &&
0 ignored issues
show
introduced by
Detected access of super global var $_POST, probably need manual inspection.
Loading history...
658
			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...
659
		) {
660
			$this->get_incrementer( true );
661
			$this->get_incrementer( true, 'give-cahce-incrementer' );
662
		}
663
	}
664
665
666
	/**
667
	 * Filter the group name
668
	 *
669
	 * @since  2.0
670
	 * @access private
671
	 *
672
	 * @param $group
673
	 *
674
	 * @return mixed
675
	 */
676
	private function filter_group_name( $group ) {
677
		if ( ! empty( $group ) ) {
678
			$incrementer = self::$instance->get_incrementer( false, 'give-cahce-incrementer' );
679
680
			if ( 'give-db-queries' === $group ) {
681
				$incrementer = self::$instance->get_incrementer();
682
			}
683
684
			$group = "{$group}_{$incrementer}";
685
		}
686
687
		/**
688
		 * Filter the group name
689
		 *
690
		 * @since 2.0
691
		 */
692
		return $group;
693
	}
694
695
696
	/**
697
	 * Disable cache.
698
	 *
699
	 * @since  2.0
700
	 * @access public
701
	 */
702
	public static function disable() {
703
		self::get_instance()->is_cache = false;
704
	}
705
706
	/**
707
	 * Enable cache.
708
	 *
709
	 * @since  2.0
710
	 * @access public
711
	 */
712
	public static function enable() {
713
		self::get_instance()->is_cache = true;
714
	}
715
}
716
717
// Initialize
718
Give_Cache::get_instance()->setup();
719