Test Failed
Push — issues/2991 ( 38aa67 )
by Ravinder
06:06
created

Give_Batch_Donors_Export::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * Batch Donors Export Class
4
 *
5
 * This class handles donor export.
6
 *
7
 * @package     Give
8
 * @subpackage  Admin/Reports
9
 * @copyright   Copyright (c) 2016, WordImpress
10
 * @license     https://opensource.org/licenses/gpl-license GNU Public License
11
 * @since       1.5
12
 */
13
14
// Exit if accessed directly.
15
if ( ! defined( 'ABSPATH' ) ) {
16
	exit;
17
}
18
19
/**
20
 * Give_Batch_Donors_Export Class
21
 *
22
 * @since 1.5
23
 */
24
class Give_Batch_Donors_Export extends Give_Batch_Export {
25
26
	/**
27
	 * Our export type. Used for export-type specific filters/actions.
28
	 *
29
	 * @var string
30
	 * @since 1.5
31
	 */
32
	public $export_type = 'donors';
33
34
	/**
35
	 * Form submission data
36
	 *
37
	 * @var array
38
	 * @since 1.5
39
	 */
40
	private $data = array();
41
42
	/**
43
	 * Array of donor ids which is already included in csv file.
44
	 *
45
	 * @since 1.8
46
	 * @var array
47
	 */
48
	private $donor_ids = array();
49
50
	/**
51
	 * Array of payment stats which is already included in csv file.
52
	 *
53
	 * @since 1.8.9
54
	 * @var array
55
	 */
56
	private $payment_stats = array();
57
58
	/**
59
	 * Export query id.
60
	 *
61
	 * @since 1.8
62
	 * @var string
63
	 */
64
	private $query_id = '';
65
66
	/**
67
	 * Give_Batch_Export constructor.
68
	 *
69
	 * @since 2.0.7
70
	 *
71
	 * @param int $_step
72
	 */
73
	public function __construct( $_step = 1 ) {
74
75
		parent::__construct( $_step );
76
77
		// Filter to change the filename.
78
		add_filter( 'give_export_filename', array( $this, 'give_export_filename' ), 10, 2 );
79
	}
80
81
	/**
82
	 * Function to change the filename
83
	 *
84
	 * @since 2.0.7
85
	 *
86
	 * @param $filename
87
	 * @param $export_type
88
	 *
89
	 * @return string
90
	 */
91
	function give_export_filename( $filename, $export_type ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
92
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
93
94
		if ( $this->export_type !== $export_type ) {
95
			return $filename;
96
		}
97
98
		if ( ! empty( (int) $_GET['forms'] ) ) {
99
			$slug     = get_post_field( 'post_name', get_post( absint( $_GET['forms'] ) ) );
0 ignored issues
show
introduced by
Detected access of super global var $_GET, probably need manual inspection.
Loading history...
100
			$filename = 'give-export-donors-' . $slug . '-' . date( 'm-d-Y' );
101
		} else {
102
			$filename = 'give-export-donors-all-forms-' . date( 'm-d-Y' );
103
		}
104
105
		return $filename;
106
	}
107
108
	/**
109
	 * Set the properties specific to the donors export.
110
	 *
111
	 * @since 1.5
112
	 *
113
	 * @param array $request The Form Data passed into the batch processing
114
	 */
115
	public function set_properties( $request ) {
116
117
		// Set data from form submission
118
		if ( isset( $_POST['form'] ) ) {
0 ignored issues
show
introduced by
Detected access of super global var $_POST, probably need manual inspection.
Loading history...
119
			parse_str( $_POST['form'], $this->data );
0 ignored issues
show
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...
120
		}
121
122
		$this->form = $this->data['forms'];
123
124
		// Setup donor ids cache.
125
		if ( ! empty( $this->form ) ) {
126
			// Cache donor ids to output unique list of donor.
127
			$this->query_id = give_clean( $_REQUEST['give_export_option']['query_id'] );
0 ignored issues
show
Documentation Bug introduced by
It seems like give_clean($_REQUEST['gi...t_option']['query_id']) can also be of type array. However, the property $query_id is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
introduced by
Detected access of super global var $_REQUEST, probably need manual inspection.
Loading history...
introduced by
Detected usage of a non-validated input variable: $_REQUEST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_REQUEST
Loading history...
128
			$this->cache_donor_ids();
129
		}
130
131
		$this->price_id = ! empty( $request['give_price_option'] ) && 'all' !== $request['give_price_option'] ? absint( $request['give_price_option'] ) : null;
132
	}
133
134
	/**
135
	 * Cache donor ids.
136
	 *
137
	 * @since  1.8.9
138
	 * @access private
139
	 */
140
	private function cache_donor_ids() {
141
		// Fetch already cached donor ids.
142
		$donor_ids = $this->donor_ids;
143
144
		if ( $cached_donor_ids = Give_Cache::get( $this->query_id, true ) ) {
145
			$donor_ids = array_unique( array_merge( $cached_donor_ids, $this->donor_ids ) );
146
		}
147
148
		$donor_ids = array_values( $donor_ids );
149
		Give_Cache::set( $this->query_id, $donor_ids, HOUR_IN_SECONDS, true );
150
	}
151
152
	/**
153
	 * Set the CSV columns.
154
	 *
155
	 * @access public
156
	 * @since  1.5
157
	 * @return array|bool $cols All the columns.
158
	 */
159
	public function csv_cols() {
160
161
		$columns = isset( $this->data['give_export_option'] ) ? $this->data['give_export_option'] : array();
162
163
		// We need columns.
164
		if ( empty( $columns ) ) {
165
			return false;
166
		}
167
168
		$cols = $this->get_cols( $columns );
169
170
		return $cols;
171
	}
172
173
	/**
174
	 * CSV file columns.
175
	 *
176
	 * @param array $columns
177
	 *
178
	 * @return array
179
	 */
180
	private function get_cols( $columns ) {
181
182
		$cols = array();
183
184
		foreach ( $columns as $key => $value ) {
185
186
			switch ( $key ) {
187
				case 'full_name' :
188
					$cols['full_name'] = esc_html__( 'Full Name', 'give' );
189
					break;
190
				case 'email' :
191
					$cols['email'] = esc_html__( 'Email Address', 'give' );
192
					break;
193
				case 'address' :
194
					$cols['address_line1']   = esc_html__( 'Address', 'give' );
195
					$cols['address_line2']   = esc_html__( 'Address 2', 'give' );
196
					$cols['address_city']    = esc_html__( 'City', 'give' );
197
					$cols['address_state']   = esc_html__( 'State', 'give' );
198
					$cols['address_zip']     = esc_html__( 'Zip', 'give' );
199
					$cols['address_country'] = esc_html__( 'Country', 'give' );
200
					break;
201
				case 'userid' :
202
					$cols['userid'] = esc_html__( 'User ID', 'give' );
203
					break;
204
				case 'donor_created_date' :
205
					$cols['donor_created_date'] = esc_html__( 'Donor Created Date', 'give' );
206
					break;
207
				case 'donations' :
208
					$cols['donations'] = esc_html__( 'Number of Donations', 'give' );
209
					break;
210
				case 'donation_sum' :
211
					$cols['donation_sum'] = esc_html__( 'Sum of Donations', 'give' );
212
					break;
213
			}
214
		}
215
216
		return $cols;
217
218
	}
219
220
	/**
221
	 * Get the Export Data
222
	 *
223
	 * @access public
224
	 * @since  1.0
225
	 *
226
	 * @return array $data The data for the CSV file.
227
	 */
228
	public function get_data() {
229
		$i = 0;
230
231
		$data             = array();
232
		$cached_donor_ids = Give_Cache::get( $this->query_id, true );
233
234
		if ( ! empty( $this->form ) ) {
235
236
			// Export donors for a specific donation form and also within specified timeframe
237
			$args = array(
238
				'output'     => 'payments', // Use 'posts' to get standard post objects
239
				'post_type'  => array( 'give_payment' ),
240
				'number'     => 30,
241
				'paged'      => $this->step,
242
				'status'     => 'publish',
243
				'meta_key'   => '_give_payment_form_id',
0 ignored issues
show
introduced by
Detected usage of meta_key, possible slow query.
Loading history...
244
				'meta_value' => absint( $this->form ),
0 ignored issues
show
introduced by
Detected usage of meta_value, possible slow query.
Loading history...
245
			);
246
247
			// Check for date option filter
248 View Code Duplication
			if ( ! empty( $this->data['donor_export_start_date'] ) || ! empty( $this->data['donor_export_end_date'] ) ) {
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...
249
				$args['start_date'] = ! empty( $this->data['donor_export_start_date'] ) ? date( 'Y-n-d 00:00:00', strtotime( $this->data['donor_export_start_date'] ) ) : date( 'Y-n-d 23:59:59', '1970-1-01 00:00:00' );
250
				$args['end_date']   = ! empty( $this->data['donor_export_end_date'] ) ? date( 'Y-n-d 23:59:59', strtotime( $this->data['donor_export_end_date'] ) ) : date( 'Y-n-d 23:59:59', current_time( 'timestamp' ) );
251
			}
252
253
			// Check for price option.
254
			if ( null !== $this->price_id ) {
255
				$args['meta_query'] = array(
0 ignored issues
show
introduced by
Detected usage of meta_query, possible slow query.
Loading history...
256
					array(
257
						'key'   => '_give_payment_price_id',
258
						'value' => (int) $this->price_id,
259
					),
260
				);
261
			}
262
263
			$payments_query = new Give_Payments_Query( $args );
264
			$payments       = $payments_query->get_payments();
265
266
			if ( $payments ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $payments 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...
267
				/* @var Give_Payment $payment */
268
				foreach ( $payments as $payment ) {
269
					// Set donation sum.
270
					$this->payment_stats[ $payment->customer_id ]['donation_sum'] = isset( $this->payment_stats[ $payment->customer_id ]['donation_sum'] ) ?
271
						$this->payment_stats[ $payment->customer_id ]['donation_sum'] :
272
						0;
273
					$this->payment_stats[ $payment->customer_id ]['donation_sum'] += $payment->total;
274
275
					// Set donation count.
276
					$this->payment_stats[ $payment->customer_id ]['donations'] = isset( $this->payment_stats[ $payment->customer_id ]['donations'] ) ?
277
						++ $this->payment_stats[ $payment->customer_id ]['donations'] :
278
						1;
279
280
					// Set donation form name.
281
					$this->payment_stats[ $payment->customer_id ]['form_title'] = $payment->form_title;
282
283
					// Continue if donor already included.
284
					if ( empty( $payment->customer_id ) ||
285
					     in_array( $payment->customer_id, $cached_donor_ids )
286
					) {
287
						continue;
288
					}
289
290
					$this->donor_ids[] = $cached_donor_ids[] = $payment->customer_id;
291
292
					$i ++;
293
				}
294
295
				if ( ! empty( $this->donor_ids ) ) {
296
					foreach ( $this->donor_ids as $donor_id ) {
297
						$donor                      = Give()->donors->get_donor_by( 'id', $donor_id );
298
						$donor->purchase_count      = $this->payment_stats[ $donor_id ]['donations'];
299
						$donor->purchase_value      = $this->payment_stats[ $donor_id ]['donation_sum'];
300
						$data[]                     = $this->set_donor_data( $i, $data, $donor );
301
					}
302
303
					// Cache donor ids only if admin export donor for specific form.
304
					$this->cache_donor_ids();
305
				}
306
			}
307
		} else {
308
309
			// Export all donors.
310
			$offset = 30 * ( $this->step - 1 );
311
312
			$args = array(
313
				'number' => 30,
314
				'offset' => $offset,
315
			);
316
317
			// Check for date option filter
318 View Code Duplication
			if ( ! empty( $this->data['donor_export_start_date'] ) || ! empty( $this->data['donor_export_end_date'] ) ) {
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...
319
				$args['date'] = array(
320
					'start' => ! empty( $this->data['donor_export_start_date'] ) ? date( 'Y-n-d 00:00:00', strtotime( $this->data['donor_export_start_date'] ) ) : date( 'Y-n-d 23:59:59', '1970-1-01 00:00:00' ),
321
					'end'   => ! empty( $this->data['donor_export_end_date'] ) ? date( 'Y-n-d 23:59:59', strtotime( $this->data['donor_export_end_date'] ) ) : date( 'Y-n-d 23:59:59', current_time( 'timestamp' ) ),
322
				);
323
			}
324
325
			$donors = Give()->donors->get_donors( $args );
326
327
			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...
328
329
				// Continue if donor already included.
330
				if ( empty( $donor->id ) || empty( $donor->payment_ids ) ) {
331
					continue;
332
				}
333
334
				$payment                    = new Give_Payment( $donor->payment_ids );
0 ignored issues
show
Unused Code introduced by
$payment is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
335
				$data[]                     = $this->set_donor_data( $i, $data, $donor );
336
				$i ++;
337
			}
338
		}// End if().
339
340
		$data = apply_filters( 'give_export_get_data', $data );
341
		$data = apply_filters( "give_export_get_data_{$this->export_type}", $data );
342
343
		return $data;
344
	}
345
346
	/**
347
	 * Return the calculated completion percentage.
348
	 *
349
	 * @since 1.5
350
	 * @return int
351
	 */
352 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...
353
354
		$percentage = 0;
355
356
		// We can't count the number when getting them for a specific form.
357
		if ( empty( $this->form ) ) {
358
359
			$total = Give()->donors->count();
360
361
			if ( $total > 0 ) {
362
363
				$percentage = ( ( 30 * $this->step ) / $total ) * 100;
364
365
			}
366
		}
367
368
		if ( $percentage > 100 ) {
369
			$percentage = 100;
370
		}
371
372
		return $percentage;
373
	}
374
375
	/**
376
	 * Set Donor Data
377
	 *
378
	 * @param int        $i
379
	 * @param array      $data
380
	 * @param Give_Donor $donor
381
	 *
382
	 * @return mixed
383
	 */
384
	private function set_donor_data( $i, $data, $donor ) {
385
386
		$columns = $this->csv_cols();
387
388
		// Set address variable
389
		$address = '';
390
		if ( isset( $donor->user_id ) && $donor->user_id > 0 ) {
391
			$address = give_get_donor_address( $donor->user_id );
392
		}
393
394
		// Set columns
395
		if ( ! empty( $columns['full_name'] ) ) {
396
			$data[ $i ]['full_name'] = $donor->name;
397
		}
398
		if ( ! empty( $columns['email'] ) ) {
399
			$data[ $i ]['email'] = $donor->email;
400
		}
401
		if ( ! empty( $columns['address_line1'] ) ) {
402
403
			$data[ $i ]['address_line1']   = isset( $address['line1'] ) ? $address['line1'] : '';
404
			$data[ $i ]['address_line2']   = isset( $address['line2'] ) ? $address['line2'] : '';
405
			$data[ $i ]['address_city']    = isset( $address['city'] ) ? $address['city'] : '';
406
			$data[ $i ]['address_state']   = isset( $address['state'] ) ? $address['state'] : '';
407
			$data[ $i ]['address_zip']     = isset( $address['zip'] ) ? $address['zip'] : '';
408
			$data[ $i ]['address_country'] = isset( $address['country'] ) ? $address['country'] : '';
409
		}
410
		if ( ! empty( $columns['userid'] ) ) {
411
			$data[ $i ]['userid'] = ! empty( $donor->user_id ) ? $donor->user_id : '';
412
		}
413
		if ( ! empty( $columns['donor_created_date'] ) ) {
414
			$data[ $i ]['donor_created_date'] = date_i18n( give_date_format(), strtotime( $donor->date_created ) );
415
		}
416
		if ( ! empty( $columns['donations'] ) ) {
417
			$data[ $i ]['donations'] = $donor->purchase_count;
418
		}
419
		if ( ! empty( $columns['donation_sum'] ) ) {
420
			$data[ $i ]['donation_sum'] = give_format_amount( $donor->purchase_value, array( 'sanitize' => false ) );
421
		}
422
423
		$data[ $i ] = apply_filters( 'give_export_set_donor_data', $data[ $i ], $donor );
424
425
		return $data[ $i ];
426
427
	}
428
429
	/**
430
	 * Unset the properties specific to the donors export.
431
	 *
432
	 * @param array             $request
433
	 * @param Give_Batch_Export $export
434
	 */
435
	public function unset_properties( $request, $export ) {
436
		if ( $export->done ) {
437
			Give_Cache::delete( "give_cache_{$this->query_id}" );
438
		}
439
	}
440
}