PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 12.1.3
Jetpack – WP Security, Backup, Speed, & Growth v12.1.3
12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 14.5.1 14.6.1 14.7.1 14.8.1 14.9.2 15.0.3 15.1.2 15.2.1 15.3.2 15.4.1 15.5.1 15.6.1 15.7.2 15.8.1 15.9.2 16.0.2 16.1.3 16.2-a.5 16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / modules / simple-payments / simple-payments.php
jetpack / modules / simple-payments Last commit date
paypal-express-checkout.js 4 years ago simple-payments.css 4 years ago simple-payments.php 3 years ago
simple-payments.php
803 lines
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
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 * @package automattic/jetpack
8 */
9
10 /**
11 * Jetpack_Simple_Payments
12 */
13 class Jetpack_Simple_Payments {
14 // These have to be under 20 chars because that is CPT limit.
15
16 /**
17 * Post type order.
18 *
19 * @var string
20 */
21 public static $post_type_order = 'jp_pay_order';
22
23 /**
24 * Post type product.
25 *
26 * @var string
27 */
28 public static $post_type_product = 'jp_pay_product';
29
30 /**
31 * Define simple payment shortcode.
32 *
33 * @var string
34 */
35 public static $shortcode = 'simple-payment';
36
37 /**
38 * Define simple payment CSS prefix.
39 *
40 * @var string
41 */
42 public static $css_classname_prefix = 'jetpack-simple-payments';
43
44 /**
45 * Which plan the user is on.
46 *
47 * @var string value_bundle or jetpack_premium
48 */
49 public static $required_plan;
50
51 /**
52 * Instance of the class.
53 *
54 * @var Jetpack_Simple_Payments
55 */
56 private static $instance;
57
58 /**
59 * Construction function.
60 */
61 private function __construct() {}
62
63 /**
64 * Original singleton.
65 *
66 * @todo Remove this when nothing calles getInstance anymore.
67 *
68 * @deprecated 10.8
69 */
70 public static function getInstance() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
71 _deprecated_function( __METHOD__, 'Jetpack 10.7.0', 'Jetpack_Simple_Payments::get_instance' );
72 return self::get_instance();
73 }
74
75 /**
76 * Create instance of class.
77 */
78 public static function get_instance() {
79 if ( ! self::$instance ) {
80 self::$instance = new self();
81 self::$instance->register_init_hooks();
82 self::$required_plan = ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ? 'value_bundle' : 'jetpack_premium';
83 }
84 return self::$instance;
85 }
86
87 /**
88 * Register scripts and styles.
89 */
90 private function register_scripts_and_styles() {
91 /**
92 * Paypal heavily discourages putting that script in your own server:
93 *
94 * @see https://developer.paypal.com/docs/integration/direct/express-checkout/integration-jsv4/add-paypal-button/
95 */
96 wp_register_script( // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Ignored here instead of on the $ver param line since wpcom isn't in sync with ruleset changes in: https://github.com/Automattic/jetpack/pull/28199
97 'paypal-checkout-js',
98 'https://www.paypalobjects.com/api/checkout.js',
99 array(),
100 null, // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
101 true
102 );
103 wp_register_script(
104 'jetpack-paypal-express-checkout',
105 plugins_url( '/paypal-express-checkout.js', __FILE__ ),
106 array( 'jquery', 'paypal-checkout-js' ),
107 JETPACK__VERSION,
108 false
109 );
110 wp_register_style(
111 'jetpack-simple-payments',
112 plugins_url( '/simple-payments.css', __FILE__ ),
113 array( 'dashicons' ),
114 JETPACK__VERSION,
115 false
116 );
117 }
118
119 /**
120 * Register init hooks.
121 */
122 private function register_init_hooks() {
123 add_action( 'init', array( $this, 'init_hook_action' ) );
124 add_action( 'rest_api_init', array( $this, 'register_meta_fields_in_rest_api' ) );
125 }
126
127 /**
128 * Register the shortcode.
129 */
130 private function register_shortcode() {
131 add_shortcode( self::$shortcode, array( $this, 'parse_shortcode' ) );
132 }
133
134 /**
135 * Actions that are run on init.
136 */
137 public function init_hook_action() {
138 add_filter( 'rest_api_allowed_post_types', array( $this, 'allow_rest_api_types' ) );
139 add_filter( 'jetpack_sync_post_meta_whitelist', array( $this, 'allow_sync_post_meta' ) );
140 if ( ! is_admin() ) {
141 $this->register_scripts_and_styles();
142 }
143 $this->register_shortcode();
144 $this->setup_cpts();
145
146 add_filter( 'the_content', array( $this, 'remove_auto_paragraph_from_product_description' ), 0 );
147 }
148
149 /**
150 * Enqueue the static assets needed in the frontend.
151 */
152 public function enqueue_frontend_assets() {
153 if ( ! wp_style_is( 'jetpack-simple-payments', 'enqueued' ) ) {
154 wp_enqueue_style( 'jetpack-simple-payments' );
155 }
156
157 if ( ! wp_script_is( 'jetpack-paypal-express-checkout', 'enqueued' ) ) {
158 wp_enqueue_script( 'jetpack-paypal-express-checkout' );
159 }
160 }
161
162 /**
163 * Add an inline script for setting up the PayPal checkout button.
164 *
165 * @param int $id Product ID.
166 * @param int $dom_id ID of the DOM element with the purchase message.
167 * @param boolean $is_multiple Whether multiple items of the same product can be purchased.
168 */
169 public function setup_paypal_checkout_button( $id, $dom_id, $is_multiple ) {
170 wp_add_inline_script(
171 'jetpack-paypal-express-checkout',
172 sprintf(
173 "try{PaypalExpressCheckout.renderButton( '%d', '%d', '%s', '%d' );}catch(e){}",
174 esc_js( $this->get_blog_id() ),
175 esc_js( $id ),
176 esc_js( $dom_id ),
177 esc_js( $is_multiple )
178 )
179 );
180 }
181
182 /**
183 * Remove auto paragraph from product description.
184 *
185 * @param string $content - the content of the post.
186 */
187 public function remove_auto_paragraph_from_product_description( $content ) {
188 if ( get_post_type() === self::$post_type_product ) {
189 remove_filter( 'the_content', 'wpautop' );
190 }
191
192 return $content;
193 }
194
195 /** Return the blog ID */
196 public function get_blog_id() {
197 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
198 return get_current_blog_id();
199 }
200
201 return Jetpack_Options::get_option( 'id' );
202 }
203
204 /**
205 * Used to check whether Simple Payments are enabled for given site.
206 *
207 * @return bool True if Simple Payments are enabled, false otherwise.
208 */
209 public function is_enabled_jetpack_simple_payments() {
210 /**
211 * Can be used by plugin authors to disable the conflicting output of Simple Payments.
212 *
213 * @since 6.3.0
214 *
215 * @param bool True if Simple Payments should be disabled, false otherwise.
216 */
217 if ( apply_filters( 'jetpack_disable_simple_payments', false ) ) {
218 return false;
219 }
220
221 return (
222 ( ( defined( 'IS_WPCOM' ) && IS_WPCOM ) || Jetpack::is_connection_ready() ) &&
223 Jetpack_Plan::supports( 'simple-payments' )
224 );
225 }
226
227 /**
228 * Get a WP_Post representation of a product
229 *
230 * @param int $id The ID of the product.
231 *
232 * @return array|false|WP_Post
233 */
234 private function get_product( $id ) {
235 if ( ! $id ) {
236 return false;
237 }
238
239 $product = get_post( $id );
240 if ( ! $product || is_wp_error( $product ) ) {
241 return false;
242 }
243 if ( $product->post_type !== self::$post_type_product || 'publish' !== $product->post_status ) {
244 return false;
245 }
246 return $product;
247 }
248
249 /**
250 * Creates the content from a shortcode
251 *
252 * @param array $attrs Shortcode attributes.
253 * @param mixed $content unused.
254 *
255 * @return string|void
256 */
257 public function parse_shortcode( $attrs, $content = false ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
258 if ( empty( $attrs['id'] ) ) {
259 return;
260 }
261 $product = $this->get_product( $attrs['id'] );
262 if ( ! $product ) {
263 return;
264 }
265
266 // We allow for overriding the presentation labels.
267 $data = shortcode_atts(
268 array(
269 'blog_id' => $this->get_blog_id(),
270 'dom_id' => uniqid( self::$css_classname_prefix . '-' . $product->ID . '_', true ),
271 'class' => self::$css_classname_prefix . '-' . $product->ID,
272 'title' => get_the_title( $product ),
273 'description' => $product->post_content,
274 'cta' => get_post_meta( $product->ID, 'spay_cta', true ),
275 'multiple' => get_post_meta( $product->ID, 'spay_multiple', true ) || '0',
276 ),
277 $attrs
278 );
279
280 $data['price'] = $this->format_price(
281 get_post_meta( $product->ID, 'spay_price', true ),
282 get_post_meta( $product->ID, 'spay_currency', true )
283 );
284
285 $data['id'] = $attrs['id'];
286
287 if ( ! $this->is_enabled_jetpack_simple_payments() ) {
288 if ( jetpack_is_frontend() ) {
289 return $this->output_admin_warning( $data );
290 }
291 return;
292 }
293
294 $this->enqueue_frontend_assets();
295 $this->setup_paypal_checkout_button( $attrs['id'], $data['dom_id'], $data['multiple'] );
296
297 return $this->output_shortcode( $data );
298 }
299
300 /**
301 * Output an admin warning if user can't use Pay with PayPal.
302 *
303 * @param array $data unused.
304 */
305 public function output_admin_warning( $data ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
306 if ( ! current_user_can( 'manage_options' ) ) {
307 return;
308 }
309
310 require_once JETPACK__PLUGIN_DIR . '_inc/lib/components.php';
311 return Jetpack_Components::render_upgrade_nudge(
312 array(
313 'plan' => self::$required_plan,
314 )
315 );
316 }
317
318 /**
319 * Get the HTML output to use as PayPal purchase box.
320 *
321 * @param string $dom_id ID of the DOM element with the purchase message.
322 * @param boolean $is_multiple Whether multiple items of the same product can be purchased.
323 *
324 * @return string
325 */
326 public function output_purchase_box( $dom_id, $is_multiple ) {
327 $items = '';
328 $css_prefix = self::$css_classname_prefix;
329
330 if ( $is_multiple ) {
331 $items = sprintf(
332 '
333 <div class="%1$s">
334 <input class="%2$s" type="number" value="1" min="1" id="%3$s" />
335 </div>
336 ',
337 esc_attr( "{$css_prefix}-items" ),
338 esc_attr( "{$css_prefix}-items-number" ),
339 esc_attr( "{$dom_id}_number" )
340 );
341 }
342
343 return sprintf(
344 '<div class="%1$s" id="%2$s"></div><div class="%3$s">%4$s<div class="%5$s" id="%6$s"></div></div>',
345 esc_attr( "{$css_prefix}-purchase-message" ),
346 esc_attr( "{$dom_id}-message-container" ),
347 esc_attr( "{$css_prefix}-purchase-box" ),
348 $items,
349 esc_attr( "{$css_prefix}-button" ),
350 esc_attr( "{$dom_id}_button" )
351 );
352 }
353
354 /**
355 * Get the HTML output to replace the `simple-payments` shortcode.
356 *
357 * @param array $data Product data.
358 * @return string
359 */
360 public function output_shortcode( $data ) {
361 $css_prefix = self::$css_classname_prefix;
362
363 $image = '';
364 if ( has_post_thumbnail( $data['id'] ) ) {
365 $image = sprintf(
366 '<div class="%1$s"><div class="%2$s">%3$s</div></div>',
367 esc_attr( "{$css_prefix}-product-image" ),
368 esc_attr( "{$css_prefix}-image" ),
369 get_the_post_thumbnail( $data['id'], 'full' )
370 );
371 }
372
373 return sprintf(
374 '
375 <div class="%1$s">
376 <div class="%2$s">
377 %3$s
378 <div class="%4$s">
379 <div class="%5$s"><p>%6$s</p></div>
380 <div class="%7$s"><p>%8$s</p></div>
381 <div class="%9$s"><p>%10$s</p></div>
382 %11$s
383 </div>
384 </div>
385 </div>
386 ',
387 esc_attr( "{$data['class']} {$css_prefix}-wrapper" ),
388 esc_attr( "{$css_prefix}-product" ),
389 $image,
390 esc_attr( "{$css_prefix}-details" ),
391 esc_attr( "{$css_prefix}-title" ),
392 esc_html( $data['title'] ),
393 esc_attr( "{$css_prefix}-description" ),
394 wp_kses( $data['description'], wp_kses_allowed_html( 'post' ) ),
395 esc_attr( "{$css_prefix}-price" ),
396 esc_html( $data['price'] ),
397 $this->output_purchase_box( $data['dom_id'], $data['multiple'] )
398 );
399 }
400
401 /**
402 * Format a price with currency
403 *
404 * Uses currency-aware formatting to output a formatted price with a simple fallback.
405 *
406 * Largely inspired by WordPress.com's Store_Price::display_currency
407 *
408 * @param string $price Price.
409 * @param string $currency Currency.
410 * @return string Formatted price.
411 */
412 private function format_price( $price, $currency ) {
413 require_once JETPACK__PLUGIN_DIR . '/_inc/lib/class-jetpack-currencies.php';
414 return Jetpack_Currencies::format_price( $price, $currency );
415 }
416
417 /**
418 * Allows custom post types to be used by REST API.
419 *
420 * @param array $post_types - the allows post types.
421 * @see hook 'rest_api_allowed_post_types'
422 * @return array
423 */
424 public function allow_rest_api_types( $post_types ) {
425 $post_types[] = self::$post_type_order;
426 $post_types[] = self::$post_type_product;
427 return $post_types;
428 }
429
430 /**
431 * Merge $post_meta with additional meta information.
432 *
433 * @param array $post_meta - the post's meta information.
434 */
435 public function allow_sync_post_meta( $post_meta ) {
436 return array_merge(
437 $post_meta,
438 array(
439 'spay_paypal_id',
440 'spay_status',
441 'spay_product_id',
442 'spay_quantity',
443 'spay_price',
444 'spay_customer_email',
445 'spay_currency',
446 'spay_cta',
447 'spay_email',
448 'spay_multiple',
449 'spay_formatted_price',
450 )
451 );
452 }
453
454 /**
455 * Enable Simple payments custom meta values for access through the REST API.
456 * Field’s value will be exposed on a .meta key in the endpoint response,
457 * and WordPress will handle setting up the callbacks for reading and writing
458 * to that meta key.
459 *
460 * @link https://developer.wordpress.org/rest-api/extending-the-rest-api/modifying-responses/
461 */
462 public function register_meta_fields_in_rest_api() {
463 register_meta(
464 'post',
465 'spay_price',
466 array(
467 'description' => esc_html__( 'Simple payments; price.', 'jetpack' ),
468 'object_subtype' => self::$post_type_product,
469 'sanitize_callback' => array( $this, 'sanitize_price' ),
470 'show_in_rest' => true,
471 'single' => true,
472 'type' => 'number',
473 )
474 );
475
476 register_meta(
477 'post',
478 'spay_currency',
479 array(
480 'description' => esc_html__( 'Simple payments; currency code.', 'jetpack' ),
481 'object_subtype' => self::$post_type_product,
482 'sanitize_callback' => array( $this, 'sanitize_currency' ),
483 'show_in_rest' => true,
484 'single' => true,
485 'type' => 'string',
486 )
487 );
488
489 register_meta(
490 'post',
491 'spay_cta',
492 array(
493 'description' => esc_html__( 'Simple payments; text with "Buy" or other CTA', 'jetpack' ),
494 'object_subtype' => self::$post_type_product,
495 'sanitize_callback' => 'sanitize_text_field',
496 'show_in_rest' => true,
497 'single' => true,
498 'type' => 'string',
499 )
500 );
501
502 register_meta(
503 'post',
504 'spay_multiple',
505 array(
506 'description' => esc_html__( 'Simple payments; allow multiple items', 'jetpack' ),
507 'object_subtype' => self::$post_type_product,
508 'sanitize_callback' => 'rest_sanitize_boolean',
509 'show_in_rest' => true,
510 'single' => true,
511 'type' => 'boolean',
512 )
513 );
514
515 register_meta(
516 'post',
517 'spay_email',
518 array(
519 'description' => esc_html__( 'Simple payments button; paypal email.', 'jetpack' ),
520 'object_subtype' => self::$post_type_product,
521 'sanitize_callback' => 'sanitize_email',
522 'show_in_rest' => true,
523 'single' => true,
524 'type' => 'string',
525 )
526 );
527
528 register_meta(
529 'post',
530 'spay_status',
531 array(
532 'description' => esc_html__( 'Simple payments; status.', 'jetpack' ),
533 'object_subtype' => self::$post_type_product,
534 'sanitize_callback' => 'sanitize_text_field',
535 'show_in_rest' => true,
536 'single' => true,
537 'type' => 'string',
538 )
539 );
540 }
541
542 /**
543 * Sanitize three-character ISO-4217 Simple payments currency
544 *
545 * List has to be in sync with list at the block's client side and widget's backend side:
546 *
547 * @param array $currency - list of currencies.
548 * @link https://github.com/Automattic/jetpack/blob/31efa189ad223c0eb7ad085ac0650a23facf9ef5/extensions/blocks/simple-payments/constants.js#L9-L39
549 * @link https://github.com/Automattic/jetpack/blob/31efa189ad223c0eb7ad085ac0650a23facf9ef5/modules/widgets/simple-payments.php#L19-L44
550 *
551 * Currencies should be supported by PayPal:
552 * @link https://developer.paypal.com/docs/api/reference/currency-codes/
553 *
554 * Indian Rupee (INR) not supported because at the time of the creation of this file
555 * because it's limited to in-country PayPal India accounts only.
556 * Discussion: https://github.com/Automattic/wp-calypso/pull/28236
557 */
558 public static function sanitize_currency( $currency ) {
559 $valid_currencies = array(
560 'USD',
561 'EUR',
562 'AUD',
563 'BRL',
564 'CAD',
565 'CZK',
566 'DKK',
567 'HKD',
568 'HUF',
569 'ILS',
570 'JPY',
571 'MYR',
572 'MXN',
573 'TWD',
574 'NZD',
575 'NOK',
576 'PHP',
577 'PLN',
578 'GBP',
579 'RUB',
580 'SGD',
581 'SEK',
582 'CHF',
583 'THB',
584 );
585
586 return in_array( $currency, $valid_currencies, true ) ? $currency : false;
587 }
588
589 /**
590 * Sanitize price:
591 *
592 * Positive integers and floats
593 * Supports two decimal places.
594 * Maximum length: 10.
595 *
596 * See `price` from PayPal docs:
597 *
598 * @link https://developer.paypal.com/docs/api/orders/v1/#definition-item
599 *
600 * @param string $price - the price we want to sanitize.
601 * @return null|string
602 */
603 public static function sanitize_price( $price ) {
604 return preg_match( '/^[0-9]{0,10}(\.[0-9]{0,2})?$/', $price ) ? $price : false;
605 }
606
607 /**
608 * Sets up the custom post types for the module.
609 */
610 public function setup_cpts() {
611 /*
612 * ORDER data structure. holds:
613 * title = customer_name | 4xproduct_name
614 * excerpt = customer_name + customer contact info + customer notes from paypal form
615 * metadata:
616 * spay_paypal_id - paypal id of transaction
617 * spay_status
618 * spay_product_id - post_id of bought product
619 * spay_quantity - quantity of product
620 * spay_price - item price at the time of purchase
621 * spay_customer_email - customer email
622 * ... (WIP)
623 */
624 $order_capabilities = array(
625 'edit_post' => 'edit_posts',
626 'read_post' => 'read_private_posts',
627 'delete_post' => 'delete_posts',
628 'edit_posts' => 'edit_posts',
629 'edit_others_posts' => 'edit_others_posts',
630 'publish_posts' => 'publish_posts',
631 'read_private_posts' => 'read_private_posts',
632 );
633 $order_args = array(
634 'label' => esc_html_x( 'Order', 'noun: a quantity of goods or items purchased or sold', 'jetpack' ),
635 'description' => esc_html__( 'Simple Payments orders', 'jetpack' ),
636 'supports' => array( 'custom-fields', 'excerpt' ),
637 'hierarchical' => false,
638 'public' => false,
639 'show_ui' => false,
640 'show_in_menu' => false,
641 'show_in_admin_bar' => false,
642 'show_in_nav_menus' => false,
643 'can_export' => true,
644 'has_archive' => false,
645 'exclude_from_search' => true,
646 'publicly_queryable' => false,
647 'rewrite' => false,
648 'capabilities' => $order_capabilities,
649 'show_in_rest' => true,
650 );
651 register_post_type( self::$post_type_order, $order_args );
652
653 /*
654 * PRODUCT data structure. Holds:
655 * title - title
656 * content - description
657 * thumbnail - image
658 * metadata:
659 * spay_price - price
660 * spay_formatted_price
661 * spay_currency - currency code
662 * spay_cta - text with "Buy" or other CTA
663 * spay_email - paypal email
664 * spay_multiple - allow for multiple items
665 * spay_status - status. { enabled | disabled }
666 */
667 $product_capabilities = array(
668 'edit_post' => 'edit_posts',
669 'read_post' => 'read_private_posts',
670 'delete_post' => 'delete_posts',
671 'edit_posts' => 'publish_posts',
672 'edit_others_posts' => 'edit_others_posts',
673 'publish_posts' => 'publish_posts',
674 'read_private_posts' => 'read_private_posts',
675 );
676 $product_args = array(
677 'label' => esc_html__( 'Product', 'jetpack' ),
678 'description' => esc_html__( 'Simple Payments products', 'jetpack' ),
679 'supports' => array( 'title', 'editor', 'thumbnail', 'custom-fields', 'author' ),
680 'hierarchical' => false,
681 'public' => false,
682 'show_ui' => false,
683 'show_in_menu' => false,
684 'show_in_admin_bar' => false,
685 'show_in_nav_menus' => false,
686 'can_export' => true,
687 'has_archive' => false,
688 'exclude_from_search' => true,
689 'publicly_queryable' => false,
690 'rewrite' => false,
691 'capabilities' => $product_capabilities,
692 'show_in_rest' => true,
693 );
694 register_post_type( self::$post_type_product, $product_args );
695 }
696
697 /**
698 * Validate the block attributes
699 *
700 * @param array $attrs The block attributes, expected to contain:
701 * * email - an email address.
702 * * price - a float between 0.01 and 9999999999.99.
703 * * productId - the ID of the product being paid for.
704 *
705 * @return bool
706 */
707 public function is_valid( $attrs ) {
708 if ( ! $this->validate_paypal_email( $attrs ) ) {
709 return false;
710 }
711
712 if ( ! $this->validate_price( $attrs ) ) {
713 return false;
714 }
715
716 if ( ! $this->validate_product( $attrs ) ) {
717 return false;
718 }
719
720 return true;
721 }
722
723 /**
724 * Check that the email address to make a payment to is valid
725 *
726 * @param array $attrs Key-value array of attributes.
727 *
728 * @return boolean
729 */
730 private function validate_paypal_email( $attrs ) {
731 if ( empty( $attrs['email'] ) ) {
732 return false;
733 }
734 return (bool) filter_var( $attrs['email'], FILTER_VALIDATE_EMAIL );
735 }
736
737 /**
738 * Check that the price is valid
739 *
740 * @param array $attrs Key-value array of attributes.
741 *
742 * @return bool
743 */
744 private function validate_price( $attrs ) {
745 if ( empty( $attrs['price'] ) ) {
746 return false;
747 }
748 return (bool) self::sanitize_price( $attrs['price'] );
749 }
750
751 /**
752 * Check that the stored product is valid
753 *
754 * Valid means it has a title, and the currency is accepted.
755 *
756 * @param array $attrs Key-value array of attributes.
757 *
758 * @return bool
759 */
760 private function validate_product( $attrs ) {
761 if ( empty( $attrs['productId'] ) ) {
762 return false;
763 }
764 $product = $this->get_product( $attrs['productId'] );
765 if ( ! $product ) {
766 return false;
767 }
768 // This title is the one used by paypal, it's set from the title set in the block content, unless the block
769 // content title is blank.
770 if ( ! get_the_title( $product ) ) {
771 return false;
772 }
773
774 $currency = get_post_meta( $product->ID, 'spay_currency', true );
775 return (bool) self::sanitize_currency( $currency );
776 }
777
778 /**
779 * Format a price for display
780 *
781 * Largely taken from WordPress.com Store_Price class
782 *
783 * The currency array will have the shape:
784 * format => string sprintf format with placeholders `%1$s`: Symbol `%2$s`: Price.
785 * symbol => string Symbol string
786 * desc => string Text description of currency
787 * decimal => int Number of decimal places
788 *
789 * @param string $the_currency The desired currency, e.g. 'USD'.
790 * @return ?array Currency object or null if not found.
791 */
792 private static function get_currency( $the_currency ) {
793 require_once JETPACK__PLUGIN_DIR . '/_inc/lib/class-jetpack-currencies.php';
794 $currencies = Jetpack_Currencies::CURRENCIES;
795
796 if ( isset( $currencies[ $the_currency ] ) ) {
797 return $currencies[ $the_currency ];
798 }
799 return null;
800 }
801 }
802 Jetpack_Simple_Payments::get_instance();
803