Completed
Push — mehul/issue/1427 ( ad1d03 )
by Ravinder
757:24 queued 740:01
created

Give_Batch_Donors_Export::set_properties()   A

Complexity

Conditions 5
Paths 16

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
nc 16
nop 1
dl 0
loc 19
rs 9.3222
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 24 and the first side effect is on line 16.

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
 * 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
	 * Set the properties specific to the donors export.
68
	 *
69
	 * @since 1.5
70
	 *
71
	 * @param array $request The Form Data passed into the batch processing
72
	 */
73
	public function set_properties( $request ) {
74
75
		// Set data from form submission
76
		if ( isset( $_POST['form'] ) ) {
77
			parse_str( $_POST['form'], $this->data );
78
		}
79
80
		$this->form = $this->data['forms'];
81
82
		// Setup donor ids cache.
83
		if ( ! empty( $this->form ) ) {
84
			// Cache donor ids to output unique list of donor.
85
			$this->query_id = give_clean( $_REQUEST['give_export_option']['query_id'] );
86
			$this->cache_donor_ids();
87
		}
88
89
		$this->price_id = ! empty( $request['give_price_option'] ) && 'all' !== $request['give_price_option'] ? absint( $request['give_price_option'] ) : null;
90
91
	}
92
93
94
	/**
95
	 * Cache donor ids.
96
	 *
97
	 * @since  1.8.9
98
	 * @access private
99
	 */
100
	private function cache_donor_ids() {
101
		// Fetch already cached donor ids.
102
		$donor_ids = $this->donor_ids;
103
104
		if ( $cached_donor_ids = Give_Cache::get( $this->query_id, true ) ) {
105
			$donor_ids = array_unique( array_merge( $cached_donor_ids, $this->donor_ids ) );
106
		}
107
108
		$donor_ids = array_values( $donor_ids );
109
		Give_Cache::set( $this->query_id, $donor_ids, HOUR_IN_SECONDS, true );
110
	}
111
112
	/**
113
	 * Set the CSV columns.
114
	 *
115
	 * @access public
116
	 * @since  1.5
117
	 * @return array|bool $cols All the columns.
118
	 */
119
	public function csv_cols() {
120
121
		$columns = isset( $this->data['give_export_option'] ) ? $this->data['give_export_option'] : array();
122
123
		// We need columns.
124
		if ( empty( $columns ) ) {
125
			return false;
126
		}
127
128
		$cols = $this->get_cols( $columns );
129
130
		return $cols;
131
	}
132
133
	/**
134
	 * CSV file columns.
135
	 *
136
	 * @param array $columns
137
	 *
138
	 * @return array
139
	 */
140
	private function get_cols( $columns ) {
141
142
		$cols = array();
143
144
		foreach ( $columns as $key => $value ) {
145
146
			switch ( $key ) {
147
				case 'full_name' :
148
					$cols['full_name'] = esc_html__( 'Full Name', 'give' );
149
					break;
150
				case 'email' :
151
					$cols['email'] = esc_html__( 'Email Address', 'give' );
152
					break;
153
				case 'address' :
154
					$cols['address_line1']   = esc_html__( 'Address', 'give' );
155
					$cols['address_line2']   = esc_html__( 'Address 2', 'give' );
156
					$cols['address_city']    = esc_html__( 'City', 'give' );
157
					$cols['address_state']   = esc_html__( 'State', 'give' );
158
					$cols['address_zip']     = esc_html__( 'Zip', 'give' );
159
					$cols['address_country'] = esc_html__( 'Country', 'give' );
160
					break;
161
				case 'userid' :
162
					$cols['userid'] = esc_html__( 'User ID', 'give' );
163
					break;
164
				case 'donation_form' :
165
					$cols['donation_form'] = esc_html__( 'Donation Form', 'give' );
166
					break;
167
				case 'date_first_donated' :
168
					$cols['date_first_donated'] = esc_html__( 'First Donation Date', 'give' );
169
					break;
170
				case 'donations' :
171
					$cols['donations'] = esc_html__( 'Number of Donations', 'give' );
172
					break;
173
				case 'donation_sum' :
174
					$cols['donation_sum'] = esc_html__( 'Sum of Donations', 'give' );
175
					break;
176
			}
177
		}
178
179
		return $cols;
180
181
	}
182
183
	/**
184
	 * Get the Export Data
185
	 *
186
	 * @access public
187
	 * @since  1.0
188
	 * @global object $give_logs Give Logs Object.
189
	 * @return array $data The data for the CSV file.
190
	 */
191
	public function get_data() {
192
		$i = 0;
193
194
		$data             = array();
195
		$cached_donor_ids = Give_Cache::get( $this->query_id, true );
196
197
		if ( ! empty( $this->form ) ) {
198
199
			// Export donors for a specific donation form and also within specified timeframe
200
			$args = array(
201
				'output'     => 'payments', // Use 'posts' to get standard post objects
202
				'post_type'  => array( 'give_payment' ),
203
				'number'     => 30,
204
				'paged'      => $this->step,
205
				'status'     => 'publish',
206
				'meta_key'   => '_give_payment_form_id',
207
				'meta_value' => absint( $this->form ),
208
			);
209
210
			// Check for date option filter
211
			if ( ! empty( $this->data['donor_export_start_date'] ) || ! empty( $this->data['donor_export_end_date'] ) ) {
212
				$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' );
213
				$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' ) );
214
			}
215
216
			// Check for price option.
217
			if ( null !== $this->price_id ) {
218
				$args['meta_query'] = array(
219
					array(
220
						'key'   => '_give_payment_price_id',
221
						'value' => (int) $this->price_id,
222
					),
223
				);
224
			}
225
226
			$payments_query = new Give_Payments_Query( $args );
227
			$payments       = $payments_query->get_payments();
228
229
			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...
230
				/* @var Give_Payment $payment */
231
				foreach ( $payments as $payment ) {
232
					// Set donation sum.
233
					$this->payment_stats[ $payment->customer_id ]['donation_sum'] = isset( $this->payment_stats[ $payment->customer_id ]['donation_sum'] ) ?
234
						$this->payment_stats[ $payment->customer_id ]['donation_sum'] :
235
						0;
236
					$this->payment_stats[ $payment->customer_id ]['donation_sum'] += $payment->total;
237
238
					// Set donation count.
239
					$this->payment_stats[ $payment->customer_id ]['donations'] = isset( $this->payment_stats[ $payment->customer_id ]['donations'] ) ?
240
						++ $this->payment_stats[ $payment->customer_id ]['donations'] :
241
						1;
242
243
					// Set donation form name.
244
					$this->payment_stats[ $payment->customer_id ]['form_title'] = $payment->form_title;
245
246
					// Continue if donor already included.
247
					if ( empty( $payment->customer_id ) ||
248
					     in_array( $payment->customer_id, $cached_donor_ids )
249
					) {
250
						continue;
251
					}
252
253
					$this->donor_ids[] = $cached_donor_ids[] = $payment->customer_id;
254
255
					$i ++;
256
				}
257
258
				if ( ! empty( $this->donor_ids ) ) {
259
					foreach ( $this->donor_ids as $donor_id ) {
260
						$donor                      = Give()->donors->get_donor_by( 'id', $donor_id );
261
						$donor->donation_form_title = $this->payment_stats[ $donor_id ]['form_title'];
262
						$donor->purchase_count      = $this->payment_stats[ $donor_id ]['donations'];
263
						$donor->purchase_value      = $this->payment_stats[ $donor_id ]['donation_sum'];
264
						$data[]                     = $this->set_donor_data( $i, $data, $donor );
265
					}
266
267
					// Cache donor ids only if admin export donor for specific form.
268
					$this->cache_donor_ids();
269
				}
270
			}
271
		} else {
272
273
			// Export all donors.
274
			$offset = 30 * ( $this->step - 1 );
275
276
			$args = array(
277
				'number' => 30,
278
				'offset' => $offset,
279
			);
280
281
			// Check for date option filter
282
			if ( ! empty( $this->data['donor_export_start_date'] ) || ! empty( $this->data['donor_export_end_date'] ) ) {
283
				$args['date'] = array(
284
					'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' ),
285
					'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' ) ),
286
				);
287
			}
288
289
			$donors = Give()->donors->get_donors( $args );
290
291
			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...
292
293
				// Continue if donor already included.
294
				if ( empty( $donor->id ) || empty( $donor->payment_ids ) ) {
295
					continue;
296
				}
297
298
				$payment                    = new Give_Payment( $donor->payment_ids );
299
				$donor->donation_form_title = $payment->form_title;
300
				$data[]                     = $this->set_donor_data( $i, $data, $donor );
301
				$i ++;
302
			}
303
		}// 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...
304
305
		$data = apply_filters( 'give_export_get_data', $data );
306
		$data = apply_filters( "give_export_get_data_{$this->export_type}", $data );
307
308
		return $data;
309
	}
310
311
	/**
312
	 * Return the calculated completion percentage.
313
	 *
314
	 * @since 1.5
315
	 * @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...
316
	 */
317
	public function get_percentage_complete() {
318
319
		$percentage = 0;
320
321
		// We can't count the number when getting them for a specific form.
322
		if ( empty( $this->form ) ) {
323
324
			$total = Give()->donors->count();
325
326
			if ( $total > 0 ) {
327
328
				$percentage = ( ( 30 * $this->step ) / $total ) * 100;
329
330
			}
331
		}
332
333
		if ( $percentage > 100 ) {
334
			$percentage = 100;
335
		}
336
337
		return $percentage;
338
	}
339
340
	/**
341
	 * Set Donor Data
342
	 *
343
	 * @param int        $i
344
	 * @param array      $data
345
	 * @param Give_Donor $donor
346
	 *
347
	 * @return mixed
348
	 */
349
	private function set_donor_data( $i, $data, $donor ) {
350
351
		$columns = $this->csv_cols();
352
353
		// Set address variable
354
		$address = '';
355
		if ( isset( $donor->user_id ) && $donor->user_id > 0 ) {
356
			$address = give_get_donor_address( $donor->user_id );
357
		}
358
359
		// Set columns
360
		if ( ! empty( $columns['full_name'] ) ) {
361
			$data[ $i ]['full_name'] = $donor->name;
362
		}
363
		if ( ! empty( $columns['email'] ) ) {
364
			$data[ $i ]['email'] = $donor->email;
365
		}
366
		if ( ! empty( $columns['address_line1'] ) ) {
367
368
			$data[ $i ]['address_line1']   = isset( $address['line1'] ) ? $address['line1'] : '';
369
			$data[ $i ]['address_line2']   = isset( $address['line2'] ) ? $address['line2'] : '';
370
			$data[ $i ]['address_city']    = isset( $address['city'] ) ? $address['city'] : '';
371
			$data[ $i ]['address_state']   = isset( $address['state'] ) ? $address['state'] : '';
372
			$data[ $i ]['address_zip']     = isset( $address['zip'] ) ? $address['zip'] : '';
373
			$data[ $i ]['address_country'] = isset( $address['country'] ) ? $address['country'] : '';
374
		}
375
		if ( ! empty( $columns['userid'] ) ) {
376
			$data[ $i ]['userid'] = ! empty( $donor->user_id ) ? $donor->user_id : '';
377
		}
378
		if ( ! empty( $columns['donation_form'] ) ) {
379
			$data[ $i ]['donation_form'] = ! empty( $donor->donation_form_title ) ? $donor->donation_form_title : '';
0 ignored issues
show
Documentation introduced by
The property donation_form_title does not exist on object<Give_Donor>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
380
		}
381
		if ( ! empty( $columns['date_first_donated'] ) ) {
382
			$data[ $i ]['date_first_donated'] = date_i18n( give_date_format(), strtotime( $donor->date_created ) );
383
		}
384
		if ( ! empty( $columns['donations'] ) ) {
385
			$data[ $i ]['donations'] = $donor->purchase_count;
386
		}
387
		if ( ! empty( $columns['donation_sum'] ) ) {
388
			$data[ $i ]['donation_sum'] = give_format_amount( $donor->purchase_value );
389
		}
390
391
		return $data[ $i ];
392
393
	}
394
395
	/**
396
	 * Unset the properties specific to the donors export.
397
	 *
398
	 * @param array             $request
399
	 * @param Give_Batch_Export $export
400
	 */
401
	public function unset_properties( $request, $export ) {
402
		if ( $export->done ) {
403
			Give_Cache::delete( "give_cache_{$this->query_id}" );
404
		}
405
	}
406
}
407