Test Failed
Push — master ( cd44a0...d8bbfb )
by Devin
01:14
created

Give_Tools_Reset_Stats   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 349
Duplicated Lines 14.61 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
dl 51
loc 349
rs 8.8
c 0
b 0
f 0
wmc 36
lcom 1
cbo 4

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
D get_data() 0 87 14
A get_percentage_complete() 17 17 3
A set_properties() 0 2 1
B process_step() 0 31 4
A headers() 0 3 1
A export() 0 7 1
B pre_fetch() 0 47 5
A get_stored_data() 15 15 3
A store_data() 19 19 2
A delete_data() 0 6 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
/**
3
 * Recount income and stats
4
 *
5
 * This class handles batch processing of resetting donations and income stats.
6
 *
7
 * @subpackage  Admin/Tools/Give_Tools_Reset_Stats
8
 * @copyright   Copyright (c) 2016, WordImpress
9
 * @license     https://opensource.org/licenses/gpl-license GNU Public License
10
 * @since       1.5
11
 */
12
13
// Exit if accessed directly.
14
if ( ! defined( 'ABSPATH' ) ) {
15
	exit;
16
}
17
18
/**
19
 * Give_Tools_Reset_Stats Class
20
 *
21
 * @since 1.5
22
 */
23
class Give_Tools_Reset_Stats extends Give_Batch_Export {
24
25
	/**
26
	 * Our export type. Used for export-type specific filters/actions
27
	 *
28
	 * @var string
29
	 * @since 1.5
30
	 */
31
	public $export_type = '';
32
33
	/**
34
	 * Allows for a non-form batch processing to be run.
35
	 *
36
	 * @since  1.5
37
	 * @var boolean
38
	 */
39
	public $is_void = true;
40
41
	/**
42
	 * Sets the number of items to pull on each step
43
	 *
44
	 * @since  1.5
45
	 * @var integer
46
	 */
47
	public $per_step = 30;
48
49
	/**
50
	 * Constructor.
51
	 */
52
	public function __construct( $_step = 1 ) {
53
		parent::__construct( $_step );
54
55
		$this->is_writable = true;
56
	}
57
58
	/**
59
	 * Get the Export Data
60
	 *
61
	 * @access public
62
	 * @since  1.5
63
	 * @global object $wpdb Used to query the database using the WordPress
64
	 *                      Database API
65
	 * @return bool   $data The data for the CSV file
66
	 */
67
	public function get_data() {
68
		global $wpdb;
69
70
		$items = $this->get_stored_data( 'give_temp_reset_ids' );
71
72
		if ( ! is_array( $items ) ) {
73
			return false;
74
		}
75
76
		$offset     = ( $this->step - 1 ) * $this->per_step;
77
		$step_items = array_slice( $items, $offset, $this->per_step );
78
79
		if ( $step_items ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $step_items of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
80
81
			$step_ids = array(
82
				'customers' => array(),
83
				'forms'     => array(),
84
				'other'     => array(),
85
			);
86
87
			foreach ( $step_items as $item ) {
88
89
				switch ( $item['type'] ) {
90
					case 'customer':
91
						$step_ids['customers'][] = $item['id'];
92
						break;
93
					case 'forms':
94
						$step_ids['give_forms'][] = $item['id'];
95
						break;
96
					default:
97
						$item_type                = apply_filters( 'give_reset_item_type', 'other', $item );
98
						$step_ids[ $item_type ][] = $item['id'];
99
						break;
100
				}
101
			}
102
103
			$sql = array();
104
			$meta_table = __give_v20_bc_table_details('form' );
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
105
106
			foreach ( $step_ids as $type => $ids ) {
107
108
				if ( empty( $ids ) ) {
109
					continue;
110
				}
111
112
				$ids = implode( ',', $ids );
113
114
				switch ( $type ) {
115
					case 'customers':
116
						$sql[]           = "DELETE FROM $wpdb->donors WHERE id IN ($ids)";
117
						$table_name      = $wpdb->donors;
118
						$meta_table_name = $wpdb->donormeta;
119
						$sql[]           = "DELETE FROM $table_name WHERE id IN ($ids)";
120
						$sql[]           = "DELETE FROM $meta_table_name WHERE donor_id IN ($ids)";
121
						break;
122
					case 'forms':
123
						$sql[] = "UPDATE {$meta_table['name']} SET meta_value = 0 WHERE meta_key = '_give_form_sales' AND {$meta_table['column']['id']} IN ($ids)";
124
						$sql[] = "UPDATE {$meta_table['name']} SET meta_value = 0.00 WHERE meta_key = '_give_form_earnings' AND {$meta_table['column']['id']} IN ($ids)";
125
						break;
126
					case 'other':
127
						$sql[] = "DELETE FROM $wpdb->posts WHERE id IN ($ids)";
128
						$sql[] = "DELETE FROM $wpdb->postmeta WHERE post_id IN ($ids)";
129
						$sql[] = "DELETE FROM $wpdb->comments WHERE comment_post_ID IN ($ids)";
130
						$sql[] = "DELETE FROM $wpdb->commentmeta WHERE comment_id NOT IN (SELECT comment_ID FROM $wpdb->comments)";
131
						break;
132
				}
133
134
				if ( ! in_array( $type, array( 'customers', 'forms', 'other' ) ) ) {
135
					// Allows other types of custom post types to filter on their own post_type
136
					// and add items to the query list, for the IDs found in their post type.
137
					$sql = apply_filters( "give_reset_add_queries_{$type}", $sql, $ids );
138
				}
139
			}
140
141
			if ( ! empty( $sql ) ) {
142
				foreach ( $sql as $query ) {
143
					$wpdb->query( $query );
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...
144
				}
145
			}
146
147
			return true;
148
149
		}// End if().
150
151
		return false;
152
153
	}
154
155
	/**
156
	 * Return the calculated completion percentage.
157
	 *
158
	 * @since 1.5
159
	 * @return int
160
	 */
161 View Code Duplication
	public function get_percentage_complete() {
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...
162
163
		$items = $this->get_stored_data( 'give_temp_reset_ids' );
164
		$total = count( $items );
165
166
		$percentage = 100;
167
168
		if ( $total > 0 ) {
169
			$percentage = ( ( $this->per_step * $this->step ) / $total ) * 100;
170
		}
171
172
		if ( $percentage > 100 ) {
173
			$percentage = 100;
174
		}
175
176
		return $percentage;
177
	}
178
179
	/**
180
	 * Set the properties specific to the payments export.
181
	 *
182
	 * @since 1.5
183
	 *
184
	 * @param array $request The Form Data passed into the batch processing.
185
	 */
186
	public function set_properties( $request ) {
187
	}
188
189
	/**
190
	 * Process a step
191
	 *
192
	 * @since 1.5
193
	 * @return bool
194
	 */
195
	public function process_step() {
196
197
		if ( ! $this->can_export() ) {
198
			wp_die( esc_html__( 'You do not have permission to reset data.', 'give' ), esc_html__( 'Error', 'give' ), array(
199
				'response' => 403,
200
			) );
201
		}
202
203
		$had_data = $this->get_data();
204
205
		if ( $had_data ) {
206
			$this->done = false;
207
208
			return true;
209
		} else {
210
			update_option( 'give_earnings_total', 0 );
211
			Give_Cache::delete( Give_Cache::get_key( 'give_estimated_monthly_stats' ) );
212
213
			$this->delete_data( 'give_temp_reset_ids' );
214
215
			// Reset the sequential order numbers
216
			if ( give_get_option( 'enable_sequential' ) ) {
217
				delete_option( 'give_last_payment_number' );
218
			}
219
220
			$this->done    = true;
221
			$this->message = esc_html__( 'Donation forms, income, donations counts, and logs successfully reset.', 'give' );
222
223
			return false;
224
		}
225
	}
226
227
	/**
228
	 * Headers
229
	 */
230
	public function headers() {
231
		give_ignore_user_abort();
232
	}
233
234
	/**
235
	 * Perform the export
236
	 *
237
	 * @access public
238
	 * @since  1.5
239
	 * @return void
240
	 */
241
	public function export() {
242
243
		// Set headers
244
		$this->headers();
245
246
		give_die();
247
	}
248
249
	/**
250
	 * Pre Fetch
251
	 */
252
	public function pre_fetch() {
253
254
		if ( $this->step == 1 ) {
0 ignored issues
show
introduced by
Found "== 1". Use Yoda Condition checks, you must
Loading history...
255
			$this->delete_data( 'give_temp_reset_ids' );
256
		}
257
258
		$items = get_option( 'give_temp_reset_ids', false );
259
260
		if ( false === $items ) {
261
			$items = array();
262
263
			$give_types_for_reset = array( 'give_forms', 'give_payment' );
264
			$give_types_for_reset = apply_filters( 'give_reset_store_post_types', $give_types_for_reset );
265
266
			$args = apply_filters( 'give_tools_reset_stats_total_args', array(
267
				'post_type'      => $give_types_for_reset,
268
				'post_status'    => 'any',
269
				'posts_per_page' => - 1,
270
			) );
271
272
			$posts = get_posts( $args );
273
			foreach ( $posts as $post ) {
274
				$items[] = array(
275
					'id'   => (int) $post->ID,
276
					'type' => $post->post_type,
277
				);
278
			}
279
280
			$donor_args = array(
281
				'number' => - 1,
282
			);
283
			$donors     = Give()->donors->get_donors( $donor_args );
284
			foreach ( $donors as $donor ) {
0 ignored issues
show
Bug introduced by
The expression $donors of type array|object|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
285
				$items[] = array(
286
					'id'   => (int) $donor->id,
287
					'type' => 'customer',
288
				);
289
			}
290
291
			// Allow filtering of items to remove with an unassociative array for each item
292
			// The array contains the unique ID of the item, and a 'type' for you to use in the execution of the get_data method
293
			$items = apply_filters( 'give_reset_items', $items );
294
295
			$this->store_data( 'give_temp_reset_ids', $items );
296
		}// End if().
297
298
	}
299
300
	/**
301
	 * Given a key, get the information from the Database Directly.
302
	 *
303
	 * @since  1.5
304
	 *
305
	 * @param  string $key The option_name
306
	 *
307
	 * @return mixed       Returns the data from the database.
308
	 */
309 View Code Duplication
	private function get_stored_data( $key ) {
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...
310
		global $wpdb;
311
		$value = $wpdb->get_var( $wpdb->prepare( "SELECT option_value FROM $wpdb->options WHERE option_name = '%s'", $key ) );
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...
312
313
		if ( empty( $value ) ) {
314
			return false;
315
		}
316
317
		$maybe_json = json_decode( $value );
318
		if ( ! is_null( $maybe_json ) ) {
319
			$value = json_decode( $value, true );
320
		}
321
322
		return $value;
323
	}
324
325
	/**
326
	 * Give a key, store the value.
327
	 *
328
	 * @since  1.5
329
	 *
330
	 * @param  string $key   The option_name.
331
	 * @param  mixed  $value The value to store.
332
	 *
333
	 * @return void
334
	 */
335 View Code Duplication
	private function store_data( $key, $value ) {
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...
336
		global $wpdb;
337
338
		$value = is_array( $value ) ? wp_json_encode( $value ) : esc_attr( $value );
339
340
		$data = array(
341
			'option_name'  => $key,
342
			'option_value' => $value,
343
			'autoload'     => 'no',
344
		);
345
346
		$formats = array(
347
			'%s',
348
			'%s',
349
			'%s',
350
		);
351
352
		$wpdb->replace( $wpdb->options, $data, $formats );
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
353
	}
354
355
	/**
356
	 * Delete an option
357
	 *
358
	 * @since  1.5
359
	 *
360
	 * @param  string $key The option_name to delete
361
	 *
362
	 * @return void
363
	 */
364
	private function delete_data( $key ) {
365
		global $wpdb;
366
		$wpdb->delete( $wpdb->options, array(
367
			'option_name' => $key,
368
		) );
369
	}
370
371
}