Completed
Push — update/simple-payments-rest-ap... ( 9eb3ab...e203f9 )
by Jon
16:39 queued 06:28
created

Jetpack_Simple_Payments::get_currency()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 154

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 154
rs 8
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/*
3
 * Simple Payments lets users embed a PayPal button fully integrated with wpcom to sell products on the site.
4
 * This is not a proper module yet, because not all the pieces are in place. Until everything is shipped, it can be turned
5
 * into module that can be enabled/disabled.
6
*/
7
class Jetpack_Simple_Payments {
8
	// These have to be under 20 chars because that is CPT limit.
9
	static $post_type_order = 'jp_pay_order';
10
	static $post_type_product = 'jp_pay_product';
11
12
	static $shortcode = 'simple-payment';
13
14
	static $css_classname_prefix = 'jetpack-simple-payments';
15
16
	// Increase this number each time there's a change in CSS or JS to bust cache.
17
	static $version = '0.25';
18
19
	// Classic singleton pattern:
20
	private static $instance;
21
	private function __construct() {}
22
	static function getInstance() {
23
		if ( ! self::$instance ) {
24
			self::$instance = new self();
25
			self::$instance->register_init_hooks();
26
		}
27
		return self::$instance;
28
	}
29
30
	private function register_scripts_and_styles() {
31
		/**
32
		 * Paypal heavily discourages putting that script in your own server:
33
		 * @see https://developer.paypal.com/docs/integration/direct/express-checkout/integration-jsv4/add-paypal-button/
34
		 */
35
		wp_register_script( 'paypal-checkout-js', 'https://www.paypalobjects.com/api/checkout.js', array(), null, true );
36
		wp_register_script( 'paypal-express-checkout', plugins_url( '/paypal-express-checkout.js', __FILE__ ),
37
			array( 'jquery', 'paypal-checkout-js' ), self::$version );
38
		wp_register_style( 'jetpack-simple-payments', plugins_url( '/simple-payments.css', __FILE__ ), array( 'dashicons' ) );
39
	}
40
41
	private function register_init_hooks() {
42
		add_action( 'init', array( $this, 'init_hook_action' ) );
43
		add_action( 'rest_api_init', array( $this, 'register_meta_fields_in_rest_api' ) );
44
	}
45
46
	private function register_shortcode() {
47
		add_shortcode( self::$shortcode, array( $this, 'parse_shortcode' ) );
48
	}
49
50
	public function init_hook_action() {
51
		add_filter( 'rest_api_allowed_post_types', array( $this, 'allow_rest_api_types' ) );
52
		add_filter( 'jetpack_sync_post_meta_whitelist', array( $this, 'allow_sync_post_meta' ) );
53
		$this->register_scripts_and_styles();
54
		$this->register_shortcode();
55
		$this->setup_cpts();
56
57
		add_filter( 'the_content', array( $this, 'remove_auto_paragraph_from_product_description' ), 0 );
58
	}
59
60
	function remove_auto_paragraph_from_product_description( $content ) {
61
		if ( get_post_type() === self::$post_type_product ) {
62
			remove_filter( 'the_content', 'wpautop' );
63
		}
64
65
		return $content;
66
	}
67
68
	function get_blog_id() {
69
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
70
			return get_current_blog_id();
71
		}
72
73
		return Jetpack_Options::get_option( 'id' );
74
	}
75
76
	/**
77
	 * Used to check whether Simple Payments are enabled for given site.
78
	 *
79
	 * @return bool True if Simple Payments are enabled, false otherwise.
80
	 */
81
	function is_enabled_jetpack_simple_payments() {
82
		/**
83
		 * Can be used by plugin authors to disable the conflicting output of Simple Payments.
84
		 *
85
		 * @since 6.3.0
86
		 *
87
		 * @param bool True if Simple Payments should be disabled, false otherwise.
88
		 */
89
		if ( apply_filters( 'jetpack_disable_simple_payments', false ) ) {
90
			return false;
91
		}
92
93
		// For WPCOM sites
94
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM && function_exists( 'has_blog_sticker' ) ) {
95
			$site_id = $this->get_blog_id();
96
			return has_blog_sticker( 'premium-plan', $site_id ) || has_blog_sticker( 'business-plan', $site_id );
97
		}
98
99
		// For all Jetpack sites
100
		return Jetpack::is_active() && Jetpack::active_plan_supports( 'simple-payments');
101
	}
102
103
	function parse_shortcode( $attrs, $content = false ) {
104
		if ( empty( $attrs['id'] ) ) {
105
			return;
106
		}
107
		$product = get_post( $attrs['id'] );
108
		if ( ! $product || is_wp_error( $product ) ) {
109
			return;
110
		}
111
		if ( $product->post_type !== self::$post_type_product || 'trash' === $product->post_status ) {
112
			return;
113
		}
114
115
		// We allow for overriding the presentation labels
116
		$data = shortcode_atts( array(
117
			'blog_id'     => $this->get_blog_id(),
118
			'dom_id'      => uniqid( self::$css_classname_prefix . '-' . $product->ID . '_', true ),
119
			'class'       => self::$css_classname_prefix . '-' . $product->ID,
120
			'title'       => get_the_title( $product ),
121
			'description' => $product->post_content,
122
			'cta'         => get_post_meta( $product->ID, 'spay_cta', true ),
123
			'multiple'    => get_post_meta( $product->ID, 'spay_multiple', true ) || '0'
124
		), $attrs );
125
126
		$data['price'] = $this->format_price(
127
			get_post_meta( $product->ID, 'spay_formatted_price', true ),
128
			get_post_meta( $product->ID, 'spay_price', true ),
129
			get_post_meta( $product->ID, 'spay_currency', true ),
130
			$data
131
		);
132
133
		$data['id'] = $attrs['id'];
134
135
		if( ! wp_style_is( 'jetpack-simple-payments', 'enqueue' ) ) {
136
			wp_enqueue_style( 'jetpack-simple-payments' );
137
		}
138
139
		if ( ! $this->is_enabled_jetpack_simple_payments() ) {
140
			return $this->output_admin_warning( $data );
141
		}
142
143
		if ( ! wp_script_is( 'paypal-express-checkout', 'enqueued' ) ) {
144
			wp_enqueue_script( 'paypal-express-checkout' );
145
		}
146
147
		wp_add_inline_script( 'paypal-express-checkout', sprintf(
148
			"try{PaypalExpressCheckout.renderButton( '%d', '%d', '%s', '%d' );}catch(e){}",
149
			esc_js( $data['blog_id'] ),
150
			esc_js( $attrs['id'] ),
151
			esc_js( $data['dom_id'] ),
152
			esc_js( $data['multiple'] )
153
		) );
154
155
		return $this->output_shortcode( $data );
156
	}
157
158
	function output_admin_warning( $data ) {
159
		if ( ! current_user_can( 'manage_options' ) ) {
160
			return;
161
		}
162
		$css_prefix = self::$css_classname_prefix;
163
164
		$support_url = ( defined( 'IS_WPCOM' ) && IS_WPCOM )
165
			? 'https://support.wordpress.com/simple-payments/'
166
			: 'https://jetpack.com/support/simple-payment-button/';
167
168
		return sprintf( '
169
<div class="%1$s">
170
	<div class="%2$s">
171
		<div class="%3$s">
172
			<div class="%4$s" id="%5$s">
173
				<p>%6$s</p>
174
				<p>%7$s</p>
175
			</div>
176
		</div>
177
	</div>
178
</div>
179
',
180
			esc_attr( "{$data['class']} ${css_prefix}-wrapper" ),
181
			esc_attr( "${css_prefix}-product" ),
182
			esc_attr( "${css_prefix}-details" ),
183
			esc_attr( "${css_prefix}-purchase-message show error" ),
184
			esc_attr( "{$data['dom_id']}-message-container" ),
185
			sprintf(
186
				wp_kses(
187
					__( 'Your plan doesn\'t include Simple Payments. <a href="%s" rel="noopener noreferrer" target="_blank">Learn more and upgrade</a>.', 'jetpack' ),
188
					array( 'a' => array( 'href' => array(), 'rel' => array(), 'target' => array() ) )
189
				),
190
				esc_url( $support_url )
191
			),
192
			esc_html__( '(Only administrators will see this message.)', 'jetpack' )
193
		);
194
	}
195
196
	function output_shortcode( $data ) {
197
		$items = '';
198
		$css_prefix = self::$css_classname_prefix;
199
200
		if ( $data['multiple'] ) {
201
			$items = sprintf( '
202
				<div class="%1$s">
203
					<input class="%2$s" type="number" value="1" min="1" id="%3$s" />
204
				</div>
205
				',
206
				esc_attr( "${css_prefix}-items" ),
207
				esc_attr( "${css_prefix}-items-number" ),
208
				esc_attr( "{$data['dom_id']}_number" )
209
			);
210
		}
211
		$image = "";
212
		if( has_post_thumbnail( $data['id'] ) ) {
213
			$image = sprintf( '<div class="%1$s"><div class="%2$s">%3$s</div></div>',
214
				esc_attr( "${css_prefix}-product-image" ),
215
				esc_attr( "${css_prefix}-image" ),
216
				get_the_post_thumbnail( $data['id'], 'full' )
217
			);
218
		}
219
		return sprintf( '
220
<div class="%1$s">
221
	<div class="%2$s">
222
		%3$s
223
		<div class="%4$s">
224
			<div class="%5$s"><p>%6$s</p></div>
225
			<div class="%7$s"><p>%8$s</p></div>
226
			<div class="%9$s"><p>%10$s</p></div>
227
			<div class="%11$s" id="%12$s"></div>
228
			<div class="%13$s">
229
				%14$s
230
				<div class="%15$s" id="%16$s"></div>
231
			</div>
232
		</div>
233
	</div>
234
</div>
235
',
236
			esc_attr( "{$data['class']} ${css_prefix}-wrapper" ),
237
			esc_attr( "${css_prefix}-product" ),
238
			$image,
239
			esc_attr( "${css_prefix}-details" ),
240
			esc_attr( "${css_prefix}-title" ),
241
			$data['title'],
242
			esc_attr( "${css_prefix}-description" ),
243
			$data['description'],
244
			esc_attr( "${css_prefix}-price" ),
245
			esc_html( $data['price'] ),
246
			esc_attr( "${css_prefix}-purchase-message" ),
247
			esc_attr( "{$data['dom_id']}-message-container" ),
248
			esc_attr( "${css_prefix}-purchase-box" ),
249
			$items,
250
			esc_attr( "${css_prefix}-button" ),
251
			esc_attr( "{$data['dom_id']}_button" )
252
		);
253
	}
254
255
	function format_price( $formatted_price, $price, $currency, $all_data ) {
256
		if ( $formatted_price ) {
257
			return $formatted_price;
258
		}
259
		return "$price $currency";
260
	}
261
262
	/**
263
	 * Allows custom post types to be used by REST API.
264
	 * @param $post_types
265
	 * @see hook 'rest_api_allowed_post_types'
266
	 * @return array
267
	 */
268
	function allow_rest_api_types( $post_types ) {
269
		$post_types[] = self::$post_type_order;
270
		$post_types[] = self::$post_type_product;
271
		return $post_types;
272
	}
273
274
	function allow_sync_post_meta( $post_meta ) {
275
		return array_merge( $post_meta, array(
276
			'spay_paypal_id',
277
			'spay_status',
278
			'spay_product_id',
279
			'spay_quantity',
280
			'spay_price',
281
			'spay_customer_email',
282
			'spay_currency',
283
			'spay_cta',
284
			'spay_email',
285
			'spay_multiple',
286
			'spay_formatted_price',
287
		) );
288
	}
289
290
	/**
291
	 * Enable Simple payments custom meta values for access through the REST API.
292
	 * Field’s value will be exposed on a .meta key in the endpoint response,
293
	 * and WordPress will handle setting up the callbacks for reading and writing
294
	 * to that meta key.
295
	 *
296
	 * @link https://developer.wordpress.org/rest-api/extending-the-rest-api/modifying-responses/
297
	 */
298
	public function register_meta_fields_in_rest_api() {
299
		register_meta( 'post', 'spay_price', array(
300
			'description'       => esc_html__( 'Simple payments; price.', 'jetpack' ),
301
			'object_subtype'    => self::$post_type_product,
302
			'sanitize_callback' => array( $this, 'sanitize_price' ),
303
			'show_in_rest'      => true,
304
			'single'            => true,
305
			'type'              => 'number',
306
		) );
307
308
		register_meta( 'post', 'spay_currency', array(
309
			'description'       => esc_html__( 'Simple payments; currency code.', 'jetpack' ),
310
			'object_subtype'    => self::$post_type_product,
311
			'sanitize_callback' => array( $this, 'sanitize_currency' ),
312
			'show_in_rest'      => true,
313
			'single'            => true,
314
			'type'              => 'string',
315
		) );
316
317
		register_meta( 'post', 'spay_cta', array(
318
			'description'       => esc_html__( 'Simple payments; text with "Buy" or other CTA', 'jetpack' ),
319
			'object_subtype'    => self::$post_type_product,
320
			'sanitize_callback' => 'sanitize_text_field',
321
			'show_in_rest'      => true,
322
			'single'            => true,
323
			'type'              => 'string',
324
		) );
325
326
		register_meta( 'post', 'spay_multiple', array(
327
			'description'       => esc_html__( 'Simple payments; allow multiple items', 'jetpack' ),
328
			'object_subtype'    => self::$post_type_product,
329
			'sanitize_callback' => 'rest_sanitize_boolean',
330
			'show_in_rest'      => true,
331
			'single'            => true,
332
			'type'              => 'boolean',
333
		) );
334
335
		register_meta( 'post', 'spay_email', array(
336
			'description'       => esc_html__( 'Simple payments button; paypal email.', 'jetpack' ),
337
			'sanitize_callback' => 'sanitize_email',
338
			'show_in_rest'      => true,
339
			'single'            => true,
340
			'type'              => 'string',
341
		) );
342
343
		register_meta( 'post', 'spay_status', array(
344
			'description'       => esc_html__( 'Simple payments; status.', 'jetpack' ),
345
			'object_subtype'    => self::$post_type_product,
346
			'sanitize_callback' => 'sanitize_text_field',
347
			'show_in_rest'      => true,
348
			'single'            => true,
349
			'type'              => 'string',
350
		) );
351
	}
352
353
	/**
354
	 * Sanitize three-character ISO-4217 Simple payments currency
355
	 *
356
	 * List has to be in sync with list at the client side:
357
	 * @link https://github.com/Automattic/wp-calypso/blob/6d02ffe73cc073dea7270a22dc30881bff17d8fb/client/lib/simple-payments/constants.js
358
	 *
359
	 * Currencies should be supported by PayPal:
360
	 * @link https://developer.paypal.com/docs/integration/direct/rest/currency-codes/
361
	 */
362
	public static function sanitize_currency( $currency ) {
363
		$valid_currencies = array(
364
			'USD',
365
			'EUR',
366
			'AUD',
367
			'BRL',
368
			'CAD',
369
			'CZK',
370
			'DKK',
371
			'HKD',
372
			'HUF',
373
			'ILS',
374
			'JPY',
375
			'MYR',
376
			'MXN',
377
			'TWD',
378
			'NZD',
379
			'NOK',
380
			'PHP',
381
			'PLN',
382
			'GBP',
383
			'RUB',
384
			'SGD',
385
			'SEK',
386
			'CHF',
387
			'THB',
388
		);
389
390
		return in_array( $currency, $valid_currencies ) ? $currency : false;
391
	}
392
393
	/**
394
	 * Sanitize price:
395
	 *
396
	 * Positive integers and floats
397
	 * Supports two decimal places.
398
	 * Maximum length: 10.
399
	 *
400
	 * See `price` from PayPal docs:
401
	 * @link https://developer.paypal.com/docs/api/orders/v1/#definition-item
402
	 *
403
	 * @param      $value
404
	 * @return null|string
405
	 */
406
	public static function sanitize_price( $price ) {
407
		return preg_match( '/^[0-9]{0,10}(\.[0-9]{0,2})?$/', $price ) ? $price : false;
408
	}
409
410
	/**
411
	 * Sets up the custom post types for the module.
412
	 */
413
	function setup_cpts() {
414
415
		/*
416
		 * ORDER data structure. holds:
417
		 * title = customer_name | 4xproduct_name
418
		 * excerpt = customer_name + customer contact info + customer notes from paypal form
419
		 * metadata:
420
		 * spay_paypal_id - paypal id of transaction
421
		 * spay_status
422
		 * spay_product_id - post_id of bought product
423
		 * spay_quantity - quantity of product
424
		 * spay_price - item price at the time of purchase
425
		 * spay_customer_email - customer email
426
		 * ... (WIP)
427
		 */
428
		$order_capabilities = array(
429
			'edit_post'             => 'edit_posts',
430
			'read_post'             => 'read_private_posts',
431
			'delete_post'           => 'delete_posts',
432
			'edit_posts'            => 'edit_posts',
433
			'edit_others_posts'     => 'edit_others_posts',
434
			'publish_posts'         => 'publish_posts',
435
			'read_private_posts'    => 'read_private_posts',
436
		);
437
		$order_args = array(
438
			'label'                 => esc_html_x( 'Order', 'noun: a quantity of goods or items purchased or sold', 'jetpack' ),
439
			'description'           => esc_html__( 'Simple Payments orders', 'jetpack' ),
440
			'supports'              => array( 'custom-fields', 'excerpt' ),
441
			'hierarchical'          => false,
442
			'public'                => false,
443
			'show_ui'               => false,
444
			'show_in_menu'          => false,
445
			'show_in_admin_bar'     => false,
446
			'show_in_nav_menus'     => false,
447
			'can_export'            => true,
448
			'has_archive'           => false,
449
			'exclude_from_search'   => true,
450
			'publicly_queryable'    => false,
451
			'rewrite'               => false,
452
			'capabilities'          => $order_capabilities,
453
			'show_in_rest'          => true,
454
		);
455
		register_post_type( self::$post_type_order, $order_args );
456
457
		/*
458
		 * PRODUCT data structure. Holds:
459
		 * title - title
460
		 * content - description
461
		 * thumbnail - image
462
		 * metadata:
463
		 * spay_price - price
464
		 * spay_formatted_price
465
		 * spay_currency - currency code
466
		 * spay_cta - text with "Buy" or other CTA
467
		 * spay_email - paypal email
468
		 * spay_multiple - allow for multiple items
469
		 * spay_status - status. { enabled | disabled }
470
		 */
471
		$product_capabilities = array(
472
			'edit_post'             => 'edit_posts',
473
			'read_post'             => 'read_private_posts',
474
			'delete_post'           => 'delete_posts',
475
			'edit_posts'            => 'publish_posts',
476
			'edit_others_posts'     => 'edit_others_posts',
477
			'publish_posts'         => 'publish_posts',
478
			'read_private_posts'    => 'read_private_posts',
479
		);
480
		$product_args = array(
481
			'label'                 => esc_html__( 'Product', 'jetpack' ),
482
			'description'           => esc_html__( 'Simple Payments products', 'jetpack' ),
483
			'supports'              => array( 'title', 'editor','thumbnail', 'custom-fields', 'author' ),
484
			'hierarchical'          => false,
485
			'public'                => false,
486
			'show_ui'               => false,
487
			'show_in_menu'          => false,
488
			'show_in_admin_bar'     => false,
489
			'show_in_nav_menus'     => false,
490
			'can_export'            => true,
491
			'has_archive'           => false,
492
			'exclude_from_search'   => true,
493
			'publicly_queryable'    => false,
494
			'rewrite'               => false,
495
			'capabilities'          => $product_capabilities,
496
			'show_in_rest'          => true,
497
		);
498
		register_post_type( self::$post_type_product, $product_args );
499
	}
500
501
}
502
Jetpack_Simple_Payments::getInstance();
503