Test Failed
Push — master ( 36b0ed...c8da5c )
by Devin
09:29 queued 10s
created

Give_Background_Updater   F

Complexity

Total Complexity 67

Size/Duplication

Total Lines 588
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
dl 0
loc 588
rs 3.04
c 0
b 0
f 0
wmc 67
lcom 1
cbo 4

22 Methods

Rating   Name   Duplication   Size   Complexity  
A dispatch() 0 7 3
A get_all_batch() 0 3 1
A has_queue() 0 3 1
A lock_process() 0 29 3
A handle_cron_healthcheck() 0 15 4
A schedule_event() 0 5 3
A is_queue_empty() 0 16 1
A get_batch() 0 24 1
A save() 0 9 2
A update() 0 7 2
A delete() 0 5 1
A is_process_running() 0 8 2
A unlock_process() 0 5 1
D task() 0 132 15
A complete() 0 20 3
A get_memory_limit() 0 15 4
A maybe_handle() 0 20 4
B handle() 0 44 11
A is_paused_process() 0 8 1
A get_identifier() 0 3 1
A get_cron_identifier() 0 3 1
A flush_cache() 0 17 2

How to fix   Complexity   

Complex Class

Complex classes like Give_Background_Updater often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Give_Background_Updater, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Background Updater
4
 *
5
 * Uses https://github.com/A5hleyRich/wp-background-processing to handle DB
6
 * updates in the background.
7
 *
8
 * @class    Give_Background_Updater
9
 * @version  2.0.0
10
 * @package  Give/Classes
11
 * @category Class
12
 * @author   GiveWP
13
 */
14
if ( ! defined( 'ABSPATH' ) ) {
15
	exit;
16
}
17
18
/**
19
 * Give_Background_Updater Class.
20
 */
21
class Give_Background_Updater extends WP_Background_Process {
22
23
	/**
24
	 * @var string
25
	 */
26
	protected $action = 'give_db_updater';
27
28
	/**
29
	 * Dispatch updater.
30
	 *
31
	 * Updater will still run via cron job if this fails for any reason.
32
	 */
33
	public function dispatch() {
34
		if ( give_test_ajax_works() ) {
35
			parent::dispatch();
36
		} elseif ( wp_doing_ajax() ) {
37
			$this->maybe_handle();
38
		}
39
	}
40
41
42
	/**
43
	 * Get all batches.
44
	 *
45
	 * @since  2.0
46
	 * @access public
47
	 * @return stdClass
48
	 */
49
	public function get_all_batch() {
50
		return $this->get_batch();
51
	}
52
53
	/**
54
	 * Is queue empty
55
	 *
56
	 * @since 2.0.3
57
	 *
58
	 * @return bool
59
	 */
60
	public function has_queue() {
61
		return ( ! $this->is_queue_empty() );
62
	}
63
64
65
	/**
66
	 * Lock process
67
	 *
68
	 * Lock the process so that multiple instances can't run simultaneously.
69
	 * Override if applicable, but the duration should be greater than that
70
	 * defined in the time_exceeded() method.
71
	 *
72
	 *
73
	 * @since 2.0.3
74
	 */
75
	protected function lock_process() {
76
		// Check if admin want to pause upgrade.
77
		if( get_option('give_pause_upgrade') ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
78
			self::flush_cache();
79
80
			delete_option( 'give_paused_batches' );
81
82
			Give_Updates::get_instance()->__pause_db_update( true );
83
84
			delete_option('give_pause_upgrade');
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
85
86
			/**
87
			 * Fire action when pause db updates
88
			 *
89
			 * @since 2.0.1
90
			 */
91
			do_action( 'give_pause_db_upgrade', Give_Updates::get_instance() );
92
93
			wp_die();
94
		}
95
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
96
97
		$this->start_time = time(); // Set start time of current process.
98
99
		$lock_duration = ( property_exists( $this, 'queue_lock_time' ) ) ? $this->queue_lock_time : 60; // 1 minute
100
		$lock_duration = apply_filters( $this->identifier . '_queue_lock_time', $lock_duration );
101
102
		set_transient( $this->identifier . '_process_lock', microtime(), $lock_duration );
103
	}
104
105
	/**
106
	 * Handle cron healthcheck
107
	 *
108
	 * Restart the background process if not already running
109
	 * and data exists in the queue.
110
	 */
111
	public function handle_cron_healthcheck() {
112
		if ( $this->is_process_running() || $this->is_paused_process()  ) {
113
			// Background process already running.
114
			return;
115
		}
116
117
		if ( $this->is_queue_empty() ) {
118
			// No data to process.
119
			$this->clear_scheduled_event();
120
121
			return;
122
		}
123
124
		$this->handle();
125
	}
126
127
	/**
128
	 * Schedule fallback event.
129
	 */
130
	protected function schedule_event() {
131
		if ( ! wp_next_scheduled( $this->cron_hook_identifier ) && ! $this->is_paused_process() ) {
132
			wp_schedule_event( time() + 10, $this->cron_interval_identifier, $this->cron_hook_identifier );
133
		}
134
	}
135
136
	/**
137
	 * Is queue empty
138
	 *
139
	 * @since 2.4.5
140
	 *
141
	 * @return bool
142
	 */
143
	protected function is_queue_empty() {
144
		global $wpdb;
145
146
		$table  = $wpdb->options;
147
		$column = 'option_name';
148
149
		$key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
150
151
		$count = $wpdb->get_var( $wpdb->prepare( "
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...
152
			SELECT COUNT(*)
153
			FROM {$table}
154
			WHERE {$column} LIKE %s
155
		", $key ) );
156
157
		return ! ( $count > 0 );
158
	}
159
160
	/**
161
	 * Get batch
162
	 *
163
	 * @since 2.4.5
164
	 *
165
	 * @return stdClass Return the first batch from the queue
166
	 */
167
	protected function get_batch() {
168
		global $wpdb;
169
170
		$table        = $wpdb->options;
171
		$column       = 'option_name';
172
		$key_column   = 'option_id';
173
		$value_column = 'option_value';
174
175
		$key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
176
177
		$query = $wpdb->get_row( $wpdb->prepare( "
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...
178
			SELECT *
179
			FROM {$table}
180
			WHERE {$column} LIKE %s
181
			ORDER BY {$key_column} ASC
182
			LIMIT 1
183
		", $key ) );
184
185
		$batch       = new stdClass();
186
		$batch->key  = $query->$column;
187
		$batch->data = maybe_unserialize( $query->$value_column );
188
189
		return $batch;
190
	}
191
192
	/**
193
	 * Save queue
194
	 *
195
	 * @since 2.4.5
196
	 *
197
	 * @return $this
198
	 */
199
	public function save() {
200
		$key = $this->generate_key();
201
202
		if ( ! empty( $this->data ) ) {
203
			update_option( $key, $this->data );
204
		}
205
206
		return $this;
207
	}
208
209
	/**
210
	 * Update queue
211
	 *
212
	 * @since 2.4.5
213
	 *
214
	 * @param string $key Key.
215
	 * @param array  $data Data.
216
	 *
217
	 * @return $this
218
	 */
219
	public function update( $key, $data ) {
220
		if ( ! empty( $data ) ) {
221
			update_option( $key, $data );
222
		}
223
224
		return $this;
225
	}
226
227
	/**
228
	 * Delete queue
229
	 *
230
	 * @since 2.4.5
231
	 *
232
	 * @param string $key Key.
233
	 *
234
	 * @return $this
235
	 */
236
	public function delete( $key ) {
237
		delete_option( $key );
238
239
		return $this;
240
	}
241
242
	/**
243
	 * Is process running
244
	 *
245
	 * @since 2.4.5
246
	 *
247
	 * Check whether the current process is already running
248
	 * in a background process.
249
	 */
250
	public function is_process_running() {
251
		if ( get_transient( $this->identifier . '_process_lock' ) ) {
252
			// Process already running.
253
			return true;
254
		}
255
256
		return false;
257
	}
258
259
	/**
260
	 * Unlock process
261
	 *
262
	 * Unlock the process so that other instances can spawn.
263
	 *
264
	 * @since 2.4.5
265
	 *
266
	 * @return $this
267
	 */
268
	protected function unlock_process() {
269
		delete_transient( $this->identifier . '_process_lock' );
270
271
		return $this;
272
	}
273
274
	/**
275
	 * Task
276
	 *
277
	 * Override this method to perform any actions required on each
278
	 * queue item. Return the modified item for further processing
279
	 * in the next pass through. Or, return false to remove the
280
	 * item from the queue.
281
	 *
282
	 * @param array $update Update info
283
	 *
284
	 * @return mixed
285
	 */
286
	protected function task( $update ) {
287
		// Pause upgrade immediately if admin pausing upgrades.
288
		if( $this->is_paused_process() ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
289
			wp_die();
290
		}
291
292
		if ( empty( $update ) ) {
293
			return false;
294
		}
295
296
		// Delete cache.
297
		self::flush_cache();
298
299
		/* @var  Give_Updates $give_updates */
300
		$give_updates  = Give_Updates::get_instance();
301
		$resume_update = get_option(
302
			'give_doing_upgrade',
303
0 ignored issues
show
Coding Style introduced by
There should be no empty lines in a multi-line function call.
Loading history...
304
			// Default update.
305
			array(
306
				'update_info'      => $update,
307
				'step'             => 1,
308
				'update'           => 1,
309
				'heading'          => sprintf( 'Update %s of {update_count}', 1 ),
310
				'percentage'       => $give_updates->percentage,
311
				'total_percentage' => 0,
312
			)
313
		);
314
315
		// Continuously skip update if previous update does not complete yet.
316
		if (
317
			$resume_update['update_info']['id'] !== $update['id'] &&
318
			! give_has_upgrade_completed( $resume_update['update_info']['id'] )
319
		) {
320
			return $update;
321
		}
322
323
		// Set params.
324
		$resume_update['update_info'] = $update;
325
		$give_updates->step           = absint( $resume_update['step'] );
326
		$give_updates->update         = absint( $resume_update['update'] );
327
		$is_parent_update_completed   = $give_updates->is_parent_updates_completed( $update );
328
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
329
330
		// Skip update if dependency update does not complete yet.
331
		if ( empty( $is_parent_update_completed ) ) {
332
			// @todo: set error when you have only one update with invalid dependency
333
			if ( ! is_null( $is_parent_update_completed ) ) {
334
				return $update;
335
			}
336
337
			return false;
338
		}
339
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
340
341
		// Pause upgrade immediately if found following:
342
		// 1. Running update number greater then total update count
343
		// 2. Processing percentage greater then 100%
344
		if( (
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
345
			101 < $resume_update['total_percentage'] ) ||
346
		    ( $give_updates->get_total_db_update_count() < $resume_update['update'] ) ||
347
		    ! in_array( $resume_update['update_info']['id'], $give_updates->get_update_ids() )
348
		) {
349
			if( ! $this->is_paused_process() ){
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
350
				$give_updates->__pause_db_update(true);
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
351
			}
352
353
			update_option( 'give_upgrade_error', 1, false );
354
355
			$log_data = 'Update Task' . "\n";
356
			$log_data .= "Total update count: {$give_updates->get_total_db_update_count()}\n";
357
			$log_data .= 'Update IDs: ' . print_r( $give_updates->get_update_ids() , true );
0 ignored issues
show
introduced by
The use of function print_r() is discouraged
Loading history...
358
			$log_data .= 'Update: ' . print_r( $resume_update , true );
0 ignored issues
show
introduced by
The use of function print_r() is discouraged
Loading history...
359
360
			Give()->logs->add( 'Update Error', $log_data, 0, 'update' );
361
362
			wp_die();
363
		}
364
365
		// Disable cache.
366
		Give_Cache::disable();
367
368
		try{
369
			// Run update.
370
			if ( is_array( $update['callback'] ) ) {
371
				$object      = $update['callback'][0];
372
				$method_name = $update['callback'][1];
373
374
				$object->$method_name();
375
376
			} else {
377
				$update['callback']();
378
			}
379
		} catch ( Exception $e ){
380
381
			if( ! $this->is_paused_process() ){
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
382
				$give_updates->__pause_db_update(true);
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
383
			}
384
385
			$log_data = 'Update Task' . "\n";
386
			$log_data .= print_r( $resume_update, true ) . "\n\n";
0 ignored issues
show
introduced by
The use of function print_r() is discouraged
Loading history...
387
			$log_data .= "Error\n {$e->getMessage()}";
388
389
			Give()->logs->add( 'Update Error', $log_data, 0, 'update' );
390
			update_option( 'give_upgrade_error', 1, false );
391
392
			wp_die();
393
		}
394
395
		// Set update info.
396
		$doing_upgrade_args = array(
397
			'update_info'      => $update,
398
			'step'             => ++ $give_updates->step,
399
			'update'           => $give_updates->update,
400
			'heading'          => sprintf( 'Update %s of %s', $give_updates->update, get_option( 'give_db_update_count' ) ),
401
			'percentage'       => $give_updates->percentage,
402
			'total_percentage' => $give_updates->get_db_update_processing_percentage(),
403
		);
404
405
		// Cache upgrade.
406
		update_option( 'give_doing_upgrade', $doing_upgrade_args, false );
407
408
		// Enable cache.
409
		Give_Cache::enable();
410
411
		// Check if current update completed or not.
412
		if ( give_has_upgrade_completed( $update['id'] ) ) {
413
			return false;
414
		}
415
416
		return $update;
417
	}
418
419
	/**
420
	 * Complete
421
	 *
422
	 * Override if applicable, but ensure that the below actions are
423
	 * performed, or, call parent::complete().
424
	 */
425
	public function complete() {
426
		if ( $this->is_paused_process() ) {
427
			return false;
428
		}
429
430
		parent::complete();
431
432
		delete_option( 'give_pause_upgrade' );
433
		delete_option( 'give_upgrade_error' );
434
		delete_option( 'give_db_update_count' );
435
		delete_option( 'give_doing_upgrade' );
436
		add_option( 'give_show_db_upgrade_complete_notice', 1, '', false );
437
438
		// Flush cache.
439
		Give_Cache::flush_cache( true );
440
441
		if ( $cache_keys = Give_Cache::get_options_like( '' ) ) {
442
			Give_Cache::delete( $cache_keys );
443
		}
444
	}
445
446
	/**
447
	 * Get memory limit
448
	 *
449
	 * @return int
450
	 */
451
	protected function get_memory_limit() {
452
		if ( function_exists( 'ini_get' ) ) {
453
			$memory_limit = ini_get( 'memory_limit' );
454
		} else {
455
			// Sensible default.
456
			$memory_limit = '128M';
457
		}
458
459
		if ( ! $memory_limit || '-1' === $memory_limit ) {
460
			// Unlimited, set to 32GB.
461
			$memory_limit = '32G';
462
		}
463
464
		return give_let_to_num( $memory_limit );
465
	}
466
467
	/**
468
	 * Maybe process queue
469
	 *
470
	 * Checks whether data exists within the queue and that
471
	 * the process is not already running.
472
	 */
473
	public function maybe_handle() {
474
		// Don't lock up other requests while processing
475
		session_write_close();
0 ignored issues
show
introduced by
The use of PHP session function session_write_close() is prohibited.
Loading history...
476
477
		if ( $this->is_process_running() || $this->is_paused_process() ) {
478
			// Background process already running.
479
			wp_die();
480
		}
481
482
		if ( $this->is_queue_empty() ) {
483
			// No data to process.
484
			wp_die();
485
		}
486
487
		check_ajax_referer( $this->identifier, 'nonce' );
488
489
		$this->handle();
490
491
		wp_die();
492
	}
493
494
	/**
495
	 * Handle
496
	 *
497
	 * Pass each queue item to the task handler, while remaining
498
	 * within server memory and time limit constraints.
499
	 */
500
	protected function handle() {
501
		$this->lock_process();
502
503
		do {
504
			$batch = $this->get_batch();
505
506
			foreach ( $batch->data as $key => $value ) {
507
				$task = $this->task( $value );
508
509
				if ( false !== $task ) {
510
					$batch->data[ $key ] = $task;
511
				} else {
512
					unset( $batch->data[ $key ] );
513
				}
514
515
				if ( $this->time_exceeded() || $this->memory_exceeded() ) {
516
					// Batch limits reached.
517
					break;
518
				}
519
			}
520
521
			// Update or delete current batch.
522
			if ( ! empty( $batch->data ) ) {
523
				$this->update( $batch->key, $batch->data );
524
			} else {
525
				$this->delete( $batch->key );
526
			}
527
		} while ( ! $this->time_exceeded() && ! $this->memory_exceeded() && ! $this->is_queue_empty() );
528
529
		$this->unlock_process();
530
531
		// Start next batch or complete process.
532
		if ( ! $this->is_queue_empty() ) {
533
534
			// Dispatch only if ajax works.
535
			if( give_test_ajax_works() ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
536
				$this->dispatch();
537
			}
538
		} else {
539
			$this->complete();
540
		}
541
542
		wp_die();
543
	}
544
545
546
	/**
547
	 * Check if backgound upgrade paused or not.
548
	 *
549
	 * @since 2.0
550
	 * @access public
551
	 * @return bool
552
	 */
553
	public function is_paused_process(){
554
		// Delete cache.
555
		wp_cache_delete( 'give_paused_batches', 'options' );
556
557
		$paused_batches = get_option('give_paused_batches');
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
558
559
		return ! empty( $paused_batches );
560
	}
561
562
563
	/**
564
	 * Get identifier
565
	 *
566
	 * @since  2.0
567
	 * @access public
568
	 * @return mixed|string
569
	 */
570
	public function get_identifier() {
571
		return $this->identifier;
572
	}
573
574
	/**
575
	 * Get cron identifier
576
	 *
577
	 * @since  2.0
578
	 * @access public
579
	 * @return mixed|string
580
	 */
581
	public function get_cron_identifier() {
582
		return $this->cron_hook_identifier;
583
	}
584
585
586
	/**
587
	 * Flush background update related cache to prevent task to go to stalled state.
588
	 *
589
	 * @since 2.0.3
590
	 */
591
	public static function flush_cache() {
592
593
		$options = array(
594
			'give_completed_upgrades',
595
			'give_doing_upgrade',
596
			'give_paused_batches',
597
			'give_upgrade_error',
598
			'give_db_update_count',
599
			'give_pause_upgrade',
600
			'give_show_db_upgrade_complete_notice',
601
		);
602
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
603
604
		foreach ( $options as $option ) {
605
			wp_cache_delete( $option, 'options' );
606
		}
607
	}
608
}
609