Test Setup Failed
Push — master ( 61ce68...ba3719 )
by Devin
09:07 queued 10s
created

Give_Donor_Wall   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 484
Duplicated Lines 1.86 %

Coupling/Cohesion

Components 2
Dependencies 3

Importance

Changes 0
Metric Value
dl 9
loc 484
rs 8.64
c 0
b 0
f 0
wmc 47
lcom 2
cbo 3

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 2 1
A get_instance() 9 9 2
A setup_actions() 0 8 1
B render_shortcode() 0 55 8
A parse_atts() 0 53 3
A get_donors() 0 6 1
A ajax_handler() 0 25 2
A get_query_param() 0 16 3
B get_donation_data() 0 56 10
B get_donations() 0 51 6
B get_donor_comments() 0 51 9
A has_donations() 0 3 1

How to fix   Duplicated Code    Complexity   

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:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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

1
<?php
2
/**
3
 * Donors Gravatars
4
 *
5
 * @package     Give
6
 * @subpackage  Classes/Give_Donors_Gravatars
7
 * @copyright   Copyright (c) 2016, GiveWP
8
 * @license     https://opensource.org/licenses/gpl-license GNU Public License
9
 * @since       1.0
10
 */
11
12
// Exit if accessed directly.
13
if ( ! defined( 'ABSPATH' ) ) {
14
	exit;
15
}
16
17
18
/**
19
 * Give_Donor_Wall Class
20
 *
21
 * This class handles donors.
22
 *
23
 * @since 2.2.0
24
 */
25
class Give_Donor_Wall {
0 ignored issues
show
Coding Style introduced by
Since you have declared the constructor as private, maybe you should also declare the class as final.
Loading history...
26
27
	/**
28
	 * Instance.
29
	 *
30
	 * @since  2.2.0
31
	 * @access private
32
	 * @var Give_Donor_Wall
33
	 */
34
	static private $instance;
35
36
	/**
37
	 * Singleton pattern.
38
	 *
39
	 * @since  2.2.0
40
	 * @access private
41
	 */
42
	private function __construct() {
43
	}
44
45
46
	/**
47
	 * Get instance.
48
	 *
49
	 * @since  2.2.0
50
	 * @access public
51
	 * @return Give_Donor_Wall
52
	 */
53 View Code Duplication
	public static function get_instance() {
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...
54
		if ( null === static::$instance ) {
55
			self::$instance = new static();
56
57
			self::$instance->setup_actions();
58
		}
59
60
		return self::$instance;
61
	}
62
63
	/**
64
	 * Setup the default hooks and actions
65
	 *
66
	 * @since  2.2.0
67
	 *
68
	 * @return void
69
	 */
70
	public function setup_actions() {
71
72
		add_shortcode( 'give_donor_wall', array( $this, 'render_shortcode' ) );
73
74
		add_action( 'wp_ajax_give_get_donor_comments', array( $this, 'ajax_handler' ) );
75
		add_action( 'wp_ajax_nopriv_give_get_donor_comments', array( $this, 'ajax_handler' ) );
76
77
	}
78
79
80
	/**
81
	 * Displays donors in a grid layout.
82
	 *
83
	 * @since  2.2.0
84
	 *
85
	 * @param array $atts                {
86
	 *                                   Optional. Attributes of the donor wall shortcode.
87
	 *
88
	 * @type int    $donors_per_page     Number of donors per page. Default '20'.
89
	 * @type int    $form_id             The donation form to filter donors by. Default is all forms (no filter).
90
	 * @type bool   $paged               Whether to paginate donors. Default 'true'.
91
	 * @type string $ids                 A comma-separated list of donor IDs to display. Default empty.
92
	 * @type string $columns             Maximum columns to display. Default 'best-fit'.
93
	 *                                   Accepts 'best-fit', '1', '2', '3', '4'.
94
	 * @type bool   $show_avatar         Whether to display the donor's gravatar image if available. Default 'true'.
95
	 * @type bool   $show_name           Whether to display the donor's full name, first and last. Default 'true'.
96
	 * @type bool   $show_total          Whether to display the donor's donation amount. Default 'true'.
97
	 * @type bool   $show_time           Whether to display date of the last donation. Default 'true'.
98
	 * @type bool   $show_comments       Whether to display the donor's comment if they left one. Default 'true'.
99
	 * @type int    $comment_length      The number of words to display for the comments before a "Read more" field
100
	 * @type int    $only_comments       Whether to display the donors only with comment. Default 'false'.
101
	 *
102
	 * @type string $readmore_text       Link label for modal in which donor can read full comment.
103
	 * @type string $loadmore_text       Button label which will load more donor comments.
104
	 * @type int    $avatar_size         Avatar image size in pixels without the "px". Default "60"
105
	 * @type string $orderby             The order in which you want the donations to appear.
106
	 *                                   Currently we are using this attribute internally and it will sort donations by created date.
107
	 * @type string $order               The order in which you want the donors to appear. Accepts "ASC". "DESC".
108
	 *
109
	 * }
110
	 * @return string|bool The markup of the form grid or false.
111
	 */
112
	public function render_shortcode( $atts ) {
113
114
		$give_settings = give_get_settings();
115
116
		$atts      = $this->parse_atts( $atts );
117
		$donations = $this->get_donation_data( $atts );
118
		$html      = '';
119
120
		if ( $donations ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $donations 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...
121
122
			ob_start();
123
124
			foreach ( $donations as $donation ) {
125
				// Give/templates/shortcode-donor-wall.php.
126
				give_get_template( 'shortcode-donor-wall', array( $donation, $give_settings, $atts ) );
127
			}
128
129
			$html = ob_get_clean();
130
131
			// Return only donor html.
132
			if (
133
				isset( $atts['only_donor_html'] )
134
				&& wp_doing_ajax()
135
				&& $atts['only_donor_html']
136
			) {
137
				return $html;
138
			}
139
		}
140
141
		$temp_atts          = $atts;
142
		$temp_atts['paged'] = $atts['paged'] + 1;
143
144
		$more_btn_html = sprintf(
145
			'<input type="hidden" class="give-donor-wall-shortcode-attrs" data-shortcode="%1$s">',
146
			rawurlencode( http_build_query( $atts ) )
147
		);
148
149
		if ( $this->has_donations( $temp_atts ) ) {
150
			$more_btn_html .= sprintf(
151
				'<button class="give-donor__load_more give-button-with-loader"><span class="give-loading-animation"></span>%1$s</button>',
152
				$atts['loadmore_text']
153
			);
154
		}
155
156
		$html = $html
157
			? sprintf(
158
				'<div class="give-wrap give-grid-ie-utility"><div class="give-grid give-grid--%1$s">%2$s</div>%3$s</div>',
159
				esc_attr( $atts['columns'] ),
160
				$html,
161
				$more_btn_html
162
			)
163
			: '';
164
165
		return $html;
166
	}
167
168
	/**
169
	 * Parse shortcode attributes
170
	 *
171
	 * @since  2.2.0
172
	 * @access public
173
	 *
174
	 * @param array $atts Shortcode attributes.
175
	 *
176
	 * @return array
177
	 */
178
	public function parse_atts( $atts ) {
179
		$atts = shortcode_atts(
180
			array(
181
				'donors_per_page' => 12,
182
				'form_id'         => 0,
183
				'paged'           => 1,
184
				'ids'             => '',
185
				'columns'         => 'best-fit',
186
				'anonymous'       => true,
187
				'show_avatar'     => true,
188
				'show_name'       => true,
189
				'show_total'      => true,
190
				'show_time'       => true,
191
				'show_comments'   => true,
192
				'comment_length'  => 140,
193
				'only_comments'   => false,
194
				'readmore_text'   => esc_html__( 'Read more', 'give' ),
195
				'loadmore_text'   => esc_html__( 'Load more', 'give' ),
196
				'avatar_size'     => 60,
197
				'orderby'         => 'post_date',
198
				'order'           => 'DESC',
199
				'hide_empty'      => true,  // Deprecated in 2.3.0
200
				'only_donor_html' => false, // Only for internal use.
201
			), $atts
202
		);
203
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
204
205
		// Validate boolean attributes.
206
		$boolean_attributes = array(
207
			'anonymous',
208
			'show_avatar',
209
			'show_name',
210
			'show_total',
211
			'show_time',
212
			'show_comments',
213
			'show_comments',
214
			'hide_empty',
215
			'only_comments',
216
			'only_donor_html',
217
		);
218
219
		foreach ( $boolean_attributes as $att ) {
220
			// Convert numeric to boolean.
221
			// It will prevent condition check against boolean value.
222
			if ( is_numeric( $atts[ $att ] ) ) {
223
				$atts[ $att ] = (bool) $atts[ $att ];
224
			}
225
226
			$atts[ $att ] = filter_var( $atts[ $att ], FILTER_VALIDATE_BOOLEAN );
227
		}
228
229
		return $atts;
230
	}
231
232
	/**
233
	 * Get donors
234
	 *
235
	 * @since  2.2.0
236
	 * @access public
237
	 *
238
	 * @param array $donor_query Dorno query.
239
	 *
240
	 * @return array
241
	 */
242
	public function get_donors( $donor_query ) {
243
		$donor_query = new Give_Donors_Query( $donor_query );
244
		$donors      = $donor_query->get_donors();
245
246
		return $donors;
247
	}
248
249
250
	/**
251
	 * Ajax handler
252
	 *
253
	 * @since  2.2.0
254
	 * @access public
255
	 */
256
	public function ajax_handler() {
257
		$shortcode_atts = wp_parse_args( give_clean( rawurldecode( $_POST['data'] ) ) ); // @codingStandardsIgnoreLine
258
259
		// Get next page donor comments.
260
		$shortcode_atts['paged']           = $shortcode_atts['paged'] + 1;
261
		$shortcode_atts['only_donor_html'] = true;
262
263
		$donors_comment_html = $this->render_shortcode( $shortcode_atts );
264
265
		// Check if donor comment remaining.
266
		$temp_atts          = $shortcode_atts;
267
		$temp_atts['paged'] = $shortcode_atts['paged'] + 1;
268
		$has_donors         = $this->has_donations( $temp_atts ) ? 1 : 0;
269
270
		// Remove internal shortcode param.
271
		unset( $shortcode_atts['only_donor_html'] );
272
273
		wp_send_json(
274
			array(
275
				'shortcode' => rawurlencode( http_build_query( $shortcode_atts ) ),
276
				'html'      => $donors_comment_html,
277
				'remaining' => $has_donors,
278
			)
279
		);
280
	}
281
282
	/**
283
	 * Get query params
284
	 *
285
	 * @since 2.3.0
286
	 *
287
	 * @param  array $atts
288
	 *
289
	 * @return array
290
	 */
291
	private function get_query_param( $atts = array() ) {
292
		$valid_order   = array( 'ASC', 'DESC' );
293
		$valid_orderby = array( 'post_date', 'donation_amount' );
294
295
		$query_atts = array();
296
297
		$query_atts['order']         = in_array( $atts['order'], $valid_order ) ? $atts['order'] : 'DESC';
298
		$query_atts['orderby']       = in_array( $atts['orderby'], $valid_orderby ) ? $atts['orderby'] : 'post_date';
299
		$query_atts['limit']         = $atts['donors_per_page'];
300
		$query_atts['offset']        = $atts['donors_per_page'] * ( $atts['paged'] - 1 );
301
		$query_atts['form_id']       = $atts['form_id'];
302
		$query_atts['only_comments'] = ( true === $atts['only_comments'] );
303
		$query_atts['anonymous']     = ( true === $atts['anonymous'] );
304
305
		return $query_atts;
306
	}
307
308
	/**
309
	 * Get donation data.
310
	 *
311
	 * @since 2.3.0
312
	 *
313
	 * @param array $atts
314
	 *
315
	 * @return array
316
	 */
317
	private function get_donation_data( $atts = array() ) {
318
		global $wpdb;
319
320
		// Bailout if donation does not exist.
321
		if ( ! ( $donation_ids = $this->get_donations( $atts ) ) ) {
322
			return array();
323
		}
324
325
		$donation_ids = ! empty( $donation_ids )
326
			? '\'' . implode( '\',\'', $donation_ids ) . '\''
327
			: '';
328
329
		// Backward compatibility
330
		$donation_id_col = Give()->payment_meta->get_meta_type() . '_id';
331
332
		$sql = "SELECT m1.*, p1.post_date as donation_date FROM {$wpdb->donationmeta} as m1
333
				INNER JOIN {$wpdb->posts} as p1 ON (m1.{$donation_id_col}=p1.ID)
334
				WHERE m1.{$donation_id_col} IN ( {$donation_ids} )
335
				ORDER BY FIELD( p1.ID, {$donation_ids} )
336
				";
337
338
		$results = (array) $wpdb->get_results( $sql );
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...
339
340
		if ( ! empty( $results ) ) {
341
			$temp = array();
342
343
			/* @var stdClass $result */
344
			foreach ( $results as $result ) {
345
				$temp[ $result->{$donation_id_col} ][ $result->meta_key ] = maybe_unserialize( $result->meta_value );
0 ignored issues
show
introduced by
Detected usage of meta_key, possible slow query.
Loading history...
346
347
				// Set donation date.
348
				if( empty( $temp[ $result->{$donation_id_col} ][ 'donation_date' ] ) ){
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
introduced by
Array keys should NOT be surrounded by spaces if they only contain a string or an integer.
Loading history...
349
					$temp[ $result->{$donation_id_col} ]['donation_date'] = $result->donation_date;
350
				}
351
			}
352
353
			$comments = $this->get_donor_comments( $temp );
354
355
			if ( ! empty( $temp ) ) {
356
				foreach ( $temp as $donation_id => $donation_data ) {
357
					$temp[ $donation_id ]['donation_id'] = $donation_id;
358
359
					$temp[ $donation_id ]['name_initial'] = give_get_name_initial( array(
360
						'firstname' => $donation_data['_give_donor_billing_first_name'],
361
						'lastname'  => $donation_data['_give_donor_billing_last_name'],
362
					) );
363
364
					$temp[ $donation_id ]['donor_comment'] = ! empty( $comments[ $donation_id ] ) ? $comments[ $donation_id ] : '';
365
				}
366
			}
367
368
			$results = ! empty( $temp ) ? $temp : array();
369
		}
370
371
		return $results;
372
	}
373
374
	/**
375
	 * Get donation list for specific query
376
	 *
377
	 * @since 2.3.0
378
	 *
379
	 * @param  array $atts
380
	 *
381
	 * @return array
382
	 */
383
	private function get_donations( $atts = array() ) {
384
		global $wpdb;
385
386
		// Backward compatibility
387
		$donation_id_col = Give()->payment_meta->get_meta_type() . '_id';
388
389
		$query_params = $this->get_query_param( $atts );
390
391
		$sql   = "SELECT p1.ID FROM {$wpdb->posts} as p1";
392
		$where = " WHERE p1.post_status IN ('publish') AND p1.post_type = 'give_payment'";
393
394
		// exclude donation with zero amount from result.
395
		$sql   .= " INNER JOIN {$wpdb->donationmeta} as m1 ON (p1.ID = m1.{$donation_id_col})";
396
		$where .= " AND m1.meta_key='_give_payment_total' AND m1.meta_value>0";
397
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
398
399
		if ( $query_params['form_id'] ) {
400
			$sql   .= " INNER JOIN {$wpdb->donationmeta} as m2 ON (p1.ID = m2.{$donation_id_col})";
401
			$where .= " AND m2.meta_key='_give_payment_form_id' AND m2.meta_value={$query_params['form_id']}";
402
		}
403
404
		// exclude donations which does not has donor comment.
405
		if ( $query_params['only_comments'] ) {
406
			$sql   .= " INNER JOIN {$wpdb->give_comments} as gc1 ON (p1.ID = gc1.comment_parent)";
407
			$where .= " AND gc1.comment_type='donor_donation'";
408
		}
409
410
		// exclude anonymous donation form query based on query parameters.
411
		if (
412
			! $query_params['anonymous']
413
			|| $query_params['only_comments']
414
		) {
415
			$where .= " AND p1.ID NOT IN ( SELECT DISTINCT({$donation_id_col}) FROM {$wpdb->donationmeta} WHERE meta_key='_give_anonymous_donation' AND meta_value='1')";
416
		}
417
418
		// order by query based on parameter.
419
		if ( 'donation_amount' === $query_params['orderby'] ) {
420
			$order = " ORDER BY m1.meta_value+0 {$query_params['order']}";
421
		} else {
422
			$order = " ORDER BY p1.{$query_params['orderby']} {$query_params['order']}, p1.ID {$query_params['order']}";
423
		}
424
425
		$limit  = " LIMIT {$query_params['limit']}";
426
		$offset = " OFFSET {$query_params['offset']}";
427
428
		$sql = $sql . $where . $order . $limit . $offset;
429
430
		$donation_ids = $wpdb->get_col( $sql );
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...
431
432
		return $donation_ids;
433
	}
434
435
	/**
436
	 * Get donor comments
437
	 *
438
	 * @since 2.3.0
439
	 *
440
	 * @param array $donations_data
441
	 *
442
	 * @return array
443
	 */
444
	private function get_donor_comments( $donations_data = array() ) {
445
		global $wpdb;
446
		$comments = array();
447
448
		// Bailout.
449
		if ( empty( $donations_data ) ) {
450
			return $comments;
451
		}
452
453
		// Backward compatibility.
454
		if (
455
			! give_has_upgrade_completed( 'v230_move_donor_note' )
456
			|| ! give_has_upgrade_completed( 'v230_move_donation_note' )
457
		) {
458
			foreach ( $donations_data as $id => $data ) {
459
				$comment         = give_get_donor_donation_comment( $id, $data['_give_payment_donor_id'] );
460
				$comments[ $id ] = ! empty( $comment ) ? $comment->comment_content : '';
461
			}
462
463
			return $comments;
464
		}
465
466
		$sql   = "SELECT c1.comment_parent as donation_id, c1.comment_content as comment FROM {$wpdb->give_comments} as c1";
467
		$sql   .= " INNER JOIN {$wpdb->give_commentmeta} as cm1 ON (c1.comment_ID=cm1.give_comment_id)";
468
		$where = array();
469
470
		foreach ( $donations_data as $id => $data ) {
471
			// Do not fetch comment for anonymous donation.
472
			if( ! empty( $data['_give_anonymous_donation'] )  ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
473
				continue;
474
			}
475
476
			$where[] = "(c1.comment_parent={$id} AND cm1.meta_key='_give_donor_id' AND cm1.meta_value={$data['_give_payment_donor_id']})";
477
		}
478
479
		$where = ' WHERE ' . implode( ' OR ', $where );
480
		$where .= " AND c1.comment_type='donor_donation'";
481
482
		$sql = $sql . $where;
483
484
		$comments = (array) $wpdb->get_results( $sql );
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...
485
486
		if ( ! empty( $comments ) ) {
487
			$comments = array_combine(
488
				wp_list_pluck( $comments, 'donation_id' ),
489
				wp_list_pluck( $comments, 'comment' )
490
			);
491
		}
492
493
		return $comments;
494
	}
495
496
	/**
497
	 * Check if donation exist or not for specific query
498
	 *
499
	 * @since 2.3.0
500
	 *
501
	 * @param  array $atts
502
	 *
503
	 * @return bool
504
	 */
505
	private function has_donations( $atts = array() ) {
506
		return (bool) $this->get_donations( $atts );
507
	}
508
}
509
510
// Initialize shortcode.
511
Give_Donor_Wall::get_instance();
512