Completed
Push — add/changelog-64 ( 49ba93...06347a )
by
unknown
169:34 queued 150:02
created

Jetpack_Simple_Payments_Widget::__construct()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
nc 8
nop 0
dl 0
loc 31
rs 8.4906
c 0
b 0
f 0
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
			global $pagenow;
60
			if ( is_customize_preview() || 'widgets.php' === $pagenow ) {
61
				add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_styles' ) );
62
			}
63
64
			$jetpack_simple_payments = Jetpack_Simple_Payments::getInstance();
65
			if ( is_customize_preview() && $jetpack_simple_payments->is_enabled_jetpack_simple_payments() ) {
66
				add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
67
68
				add_filter( 'customize_refresh_nonces', array( $this, 'filter_nonces' ) );
69
				add_action( 'wp_ajax_customize-jetpack-simple-payments-buttons-get', array( $this, 'ajax_get_payment_buttons' ) );
70
				add_action( 'wp_ajax_customize-jetpack-simple-payments-button-save', array( $this, 'ajax_save_payment_button' ) );
71
				add_action( 'wp_ajax_customize-jetpack-simple-payments-button-delete', array( $this, 'ajax_delete_payment_button' ) );
72
			}
73
74
			if ( is_active_widget( false, false, $this->id_base ) || is_customize_preview() ) {
75
				add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_style' ) );
76
			}
77
		}
78
79
		/**
80
		 * Return an associative array of default values.
81
		 *
82
		 * These values are used in new widgets.
83
		 *
84
		 * @return array Default values for the widget options.
85
		 */
86
		private function defaults() {
87
			$current_user = wp_get_current_user();
88
			$default_product_id = $this->get_first_product_id();
89
90
			return array(
91
				'title' => '',
92
				'product_post_id' => $default_product_id,
93
				'form_action' => '',
94
				'form_product_id' => 0,
95
				'form_product_title' => '',
96
				'form_product_description' => '',
97
				'form_product_image_id' => 0,
98
				'form_product_image_src' => '',
99
				'form_product_currency' => '',
100
				'form_product_price' => '',
101
				'form_product_multiple' => '',
102
				'form_product_email' => $current_user->user_email,
103
			);
104
		}
105
106
		/**
107
		 * Adds a nonce for customizing menus.
108
		 *
109
		 * @param array $nonces Array of nonces.
110
		 * @return array $nonces Modified array of nonces.
111
		 */
112
		function filter_nonces( $nonces ) {
113
			$nonces['customize-jetpack-simple-payments'] = wp_create_nonce( 'customize-jetpack-simple-payments' );
114
			return $nonces;
115
		}
116
117
		function enqueue_style() {
118
			wp_enqueue_style( 'jetpack-simple-payments-widget-style', plugins_url( 'simple-payments/style.css', __FILE__ ), array(), '20180518' );
119
		}
120
121
		function admin_enqueue_styles() {
122
			wp_enqueue_style( 'jetpack-simple-payments-widget-customizer', plugins_url( 'simple-payments/customizer.css', __FILE__ ) );
123
		}
124
125
		function admin_enqueue_scripts() {
126
				wp_enqueue_media();
127
				wp_enqueue_script( 'jetpack-simple-payments-widget-customizer', plugins_url( '/simple-payments/customizer.js', __FILE__ ), array( 'jquery' ), false, true );
128
				wp_localize_script( 'jetpack-simple-payments-widget-customizer', 'jpSimplePaymentsStrings', array(
129
					'deleteConfirmation' => __( 'Are you sure you want to delete this item? It will be disabled and removed from all locations where it currently appears.', 'jetpack' )
130
				) );
131
		}
132
133
		public function ajax_get_payment_buttons() {
134
			if ( ! check_ajax_referer( 'customize-jetpack-simple-payments', 'customize-jetpack-simple-payments-nonce', false ) ) {
135
				wp_send_json_error( 'bad_nonce', 400 );
136
			}
137
138
			if ( ! current_user_can( 'customize' ) ) {
139
				wp_send_json_error( 'customize_not_allowed', 403 );
140
			}
141
142
			$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...
143 View Code Duplication
			if ( ! current_user_can( $post_type_object->cap->create_posts ) || ! current_user_can( $post_type_object->cap->publish_posts ) ) {
144
				wp_send_json_error( 'insufficient_post_permissions', 403 );
145
			}
146
147
			$product_posts = get_posts( array(
148
				'numberposts' => 100,
149
				'orderby' => 'date',
150
				'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...
151
				'post_status' => 'publish',
152
			 ) );
153
154
			 $formatted_products = array_map( array( $this, 'format_product_post_for_ajax_reponse' ), $product_posts );
155
156
			 wp_send_json_success( $formatted_products );
157
		}
158
159
		public function format_product_post_for_ajax_reponse( $product_post ) {
160
			return array(
161
				'ID' => $product_post->ID,
162
				'post_title' => $product_post->post_title,
163
			);
164
		}
165
166
		public function ajax_save_payment_button() {
167
			if ( ! check_ajax_referer( 'customize-jetpack-simple-payments', 'customize-jetpack-simple-payments-nonce', false ) ) {
168
				wp_send_json_error( 'bad_nonce', 400 );
169
			}
170
171
			if ( ! current_user_can( 'customize' ) ) {
172
				wp_send_json_error( 'customize_not_allowed', 403 );
173
			}
174
175
			$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...
176 View Code Duplication
			if ( ! current_user_can( $post_type_object->cap->create_posts ) || ! current_user_can( $post_type_object->cap->publish_posts ) ) {
177
				wp_send_json_error( 'insufficient_post_permissions', 403 );
178
			}
179
180 View Code Duplication
			if ( empty( $_POST['params'] ) || ! is_array( $_POST['params'] ) ) {
181
				wp_send_json_error( 'missing_params', 400 );
182
			}
183
184
			$params = wp_unslash( $_POST['params'] );
185
			$errors = $this->validate_ajax_params( $params );
186
			if ( ! empty( $errors->errors ) ) {
187
				wp_send_json_error( $errors );
188
			}
189
190
			$product_post_id = isset( $params['product_post_id'] ) ? intval( $params['product_post_id'] ) : 0;
191
192
			$product_post = array(
193
				'ID' => $product_post_id,
194
				'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...
195
				'post_status' => 'publish',
196
				'post_title' => $params['post_title'],
197
				'post_content' => $params['post_content'],
198
				'_thumbnail_id' => ! empty( $params['image_id'] ) ? $params['image_id'] : -1,
199
				'meta_input' => array(
200
					'spay_currency' => $params['currency'],
201
					'spay_price' => $params['price'],
202
					'spay_multiple' => isset( $params['multiple'] ) ? intval( $params['multiple'] ) : 0,
203
					'spay_email' => is_email( $params['email'] ),
204
				),
205
			);
206
207
			if ( empty( $product_post_id ) ) {
208
				$product_post_id = wp_insert_post( $product_post );
209
			} else {
210
				$product_post_id = wp_update_post( $product_post );
211
			}
212
213
			if ( ! $product_post_id || is_wp_error( $product_post_id ) ) {
214
				wp_send_json_error( $product_post_id );
215
			}
216
217
			$tracks_properties = array(
218
				'id'       => $product_post_id,
219
				'currency' => $params['currency'],
220
				'price'    => $params['price']
221
			);
222
			if ( 0 === $product_post['ID'] ) {
223
				$this->record_event( 'created', 'create', $tracks_properties );
224
			} else {
225
				$this->record_event( 'updated', 'update', $tracks_properties );
226
			}
227
228
			wp_send_json_success( array(
229
				'product_post_id' => $product_post_id,
230
				'product_post_title' => $params['post_title'],
231
			) );
232
		}
233
234
		public function ajax_delete_payment_button() {
235
			if ( ! check_ajax_referer( 'customize-jetpack-simple-payments', 'customize-jetpack-simple-payments-nonce', false ) ) {
236
				wp_send_json_error( 'bad_nonce', 400 );
237
			}
238
239
			if ( ! current_user_can( 'customize' ) ) {
240
				wp_send_json_error( 'customize_not_allowed', 403 );
241
			}
242
243 View Code Duplication
			if ( empty( $_POST['params'] ) || ! is_array( $_POST['params'] ) ) {
244
				wp_send_json_error( 'missing_params', 400 );
245
			}
246
247
			$params = wp_unslash( $_POST['params'] );
248
			$illegal_params = array_diff( array_keys( $params ), array( 'product_post_id' ) );
249
			if ( ! empty( $illegal_params ) ) {
250
				wp_send_json_error( 'illegal_params', 400 );
251
			}
252
253
			$product_id = ( int ) $params['product_post_id'];
254
			$product_post = get_post( $product_id );
255
256
			$return = array( 'status' => $product_post->post_status );
257
258
			wp_delete_post( $product_id, true );
259
			$status = get_post_status( $product_id );
260
			if ( false === $status ) {
261
				$return['status'] = 'deleted';
262
			}
263
264
			$this->record_event( 'deleted', 'delete', array( 'id' => $product_id ) );
265
266
			wp_send_json_success( $return );
267
		}
268
269
		public function validate_ajax_params( $params ) {
270
			$errors = new WP_Error();
271
272
			$illegal_params = array_diff( array_keys( $params ), array( 'product_post_id', 'post_title', 'post_content', 'image_id', 'currency', 'price', 'multiple', 'email' ) );
273
			if ( ! empty( $illegal_params ) ) {
274
				$errors->add( 'illegal_params', __( 'Invalid parameters.', 'jetpack' ) );
275
			}
276
277
			if ( empty( $params['post_title'] ) ) {
278
				$errors->add( 'post_title', __( "People need to know what they're paying for! Please add a brief title.", 'jetpack' ) );
279
			}
280
281
			if ( empty( $params['price'] ) || floatval( $params['price'] ) <= 0 ) {
282
				$errors->add( 'price', __( 'Everything comes with a price tag these days. Please add a your product price.', 'jetpack' ) );
283
			}
284
285
			if ( empty( $params['email'] ) || ! is_email( $params['email'] ) ) {
286
				$errors->add( 'email', __( 'We want to make sure payments reach you, so please add an email address.', 'jetpack' ) );
287
			}
288
289
			return $errors;
290
		}
291
292
		function get_first_product_id() {
293
			$product_posts = get_posts( array(
294
				'numberposts' => 1,
295
				'orderby' => 'date',
296
				'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...
297
				'post_status' => 'publish',
298
			 ) );
299
300
			return ! empty( $product_posts ) ? $product_posts[0]->ID : null;
301
		}
302
303
		/**
304
		 * Front-end display of widget.
305
		 *
306
		 * @see WP_Widget::widget()
307
		 *
308
		 * @param array $args     Widget arguments.
309
		 * @param array $instance Saved values from database.
310
		 */
311
		function widget( $args, $instance ) {
312
			$instance = wp_parse_args( $instance, $this->defaults() );
313
314
			echo $args['before_widget'];
315
316
			/** This filter is documented in core/src/wp-includes/default-widgets.php */
317
			$title = apply_filters( 'widget_title', $instance['title'] );
318
			if ( ! empty( $title ) ) {
319
				echo $args['before_title'] . $title . $args['after_title'];
320
			}
321
322
			echo '<div class="jetpack-simple-payments-content">';
323
324
			if ( ! empty( $instance['form_action'] ) && in_array( $instance['form_action'], array( 'add', 'edit' ) ) && is_customize_preview() ) {
325
				require( dirname( __FILE__ ) . '/simple-payments/widget.php' );
326
			} else {
327
				$jsp = Jetpack_Simple_Payments::getInstance();
328
				$simple_payments_button = $jsp->parse_shortcode( array(
329
					'id' => $instance['product_post_id'],
330
				) );
331
332
				if ( ! is_null( $simple_payments_button ) || is_customize_preview() ) {
333
					echo $simple_payments_button;
334
				}
335
			}
336
337
			echo '</div><!--simple-payments-->';
338
339
			echo $args['after_widget'];
340
341
			/** This action is already documented in modules/widgets/gravatar-profile.php */
342
			do_action( 'jetpack_stats_extra', 'widget_view', 'simple_payments' );
343
		}
344
345
		/**
346
		 * Gets the latests field value from either the old instance or the new instance.
347
		 *
348
		 * @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...
349
		 * @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...
350
		 * @return mixed $mixed Field value.
351
		 */
352
		private function get_latest_field_value( $new_instance, $old_instance, $field) {
353
			return ! empty( $new_instance[ $field ] )
354
				? sanitize_text_field( $new_instance[ $field ] )
355
				: $old_instance[ $field ];
356
		}
357
358
		/**
359
		 * Gets the product fields from the product post. If no post found
360
		 * it returns the default values.
361
		 *
362
		 * @param int Product Post ID.
363
		 * @return array $fields Product Fields from the Product Post.
364
		 */
365
		private function get_product_from_post( $product_post_id ) {
366
			$product_post = get_post( $product_post_id );
367
			$form_product_id = $product_post_id;
368
			if( ! empty( $product_post ) ) {
369
				$form_product_image_id = get_post_thumbnail_id( $product_post_id );
370
371
				return array(
372
					'form_product_id' => $form_product_id,
373
					'form_product_title' => get_the_title( $product_post ),
374
					'form_product_description' => $product_post->post_content,
375
					'form_product_image_id' => $form_product_image_id,
376
					'form_product_image_src' => wp_get_attachment_image_url( $form_product_image_id, 'thumbnail' ),
377
					'form_product_currency' => get_post_meta( $product_post_id, 'spay_currency', true ),
378
					'form_product_price' => get_post_meta( $product_post_id, 'spay_price', true ),
379
					'form_product_multiple' => get_post_meta( $product_post_id, 'spay_multiple', true ) || '0',
380
					'form_product_email' => get_post_meta( $product_post_id, 'spay_email', true ),
381
				);
382
			}
383
384
			return $this->defaults();
385
		}
386
387
		/**
388
		 * Record a Track event and bump a MC stat.
389
		 *
390
		 * @param string $stat_name
391
		 * @param string $event_action
392
		 * @param array $event_properties
393
		 */
394
		private function record_event( $stat_name, $event_action, $event_properties = array() ) {
395
			$current_user = wp_get_current_user();
396
397
			// `bumps_stats_extra` only exists on .com
398
			if ( function_exists( 'bump_stats_extras' ) ) {
399
				require_lib( 'tracks/client' );
400
				tracks_record_event( $current_user, 'simple_payments_button_' . $event_action, $event_properties );
401
				/** This action is documented in modules/widgets/social-media-icons.php */
402
				do_action( 'jetpack_bump_stats_extra', 'jetpack-simple_payments', $stat_name );
403
				return;
404
			}
405
406
			jetpack_tracks_record_event( $current_user, 'jetpack_wpa_simple_payments_button_' . $event_action, $event_properties );
407
			$jetpack = Jetpack::init();
408
			// $jetpack->stat automatically prepends the stat group with 'jetpack-'
409
			$jetpack->stat( 'simple_payments', $stat_name ) ;
410
			$jetpack->do_stats( 'server_side' );
411
		}
412
413
		/**
414
		 * Sanitize widget form values as they are saved.
415
		 *
416
		 * @see WP_Widget::update()
417
		 *
418
		 * @param array $new_instance Values just sent to be saved.
419
		 * @param array $old_instance Previously saved values from database.
420
		 *
421
		 * @return array Updated safe values to be saved.
422
		 */
423
		function update( $new_instance, $old_instance ) {
424
			$defaults = $this->defaults();
425
			//do not overrite `product_post_id` for `$new_instance` with the defaults
426
			$new_instance = wp_parse_args( $new_instance, array_diff_key( $defaults, array( 'product_post_id' => 0 ) ) );
427
			$old_instance = wp_parse_args( $old_instance, $defaults );
428
429
			$required_widget_props = array(
430
				'title' => $this->get_latest_field_value( $new_instance, $old_instance, 'title' ),
431
				'product_post_id' => $this->get_latest_field_value( $new_instance, $old_instance, 'product_post_id' ),
432
				'form_action' => $this->get_latest_field_value( $new_instance, $old_instance, 'form_action' ),
433
			);
434
435
			if ( strcmp( $new_instance['form_action'], $old_instance['form_action'] ) !== 0 ) {
436
				if ( $new_instance['form_action'] == 'edit' ) {
437
					return array_merge( $this->get_product_from_post( ( int ) $old_instance['product_post_id'] ), $required_widget_props );
438
				}
439
440
				if ( $new_instance['form_action'] == 'clear' ) {
441
					return array_merge( $this->defaults(), $required_widget_props );
442
				}
443
			}
444
445
			$form_product_image_id = (int) $new_instance['form_product_image_id'];
446
447
			$form_product_email = ! empty( $new_instance['form_product_email'] )
448
				? sanitize_text_field( $new_instance['form_product_email'] )
449
				: $defaults['form_product_email'];
450
451
			return array_merge( $required_widget_props, array(
452
				'form_product_id' => ( int ) $new_instance['form_product_id'],
453
				'form_product_title' => sanitize_text_field( $new_instance['form_product_title'] ),
454
				'form_product_description' => sanitize_text_field( $new_instance['form_product_description'] ),
455
				'form_product_image_id' => $form_product_image_id,
456
				'form_product_image_src' => wp_get_attachment_image_url( $form_product_image_id, 'thumbnail' ),
457
				'form_product_currency' => sanitize_text_field( $new_instance['form_product_currency'] ),
458
				'form_product_price' => sanitize_text_field( $new_instance['form_product_price'] ),
459
				'form_product_multiple' => sanitize_text_field( $new_instance['form_product_multiple'] ),
460
				'form_product_email' => $form_product_email,
461
			) );
462
		}
463
464
		/**
465
		 * Back-end widget form.
466
		 *
467
		 * @see WP_Widget::form()
468
		 *
469
		 * @param array $instance Previously saved values from database.
470
		 */
471
		function form( $instance ) {
472
			$jetpack_simple_payments = Jetpack_Simple_Payments::getInstance();
473
			if ( ! $jetpack_simple_payments->is_enabled_jetpack_simple_payments() ) {
474
				require dirname( __FILE__ ) . '/simple-payments/admin-warning.php';
475
				return;
476
			}
477
478
			$instance = wp_parse_args( $instance, $this->defaults() );
479
480
			$product_posts = get_posts( array(
481
				'numberposts' => 100,
482
				'orderby' => 'date',
483
				'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...
484
				'post_status' => 'publish',
485
			) );
486
487
			require dirname( __FILE__ ) . '/simple-payments/form.php';
488
		}
489
	}
490
491
	// Register Jetpack_Simple_Payments_Widget widget.
492
	function register_widget_jetpack_simple_payments() {
493
		if ( ! class_exists( 'Jetpack_Simple_Payments' ) ) {
494
			return;
495
		}
496
497
		$jetpack_simple_payments = Jetpack_Simple_Payments::getInstance();
498
		if ( ! $jetpack_simple_payments->is_enabled_jetpack_simple_payments() ) {
499
			return;
500
		}
501
502
		register_widget( 'Jetpack_Simple_Payments_Widget' );
503
	}
504
	add_action( 'widgets_init', 'register_widget_jetpack_simple_payments' );
505
}
506