Completed
Push — release/2.0 ( 4d845f...d9fa8a )
by Ravinder
18:24
created

Give_Tools_Reset_Stats::store_data()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 12
nc 1
nop 2
dl 0
loc 19
rs 9.4285
c 0
b 0
f 0
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 23 and the first side effect is on line 15.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
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
	 * Get the Export Data
51
	 *
52
	 * @access public
53
	 * @since  1.5
54
	 * @global object $wpdb Used to query the database using the WordPress
55
	 *                      Database API
56
	 * @return bool   $data The data for the CSV file
57
	 */
58
	public function get_data() {
59
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
60
61
		$items = $this->get_stored_data( 'give_temp_reset_ids' );
62
63
		if ( ! is_array( $items ) ) {
64
			return false;
65
		}
66
67
		$offset     = ( $this->step - 1 ) * $this->per_step;
68
		$step_items = array_slice( $items, $offset, $this->per_step );
69
70
		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...
71
72
			$step_ids = array(
73
				'customers' => array(),
74
				'forms'     => array(),
75
				'other'     => array(),
76
			);
77
78
			foreach ( $step_items as $item ) {
79
80
				switch ( $item['type'] ) {
81
					case 'customer':
82
						$step_ids['customers'][] = $item['id'];
83
						break;
84
					case 'forms':
85
						$step_ids['give_forms'][] = $item['id'];
86
						break;
87
					default:
88
						$item_type                = apply_filters( 'give_reset_item_type', 'other', $item );
89
						$step_ids[ $item_type ][] = $item['id'];
90
						break;
91
				}
92
			}
93
94
			$sql = array();
95
96
			foreach ( $step_ids as $type => $ids ) {
97
98
				if ( empty( $ids ) ) {
99
					continue;
100
				}
101
102
				$ids = implode( ',', $ids );
103
104
				switch ( $type ) {
105
					case 'customers':
106
						$table_name = $wpdb->prefix . 'give_customers';
107
						$sql[]      = "DELETE FROM $table_name WHERE id IN ($ids)";
108
						break;
109
					case 'forms':
110
						$sql[] = "UPDATE $wpdb->postmeta SET meta_value = 0 WHERE meta_key = '_give_form_sales' AND post_id IN ($ids)";
111
						$sql[] = "UPDATE $wpdb->postmeta SET meta_value = 0.00 WHERE meta_key = '_give_form_earnings' AND post_id IN ($ids)";
112
						break;
113
					case 'other':
114
						$sql[] = "DELETE FROM $wpdb->posts WHERE id IN ($ids)";
115
						$sql[] = "DELETE FROM $wpdb->postmeta WHERE post_id IN ($ids)";
116
						$sql[] = "DELETE FROM $wpdb->comments WHERE comment_post_ID IN ($ids)";
117
						$sql[] = "DELETE FROM $wpdb->commentmeta WHERE comment_id NOT IN (SELECT comment_ID FROM $wpdb->comments)";
118
						break;
119
				}
120
121
				if ( ! in_array( $type, array( 'customers', 'forms', 'other' ) ) ) {
122
					// Allows other types of custom post types to filter on their own post_type
123
					// and add items to the query list, for the IDs found in their post type.
124
					$sql = apply_filters( "give_reset_add_queries_{$type}", $sql, $ids );
125
				}
126
			}
127
128
			if ( ! empty( $sql ) ) {
129
				foreach ( $sql as $query ) {
130
					$wpdb->query( $query );
131
				}
132
			}
133
134
			return true;
135
136
		}// End if().
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
137
138
		return false;
139
140
	}
141
142
	/**
143
	 * Return the calculated completion percentage.
144
	 *
145
	 * @since 1.5
146
	 * @return int
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|double?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
147
	 */
148
	public function get_percentage_complete() {
149
150
		$items = $this->get_stored_data( 'give_temp_reset_ids', false );
0 ignored issues
show
Unused Code introduced by
The call to Give_Tools_Reset_Stats::get_stored_data() has too many arguments starting with false.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
151
		$total = count( $items );
152
153
		$percentage = 100;
154
155
		if ( $total > 0 ) {
156
			$percentage = ( ( $this->per_step * $this->step ) / $total ) * 100;
157
		}
158
159
		if ( $percentage > 100 ) {
160
			$percentage = 100;
161
		}
162
163
		return $percentage;
164
	}
165
166
	/**
167
	 * Set the properties specific to the payments export.
168
	 *
169
	 * @since 1.5
170
	 *
171
	 * @param array $request The Form Data passed into the batch processing.
172
	 */
173
	public function set_properties( $request ) {
174
	}
175
176
	/**
177
	 * Process a step
178
	 *
179
	 * @since 1.5
180
	 * @return bool
181
	 */
182
	public function process_step() {
183
184
		if ( ! $this->can_export() ) {
185
			wp_die( esc_html__( 'You do not have permission to reset data.', 'give' ), esc_html__( 'Error', 'give' ), array(
186
				'response' => 403,
187
			) );
188
		}
189
190
		$had_data = $this->get_data();
191
192
		if ( $had_data ) {
193
			$this->done = false;
0 ignored issues
show
Bug introduced by
The property done does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
194
195
			return true;
196
		} else {
197
			update_option( 'give_earnings_total', 0 );
198
			Give_Cache::delete( Give_Cache::get_key( 'give_estimated_monthly_stats' ) );
199
200
			$this->delete_data( 'give_temp_reset_ids' );
201
202
			// Reset the sequential order numbers
203
			if ( give_get_option( 'enable_sequential' ) ) {
204
				delete_option( 'give_last_payment_number' );
205
			}
206
207
			$this->done    = true;
208
			$this->message = esc_html__( 'Donation forms, income, donations counts, and logs successfully reset.', 'give' );
0 ignored issues
show
Bug introduced by
The property message does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
209
210
			return false;
211
		}
212
	}
213
214
	/**
215
	 * Headers
216
	 */
217
	public function headers() {
218
		ignore_user_abort( true );
219
220
		if ( ! give_is_func_disabled( 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) {
221
			set_time_limit( 0 );
222
		}
223
	}
224
225
	/**
226
	 * Perform the export
227
	 *
228
	 * @access public
229
	 * @since  1.5
230
	 * @return void
231
	 */
232
	public function export() {
233
234
		// Set headers
235
		$this->headers();
236
237
		give_die();
238
	}
239
240
	/**
241
	 * Pre Fetch
242
	 */
243
	public function pre_fetch() {
244
245
		if ( $this->step == 1 ) {
246
			$this->delete_data( 'give_temp_reset_ids' );
247
		}
248
249
		$items = get_option( 'give_temp_reset_ids', false );
250
251
		if ( false === $items ) {
252
			$items = array();
253
254
			$give_types_for_reset = array( 'give_forms', 'give_log', 'give_payment' );
255
			$give_types_for_reset = apply_filters( 'give_reset_store_post_types', $give_types_for_reset );
256
257
			$args = apply_filters( 'give_tools_reset_stats_total_args', array(
258
				'post_type'      => $give_types_for_reset,
259
				'post_status'    => 'any',
260
				'posts_per_page' => - 1,
261
			) );
262
263
			$posts = get_posts( $args );
264
			foreach ( $posts as $post ) {
265
				$items[] = array(
266
					'id'   => (int) $post->ID,
267
					'type' => $post->post_type,
268
				);
269
			}
270
271
			$donor_args = array(
272
				'number' => - 1,
273
			);
274
			$donors     = Give()->donors->get_donors( $donor_args );
275
			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...
276
				$items[] = array(
277
					'id'   => (int) $donor->id,
278
					'type' => 'customer',
279
				);
280
			}
281
282
			// Allow filtering of items to remove with an unassociative array for each item
283
			// The array contains the unique ID of the item, and a 'type' for you to use in the execution of the get_data method
284
			$items = apply_filters( 'give_reset_items', $items );
285
286
			$this->store_data( 'give_temp_reset_ids', $items );
287
		}// End if().
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
288
289
	}
290
291
	/**
292
	 * Given a key, get the information from the Database Directly.
293
	 *
294
	 * @since  1.5
295
	 *
296
	 * @param  string $key The option_name
297
	 *
298
	 * @return mixed       Returns the data from the database.
299
	 */
300
	private function get_stored_data( $key ) {
301
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
302
		$value = $wpdb->get_var( $wpdb->prepare( "SELECT option_value FROM $wpdb->options WHERE option_name = '%s'", $key ) );
303
304
		return empty( $value ) ? false : maybe_unserialize( $value );
305
	}
306
307
	/**
308
	 * Give a key, store the value.
309
	 *
310
	 * @since  1.5
311
	 *
312
	 * @param  string $key   The option_name.
313
	 * @param  mixed  $value The value to store.
314
	 *
315
	 * @return void
316
	 */
317
	private function store_data( $key, $value ) {
318
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
319
320
		$value = maybe_serialize( $value );
321
322
		$data = array(
323
			'option_name'  => $key,
324
			'option_value' => $value,
325
			'autoload'     => 'no',
326
		);
327
328
		$formats = array(
329
			'%s',
330
			'%s',
331
			'%s',
332
		);
333
334
		$wpdb->replace( $wpdb->options, $data, $formats );
335
	}
336
337
	/**
338
	 * Delete an option
339
	 *
340
	 * @since  1.5
341
	 *
342
	 * @param  string $key The option_name to delete
343
	 *
344
	 * @return void
345
	 */
346
	private function delete_data( $key ) {
347
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
348
		$wpdb->delete( $wpdb->options, array(
349
			'option_name' => $key,
350
		) );
351
	}
352
353
}
354