@automattic/newspack-blocks 1.54.0 → 1.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,7 +13,9 @@ class Newspack_Blocks {
13
13
  /**
14
14
  * Script handle for the streamlined donate block script.
15
15
  */
16
- const DONATE_STREAMLINED_SCRIPT_HANDLE = 'newspack-blocks-donate-streamlined';
16
+ const DONATE_STREAMLINED_SCRIPT_HANDLE = 'newspack-blocks-donate-streamlined';
17
+ const DONATE_STREAMLINED_CAPTCHA_HANDLE = 'newspack-blocks-recaptcha';
18
+ const DONATE_STREAMLINED_CAPTCHA_THRESHOLD = 0.5;
17
19
 
18
20
  /**
19
21
  * Regex pattern we can use to search for and remove custom SQL statements.
@@ -75,7 +77,7 @@ class Newspack_Blocks {
75
77
  * @param string $handle The script handle.
76
78
  */
77
79
  public static function mark_view_script_as_amp_plus_allowed( $tag, $handle ) {
78
- if ( self::DONATE_STREAMLINED_SCRIPT_HANDLE === $handle ) {
80
+ if ( self::DONATE_STREAMLINED_SCRIPT_HANDLE === $handle || self::DONATE_STREAMLINED_CAPTCHA_HANDLE === $handle ) {
79
81
  return str_replace( '<script', '<script data-amp-plus-allowed', $tag );
80
82
  }
81
83
  return $tag;
@@ -7,7 +7,7 @@
7
7
  * Author URI: https://newspack.blog/
8
8
  * Text Domain: newspack-blocks
9
9
  * Domain Path: /languages
10
- * Version: 1.54.0
10
+ * Version: 1.55.0
11
11
  *
12
12
  * @package Newspack_Blocks
13
13
  */
@@ -15,7 +15,7 @@
15
15
  define( 'NEWSPACK_BLOCKS__PLUGIN_FILE', __FILE__ );
16
16
  define( 'NEWSPACK_BLOCKS__BLOCKS_DIRECTORY', 'dist/' );
17
17
  define( 'NEWSPACK_BLOCKS__PLUGIN_DIR', plugin_dir_path( NEWSPACK_BLOCKS__PLUGIN_FILE ) );
18
- define( 'NEWSPACK_BLOCKS__VERSION', '1.54.0' );
18
+ define( 'NEWSPACK_BLOCKS__VERSION', '1.55.0' );
19
19
 
20
20
  require_once NEWSPACK_BLOCKS__PLUGIN_DIR . 'includes/class-newspack-blocks.php';
21
21
  require_once NEWSPACK_BLOCKS__PLUGIN_DIR . 'includes/class-newspack-blocks-api.php';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/newspack-blocks",
3
- "version": "1.54.0",
3
+ "version": "1.55.0",
4
4
  "author": "Automattic",
5
5
  "devDependencies": {
6
6
  "@rushstack/eslint-patch": "^1.1.4",
@@ -41,6 +41,10 @@ class WP_REST_Newspack_Donate_Controller extends WP_REST_Controller {
41
41
  'methods' => WP_REST_Server::EDITABLE,
42
42
  'callback' => [ $this, 'api_process_donation' ],
43
43
  'args' => [
44
+ 'captchaToken' => [
45
+ 'sanitize_callback' => 'sanitize_text_field',
46
+ 'required' => false,
47
+ ],
44
48
  'tokenData' => [
45
49
  'type' => 'object',
46
50
  'properties' => [
@@ -95,6 +99,65 @@ class WP_REST_Newspack_Donate_Controller extends WP_REST_Controller {
95
99
  * @return WP_REST_Response
96
100
  */
97
101
  public function api_process_donation( $request ) {
102
+ // If reCaptcha is available, verify the user action.
103
+ $use_captcha = \Newspack\Stripe_Connection::can_use_captcha();
104
+ if ( $use_captcha ) {
105
+ $captcha_token = $request->get_param( 'captchaToken' );
106
+ if ( ! $captcha_token ) {
107
+ return rest_ensure_response(
108
+ [
109
+ 'error' => __( 'Missing or invalid captcha token.', 'newspack-blocks' ),
110
+ ]
111
+ );
112
+ }
113
+
114
+ $stripe_settings = \Newspack\Stripe_Connection::get_stripe_data();
115
+ $captcha_secret = $stripe_settings['captchaSiteSecret'];
116
+ $captcha_verify = wp_safe_remote_post(
117
+ add_query_arg(
118
+ [
119
+ 'secret' => $captcha_secret,
120
+ 'response' => $captcha_token,
121
+ ],
122
+ 'https://www.google.com/recaptcha/api/siteverify'
123
+ )
124
+ );
125
+
126
+ // If the reCaptcha verification request fails.
127
+ if ( is_wp_error( $captcha_verify ) ) {
128
+ return rest_ensure_response(
129
+ [
130
+ 'error' => wp_strip_all_tags( $captcha_verify->get_error_message() ),
131
+ ]
132
+ );
133
+ }
134
+
135
+ $captcha_verify = json_decode( $captcha_verify['body'], true );
136
+
137
+ // If the reCaptcha verification request succeeds, but with error.
138
+ if ( ! boolval( $captcha_verify['success'] ) ) {
139
+ $error = isset( $captcha_verify['error-codes'] ) ? reset( $captcha_verify['error-codes'] ) : __( 'Error validating captcha.', 'newspack-blocks' );
140
+ return rest_ensure_response(
141
+ [
142
+ // Translators: error message for reCaptcha.
143
+ 'error' => sprintf( __( 'reCaptcha error: %s', 'newspack-blocks' ), $error ),
144
+ ]
145
+ );
146
+ }
147
+
148
+ // If the reCaptcha verification score is below our threshold for valid user input.
149
+ if (
150
+ isset( $captcha_verify['score'] ) &&
151
+ Newspack_Blocks::DONATE_STREAMLINED_CAPTCHA_THRESHOLD > floatval( $captcha_verify['score'] )
152
+ ) {
153
+ return rest_ensure_response(
154
+ [
155
+ 'error' => __( 'User action failed captcha challenge.', 'newspack-blocks' ),
156
+ ]
157
+ );
158
+ }
159
+ }
160
+
98
161
  $payment_metadata = [
99
162
  'referer' => wp_get_referer(),
100
163
  ];
@@ -28,6 +28,25 @@ export const processStreamlinedElements = ( parentElement = document ) =>
28
28
  const enableForm = () => el.classList.remove( 'stripe-payment--disabled' );
29
29
  enableForm();
30
30
 
31
+ const getCaptchaToken = async reCaptchaKey => {
32
+ return new Promise( ( res, rej ) => {
33
+ const { grecaptcha } = window;
34
+
35
+ if ( ! grecaptcha?.ready ) {
36
+ rej( __( 'Error loading the reCaptcha library.', 'newspack-blocks' ) );
37
+ }
38
+
39
+ grecaptcha.ready( async () => {
40
+ try {
41
+ const token = await grecaptcha.execute( reCaptchaKey, { action: 'submit' } );
42
+ return res( token );
43
+ } catch ( e ) {
44
+ rej( e );
45
+ }
46
+ } );
47
+ } );
48
+ };
49
+
31
50
  // Universal payment handling, for both card and payment request button flows.
32
51
  // In card flow, this will happen after user submits their card data in the HTML form.
33
52
  // In payment request flow, this will happen after the user validates the payment in
@@ -41,8 +60,24 @@ export const processStreamlinedElements = ( parentElement = document ) =>
41
60
  */
42
61
  requestPayloadOverrides = {}
43
62
  ) => {
63
+ // Add reCaptcha challenge to form submission, if available.
64
+ const reCaptchaKey = settings?.captchaSiteKey;
65
+ let reCaptchaToken;
66
+ if ( reCaptchaKey ) {
67
+ try {
68
+ reCaptchaToken = await getCaptchaToken( reCaptchaKey );
69
+ } catch ( e ) {
70
+ const errorMessage =
71
+ e instanceof Error
72
+ ? e.message
73
+ : __( 'Error processing captcha request.', 'newspack-blocks' );
74
+ utils.renderMessages( [ errorMessage ], messagesEl );
75
+ return { error: true };
76
+ }
77
+ }
44
78
  const formValues = utils.getFormValues( formElement );
45
79
  const apiRequestPayload = {
80
+ captchaToken: reCaptchaToken,
46
81
  tokenData: token,
47
82
  amount: utils.getTotalAmount( formElement ),
48
83
  email: formValues.email,
@@ -109,6 +109,7 @@
109
109
  &.type-success {
110
110
  background-color: rgba( colors.$color__success, 0.075 );
111
111
  border-color: colors.$color__success;
112
+ margin: 1.12rem;
112
113
  }
113
114
  }
114
115
  }
@@ -29,6 +29,10 @@ export const renderMessages = ( messages, el, type = 'error' ) => {
29
29
  messageEl.innerHTML = message;
30
30
  el.appendChild( messageEl );
31
31
  } );
32
+
33
+ if ( 'success' === type ) {
34
+ el.parentElement.replaceWith( el );
35
+ }
32
36
  };
33
37
 
34
38
  const getCookies = () =>
@@ -53,6 +57,7 @@ export const getSettings = formElement => {
53
57
  feeMultiplier,
54
58
  feeStatic,
55
59
  stripePublishableKey,
60
+ captchaSiteKey,
56
61
  ] = JSON.parse( formElement.getAttribute( 'data-settings' ) );
57
62
  return {
58
63
  currency: currency.toLowerCase(),
@@ -64,6 +69,7 @@ export const getSettings = formElement => {
64
69
  feeMultiplier: parseFloat( feeMultiplier ),
65
70
  feeStatic: parseFloat( feeStatic ),
66
71
  stripePublishableKey,
72
+ captchaSiteKey,
67
73
  };
68
74
  };
69
75
 
@@ -162,7 +168,7 @@ export const sendAPIRequest = async ( endpoint, data, method = 'POST' ) =>
162
168
  } );
163
169
 
164
170
  export const renderSuccessMessageWithEmail = ( emailAddress, messagesEl ) => {
165
- const successMessge = sprintf(
171
+ const successMessage = sprintf(
166
172
  /* Translators: %s is the email address of the current user. */
167
173
  __(
168
174
  'Your payment has been processed. Thank you for your contribution! You will receive a confirmation email at %s.',
@@ -170,5 +176,5 @@ export const renderSuccessMessageWithEmail = ( emailAddress, messagesEl ) => {
170
176
  ),
171
177
  emailAddress
172
178
  );
173
- renderMessages( [ successMessge ], messagesEl, 'success' );
179
+ renderMessages( [ successMessage ], messagesEl, 'success' );
174
180
  };
@@ -166,11 +166,29 @@ function newspack_blocks_render_block_donate_footer( $attributes ) {
166
166
  */
167
167
  function newspack_blocks_enqueue_streamlined_donate_block_scripts() {
168
168
  if ( Newspack_Blocks::is_rendering_streamlined_block() ) {
169
+ $dependencies = [ 'wp-i18n' ];
170
+
171
+ if ( \Newspack\Stripe_Connection::can_use_captcha() ) {
172
+ $stripe_settings = \Newspack\Stripe_Connection::get_stripe_data();
173
+ $captcha_site_key = $stripe_settings['captchaSiteKey'];
174
+
175
+ // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
176
+ wp_register_script(
177
+ Newspack_Blocks::DONATE_STREAMLINED_CAPTCHA_HANDLE,
178
+ esc_url( 'https://www.google.com/recaptcha/api.js?render=' . $captcha_site_key ),
179
+ null,
180
+ null,
181
+ true
182
+ );
183
+
184
+ $dependencies[] = Newspack_Blocks::DONATE_STREAMLINED_CAPTCHA_HANDLE;
185
+ }
186
+
169
187
  $script_data = Newspack_Blocks::script_enqueue_helper( NEWSPACK_BLOCKS__BLOCKS_DIRECTORY . '/donateStreamlined.js' );
170
188
  wp_enqueue_script(
171
189
  Newspack_Blocks::DONATE_STREAMLINED_SCRIPT_HANDLE,
172
190
  $script_data['script_path'],
173
- [ 'wp-i18n' ],
191
+ $dependencies,
174
192
  $script_data['version'],
175
193
  true
176
194
  );
@@ -207,6 +225,8 @@ function newspack_blocks_render_block_donate( $attributes ) {
207
225
  return '';
208
226
  }
209
227
 
228
+ $configuration['defaultFrequency'] = $attributes['defaultFrequency'];
229
+
210
230
  /* If block has additional CSS class(es) */
211
231
  if ( isset( $attributes['className'] ) ) {
212
232
  $classname = $attributes['className'];
@@ -297,6 +317,7 @@ function newspack_blocks_render_block_donate( $attributes ) {
297
317
  $stripe_data['fee_multiplier'],
298
318
  $stripe_data['fee_static'],
299
319
  $stripe_data['usedPublishableKey'],
320
+ \Newspack\Stripe_Connection::can_use_captcha() ? $stripe_data['captchaSiteKey'] : null,
300
321
  ];
301
322
  } else {
302
323
  $configuration_for_frontend = [];
@@ -4,4 +4,4 @@
4
4
 
5
5
  require_once __DIR__ . '/composer/autoload_real.php';
6
6
 
7
- return ComposerAutoloaderInit49fbd6ec7deee2cfafc0623f87888fe4::getLoader();
7
+ return ComposerAutoloaderInit8c97be8c03efff8fe0dfa7e225c9c83a::getLoader();
@@ -2,7 +2,7 @@
2
2
 
3
3
  // autoload_real.php @generated by Composer
4
4
 
5
- class ComposerAutoloaderInit49fbd6ec7deee2cfafc0623f87888fe4
5
+ class ComposerAutoloaderInit8c97be8c03efff8fe0dfa7e225c9c83a
6
6
  {
7
7
  private static $loader;
8
8
 
@@ -22,15 +22,15 @@ class ComposerAutoloaderInit49fbd6ec7deee2cfafc0623f87888fe4
22
22
  return self::$loader;
23
23
  }
24
24
 
25
- spl_autoload_register(array('ComposerAutoloaderInit49fbd6ec7deee2cfafc0623f87888fe4', 'loadClassLoader'), true, true);
25
+ spl_autoload_register(array('ComposerAutoloaderInit8c97be8c03efff8fe0dfa7e225c9c83a', 'loadClassLoader'), true, true);
26
26
  self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
27
- spl_autoload_unregister(array('ComposerAutoloaderInit49fbd6ec7deee2cfafc0623f87888fe4', 'loadClassLoader'));
27
+ spl_autoload_unregister(array('ComposerAutoloaderInit8c97be8c03efff8fe0dfa7e225c9c83a', 'loadClassLoader'));
28
28
 
29
29
  $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
30
30
  if ($useStaticLoader) {
31
31
  require __DIR__ . '/autoload_static.php';
32
32
 
33
- call_user_func(\Composer\Autoload\ComposerStaticInit49fbd6ec7deee2cfafc0623f87888fe4::getInitializer($loader));
33
+ call_user_func(\Composer\Autoload\ComposerStaticInit8c97be8c03efff8fe0dfa7e225c9c83a::getInitializer($loader));
34
34
  } else {
35
35
  $map = require __DIR__ . '/autoload_namespaces.php';
36
36
  foreach ($map as $namespace => $path) {
@@ -4,7 +4,7 @@
4
4
 
5
5
  namespace Composer\Autoload;
6
6
 
7
- class ComposerStaticInit49fbd6ec7deee2cfafc0623f87888fe4
7
+ class ComposerStaticInit8c97be8c03efff8fe0dfa7e225c9c83a
8
8
  {
9
9
  public static $classMap = array (
10
10
  'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
@@ -13,7 +13,7 @@ class ComposerStaticInit49fbd6ec7deee2cfafc0623f87888fe4
13
13
  public static function getInitializer(ClassLoader $loader)
14
14
  {
15
15
  return \Closure::bind(function () use ($loader) {
16
- $loader->classMap = ComposerStaticInit49fbd6ec7deee2cfafc0623f87888fe4::$classMap;
16
+ $loader->classMap = ComposerStaticInit8c97be8c03efff8fe0dfa7e225c9c83a::$classMap;
17
17
 
18
18
  }, null, ClassLoader::class);
19
19
  }
@@ -5,7 +5,7 @@
5
5
  'type' => 'wordpress-plugin',
6
6
  'install_path' => __DIR__ . '/../../',
7
7
  'aliases' => array(),
8
- 'reference' => 'd0c5ef37143589ab35d9926bda95d6de2e196b63',
8
+ 'reference' => '9daa964f1c2b26cdbc348c17f5ba4ce9171dec05',
9
9
  'name' => 'automattic/newspack-blocks',
10
10
  'dev' => false,
11
11
  ),
@@ -16,7 +16,7 @@
16
16
  'type' => 'wordpress-plugin',
17
17
  'install_path' => __DIR__ . '/../../',
18
18
  'aliases' => array(),
19
- 'reference' => 'd0c5ef37143589ab35d9926bda95d6de2e196b63',
19
+ 'reference' => '9daa964f1c2b26cdbc348c17f5ba4ce9171dec05',
20
20
  'dev_requirement' => false,
21
21
  ),
22
22
  ),