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

Give_Batch_Donors_Export   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 294
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 0
Metric Value
dl 0
loc 294
rs 8.439
c 0
b 0
f 0
wmc 47
lcom 1
cbo 6

6 Methods

Rating   Name   Duplication   Size   Complexity  
B set_properties() 0 22 6
A csv_cols() 0 13 3
D get_cols() 0 39 9
C get_data() 0 71 8
B get_percentage_complete() 0 22 4
F set_donor_data() 0 42 17

How to fix   Complexity   

Complex Class

Complex classes like Give_Batch_Donors_Export 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_Batch_Donors_Export, and based on these observations, apply Extract Interface, too.

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
	 * Export query id.
52
	 *
53
	 * @since 1.8
54
	 * @var array
55
	 */
56
	private $query_id = '';
57
58
	/**
59
	 * Set the properties specific to the donors export.
60
	 *
61
	 * @since 1.5
62
	 *
63
	 * @param array $request The Form Data passed into the batch processing
64
	 */
65
	public function set_properties( $request ) {
66
67
		// Set data from form submission
68
		if ( isset( $_POST['form'] ) ) {
69
			parse_str( $_POST['form'], $this->data );
70
		}
71
72
		$this->form = $this->data['forms'];
73
74
		// Setup donor ids cache.
75
		if ( ! empty( $this->form ) ) {
76
			// Cache donor ids to output unique list of donor.
77
			$this->query_id = give_clean( $_REQUEST['give_export_option']['query_id'] );
78
			if ( ! ( $this->donor_ids = Give_Cache::get( $this->query_id, true ) ) ) {
79
				$this->donor_ids = array();
80
				Give_Cache::set( $this->query_id, $this->donor_ids, HOUR_IN_SECONDS, true );
81
			}
82
		}
83
84
		$this->price_id = ! empty( $request['give_price_option'] ) && 'all' !== $request['give_price_option'] ? absint( $request['give_price_option'] ) : null;
85
86
	}
87
88
	/**
89
	 * Set the CSV columns.
90
	 *
91
	 * @access public
92
	 * @since  1.5
93
	 * @return array|bool $cols All the columns.
94
	 */
95
	public function csv_cols() {
96
97
		$columns = isset( $this->data['give_export_option'] ) ? $this->data['give_export_option'] : array();
98
99
		// We need columns.
100
		if ( empty( $columns ) ) {
101
			return false;
102
		}
103
104
		$cols = $this->get_cols( $columns );
105
106
		return $cols;
107
	}
108
109
	/**
110
	 * CSV file columns.
111
	 *
112
	 * @param array $columns
113
	 *
114
	 * @return array
115
	 */
116
	private function get_cols( $columns ) {
117
118
		$cols = array();
119
120
		foreach ( $columns as $key => $value ) {
121
122
			switch ( $key ) {
123
				case 'full_name' :
124
					$cols['full_name'] = esc_html__( 'Full Name', 'give' );
125
					break;
126
				case 'email' :
127
					$cols['email'] = esc_html__( 'Email Address', 'give' );
128
					break;
129
				case 'address' :
130
					$cols['address_line1']   = esc_html__( 'Address', 'give' );
131
					$cols['address_line2']   = esc_html__( 'Address 2', 'give' );
132
					$cols['address_city']    = esc_html__( 'City', 'give' );
133
					$cols['address_state']   = esc_html__( 'State', 'give' );
134
					$cols['address_zip']     = esc_html__( 'Zip', 'give' );
135
					$cols['address_country'] = esc_html__( 'Country', 'give' );
136
					break;
137
				case 'userid' :
138
					$cols['userid'] = esc_html__( 'User ID', 'give' );
139
					break;
140
				case 'date_first_donated' :
141
					$cols['date_first_donated'] = esc_html__( 'First Donation Date', 'give' );
142
					break;
143
				case 'donations' :
144
					$cols['donations'] = esc_html__( 'Number of Donations', 'give' );
145
					break;
146
				case 'donation_sum' :
147
					$cols['donation_sum'] = esc_html__( 'Sum of Donations', 'give' );
148
					break;
149
			}
150
		}
151
152
		return $cols;
153
154
	}
155
156
	/**
157
	 * Get the Export Data
158
	 *
159
	 * @access public
160
	 * @since  1.0
161
	 * @global object $give_logs Give Logs Object.
162
	 * @return array $data The data for the CSV file.
163
	 */
164
	public function get_data() {
165
166
		$data = array();
167
168
		$i = 0;
169
170
		if ( ! empty( $this->form ) ) {
171
172
			// Export donors of a specific product.
173
			global $give_logs;
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...
174
175
			$args = array(
176
				'post_parent'    => absint( $this->form ),
177
				'log_type'       => 'sale',
178
				'posts_per_page' => 30,
179
				'paged'          => $this->step,
180
			);
181
182
			// Check for price option.
183
			if ( null !== $this->price_id ) {
184
				$args['meta_query'] = array(
185
					array(
186
						'key'   => '_give_log_price_id',
187
						'value' => (int) $this->price_id,
188
					),
189
				);
190
			}
191
192
			$logs = $give_logs->get_connected_logs( $args );
193
194
			if ( $logs ) {
195
				foreach ( $logs as $log ) {
196
					$payment_id = give_get_meta( $log->ID, '_give_log_payment_id', true );
197
					$payment    = new Give_Payment( $payment_id );
198
199
					// Continue if donor already included.
200
					if ( empty( $payment->customer_id ) || in_array( $payment->customer_id , $this->donor_ids ) ) {
201
						continue;
202
					}
203
204
					$this->donor_ids[] = $payment->customer_id;
205
206
					$donor      = Give()->donors->get_donor_by( 'id', $payment->customer_id );
207
					$data[]     = $this->set_donor_data( $i, $data, $donor );
208
					$i ++;
209
				}
210
211
				// Cache donor ids only if admin export donor for specific form.
212
				Give_Cache::set( $this->query_id, array_unique( $this->donor_ids ), HOUR_IN_SECONDS, true );
213
			}
214
		} else {
215
216
			// Export all donors.
217
			$offset = 30 * ( $this->step - 1 );
218
			$donors = Give()->donors->get_donors( array(
219
				'number' => 30,
220
				'offset' => $offset,
221
			) );
222
223
			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...
224
225
				$data[] = $this->set_donor_data( $i, $data, $donor );
226
				$i ++;
227
			}
228
		}// 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...
229
230
		$data = apply_filters( 'give_export_get_data', $data );
231
		$data = apply_filters( "give_export_get_data_{$this->export_type}", $data );
232
233
		return $data;
234
	}
235
236
	/**
237
	 * Return the calculated completion percentage.
238
	 *
239
	 * @since 1.5
240
	 * @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...
241
	 */
242
	public function get_percentage_complete() {
243
244
		$percentage = 0;
245
246
		// We can't count the number when getting them for a specific form.
247
		if ( empty( $this->form ) ) {
248
249
			$total = Give()->donors->count();
250
251
			if ( $total > 0 ) {
252
253
				$percentage = ( ( 30 * $this->step ) / $total ) * 100;
254
255
			}
256
		}
257
258
		if ( $percentage > 100 ) {
259
			$percentage = 100;
260
		}
261
262
		return $percentage;
263
	}
264
265
	/**
266
	 * Set Donor Data
267
	 *
268
	 * @param int        $i
269
	 * @param array      $data
270
	 * @param Give_Donor $donor
271
	 *
272
	 * @return mixed
273
	 */
274
	private function set_donor_data( $i, $data, $donor ) {
275
276
		$columns = $this->csv_cols();
277
278
		// Set address variable
279
		$address = '';
280
		if ( isset( $donor->user_id ) && $donor->user_id > 0 ) {
281
			$address = give_get_donor_address( $donor->user_id );
282
		}
283
284
		// Set columns
285
		if ( ! empty( $columns['full_name'] ) ) {
286
			$data[ $i ]['full_name'] = $donor->name;
287
		}
288
		if ( ! empty( $columns['email'] ) ) {
289
			$data[ $i ]['email'] = $donor->email;
290
		}
291
		if ( ! empty( $columns['address_line1'] ) ) {
292
293
			$data[ $i ]['address_line1']   = isset( $address['line1'] ) ? $address['line1'] : '';
294
			$data[ $i ]['address_line2']   = isset( $address['line2'] ) ? $address['line2'] : '';
295
			$data[ $i ]['address_city']    = isset( $address['city'] ) ? $address['city'] : '';
296
			$data[ $i ]['address_state']   = isset( $address['state'] ) ? $address['state'] : '';
297
			$data[ $i ]['address_zip']     = isset( $address['zip'] ) ? $address['zip'] : '';
298
			$data[ $i ]['address_country'] = isset( $address['country'] ) ? $address['country'] : '';
299
		}
300
		if ( ! empty( $columns['userid'] ) ) {
301
			$data[ $i ]['userid'] = ! empty( $donor->user_id ) ? $donor->user_id : '';
302
		}
303
		if ( ! empty( $columns['date_first_donated'] ) ) {
304
			$data[ $i ]['date_first_donated'] = date_i18n( give_date_format(), strtotime( $donor->date_created ) );
305
		}
306
		if ( ! empty( $columns['donations'] ) ) {
307
			$data[ $i ]['donations'] = $donor->purchase_count;
308
		}
309
		if ( ! empty( $columns['donation_sum'] ) ) {
310
			$data[ $i ]['donation_sum'] = give_format_amount( $donor->purchase_value );
311
		}
312
313
		return $data[ $i ];
314
315
	}
316
317
}
318