@automattic/newspack-blocks 4.28.0-alpha.1 → 4.28.0-alpha.2

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.
package/CHANGELOG.md CHANGED
@@ -1,20 +1,16 @@
1
- # @automattic/newspack-blocks [4.28.0-alpha.1](https://github.com/Automattic/newspack-workspace/compare/newspack-blocks@4.27.0...newspack-blocks@4.28.0-alpha.1) (2026-07-10)
1
+ # @automattic/newspack-blocks [4.28.0-alpha.2](https://github.com/Automattic/newspack-workspace/compare/newspack-blocks@4.28.0-alpha.1...newspack-blocks@4.28.0-alpha.2) (2026-07-14)
2
2
 
3
3
 
4
4
  ### Bug Fixes
5
5
 
6
- * **checkout:** sync repeat trial pricing in modal checkout ([#264](https://github.com/Automattic/newspack-workspace/issues/264)) ([ce943c6](https://github.com/Automattic/newspack-workspace/commit/ce943c61d137f81326704deb7be0b73d82da3f17))
6
+ * **blocks:** fix express checkout address validation in modal checkout ([#597](https://github.com/Automattic/newspack-workspace/issues/597)) ([09e001f](https://github.com/Automattic/newspack-workspace/commit/09e001fb8333ae9ab5f7d03c894e9bbeefe3144c))
7
7
 
8
-
9
- ### Features
10
-
11
- * add tag labels support to Content Loop block ([#2282](https://github.com/Automattic/newspack-workspace/issues/2282)) ([b5ef988](https://github.com/Automattic/newspack-workspace/commit/b5ef988bc2007bdc509ef872ffc54e6220216786))
12
- * **newsletters:** add redirect-to-URL option to subscription form ([#432](https://github.com/Automattic/newspack-workspace/issues/432)) ([5c56caa](https://github.com/Automattic/newspack-workspace/commit/5c56caa5c3e8dc93eb09548f45ffb93084ce42ee))
8
+ ## @automattic/newspack-blocks [4.27.1](https://github.com/Automattic/newspack-workspace/compare/newspack-blocks@4.27.0...newspack-blocks@4.27.1) (2026-07-14)
13
9
 
14
10
 
15
- ### Dependencies
11
+ ### Bug Fixes
16
12
 
17
- * **newspack-components:** upgraded to 4.5.0-alpha.1
13
+ * **blocks:** fix express checkout address validation in modal checkout ([#597](https://github.com/Automattic/newspack-workspace/issues/597)) ([09e001f](https://github.com/Automattic/newspack-workspace/commit/09e001fb8333ae9ab5f7d03c894e9bbeefe3144c))
18
14
 
19
15
  # @automattic/newspack-blocks [4.27.0](https://github.com/Automattic/newspack-workspace/compare/newspack-blocks@4.26.8...newspack-blocks@4.27.0) (2026-07-08)
20
16
 
@@ -29,6 +29,17 @@ final class Modal_Checkout {
29
29
  */
30
30
  const CHECKOUT_REGISTRATION_ORDER_META_KEY = '_newspack_checkout_registration_meta';
31
31
 
32
+ /**
33
+ * Billing fields with server-side format validation in the Store API,
34
+ * mapped from their classic checkout keys to Store API address keys.
35
+ *
36
+ * @var string[]
37
+ */
38
+ const STORE_API_VALIDATED_ADDRESS_FIELDS = [
39
+ 'billing_state' => 'state',
40
+ 'billing_postcode' => 'postcode',
41
+ ];
42
+
32
43
  /**
33
44
  * Whether the modal checkout has been enqueued.
34
45
  *
@@ -157,6 +168,8 @@ final class Modal_Checkout {
157
168
  add_filter( 'woocommerce_get_checkout_order_received_url', [ __CLASS__, 'woocommerce_get_return_url' ], 10, 2 );
158
169
  add_filter( 'wc_get_template', [ __CLASS__, 'wc_get_template' ], 10, 2 );
159
170
  add_filter( 'woocommerce_checkout_fields', [ __CLASS__, 'woocommerce_checkout_fields' ] );
171
+ add_filter( 'rest_pre_dispatch', [ __CLASS__, 'scrub_store_api_checkout_address' ], 10, 3 );
172
+ add_filter( 'woocommerce_get_country_locale', [ __CLASS__, 'relax_configured_off_locale_fields' ] );
160
173
  add_filter( 'woocommerce_update_order_review_fragments', [ __CLASS__, 'order_review_fragments' ] );
161
174
  add_filter( 'woocommerce_cart_needs_payment', [ __CLASS__, 'cart_needs_payment' ] );
162
175
  add_filter( 'newspack_recaptcha_verify_captcha', [ __CLASS__, 'recaptcha_verify_captcha' ], 10, 3 );
@@ -1332,7 +1345,13 @@ final class Modal_Checkout {
1332
1345
  }
1333
1346
 
1334
1347
  /**
1335
- * Modify fields for modal checkout.
1348
+ * Remove the configured-off billing fields from modal checkout requests.
1349
+ *
1350
+ * Deliberately scoped to modal checkout: some publishers rely on standard
1351
+ * Woo checkout flows that predate Audience Management, so those keep the
1352
+ * stock field set. The express checkout gap this leaves (wallets submitting
1353
+ * values for fields the buyer cannot see) is handled for modal-originated
1354
+ * requests by scrub_store_api_checkout_address() below.
1336
1355
  *
1337
1356
  * @param array $fields Checkout fields.
1338
1357
  *
@@ -1343,8 +1362,8 @@ final class Modal_Checkout {
1343
1362
  return $fields;
1344
1363
  }
1345
1364
  $cart = \WC()->cart;
1346
- // Don't modify fields if shipping is required.
1347
- if ( $cart->needs_shipping_address() ) {
1365
+ // Don't modify fields if there is no cart or shipping is required.
1366
+ if ( ! $cart || $cart->needs_shipping_address() ) {
1348
1367
  return $fields;
1349
1368
  }
1350
1369
  /**
@@ -1375,6 +1394,199 @@ final class Modal_Checkout {
1375
1394
  return $fields;
1376
1395
  }
1377
1396
 
1397
+ /**
1398
+ * Scrub invalid address values from Store API checkout requests when the
1399
+ * corresponding billing fields are configured off.
1400
+ *
1401
+ * Express checkout wallets submit to wc/store/v1/checkout and can supply
1402
+ * values the buyer never sees or corrects (e.g. Apple Pay sending a suburb
1403
+ * as the state), which hard-fails Store API address validation. Values the
1404
+ * validation would accept are left untouched, and only the billing address
1405
+ * is scrubbed. Runs on rest_pre_dispatch because the validation happens
1406
+ * during dispatch.
1407
+ *
1408
+ * The shipping address is intentionally out of scope. Carts that need a
1409
+ * shipping address bail below, and wallets return only billing contact when
1410
+ * shipping is not requested, so a virtual-cart request carries no shipping
1411
+ * address to scrub.
1412
+ *
1413
+ * Scoped to requests originating from the modal checkout, so any Store API
1414
+ * checkout outside the modal (e.g. the blocks checkout page or express
1415
+ * buttons on product pages) keeps stock behavior.
1416
+ *
1417
+ * @param mixed $result Response to replace the requested version with.
1418
+ * @param \WP_REST_Server $server Server instance.
1419
+ * @param \WP_REST_Request $request Request used to generate the response.
1420
+ *
1421
+ * @return mixed
1422
+ */
1423
+ public static function scrub_store_api_checkout_address( $result, $server, $request ) {
1424
+ if ( null !== $result ) {
1425
+ return $result;
1426
+ }
1427
+
1428
+ if (
1429
+ ! $request instanceof \WP_REST_Request ||
1430
+ 'POST' !== $request->get_method() ||
1431
+ 0 !== strpos( $request->get_route(), '/wc/store/v1/checkout' )
1432
+ ) {
1433
+ return $result;
1434
+ }
1435
+
1436
+ if ( ! self::is_modal_checkout_referer() ) {
1437
+ return $result;
1438
+ }
1439
+
1440
+ if ( ! function_exists( 'WC' ) || ! class_exists( 'WC_Validation' ) ) {
1441
+ return $result;
1442
+ }
1443
+
1444
+ $billing_fields = apply_filters( 'newspack_blocks_donate_billing_fields_keys', [] );
1445
+
1446
+ if ( empty( $billing_fields ) ) {
1447
+ return $result;
1448
+ }
1449
+
1450
+ // Physical-goods flows are never modified. The cart is not always
1451
+ // initialized this early in Store API requests; when it is, bail for
1452
+ // carts that need a shipping address. Only the billing address is
1453
+ // scrubbed, so shipping data is never touched either way.
1454
+ if ( \WC()->cart && \WC()->cart->needs_shipping_address() ) {
1455
+ return $result;
1456
+ }
1457
+
1458
+ $address = $request->get_param( 'billing_address' );
1459
+
1460
+ if ( is_array( $address ) ) {
1461
+ $scrubbed = self::scrub_invalid_address_values( $address, $billing_fields );
1462
+
1463
+ if ( $scrubbed !== $address ) {
1464
+ $request->set_param( 'billing_address', $scrubbed );
1465
+ }
1466
+ }
1467
+
1468
+ return $result;
1469
+ }
1470
+
1471
+ /**
1472
+ * Whether the current request originates from the modal checkout, based on
1473
+ * the referer.
1474
+ *
1475
+ * Express checkout submissions go to the Store API as JSON, so the usual
1476
+ * modal_checkout request params are absent. The requests are made from the
1477
+ * modal checkout iframe, whose URL carries modal_checkout=1, so it shows up
1478
+ * as the (same-origin) referer.
1479
+ *
1480
+ * @return bool
1481
+ */
1482
+ private static function is_modal_checkout_referer() {
1483
+ $referer = isset( $_SERVER['HTTP_REFERER'] ) ? esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1484
+
1485
+ if ( ! $referer ) {
1486
+ return false;
1487
+ }
1488
+
1489
+ $query_string = \wp_parse_url( $referer, PHP_URL_QUERY );
1490
+ \wp_parse_str( (string) $query_string, $query_params );
1491
+
1492
+ return ! empty( $query_params['modal_checkout'] );
1493
+ }
1494
+
1495
+ /**
1496
+ * Drop configured-off address values that would fail Store API validation.
1497
+ *
1498
+ * @param array $address Store API address (billing or shipping).
1499
+ * @param string[] $billing_fields Configured billing field keys.
1500
+ *
1501
+ * @return array Scrubbed address.
1502
+ */
1503
+ private static function scrub_invalid_address_values( $address, $billing_fields ) {
1504
+ $country = isset( $address['country'] ) ? $address['country'] : '';
1505
+
1506
+ if (
1507
+ ! in_array( 'billing_state', $billing_fields, true ) &&
1508
+ ! empty( $address['state'] ) &&
1509
+ is_string( $address['state'] ) &&
1510
+ $country
1511
+ ) {
1512
+ $states = \WC()->countries->get_states( $country );
1513
+
1514
+ if ( is_array( $states ) && ! empty( $states ) ) {
1515
+ $code_match = array_key_exists( strtoupper( $address['state'] ), array_change_key_case( $states, CASE_UPPER ) );
1516
+ $name_match = in_array( strtolower( $address['state'] ), array_map( 'strtolower', $states ), true );
1517
+
1518
+ if ( ! $code_match && ! $name_match ) {
1519
+ $address['state'] = '';
1520
+ }
1521
+ }
1522
+ }
1523
+
1524
+ if (
1525
+ ! in_array( 'billing_postcode', $billing_fields, true ) &&
1526
+ ! empty( $address['postcode'] ) &&
1527
+ is_string( $address['postcode'] ) &&
1528
+ ! \WC_Validation::is_postcode( $address['postcode'], $country )
1529
+ ) {
1530
+ $address['postcode'] = '';
1531
+ }
1532
+
1533
+ return $address;
1534
+ }
1535
+
1536
+ /**
1537
+ * Relax locale-required flags for configured-off billing fields.
1538
+ *
1539
+ * After an invalid wallet value is scrubbed (see
1540
+ * scrub_store_api_checkout_address), the Store API order validation would
1541
+ * still reject the checkout when the country locale marks the field as
1542
+ * required. A field the site is configured not to collect cannot be
1543
+ * required.
1544
+ *
1545
+ * Scoped to modal-originated requests so standard Woo flows keep the stock
1546
+ * locale rules.
1547
+ *
1548
+ * @param array $locale Country locale field settings.
1549
+ *
1550
+ * @return array
1551
+ */
1552
+ public static function relax_configured_off_locale_fields( $locale ) {
1553
+ if ( ! self::is_modal_checkout_referer() && ! self::is_modal_checkout() ) {
1554
+ return $locale;
1555
+ }
1556
+
1557
+ $billing_fields = apply_filters( 'newspack_blocks_donate_billing_fields_keys', [] );
1558
+
1559
+ if ( empty( $billing_fields ) ) {
1560
+ return $locale;
1561
+ }
1562
+
1563
+ // Never relax requirements when the cart needs a shipping address:
1564
+ // physical-goods flows keep the full locale rules.
1565
+ if ( function_exists( 'WC' ) && \WC()->cart && \WC()->cart->needs_shipping_address() ) {
1566
+ return $locale;
1567
+ }
1568
+
1569
+ $off = [];
1570
+
1571
+ foreach ( self::STORE_API_VALIDATED_ADDRESS_FIELDS as $config_key => $address_key ) {
1572
+ if ( ! in_array( $config_key, $billing_fields, true ) ) {
1573
+ $off[] = $address_key;
1574
+ }
1575
+ }
1576
+
1577
+ if ( empty( $off ) ) {
1578
+ return $locale;
1579
+ }
1580
+
1581
+ foreach ( array_keys( $locale ) as $country ) {
1582
+ foreach ( $off as $address_key ) {
1583
+ $locale[ $country ][ $address_key ]['required'] = false;
1584
+ }
1585
+ }
1586
+
1587
+ return $locale;
1588
+ }
1589
+
1378
1590
  /**
1379
1591
  * If WooCommerce Subscriptions Gifting extension is available, render its fields.
1380
1592
  *
@@ -7,7 +7,7 @@
7
7
  * Author URI: https://newspack.com/
8
8
  * Text Domain: newspack-blocks
9
9
  * Domain Path: /languages
10
- * Version: 4.28.0-alpha.1
10
+ * Version: 4.28.0-alpha.2
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', '4.28.0-alpha.1' );
18
+ define( 'NEWSPACK_BLOCKS__VERSION', '4.28.0-alpha.2' );
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": "4.28.0-alpha.1",
3
+ "version": "4.28.0-alpha.2",
4
4
  "author": "Automattic",
5
5
  "description": "=== Newspack Blocks === Contributors: (this should be a list of wordpress.org userid's) Donate link: https://example.com/ Tags: comments, spam Requires at least: 4.5 Tested up to: 5.1.1 Stable tag: 0.1.0 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html",
6
6
  "repository": {
@@ -3,7 +3,7 @@
3
3
  'name' => 'automattic/newspack-blocks',
4
4
  'pretty_version' => 'dev-main',
5
5
  'version' => 'dev-main',
6
- 'reference' => 'e22e12e09346681c13d9bf30ef51767f3c0f3bad',
6
+ 'reference' => '2f93be95742dd767b39b811608d05fab0d2955fb',
7
7
  'type' => 'wordpress-plugin',
8
8
  'install_path' => __DIR__ . '/../../',
9
9
  'aliases' => array(),
@@ -13,7 +13,7 @@
13
13
  'automattic/newspack-blocks' => array(
14
14
  'pretty_version' => 'dev-main',
15
15
  'version' => 'dev-main',
16
- 'reference' => 'e22e12e09346681c13d9bf30ef51767f3c0f3bad',
16
+ 'reference' => '2f93be95742dd767b39b811608d05fab0d2955fb',
17
17
  'type' => 'wordpress-plugin',
18
18
  'install_path' => __DIR__ . '/../../',
19
19
  'aliases' => array(),