Completed
Push — add/simple-payments-widget-pla... ( 2a5253...a34853 )
by
unknown
97:06 queued 80:29
created

Jetpack_Simple_Payments_Widget   F

Complexity

Total Complexity 60

Size/Duplication

Total Lines 441
Duplicated Lines 1.36 %

Coupling/Cohesion

Components 2
Dependencies 2

Importance

Changes 0
Metric Value
dl 6
loc 441
rs 3.6
c 0
b 0
f 0
wmc 60
lcom 2
cbo 2

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 24 4
A filter_nonces() 0 4 1
A enqueue_style() 0 3 1
A admin_enqueue_styles_and_scripts() 0 9 1
F ajax_save_payment_button() 3 67 15
B ajax_delete_payment_button() 3 34 7
B validate_ajax_params() 0 22 7
A get_first_product_id() 0 10 2
A defaults() 0 6 1
B widget() 0 33 7
A get_latest_field_value() 0 5 2
A get_product_from_post() 0 21 3
A record_event() 0 18 2
B update() 0 40 5
A form() 0 12 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 Jetpack_Simple_Payments_Widget 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 Jetpack_Simple_Payments_Widget, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Disable direct access/execution to/of the widget code.
4
 */
5
if ( ! defined( 'ABSPATH' ) ) {
6
	exit;
7
}
8
9
if ( ! class_exists( 'Jetpack_Simple_Payments_Widget' ) ) {
10
	/**
11
	 * Simple Payments Button
12
	 *
13
	 * Display a Simple Payment Button as a Widget.
14
	 */
15
	class Jetpack_Simple_Payments_Widget extends WP_Widget {
16
		// https://developer.paypal.com/docs/integration/direct/rest/currency-codes/
17
		private static $supported_currency_list = array(
0 ignored issues
show
Unused Code introduced by
The property $supported_currency_list is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
18
			'USD' => '$',
19
			'GBP' => '&#163;',
20
			'JPY' => '&#165;',
21
			'BRL' => 'R$',
22
			'EUR' => '&#8364;',
23
			'NZD' => 'NZ$',
24
			'AUD' => 'A$',
25
			'CAD' => 'C$',
26
			'INR' => '₹',
27
			'ILS' => '₪',
28
			'RUB' => '₽',
29
			'MXN' => 'MX$',
30
			'SEK' => 'Skr',
31
			'HUF' => 'Ft',
32
			'CHF' => 'CHF',
33
			'CZK' => 'Kč',
34
			'DKK' => 'Dkr',
35
			'HKD' => 'HK$',
36
			'NOK' => 'Kr',
37
			'PHP' => '₱',
38
			'PLN' => 'PLN',
39
			'SGD' => 'S$',
40
			'TWD' => 'NT$',
41
			'THB' => '฿',
42
		);
43
44
		/**
45
		 * Constructor.
46
		 */
47
		function __construct() {
48
			parent::__construct(
49
				'jetpack_simple_payments_widget',
50
				/** This filter is documented in modules/widgets/facebook-likebox.php */
51
				apply_filters( 'jetpack_widget_name', __( 'Simple Payments', 'jetpack' ) ),
52
				array(
53
					'classname' => 'jetpack-simple-payments',
54
					'description' => __( 'Add a Simple Payment Button as a Widget.', 'jetpack' ),
55
					'customize_selective_refresh' => true,
56
				)
57
			);
58
59
			if ( is_customize_preview() ) {
60
				add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_styles_and_scripts' ) );
61
62
				add_filter( 'customize_refresh_nonces', array( $this, 'filter_nonces' ) );
63
				add_action( 'wp_ajax_customize-jetpack-simple-payments-button-save', array( $this, 'ajax_save_payment_button' ) );
64
				add_action( 'wp_ajax_customize-jetpack-simple-payments-button-delete', array( $this, 'ajax_delete_payment_button' ) );
65
			}
66
67
			if ( is_active_widget( false, false, $this->id_base ) || is_customize_preview() ) {
68
				add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_style' ) );
69
			}
70
		}
71
72
		/**
73
		 * Return an associative array of default values.
74
		 *
75
		 * These values are used in new widgets.
76
		 *
77
		 * @return array Default values for the widget options.
78
		 */
79
		private function defaults() {
80
			$current_user = wp_get_current_user();
81
			$default_product_id = $this->get_first_product_id();
82
83
			return array(
84
				'title' => '',
85
				'product_post_id' => $default_product_id,
86
				'form_action' => '',
87
				'form_product_id' => 0,
88
				'form_product_title' => '',
89
				'form_product_description' => '',
90
				'form_product_image_id' => 0,
91
				'form_product_image_src' => '',
92
				'form_product_currency' => '',
93
				'form_product_price' => '',
94
				'form_product_multiple' => '',
95
				'form_product_email' => $current_user->user_email,
96
			);
97
		}
98
99
		/**
100
		 * Adds a nonce for customizing menus.
101
		 *
102
		 * @param array $nonces Array of nonces.
103
		 * @return array $nonces Modified array of nonces.
104
		 */
105
		function filter_nonces( $nonces ) {
106
			$nonces['customize-jetpack-simple-payments'] = wp_create_nonce( 'customize-jetpack-simple-payments' );
107
			return $nonces;
108
		}
109
110
		function enqueue_style() {
111
			wp_enqueue_style( 'jetpack-simple-payments-widget-style', plugins_url( 'simple-payments/style.css', __FILE__ ), array(), '20180518' );
112
		}
113
114
		function admin_enqueue_styles_and_scripts(){
115
				wp_enqueue_style( 'jetpack-simple-payments-widget-customizer', plugins_url( 'simple-payments/customizer.css', __FILE__ ) );
116
117
				wp_enqueue_media();
118
				wp_enqueue_script( 'jetpack-simple-payments-widget-customizer', plugins_url( '/simple-payments/customizer.js', __FILE__ ), array( 'jquery' ), false, true );
119
				wp_localize_script( 'jetpack-simple-payments-widget-customizer', 'jpSimplePaymentsStrings', array(
120
					'deleteConfirmation' => __( 'Are you sure you want to delete this item? It will be disabled and removed from all locations where it currently appears.', 'jetpack' )
121
				) );
122
		}
123
124
		public function ajax_save_payment_button() {
125
			if ( ! check_ajax_referer( 'customize-jetpack-simple-payments', 'customize-jetpack-simple-payments-nonce', false ) ) {
126
				wp_send_json_error( 'bad_nonce', 400 );
127
			}
128
129
			if ( ! current_user_can( 'customize' ) ) {
130
				wp_send_json_error( 'customize_not_allowed', 403 );
131
			}
132
133
			$post_type_object = get_post_type_object( Jetpack_Simple_Payments::$post_type_product );
0 ignored issues
show
Bug introduced by
The property post_type_product cannot be accessed from this context as it is declared private in class Jetpack_Simple_Payments.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
134
			if ( ! current_user_can( $post_type_object->cap->create_posts ) || ! current_user_can( $post_type_object->cap->publish_posts ) ) {
135
				wp_send_json_error( 'insufficient_post_permissions', 403 );
136
			}
137
138 View Code Duplication
			if ( empty( $_POST['params'] ) || ! is_array( $_POST['params'] ) ) {
139
				wp_send_json_error( 'missing_params', 400 );
140
			}
141
142
			$params = wp_unslash( $_POST['params'] );
143
			$errors = $this->validate_ajax_params( $params );
144
			if ( ! empty( $errors->errors ) ) {
145
				wp_send_json_error( $errors );
146
			}
147
148
			$product_post_id = isset( $params['product_post_id'] ) ? intval( $params['product_post_id'] ) : 0;
149
150
			$product_post = array(
151
				'ID' => $product_post_id,
152
				'post_type' => Jetpack_Simple_Payments::$post_type_product,
0 ignored issues
show
Bug introduced by
The property post_type_product cannot be accessed from this context as it is declared private in class Jetpack_Simple_Payments.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
153
				'post_status' => 'publish',
154
				'post_title' => $params['post_title'],
155
				'post_content' => $params['post_content'],
156
				'_thumbnail_id' => ! empty( $params['image_id'] ) ? $params['image_id'] : -1,
157
				'meta_input' => array(
158
					'spay_currency' => $params['currency'],
159
					'spay_price' => $params['price'],
160
					'spay_multiple' => isset( $params['multiple'] ) ? intval( $params['multiple'] ) : 0,
161
					'spay_email' => is_email( $params['email'] ),
162
				),
163
			);
164
165
			if ( empty( $product_post_id ) ) {
166
				$product_post_id = wp_insert_post( $product_post );
167
			} else {
168
				$product_post_id = wp_update_post( $product_post );
169
			}
170
171
			if ( ! $product_post_id || is_wp_error( $product_post_id ) ) {
172
				wp_send_json_error( $product_post_id );
173
			}
174
175
			$tracks_properties = array(
176
				'id'       => $product_post_id,
177
				'currency' => $params['currency'],
178
				'price'    => $params['price']
179
			);
180
			if ( 0 === $product_post['ID'] ) {
181
				$this->record_event( 'created', 'create', $tracks_properties );
182
			} else {
183
				$this->record_event( 'updated', 'update', $tracks_properties );
184
			}
185
186
			wp_send_json_success( [
187
				'product_post_id' => $product_post_id,
188
				'product_post_title' => $params['post_title'],
189
			] );
190
		}
191
192
		public function ajax_delete_payment_button() {
193
			if ( ! check_ajax_referer( 'customize-jetpack-simple-payments', 'customize-jetpack-simple-payments-nonce', false ) ) {
194
				wp_send_json_error( 'bad_nonce', 400 );
195
			}
196
197
			if ( ! current_user_can( 'customize' ) ) {
198
				wp_send_json_error( 'customize_not_allowed', 403 );
199
			}
200
201 View Code Duplication
			if ( empty( $_POST['params'] ) || ! is_array( $_POST['params'] ) ) {
202
				wp_send_json_error( 'missing_params', 400 );
203
			}
204
205
			$params = wp_unslash( $_POST['params'] );
206
			$illegal_params = array_diff( array_keys( $params ), array( 'product_post_id' ) );
207
			if ( ! empty( $illegal_params ) ) {
208
				wp_send_json_error( 'illegal_params', 400 );
209
			}
210
211
			$product_id = ( int ) $params['product_post_id'];
212
			$product_post = get_post( $product_id );
213
214
			$return = array( 'status' => $product_post->post_status );
215
216
			wp_delete_post( $product_id, true );
217
			$status = get_post_status( $product_id );
218
			if ( false === $status ) {
219
				$return['status'] = 'deleted';
220
			}
221
222
			$this->record_event( 'deleted', 'delete', array( 'id' => $product_id ) );
223
224
			wp_send_json_success( $return );
225
		}
226
227
		public function validate_ajax_params( $params ) {
228
			$errors = new WP_Error();
229
230
			$illegal_params = array_diff( array_keys( $params ), array( 'product_post_id', 'post_title', 'post_content', 'image_id', 'currency', 'price', 'multiple', 'email' ) );
231
			if ( ! empty( $illegal_params ) ) {
232
				$errors.add( 'illegal_params' );
233
			}
234
235
			if ( empty( $params['post_title'] ) ) {
236
				$errors->add( 'post_title', __( 'People need to know what they\'re paying for! Please add a brief title.' ) );
237
			}
238
239
			if ( empty( $params['price'] ) || intval( $params['price'] ) < 0 ) {
240
				$errors->add( 'price', __( 'Everything comes with a price tag these days. Please add a your product price.' ) );
241
			}
242
243
			if ( empty( $params['email'] ) || ! is_email( $params['email'] ) ) {
244
				$errors->add( 'email', __( 'We want to make sure payments reach you, so please add an email address.' ) );
245
			}
246
247
			return $errors;
248
		}
249
250
		function get_first_product_id() {
251
			$product_posts = get_posts( array(
252
				'numberposts' => 1,
253
				'orderby' => 'date',
254
				'post_type' => Jetpack_Simple_Payments::$post_type_product,
0 ignored issues
show
Bug introduced by
The property post_type_product cannot be accessed from this context as it is declared private in class Jetpack_Simple_Payments.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
255
				'post_status' => 'publish',
256
			 ) );
257
258
			return ! empty( $product_posts ) ? $product_posts[0]->ID : null;
259
		}
260
261
		/**
262
		 * Return an associative array of default values.
263
		 *
264
		 * These values are used in new widgets.
265
		 *
266
		 * @return array Default values for the widget options.
267
		 */
268
		public function defaults() {
269
			return array(
270
				'title' => '',
271
				'product_post_id' => 0,
272
			);
273
		}
274
275
		/**
276
		 * Front-end display of widget.
277
		 *
278
		 * @see WP_Widget::widget()
279
		 *
280
		 * @param array $args     Widget arguments.
281
		 * @param array $instance Saved values from database.
282
		 */
283
		function widget( $args, $instance ) {
284
			$instance = wp_parse_args( $instance, $this->defaults() );
285
286
			echo $args['before_widget'];
287
288
			/** This filter is documented in core/src/wp-includes/default-widgets.php */
289
			$title = apply_filters( 'widget_title', $instance['title'] );
290
			if ( ! empty( $title ) ) {
291
				echo $args['before_title'] . $title . $args['after_title'];
292
			}
293
294
			echo '<div class="jetpack-simple-payments-content">';
295
296
			if ( ! empty( $instance['form_action'] ) && in_array( $instance['form_action'], array( 'add', 'edit' ) ) && is_customize_preview() ) {
297
				require( dirname( __FILE__ ) . '/simple-payments/widget.php' );
298
			} else {
299
				$jsp = Jetpack_Simple_Payments::getInstance();
300
				$simple_payments_button = $jsp->parse_shortcode( array(
301
					'id' => $instance['product_post_id'],
302
				) );
303
304
				if ( ! is_null( $simple_payments_button ) || is_customize_preview() ) {
305
					echo $simple_payments_button;
306
				}
307
			}
308
309
			echo '</div><!--simple-payments-->';
310
311
			echo $args['after_widget'];
312
313
			/** This action is already documented in modules/widgets/gravatar-profile.php */
314
			do_action( 'jetpack_stats_extra', 'widget_view', 'simple_payments' );
315
		}
316
317
		/**
318
		 * Gets the latests field value from either the old instance or the new instance.
319
		 *
320
		 * @param array $mixed Array of values for the new form instance.
0 ignored issues
show
Bug introduced by
There is no parameter named $mixed. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
321
		 * @param array $mixed Array of values for the old form instance.
0 ignored issues
show
Bug introduced by
There is no parameter named $mixed. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
322
		 * @return mixed $mixed Field value.
323
		 */
324
		private function get_latest_field_value( $new_instance, $old_instance, $field) {
325
			return ! empty( $new_instance[ $field ] )
326
				? sanitize_text_field( $new_instance[ $field ] )
327
				: $old_instance[ $field ];
328
		}
329
330
		/**
331
		 * Gets the product fields from the product post. If no post found
332
		 * it returns the default values.
333
		 *
334
		 * @param int Product Post ID.
335
		 * @return array $fields Product Fields from the Product Post.
336
		 */
337
		private function get_product_from_post( $product_post_id ) {
338
			$product_post = get_post( $product_post_id );
339
			$form_product_id = $product_post_id;
340
			if( ! empty( $product_post ) ) {
341
				$form_product_image_id = get_post_thumbnail_id( $product_post_id );
342
343
				return array(
344
					'form_product_id' => $form_product_id,
345
					'form_product_title' => get_the_title( $product_post ),
346
					'form_product_description' => $product_post->post_content,
347
					'form_product_image_id' => $form_product_image_id,
348
					'form_product_image_src' => wp_get_attachment_image_url( $form_product_image_id, 'thumbnail' ),
349
					'form_product_currency' => get_post_meta( $product_post_id, 'spay_currency', true ),
350
					'form_product_price' => get_post_meta( $product_post_id, 'spay_price', true ),
351
					'form_product_multiple' => get_post_meta( $product_post_id, 'spay_multiple', true ) || '0',
352
					'form_product_email' => get_post_meta( $product_post_id, 'spay_email', true ),
353
				);
354
			}
355
356
			return $this->defaults();
357
		}
358
359
		/**
360
		 * Record a Track event and bump a MC stat.
361
		 *
362
		 * @param string $stat_name
363
		 * @param string $event_action
364
		 * @param array $event_properties
365
		 */
366
		private function record_event( $stat_name, $event_action, $event_properties = array() ) {
367
			$current_user = wp_get_current_user();
368
369
			// `bumps_stats_extra` only exists on .com
370
			if ( function_exists( 'bump_stats_extras' ) ) {
371
				require_lib( 'tracks/client' );
372
				tracks_record_event( $current_user, 'simple_payments_button_' . $event_action, $event_properties );
373
				/** This action is documented in modules/widgets/social-media-icons.php */
374
				do_action( 'jetpack_bump_stats_extra', 'jetpack-simple_payments', $stat_name );
375
				return;
376
			}
377
378
			jetpack_tracks_record_event( $current_user, 'jetpack_wpa_simple_payments_button_' . $event_action, $event_properties );
379
			$jetpack = Jetpack::init();
380
			// $jetpack->stat automatically prepends the stat group with 'jetpack-'
381
			$jetpack->stat( 'simple_payments', $stat_name ) ;
382
			$jetpack->do_stats( 'server_side' );
383
		}
384
385
		/**
386
		 * Sanitize widget form values as they are saved.
387
		 *
388
		 * @see WP_Widget::update()
389
		 *
390
		 * @param array $new_instance Values just sent to be saved.
391
		 * @param array $old_instance Previously saved values from database.
392
		 *
393
		 * @return array Updated safe values to be saved.
394
		 */
395
		function update( $new_instance, $old_instance ) {
396
			$defaults = $this->defaults();
397
			//do not overrite `product_post_id` for `$new_instance` with the defaults
398
			$new_instance = wp_parse_args( $new_instance, array_diff_key( $defaults, array( 'product_post_id' => 0 ) ) );
399
			$old_instance = wp_parse_args( $old_instance, $defaults );
400
401
			$required_widget_props = array(
402
				'title' => $this->get_latest_field_value( $new_instance, $old_instance, 'title' ),
403
				'product_post_id' => $this->get_latest_field_value( $new_instance, $old_instance, 'product_post_id' ),
404
				'form_action' => $this->get_latest_field_value( $new_instance, $old_instance, 'form_action' ),
405
			);
406
407
			if ( strcmp( $new_instance['form_action'], $old_instance['form_action'] ) !== 0 ) {
408
				if ( $new_instance['form_action'] == 'edit' ) {
409
					return array_merge( $this->get_product_from_post( ( int ) $old_instance['product_post_id'] ), $required_widget_props );
410
				}
411
412
				if ( $new_instance['form_action'] == 'clear' ) {
413
					return array_merge( $this->defaults(), $required_widget_props );
414
				}
415
			}
416
417
			$form_product_image_id = (int) $new_instance['form_product_image_id'];
418
419
			$form_product_email = ! empty( $new_instance['form_product_email'] )
420
				? sanitize_text_field( $new_instance['form_product_email'] )
421
				: $this->defaults()['form_product_email'];
422
423
			return array_merge( $required_widget_props, array(
424
				'form_product_id' => ( int ) $new_instance['form_product_id'],
425
				'form_product_title' => sanitize_text_field( $new_instance['form_product_title'] ),
426
				'form_product_description' => sanitize_text_field( $new_instance['form_product_description'] ),
427
				'form_product_image_id' => $form_product_image_id,
428
				'form_product_image_src' => wp_get_attachment_image_url( $form_product_image_id, 'thumbnail' ),
429
				'form_product_currency' => sanitize_text_field( $new_instance['form_product_currency'] ),
430
				'form_product_price' => sanitize_text_field( $new_instance['form_product_price'] ),
431
				'form_product_multiple' => sanitize_text_field( $new_instance['form_product_multiple'] ),
432
				'form_product_email' => $form_product_email,
433
			) );
434
		}
435
436
		/**
437
		 * Back-end widget form.
438
		 *
439
		 * @see WP_Widget::form()
440
		 *
441
		 * @param array $instance Previously saved values from database.
442
		 */
443
		function form( $instance ) {
444
			$instance = wp_parse_args( $instance, $this->defaults() );
445
446
			$product_posts = get_posts( array(
447
				'numberposts' => 100,
448
				'orderby' => 'date',
449
				'post_type' => Jetpack_Simple_Payments::$post_type_product,
0 ignored issues
show
Bug introduced by
The property post_type_product cannot be accessed from this context as it is declared private in class Jetpack_Simple_Payments.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
450
				'post_status' => 'publish',
451
			 ) );
452
453
			require( dirname( __FILE__ ) . '/simple-payments/form.php' );
454
		}
455
	}
456
457
	// Register Jetpack_Simple_Payments_Widget widget.
458
	function register_widget_jetpack_simple_payments() {
459
		$jetpack_simple_payments = Jetpack_Simple_Payments::getInstance();
460
		if ( ! $jetpack_simple_payments->is_enabled_jetpack_simple_payments() ) {
461
			return;
462
		}
463
464
		register_widget( 'Jetpack_Simple_Payments_Widget' );
465
	}
466
	add_action( 'widgets_init', 'register_widget_jetpack_simple_payments' );
467
}
468