Completed
Pull Request — master (#8119)
by Rastislav
21:27 queued 10:57
created

Jetpack_Simple_Payments::render_gutenberg_block()   B

Complexity

Conditions 3
Paths 4

Size

Total Lines 25
Code Lines 16

Duplication

Lines 3
Ratio 12 %

Importance

Changes 0
Metric Value
cc 3
eloc 16
nc 4
nop 2
dl 3
loc 25
rs 8.8571
c 0
b 0
f 0
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';
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $post_type_order.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
10
	static $post_type_product = 'jp_pay_product';
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $post_type_product.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
11
12
	static $shortcode = 'simple-payment';
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $shortcode.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
13
14
	static $css_classname_prefix = 'jetpack-simple-payments';
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $css_classname_prefix.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
15
16
	// Increase this number each time there's a change in CSS or JS to bust cache.
17
	static $version = '0.25';
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $version.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
18
19
	// Classic singleton pattern:
20
	private static $instance;
21
22
	private function __construct() {}
23
24
	static function getInstance() {
25
		if ( ! self::$instance ) {
26
			self::$instance = new self();
27
			self::$instance->register_init_hook();
28
			self::$instance->register_gutenberg_block();
29
		}
30
		return self::$instance;
31
	}
32
33
	private function register_scripts() {
34
		/**
35
		 * Paypal heavily discourages putting that script in your own server:
36
		 * @see https://developer.paypal.com/docs/integration/direct/express-checkout/integration-jsv4/add-paypal-button/
37
		 */
38
		wp_register_script( 'paypal-checkout-js', 'https://www.paypalobjects.com/api/checkout.js', array(), null, true );
39
		wp_register_script( 'paypal-express-checkout', plugins_url( '/paypal-express-checkout.js', __FILE__ ),
40
			array( 'jquery', 'paypal-checkout-js' ), self::$version );
41
	}
42
43
	private function register_init_hook() {
44
		add_action( 'init', array( $this, 'init_hook_action' ) );
45
	}
46
47
	private function register_shortcode() {
48
		add_shortcode( self::$shortcode, array( $this, 'parse_shortcode' ) );
49
	}
50
51
	public function init_hook_action() {
52
		add_filter( 'rest_api_allowed_post_types', array( $this, 'allow_rest_api_types' ) );
53
		add_filter( 'jetpack_sync_post_meta_whitelist', array( $this, 'allow_sync_post_meta' ) );
54
		$this->register_scripts();
55
		$this->register_shortcode();
56
		$this->setup_cpts();
57
		$this->setup_meta_fields_for_rest();
58
59
		add_filter( 'the_content', array( $this, 'remove_auto_paragraph_from_product_description' ), 0 );
60
	}
61
62
	private function register_gutenberg_block() {
63
		add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_block_editor_assets' ) );
64
		add_action( 'enqueue_block_assets', array( $this, 'enqueue_block_assets' ) );
65
66
		register_block_type( 'jetpack/simple-payments-button', array(
67
			'render_callback' => array( $this, 'render_gutenberg_block' ),
68
		) );
69
	}
70
71
	public function enqueue_block_editor_assets() {
72
		wp_enqueue_script(
73
			'gutenberg-simple-payments-button',
74
			plugins_url( 'simple-payments-block.js', __FILE__ ),
75
			array( 'wp-blocks', 'wp-element' )
76
		);
77
	}
78
79
	public function enqueue_block_assets() {
80
		if ( is_admin() ) {
81
			// Load the styles just in admin area and rely on custom PayPal
82
			// styles on front end
83
			wp_enqueue_style(
84
				'gutenberg-simple-payments-button-styles',
85
				plugins_url( 'simple-payments-block.css', __FILE__ ),
86
				array(),
87
				JETPACK__VERSION
88
			);
89
		}
90
	}
91
92
	public function render_gutenberg_block( $attributes, $content ) {
93
		$blog_id = $this->get_blog_id();
94
		$dom_id = 'paypal-express-checkout';
95
		$multiple = $attributes[ 'multipleItems' ];
96
		// mock id for now
97
		// $id = $attributes['id'];
98
		$id = 357;
99
100
		if ( ! wp_script_is( 'paypal-express-checkout', 'enqueued' ) ) {
101
			wp_enqueue_script( 'paypal-express-checkout' );
102
		}
103 View Code Duplication
		if ( ! wp_style_is( 'simple-payments', 'enqueued' ) ) {
104
			wp_enqueue_style( 'simple-payments', plugins_url( 'simple-payments.css', __FILE__ ), array( 'dashicons' ) );
105
		}
106
107
		wp_add_inline_script( 'paypal-express-checkout', sprintf(
108
			"try{PaypalExpressCheckout.renderButton( '%d', '%d', '%s', '%d' );}catch(e){}",
109
			esc_js( $blog_id ),
110
			esc_js( $id ),
111
			esc_js( $dom_id ),
112
			esc_js( $multiple )
113
		) );
114
115
		return $content;
116
	}
117
118
	function remove_auto_paragraph_from_product_description( $content ) {
119
		if ( get_post_type() === self::$post_type_product ) {
120
			remove_filter( 'the_content', 'wpautop' );
121
		}
122
123
		return $content;
124
	}
125
126
	function get_blog_id() {
127
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
128
			return get_current_blog_id();
129
		}
130
131
		return Jetpack_Options::get_option( 'id' );
132
	}
133
134
	function parse_shortcode( $attrs, $content = false ) {
135
		if ( empty( $attrs['id'] ) ) {
136
			return;
137
		}
138
		$product = get_post( $attrs['id'] );
139
		if ( ! $product || is_wp_error( $product ) ) {
140
			return;
141
		}
142
		if ( $product->post_type !== self::$post_type_product || 'trash' === $product->post_status ) {
143
			return;
144
		}
145
146
		// We allow for overriding the presentation labels
147
		$data = shortcode_atts( array(
148
			'blog_id'     => $this->get_blog_id(),
149
			'dom_id'      => uniqid( self::$css_classname_prefix . '-' . $product->ID . '_', true ),
150
			'class'       => self::$css_classname_prefix . '-' . $product->ID,
151
			'title'       => get_the_title( $product ),
152
			'description' => $product->post_content,
153
			'cta'         => get_post_meta( $product->ID, 'spay_cta', true ),
154
			'multiple'    => get_post_meta( $product->ID, 'spay_multiple', true ) || '0'
155
		), $attrs );
156
157
		$data['price'] = $this->format_price(
158
			get_post_meta( $product->ID, 'spay_formatted_price', true ),
159
			get_post_meta( $product->ID, 'spay_price', true ),
160
			get_post_meta( $product->ID, 'spay_currency', true ),
161
			$data
162
		);
163
164
		$data['id'] = $attrs['id'];
165
		if ( ! wp_script_is( 'paypal-express-checkout', 'enqueued' ) ) {
166
			wp_enqueue_script( 'paypal-express-checkout' );
167
		}
168 View Code Duplication
		if ( ! wp_style_is( 'simple-payments', 'enqueued' ) ) {
169
			wp_enqueue_style( 'simple-payments', plugins_url( 'simple-payments.css', __FILE__ ), array( 'dashicons' ) );
170
		}
171
172
		wp_add_inline_script( 'paypal-express-checkout', sprintf(
173
			"try{PaypalExpressCheckout.renderButton( '%d', '%d', '%s', '%d' );}catch(e){}",
174
			esc_js( $data['blog_id'] ),
175
			esc_js( $attrs['id'] ),
176
			esc_js( $data['dom_id'] ),
177
			esc_js( $data['multiple'] )
178
		) );
179
180
		return $this->output_shortcode( $data );
181
	}
182
183
	function output_shortcode( $data ) {
184
		$items = '';
185
		$css_prefix = self::$css_classname_prefix;
186
187
		if ( $data['multiple'] ) {
188
			$items="<div class='${css_prefix}-items'>
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned correctly; expected 1 space but found 0 spaces

This check looks for improperly formatted assignments.

Every assignment must have exactly one space before and one space after the equals operator.

To illustrate:

$a = "a";
$ab = "ab";
$abc = "abc";

will have no issues, while

$a   = "a";
$ab  = "ab";
$abc = "abc";

will report issues in lines 1 and 2.

Loading history...
189
				<input class='${css_prefix}-items-number' type='number' value='1' min='1' id='{$data['dom_id']}_number' />
190
			</div>";
191
		}
192
		$image = "";
193
		if( has_post_thumbnail( $data['id'] ) ) {
194
			$image = "<div class='${css_prefix}-product-image'><div class='${css_prefix}-image'>" . get_the_post_thumbnail( $data['id'], 'full' ) . "</div></div>";
195
		}
196
		return "
197
<div class='{$data['class']} ${css_prefix}-wrapper'>
198
	<div class='${css_prefix}-product'>
199
		{$image}
200
		<div class='${css_prefix}-details'>
201
			<div class='${css_prefix}-title'><p>{$data['title']}</p></div>
202
			<div class='${css_prefix}-description'><p>{$data['description']}</p></div>
203
			<div class='${css_prefix}-price'><p>{$data['price']}</p></div>
204
			<div class='${css_prefix}-purchase-message' id='{$data['dom_id']}-message-container'></div>
205
			<div class='${css_prefix}-purchase-box'>
206
				{$items}
207
				<div class='${css_prefix}-button' id='{$data['dom_id']}_button'></div>
208
			</div>
209
		</div>
210
	</div>
211
</div>
212
		";
213
	}
214
215
	function format_price( $formatted_price, $price, $currency, $all_data ) {
216
		if ( $formatted_price ) {
217
			return $formatted_price;
218
		}
219
		return "$price $currency";
220
	}
221
222
	/**
223
	 * Allows custom post types to be used by REST API.
224
	 * @param $post_types
225
	 * @see hook 'rest_api_allowed_post_types'
226
	 * @return array
227
	 */
228
	function allow_rest_api_types( $post_types ) {
229
		$post_types[] = self::$post_type_order;
230
		$post_types[] = self::$post_type_product;
231
		return $post_types;
232
	}
233
234
	function allow_sync_post_meta( $post_meta ) {
235
		return array_merge( $post_meta, array(
236
			'spay_paypal_id',
237
			'spay_status',
238
			'spay_product_id',
239
			'spay_quantity',
240
			'spay_price',
241
			'spay_customer_email',
242
			'spay_currency',
243
			'spay_cta',
244
			'spay_email',
245
			'spay_multiple',
246
			'spay_formatted_price',
247
		) );
248
	}
249
250
	/**
251
	 * Sets up the custom post types for the module.
252
	 */
253
	function setup_cpts() {
254
255
		/*
256
		 * ORDER data structure. holds:
257
		 * title = customer_name | 4xproduct_name
258
		 * excerpt = customer_name + customer contact info + customer notes from paypal form
259
		 * metadata:
260
		 * spay_paypal_id - paypal id of transaction
261
		 * spay_status
262
		 * spay_product_id - post_id of bought product
263
		 * spay_quantity - quantity of product
264
		 * spay_price - item price at the time of purchase
265
		 * spay_customer_email - customer email
266
		 * ... (WIP)
267
		 */
268
		$order_capabilities = array(
269
			'edit_post'             => 'edit_posts',
270
			'read_post'             => 'read_private_posts',
271
			'delete_post'           => 'delete_posts',
272
			'edit_posts'            => 'edit_posts',
273
			'edit_others_posts'     => 'edit_others_posts',
274
			'publish_posts'         => 'publish_posts',
275
			'read_private_posts'    => 'read_private_posts',
276
		);
277
		$order_args = array(
278
			'label'                 => esc_html__( 'Order', 'jetpack' ),
279
			'description'           => esc_html__( 'Simple Payments orders', 'jetpack' ),
280
			'supports'              => array( 'custom-fields', 'excerpt' ),
281
			'hierarchical'          => false,
282
			'public'                => false,
283
			'show_ui'               => false,
284
			'show_in_menu'          => false,
285
			'show_in_admin_bar'     => false,
286
			'show_in_nav_menus'     => false,
287
			'can_export'            => true,
288
			'has_archive'           => false,
289
			'exclude_from_search'   => true,
290
			'publicly_queryable'    => false,
291
			'rewrite'               => false,
292
			'capabilities'          => $order_capabilities,
293
			'show_in_rest'          => true,
294
		);
295
		register_post_type( self::$post_type_order, $order_args );
296
297
		/*
298
		 * PRODUCT data structure. Holds:
299
		 * title - title
300
		 * content - description
301
		 * thumbnail - image
302
		 * metadata:
303
		 * spay_price - price
304
		 * spay_formatted_price
305
		 * spay_currency - currency code
306
		 * spay_cta - text with "Buy" or other CTA
307
		 * spay_email - paypal email
308
		 * spay_multiple - allow for multiple items
309
		 * spay_status - status. { enabled | disabled }
310
		 */
311
		$product_capabilities = array(
312
			'edit_post'             => 'edit_posts',
313
			'read_post'             => 'read_private_posts',
314
			'delete_post'           => 'delete_posts',
315
			'edit_posts'            => 'edit_posts',
316
			'edit_others_posts'     => 'edit_others_posts',
317
			'publish_posts'         => 'publish_posts',
318
			'read_private_posts'    => 'read_private_posts',
319
		);
320
		$product_args = array(
321
			'label'                 => esc_html__( 'Product', 'jetpack' ),
322
			'description'           => esc_html__( 'Simple Payments products', 'jetpack' ),
323
			'supports'              => array( 'title', 'editor','thumbnail', 'custom-fields', 'author' ),
324
			'hierarchical'          => false,
325
			'public'                => false,
326
			'show_ui'               => false,
327
			'show_in_menu'          => false,
328
			'show_in_admin_bar'     => false,
329
			'show_in_nav_menus'     => false,
330
			'can_export'            => true,
331
			'has_archive'           => false,
332
			'exclude_from_search'   => true,
333
			'publicly_queryable'    => false,
334
			'rewrite'               => false,
335
			'capabilities'          => $product_capabilities,
336
			'show_in_rest'          => true,
337
		);
338
		register_post_type( self::$post_type_product, $product_args );
339
	}
340
341
	public function update_post_meta_for_api( $meta_arr, $object ) {
342
		//get the id of the post object array
343
		$post_id = $object->ID;
344
345
		foreach( $meta_arr as $meta_key => $meta_val ) {
346
			update_post_meta( $post_id, $meta_key, $meta_val );
347
		}
348
349
		return true;
350
	}
351
352
	function setup_meta_fields_for_rest() {
353
		$args = array(
354
		   'update_callback' => array( $this, 'update_post_meta_for_api'),
355
		);
356
357
		register_rest_field( self::$post_type_product, 'meta', $args );
358
	}
359
360
}
361
Jetpack_Simple_Payments::getInstance();
362