@automattic/newspack-blocks 4.31.1 → 4.32.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 +7 -0
- package/composer.lock +73 -994
- package/dist/blocks/checkout-button/block.json +3 -0
- package/dist/carousel/view-rtl.css +1 -1
- package/dist/carousel/view.asset.php +1 -1
- package/dist/carousel/view.css +1 -1
- package/dist/editor-rtl.css +20 -16
- package/dist/editor.asset.php +1 -1
- package/dist/editor.css +20 -16
- package/dist/editor.js +32 -28
- package/dist/homepage-articles/view-rtl.css +1 -1
- package/dist/homepage-articles/view.asset.php +1 -1
- package/dist/homepage-articles/view.css +1 -1
- package/dist/modal.asset.php +1 -1
- package/dist/modal.js +1 -1
- package/dist/modalCheckout-rtl.css +1 -1
- package/dist/modalCheckout.asset.php +1 -1
- package/dist/modalCheckout.css +1 -1
- package/dist/modalCheckout.js +1 -1
- package/dist/tiersBased.asset.php +1 -1
- package/dist/tiersBased.js +1 -1
- package/includes/class-modal-checkout.php +354 -5
- package/includes/modal-checkout/class-checkout-data.php +30 -6
- package/languages/newspack-blocks-de_DE-37552bb09e2a9fceec1970e3c6d46557.json +1 -1
- package/languages/newspack-blocks-de_DE-53e2a1d5945b8d2b1c35e81ae1e532f3.json +1 -1
- package/languages/newspack-blocks-de_DE-5c566531829f64486c13d7d8df1d6151.json +1 -0
- package/languages/newspack-blocks-de_DE-7c29764d5ef91723f5ce454f66431b9e.json +1 -1
- package/languages/newspack-blocks-de_DE-eeb28f99adedd5fad3081d2756d6f4a4.json +1 -1
- package/languages/newspack-blocks-de_DE-fef17c08abbcd3d3c4cdc0fd13a9bff5.json +1 -0
- package/languages/newspack-blocks-de_DE.po +427 -322
- package/languages/newspack-blocks-es_ES-37552bb09e2a9fceec1970e3c6d46557.json +1 -1
- package/languages/newspack-blocks-es_ES-53e2a1d5945b8d2b1c35e81ae1e532f3.json +1 -1
- package/languages/newspack-blocks-es_ES-5c566531829f64486c13d7d8df1d6151.json +1 -0
- package/languages/newspack-blocks-es_ES-7c29764d5ef91723f5ce454f66431b9e.json +1 -1
- package/languages/newspack-blocks-es_ES-eeb28f99adedd5fad3081d2756d6f4a4.json +1 -1
- package/languages/newspack-blocks-es_ES-fef17c08abbcd3d3c4cdc0fd13a9bff5.json +1 -0
- package/languages/newspack-blocks-es_ES.po +425 -322
- package/languages/newspack-blocks-fr_BE-37552bb09e2a9fceec1970e3c6d46557.json +1 -1
- package/languages/newspack-blocks-fr_BE-53e2a1d5945b8d2b1c35e81ae1e532f3.json +1 -1
- package/languages/newspack-blocks-fr_BE-5c566531829f64486c13d7d8df1d6151.json +1 -0
- package/languages/newspack-blocks-fr_BE-7c29764d5ef91723f5ce454f66431b9e.json +1 -1
- package/languages/newspack-blocks-fr_BE-eeb28f99adedd5fad3081d2756d6f4a4.json +1 -1
- package/languages/newspack-blocks-fr_BE-fef17c08abbcd3d3c4cdc0fd13a9bff5.json +1 -0
- package/languages/newspack-blocks-fr_BE.po +427 -322
- package/languages/newspack-blocks-nb_NO-37552bb09e2a9fceec1970e3c6d46557.json +1 -1
- package/languages/newspack-blocks-nb_NO-53e2a1d5945b8d2b1c35e81ae1e532f3.json +1 -1
- package/languages/newspack-blocks-nb_NO-5c566531829f64486c13d7d8df1d6151.json +1 -0
- package/languages/newspack-blocks-nb_NO-7c29764d5ef91723f5ce454f66431b9e.json +1 -1
- package/languages/newspack-blocks-nb_NO-eeb28f99adedd5fad3081d2756d6f4a4.json +1 -1
- package/languages/newspack-blocks-nb_NO-fef17c08abbcd3d3c4cdc0fd13a9bff5.json +1 -0
- package/languages/newspack-blocks-nb_NO.po +426 -322
- package/languages/newspack-blocks-pt_PT-37552bb09e2a9fceec1970e3c6d46557.json +1 -1
- package/languages/newspack-blocks-pt_PT-53e2a1d5945b8d2b1c35e81ae1e532f3.json +1 -1
- package/languages/newspack-blocks-pt_PT-5c566531829f64486c13d7d8df1d6151.json +1 -0
- package/languages/newspack-blocks-pt_PT-7c29764d5ef91723f5ce454f66431b9e.json +1 -1
- package/languages/newspack-blocks-pt_PT-eeb28f99adedd5fad3081d2756d6f4a4.json +1 -1
- package/languages/newspack-blocks-pt_PT-fef17c08abbcd3d3c4cdc0fd13a9bff5.json +1 -0
- package/languages/newspack-blocks-pt_PT.po +426 -322
- package/languages/newspack-blocks.pot +506 -379
- package/newspack-blocks.php +2 -2
- package/package.json +3 -2
- package/src/blocks/carousel/edit.js +1 -1
- package/src/blocks/carousel/view.scss +5 -1
- package/src/blocks/checkout-button/block.json +3 -0
- package/src/blocks/checkout-button/edit.js +32 -5
- package/src/blocks/checkout-button/view.php +37 -0
- package/src/blocks/donate/consts.ts +2 -1
- package/src/blocks/donate/frequency-slugs.js +10 -0
- package/src/blocks/donate/tiers-based/view.ts +8 -0
- package/src/blocks/homepage-articles/edit.tsx +1 -1
- package/src/blocks/homepage-articles/editor.scss +2 -1
- package/src/blocks/homepage-articles/view.scss +15 -5
- package/src/components/autocomplete-tokenfield.js +21 -7
- package/src/components/autocomplete-tokenfield.scss +1 -1
- package/src/components/autocomplete-tokenfield.test.js +58 -0
- package/src/components/query-controls.js +28 -6
- package/src/components/query-controls.test.js +56 -0
- package/src/components/reorder-modal.js +382 -0
- package/src/components/reorder-modal.scss +87 -0
- package/src/components/reorder-modal.test.js +487 -0
- package/src/components/specific-posts-control.js +91 -0
- package/src/components/specific-posts-control.scss +5 -0
- package/src/components/specific-posts-control.test.js +173 -0
- package/src/modal-checkout/analytics/ga4/utils/index.js +1 -0
- package/src/modal-checkout/checkout-button-trigger.js +29 -2
- package/src/modal-checkout/checkout-button-trigger.test.js +37 -0
- package/src/modal-checkout/checkout.scss +28 -0
- package/src/modal-checkout/donate-trigger.js +134 -0
- package/src/modal-checkout/donate-trigger.test.js +233 -0
- package/src/modal-checkout/index.js +91 -0
- package/src/modal-checkout/modal.js +129 -45
- package/src/modal-checkout/utils.test.js +49 -0
- package/src/shared/js/tiers-based-ready.js +26 -0
- package/vendor/autoload.php +1 -1
- package/vendor/composer/autoload_real.php +4 -4
- package/vendor/composer/autoload_static.php +2 -2
- package/vendor/composer/installed.php +2 -2
|
@@ -48,6 +48,44 @@ final class Modal_Checkout {
|
|
|
48
48
|
'billing_postcode' => 'postcode',
|
|
49
49
|
];
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Cart item keys the in-modal quantity form carries over when it re-adds the
|
|
53
|
+
* product at a new quantity.
|
|
54
|
+
*
|
|
55
|
+
* WC_Cart has no "change this line item's quantity, keep everything else"
|
|
56
|
+
* path that also re-runs the add-to-cart guards, so the item is emptied and
|
|
57
|
+
* re-added. Anything the original add attached to it — the name-your-price
|
|
58
|
+
* amount, the prompt/gate attribution the Data Events layer reads back at
|
|
59
|
+
* checkout — would be lost without this.
|
|
60
|
+
*
|
|
61
|
+
* @var string[]
|
|
62
|
+
*/
|
|
63
|
+
const PRESERVED_CART_ITEM_KEYS = [
|
|
64
|
+
'nyp',
|
|
65
|
+
'base_price',
|
|
66
|
+
'referer',
|
|
67
|
+
'gate_post_id',
|
|
68
|
+
'newspack_popup_id',
|
|
69
|
+
'prompt_title',
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Cart item keys by which WooCommerce Subscriptions marks a cart it built to
|
|
74
|
+
* change or take payment for a subscription the reader already has. Each is
|
|
75
|
+
* written from the request that started it, so a cart rebuilt without that
|
|
76
|
+
* request loses it -- and the checkout then raises a new subscription instead.
|
|
77
|
+
*
|
|
78
|
+
* Taken from the `$cart_item_key` property of the class that sets each one.
|
|
79
|
+
*
|
|
80
|
+
* @var string[]
|
|
81
|
+
*/
|
|
82
|
+
const WCS_SUBSCRIPTION_CART_ITEM_KEYS = [
|
|
83
|
+
'subscription_switch',
|
|
84
|
+
'subscription_renewal',
|
|
85
|
+
'subscription_resubscribe',
|
|
86
|
+
'subscription_initial_payment',
|
|
87
|
+
];
|
|
88
|
+
|
|
51
89
|
/**
|
|
52
90
|
* Whether the modal checkout has been enqueued.
|
|
53
91
|
*
|
|
@@ -187,6 +225,7 @@ final class Modal_Checkout {
|
|
|
187
225
|
add_filter( 'option_woocommerce_subscriptions_order_button_text', [ __CLASS__, 'order_button_text' ], 5 );
|
|
188
226
|
add_action( 'woocommerce_before_checkout_form', [ __CLASS__, 'render_before_checkout_form' ] );
|
|
189
227
|
add_action( 'woocommerce_before_checkout_form', [ __CLASS__, 'render_name_your_price_form' ], 11 );
|
|
228
|
+
add_action( 'woocommerce_before_checkout_form', [ __CLASS__, 'render_quantity_form' ], 12 );
|
|
190
229
|
add_action( 'woocommerce_checkout_before_customer_details', [ __CLASS__, 'render_before_customer_details' ] );
|
|
191
230
|
add_filter( 'woocommerce_enable_order_notes_field', [ __CLASS__, 'enable_order_notes_field' ] );
|
|
192
231
|
add_action( 'woocommerce_checkout_process', [ __CLASS__, 'wcsg_apply_gift_subscription' ] );
|
|
@@ -196,6 +235,8 @@ final class Modal_Checkout {
|
|
|
196
235
|
add_action( 'default_option_woocommerce_default_customer_address', [ __CLASS__, 'ensure_base_default_customer_address' ] );
|
|
197
236
|
add_action( 'wp_ajax_process_name_your_price_request', [ __CLASS__, 'process_name_your_price_request' ] );
|
|
198
237
|
add_action( 'wp_ajax_nopriv_process_name_your_price_request', [ __CLASS__, 'process_name_your_price_request' ] );
|
|
238
|
+
add_action( 'wp_ajax_process_quantity_request', [ __CLASS__, 'process_quantity_request' ] );
|
|
239
|
+
add_action( 'wp_ajax_nopriv_process_quantity_request', [ __CLASS__, 'process_quantity_request' ] );
|
|
199
240
|
add_filter( 'option_woocommerce_woocommerce_payments_settings', [ __CLASS__, 'filter_woocommerce_payments_settings' ] );
|
|
200
241
|
add_action( 'init', [ __CLASS__, 'unhook_woocommerce_payments_update_billing_fields' ] );
|
|
201
242
|
add_action( 'init', [ __CLASS__, 'unhook_woocommerce_payments_express_checkout_buttons' ], 20 );
|
|
@@ -364,6 +405,55 @@ final class Modal_Checkout {
|
|
|
364
405
|
\WC()->checkout()->process_checkout();
|
|
365
406
|
}
|
|
366
407
|
|
|
408
|
+
/**
|
|
409
|
+
* Seats (line-item quantity) requested when the modal checkout is opened.
|
|
410
|
+
* Reads $_GET and $_POST, since a Checkout Button sends its default seats as a
|
|
411
|
+
* query argument and the tier picker posts them as a form field.
|
|
412
|
+
*
|
|
413
|
+
* @param int $product_id Product the quantity is for (variation preferred), 0 when unknown.
|
|
414
|
+
*
|
|
415
|
+
* @return int
|
|
416
|
+
*/
|
|
417
|
+
public static function get_requested_quantity( $product_id = 0 ) {
|
|
418
|
+
// intval(), not absint(): absint() takes the absolute value, so a negative
|
|
419
|
+
// request (e.g. ?quantity=-5) would floor at 5 instead of 1.
|
|
420
|
+
$quantity = isset( $_REQUEST['quantity'] ) ? intval( wp_unslash( $_REQUEST['quantity'] ) ) : 1; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
|
421
|
+
return self::clamp_quantity( $quantity, $product_id );
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Floor a quantity at one and let the product's owner clamp it further.
|
|
426
|
+
*
|
|
427
|
+
* The one place a requested quantity becomes the quantity actually added to
|
|
428
|
+
* the cart, so every path shares one rule: the initial request via
|
|
429
|
+
* get_requested_quantity(), and the in-modal seats form via
|
|
430
|
+
* update_cart_quantity().
|
|
431
|
+
*
|
|
432
|
+
* @param int $quantity Requested quantity.
|
|
433
|
+
* @param int $product_id Product the quantity is for (variation preferred), 0 when unknown.
|
|
434
|
+
*
|
|
435
|
+
* @return int
|
|
436
|
+
*/
|
|
437
|
+
private static function clamp_quantity( $quantity, $product_id ) {
|
|
438
|
+
$quantity = max( 1, (int) $quantity );
|
|
439
|
+
/**
|
|
440
|
+
* Filters the line-item quantity the modal checkout will use for a product.
|
|
441
|
+
*
|
|
442
|
+
* The modal is single-quantity by default. A quantity arrives in the request,
|
|
443
|
+
* and `newspack_checkout=1` needs no nonce, so on its own it is the caller's
|
|
444
|
+
* claim about how many of something to charge for — which is what makes a
|
|
445
|
+
* crafted link a multiplier on the price. Answering this filter is how a
|
|
446
|
+
* plugin that owns the product's quantity rules vouches for one. Null —
|
|
447
|
+
* nobody has claimed this product — means one, whatever the request asked for.
|
|
448
|
+
*
|
|
449
|
+
* @param null|int $vouched The quantity to use, or null for the default of one.
|
|
450
|
+
* @param int $product_id Product the quantity is for (variation preferred), 0 when unknown.
|
|
451
|
+
* @param int $requested Requested quantity, at least 1.
|
|
452
|
+
*/
|
|
453
|
+
$vouched = apply_filters( 'newspack_blocks_modal_checkout_quantity', null, (int) $product_id, $quantity );
|
|
454
|
+
return null === $vouched ? 1 : max( 1, (int) $vouched );
|
|
455
|
+
}
|
|
456
|
+
|
|
367
457
|
/**
|
|
368
458
|
* Process checkout request for modal.
|
|
369
459
|
*/
|
|
@@ -456,7 +546,7 @@ final class Modal_Checkout {
|
|
|
456
546
|
$cart_item_data = apply_filters( 'newspack_blocks_modal_checkout_cart_item_data', $cart_item_data );
|
|
457
547
|
|
|
458
548
|
\WC()->cart->empty_cart();
|
|
459
|
-
$cart_item_key = \WC()->cart->add_to_cart( $product_id,
|
|
549
|
+
$cart_item_key = \WC()->cart->add_to_cart( $product_id, self::get_requested_quantity( $product_id ), 0, [], $cart_item_data );
|
|
460
550
|
|
|
461
551
|
// Auto-apply a coupon attached to the Checkout Button block, if present and
|
|
462
552
|
// valid. Read with a sanitizing filter (satisfies input-sanitization
|
|
@@ -691,11 +781,16 @@ final class Modal_Checkout {
|
|
|
691
781
|
|
|
692
782
|
$cart_item_data = self::amend_cart_item_data( [ 'referer' => wp_get_referer() ] );
|
|
693
783
|
|
|
784
|
+
$quantity = 1;
|
|
694
785
|
foreach ( \WC()->cart->get_cart() as $cart_item_key => $cart_item ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
|
695
786
|
if ( $cart_item['product_id'] !== (int) $product_id && $cart_item['variation_id'] !== (int) $product_id ) {
|
|
696
787
|
continue;
|
|
697
788
|
}
|
|
698
789
|
|
|
790
|
+
// Re-adding the item for the adjusted price must not silently drop the
|
|
791
|
+
// seats already in the cart.
|
|
792
|
+
$quantity = max( 1, (int) $cart_item['quantity'] );
|
|
793
|
+
|
|
699
794
|
$cart_item_data['nyp'] = $price;
|
|
700
795
|
$cart_item_data['base_price'] = isset( $cart_item['base_price'] ) ? $cart_item['base_price'] : $cart_item['nyp'];
|
|
701
796
|
|
|
@@ -717,7 +812,7 @@ final class Modal_Checkout {
|
|
|
717
812
|
$coupons = \WC()->cart->get_applied_coupons();
|
|
718
813
|
|
|
719
814
|
\WC()->cart->empty_cart();
|
|
720
|
-
$cart_item_key = \WC()->cart->add_to_cart( $product_id,
|
|
815
|
+
$cart_item_key = \WC()->cart->add_to_cart( $product_id, $quantity, 0, [], $cart_item_data );
|
|
721
816
|
|
|
722
817
|
// A rejected add (e.g. an add-to-cart guard) must not report success: surface
|
|
723
818
|
// the error notice the cart queued for it instead of a thank-you message.
|
|
@@ -726,7 +821,7 @@ final class Modal_Checkout {
|
|
|
726
821
|
// third-party notice content here.
|
|
727
822
|
if ( ! $cart_item_key ) {
|
|
728
823
|
$error_notices = \wc_get_notices( 'error' );
|
|
729
|
-
\wc_clear_notices();
|
|
824
|
+
\wc_clear_notices( 'error' );
|
|
730
825
|
wp_send_json_error(
|
|
731
826
|
[
|
|
732
827
|
'message' => ! empty( $error_notices[0]['notice'] )
|
|
@@ -754,6 +849,177 @@ final class Modal_Checkout {
|
|
|
754
849
|
wp_die();
|
|
755
850
|
}
|
|
756
851
|
|
|
852
|
+
/**
|
|
853
|
+
* Re-add the cart's product at a new quantity, preserving the line item's data.
|
|
854
|
+
*
|
|
855
|
+
* WC_Cart offers no "change this quantity and re-run the add-to-cart guards"
|
|
856
|
+
* path, so the item is emptied and re-added. That makes the rejected case
|
|
857
|
+
* dangerous: an add_to_cart() a guard turned down leaves the cart empty, and
|
|
858
|
+
* WooCommerce answers update_order_review on an empty cart by replacing the
|
|
859
|
+
* whole checkout form with a "your session has expired" notice
|
|
860
|
+
* (WC_AJAX::update_order_review_expired()). So a rejection puts the reader's
|
|
861
|
+
* original line item back before it reports anything.
|
|
862
|
+
*
|
|
863
|
+
* Separate from the AJAX handler so it can be tested: filter_input( INPUT_POST )
|
|
864
|
+
* reads the real request body, which a PHPUnit process does not have.
|
|
865
|
+
*
|
|
866
|
+
* @param int $product_id Product ID of the cart's line item.
|
|
867
|
+
* @param int $quantity Requested quantity.
|
|
868
|
+
*
|
|
869
|
+
* @return string|\WP_Error New cart item key, or a WP_Error carrying the message to show the reader.
|
|
870
|
+
*/
|
|
871
|
+
public static function update_cart_quantity( $product_id, $quantity ) {
|
|
872
|
+
// Same clamp the initial add goes through, so a product that may not be sold
|
|
873
|
+
// in multiples cannot be raised past one from inside the modal either.
|
|
874
|
+
$quantity = self::clamp_quantity( $quantity, $product_id );
|
|
875
|
+
$cart = \WC()->cart;
|
|
876
|
+
$cart_item_data = self::amend_cart_item_data( [ 'referer' => wp_get_referer() ] );
|
|
877
|
+
|
|
878
|
+
$original_quantity = 0;
|
|
879
|
+
foreach ( $cart->get_cart() as $cart_item ) {
|
|
880
|
+
if ( (int) $cart_item['product_id'] !== (int) $product_id && (int) $cart_item['variation_id'] !== (int) $product_id ) {
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
883
|
+
// WooCommerce Subscriptions writes these onto the cart item from the request
|
|
884
|
+
// that started the switch, renewal, resubscribe or first payment, and this
|
|
885
|
+
// rebuild has no such request behind it. Re-adding would drop the record and
|
|
886
|
+
// raise a second full-price subscription at checkout. A switch chooses its
|
|
887
|
+
// seat count in the tier picker, which submits the whole switch again.
|
|
888
|
+
foreach ( self::WCS_SUBSCRIPTION_CART_ITEM_KEYS as $wcs_key ) {
|
|
889
|
+
if ( ! empty( $cart_item[ $wcs_key ] ) ) {
|
|
890
|
+
return new \WP_Error(
|
|
891
|
+
'newspack_blocks_quantity_subscription_cart',
|
|
892
|
+
__( 'Go back and choose the number you want before changing your subscription.', 'newspack-blocks' )
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
$original_quantity = max( 1, (int) $cart_item['quantity'] );
|
|
897
|
+
foreach ( self::get_preserved_cart_item_keys() as $key ) {
|
|
898
|
+
if ( isset( $cart_item[ $key ] ) ) {
|
|
899
|
+
$cart_item_data[ $key ] = $cart_item[ $key ];
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// Nothing to change, and emptying the cart for a product that was never in
|
|
905
|
+
// it would destroy whatever the reader is actually buying.
|
|
906
|
+
if ( ! $original_quantity ) {
|
|
907
|
+
return new \WP_Error(
|
|
908
|
+
'newspack_blocks_quantity_not_in_cart',
|
|
909
|
+
__( 'This product is not in the cart.', 'newspack-blocks' )
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
$coupons = $cart->get_applied_coupons();
|
|
914
|
+
|
|
915
|
+
$cart->empty_cart();
|
|
916
|
+
$cart_item_key = $cart->add_to_cart( $product_id, $quantity, 0, [], $cart_item_data );
|
|
917
|
+
|
|
918
|
+
if ( ! $cart_item_key ) {
|
|
919
|
+
// Read the rejection before restoring, so the restore's own notices
|
|
920
|
+
// cannot be mistaken for it.
|
|
921
|
+
$error_notices = \wc_get_notices( 'error' );
|
|
922
|
+
\wc_clear_notices( 'error' );
|
|
923
|
+
$cart->add_to_cart( $product_id, $original_quantity, 0, [], $cart_item_data );
|
|
924
|
+
self::reapply_coupons( $coupons );
|
|
925
|
+
|
|
926
|
+
// The consumer appends this message as an HTML string, and unlike
|
|
927
|
+
// template-rendered notices it never passes through kses on output — so
|
|
928
|
+
// filter any third-party notice content here.
|
|
929
|
+
return new \WP_Error(
|
|
930
|
+
'newspack_blocks_quantity_rejected',
|
|
931
|
+
! empty( $error_notices[0]['notice'] )
|
|
932
|
+
? wp_kses_post( $error_notices[0]['notice'] )
|
|
933
|
+
: __( 'This product could not be added to the cart.', 'newspack-blocks' )
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
self::reapply_coupons( $coupons );
|
|
938
|
+
|
|
939
|
+
return $cart_item_key;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/**
|
|
943
|
+
* Re-apply coupons to the cart after it has been emptied and rebuilt.
|
|
944
|
+
*
|
|
945
|
+
* @param string[] $coupons Coupon codes.
|
|
946
|
+
*/
|
|
947
|
+
private static function reapply_coupons( $coupons ) {
|
|
948
|
+
if ( empty( $coupons ) ) {
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
foreach ( $coupons as $coupon ) {
|
|
952
|
+
\WC()->cart->apply_coupon( $coupon );
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
/**
|
|
957
|
+
* Process an in-modal quantity change: re-add the cart's single product at
|
|
958
|
+
* the requested quantity.
|
|
959
|
+
*/
|
|
960
|
+
public static function process_quantity_request() {
|
|
961
|
+
if ( ! defined( 'DOING_AJAX' ) ) {
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
if ( ! function_exists( 'WC' ) || ! \WC()->cart ) {
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
check_ajax_referer( 'newspack_checkout_quantity' );
|
|
970
|
+
|
|
971
|
+
$product_id = filter_input( INPUT_POST, 'product_id', FILTER_SANITIZE_NUMBER_INT );
|
|
972
|
+
$quantity = filter_input( INPUT_POST, 'quantity', FILTER_SANITIZE_NUMBER_INT );
|
|
973
|
+
|
|
974
|
+
// A missing quantity is a malformed request. "0" is a real value the floor
|
|
975
|
+
// below handles, so it must not be mistaken for an absent one.
|
|
976
|
+
if ( ! $product_id || ! is_string( $quantity ) || '' === $quantity ) {
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// The real bounds check belongs to whoever hooked the field filter and runs
|
|
981
|
+
// inside add_to_cart(). This only keeps a 0 or negative from reaching the
|
|
982
|
+
// cart as a silent item removal.
|
|
983
|
+
$quantity = max( 1, (int) $quantity );
|
|
984
|
+
|
|
985
|
+
$result = self::update_cart_quantity( (int) $product_id, $quantity );
|
|
986
|
+
|
|
987
|
+
if ( is_wp_error( $result ) ) {
|
|
988
|
+
wp_send_json_error( [ 'message' => $result->get_error_message() ] );
|
|
989
|
+
|
|
990
|
+
wp_die();
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
wp_send_json_success(
|
|
994
|
+
[
|
|
995
|
+
'message' => __( 'Updated.', 'newspack-blocks' ),
|
|
996
|
+
// update_order_review only returns the review-order table and payment-box
|
|
997
|
+
// fragments, so the #modal-checkout-product-details data carrier — which
|
|
998
|
+
// sits outside the checkout form and feeds the GA4 events — would keep
|
|
999
|
+
// reporting the page-load quantity.
|
|
1000
|
+
'checkout_data' => Checkout_Data::get_checkout_data( \WC()->cart ),
|
|
1001
|
+
]
|
|
1002
|
+
);
|
|
1003
|
+
|
|
1004
|
+
wp_die();
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
/**
|
|
1008
|
+
* The cart item keys a quantity change carries over.
|
|
1009
|
+
*
|
|
1010
|
+
* @return string[]
|
|
1011
|
+
*/
|
|
1012
|
+
private static function get_preserved_cart_item_keys() {
|
|
1013
|
+
/**
|
|
1014
|
+
* Filters the cart item keys the in-modal quantity form carries over when it
|
|
1015
|
+
* re-adds the product at a new quantity. A plugin that turned the quantity
|
|
1016
|
+
* field on for a product is the one that knows what its cart item holds.
|
|
1017
|
+
*
|
|
1018
|
+
* @param string[] $keys Cart item keys to preserve.
|
|
1019
|
+
*/
|
|
1020
|
+
return (array) apply_filters( 'newspack_blocks_modal_checkout_preserved_cart_item_keys', self::PRESERVED_CART_ITEM_KEYS );
|
|
1021
|
+
}
|
|
1022
|
+
|
|
757
1023
|
/**
|
|
758
1024
|
* Render the markup necessary for the modal checkout.
|
|
759
1025
|
*/
|
|
@@ -917,6 +1183,8 @@ final class Modal_Checkout {
|
|
|
917
1183
|
[
|
|
918
1184
|
'ajax_url' => admin_url( 'admin-ajax.php' ),
|
|
919
1185
|
'nyp_nonce' => wp_create_nonce( 'newspack_checkout_name_your_price' ),
|
|
1186
|
+
'quantity_nonce' => wp_create_nonce( 'newspack_checkout_quantity' ),
|
|
1187
|
+
'quantity_error' => __( 'Something went wrong. Please try again.', 'newspack-blocks' ),
|
|
920
1188
|
'checkout_nonce' => wp_create_nonce( 'newspack_modal_checkout_nonce' ),
|
|
921
1189
|
'newspack_class_prefix' => self::get_class_prefix(),
|
|
922
1190
|
'is_checkout_complete' => function_exists( 'is_order_received_page' ) && is_order_received_page(),
|
|
@@ -2458,7 +2726,9 @@ final class Modal_Checkout {
|
|
|
2458
2726
|
return;
|
|
2459
2727
|
}
|
|
2460
2728
|
$cart = \WC()->cart;
|
|
2461
|
-
|
|
2729
|
+
// Line-item count, not get_cart_contents_count(): that sums quantities, which
|
|
2730
|
+
// would hide this order-summary data carrier for any multi-seat purchase.
|
|
2731
|
+
if ( 1 !== count( $cart->get_cart() ) ) {
|
|
2462
2732
|
return;
|
|
2463
2733
|
}
|
|
2464
2734
|
$cart_item_key = array_key_first( $cart->get_cart() );
|
|
@@ -2496,7 +2766,9 @@ final class Modal_Checkout {
|
|
|
2496
2766
|
}
|
|
2497
2767
|
|
|
2498
2768
|
$cart = \WC()->cart;
|
|
2499
|
-
|
|
2769
|
+
// Line-item count, not get_cart_contents_count(): that sums quantities, which
|
|
2770
|
+
// would hide this order-summary data carrier for any multi-seat purchase.
|
|
2771
|
+
if ( 1 !== count( $cart->get_cart() ) ) {
|
|
2500
2772
|
return;
|
|
2501
2773
|
}
|
|
2502
2774
|
$class_prefix = self::get_class_prefix();
|
|
@@ -2538,6 +2810,83 @@ final class Modal_Checkout {
|
|
|
2538
2810
|
<?php
|
|
2539
2811
|
}
|
|
2540
2812
|
|
|
2813
|
+
/**
|
|
2814
|
+
* Render the in-modal quantity form, when a plugin has asked for one.
|
|
2815
|
+
*
|
|
2816
|
+
* The modal is single-quantity by default, and stays that way unless a
|
|
2817
|
+
* consumer of the field filter below returns arguments for this product.
|
|
2818
|
+
* The label and bounds all come from the filter.
|
|
2819
|
+
*/
|
|
2820
|
+
public static function render_quantity_form() {
|
|
2821
|
+
if ( ! self::is_modal_checkout() || ! function_exists( 'WC' ) || ! \WC()->cart ) {
|
|
2822
|
+
return;
|
|
2823
|
+
}
|
|
2824
|
+
|
|
2825
|
+
// Donation carts have their own amount controls; a quantity field on top of
|
|
2826
|
+
// them would be a second, conflicting way to change what the reader pays.
|
|
2827
|
+
$is_donation = method_exists( 'Newspack\Donations', 'is_donation_cart' ) && \Newspack\Donations::is_donation_cart();
|
|
2828
|
+
if ( $is_donation ) {
|
|
2829
|
+
return;
|
|
2830
|
+
}
|
|
2831
|
+
|
|
2832
|
+
$cart = \WC()->cart;
|
|
2833
|
+
// Line-item count, not get_cart_contents_count(): that sums quantities, so a
|
|
2834
|
+
// multi-seat purchase would fail this guard and hide its own field.
|
|
2835
|
+
if ( 1 !== count( $cart->get_cart() ) ) {
|
|
2836
|
+
return;
|
|
2837
|
+
}
|
|
2838
|
+
$cart_item_key = array_key_first( $cart->get_cart() );
|
|
2839
|
+
$cart_item = $cart->get_cart_item( $cart_item_key );
|
|
2840
|
+
$product = isset( $cart_item['data'] ) ? $cart_item['data'] : null;
|
|
2841
|
+
// The filter's consumers are handed this product to decide on.
|
|
2842
|
+
if ( ! $product || ! method_exists( $product, 'get_id' ) ) {
|
|
2843
|
+
return;
|
|
2844
|
+
}
|
|
2845
|
+
// A cart WooCommerce Subscriptions is holding for an existing subscription
|
|
2846
|
+
// cannot be rebuilt at a new quantity without losing that record (see
|
|
2847
|
+
// update_cart_quantity()), so it gets no control that would invite one.
|
|
2848
|
+
if ( array_intersect_key( array_filter( $cart_item ), array_flip( self::WCS_SUBSCRIPTION_CART_ITEM_KEYS ) ) ) {
|
|
2849
|
+
return;
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
/**
|
|
2853
|
+
* Lets a plugin turn on a quantity field for this purchase. Null keeps the
|
|
2854
|
+
* modal single-quantity. Array keys: label, min, max (0 = none), help.
|
|
2855
|
+
*
|
|
2856
|
+
* @param null|array $args Field arguments or null.
|
|
2857
|
+
* @param \WC_Product $product The product in the cart.
|
|
2858
|
+
* @param array $cart_item The cart item.
|
|
2859
|
+
*/
|
|
2860
|
+
$args = apply_filters( 'newspack_blocks_modal_checkout_quantity_field', null, $product, $cart_item );
|
|
2861
|
+
if ( empty( $args ) ) {
|
|
2862
|
+
return;
|
|
2863
|
+
}
|
|
2864
|
+
$args = wp_parse_args(
|
|
2865
|
+
$args,
|
|
2866
|
+
[
|
|
2867
|
+
'label' => '',
|
|
2868
|
+
'min' => 1,
|
|
2869
|
+
'max' => 0,
|
|
2870
|
+
'help' => '',
|
|
2871
|
+
]
|
|
2872
|
+
);
|
|
2873
|
+
|
|
2874
|
+
$class_prefix = self::get_class_prefix();
|
|
2875
|
+
$quantity = max( 1, (int) $cart_item['quantity'] );
|
|
2876
|
+
?>
|
|
2877
|
+
<form class="modal_checkout_quantity">
|
|
2878
|
+
<?php // A single line item per modal, so one fixed input id cannot collide. ?>
|
|
2879
|
+
<h3><label for="modal_checkout_quantity"><?php echo esc_html( $args['label'] ); ?></label></h3>
|
|
2880
|
+
<input type="hidden" name="product_id" value="<?php echo esc_attr( $product->get_id() ); ?>" />
|
|
2881
|
+
<p class="input-quantity">
|
|
2882
|
+
<input type="number" id="modal_checkout_quantity" name="quantity" step="1" min="<?php echo esc_attr( $args['min'] ); ?>" <?php echo $args['max'] > 0 ? 'max="' . esc_attr( $args['max'] ) . '"' : ''; ?> value="<?php echo esc_attr( $quantity ); ?>" onwheel="return false" />
|
|
2883
|
+
<button type="submit" class="<?php echo esc_attr( "{$class_prefix}__button {$class_prefix}__button--outline" ); ?>"><?php esc_html_e( 'Update', 'newspack-blocks' ); ?></button>
|
|
2884
|
+
</p>
|
|
2885
|
+
<p class="result <?php echo esc_attr( "{$class_prefix}__helper-text" ); ?>"><?php echo esc_html( $args['help'] ); ?></p>
|
|
2886
|
+
</form>
|
|
2887
|
+
<?php
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2541
2890
|
/**
|
|
2542
2891
|
* Render before customer details.
|
|
2543
2892
|
*
|
|
@@ -18,10 +18,13 @@ final class Checkout_Data {
|
|
|
18
18
|
* @param string $price The price. Optional. If not provided, the price string will contain 0.
|
|
19
19
|
* @param string $frequency The frequency. Optional. If not provided, the price will be treated as a one-time payment.
|
|
20
20
|
* @param int $product_id Product ID to get additional subscription details. Optional.
|
|
21
|
+
* @param int $quantity Seats (line-item quantity) purchased. Optional, defaults to 1. Multiplies
|
|
22
|
+
* the sign-up fee, since WCS charges it per unit; $price is already the
|
|
23
|
+
* line amount, so it is not multiplied here.
|
|
21
24
|
*
|
|
22
25
|
* @return string The price string.
|
|
23
26
|
*/
|
|
24
|
-
public static function get_price_summary( $name, $price = '', $frequency = '', $product_id = null ) {
|
|
27
|
+
public static function get_price_summary( $name, $price = '', $frequency = '', $product_id = null, $quantity = 1 ) {
|
|
25
28
|
if ( ! $price ) {
|
|
26
29
|
$price = '0';
|
|
27
30
|
}
|
|
@@ -39,7 +42,8 @@ final class Checkout_Data {
|
|
|
39
42
|
$subscription_interval = \WC_Subscriptions_Product::get_interval( $product );
|
|
40
43
|
$trial_length = \WC_Subscriptions_Product::get_trial_length( $product );
|
|
41
44
|
$trial_period = \WC_Subscriptions_Product::get_trial_period( $product );
|
|
42
|
-
|
|
45
|
+
// The sign-up fee is charged per seat, so multiply it by quantity.
|
|
46
|
+
$initial_amount = \WC_Subscriptions_Product::get_sign_up_fee( $product ) * $quantity;
|
|
43
47
|
|
|
44
48
|
if ( empty( $subscription_interval ) ) {
|
|
45
49
|
$subscription_interval = 1;
|
|
@@ -273,6 +277,7 @@ final class Checkout_Data {
|
|
|
273
277
|
$referrer = '';
|
|
274
278
|
$product_id = null;
|
|
275
279
|
$amount = 0;
|
|
280
|
+
$quantity = 1;
|
|
276
281
|
$variation_id = null;
|
|
277
282
|
$is_variable = false;
|
|
278
283
|
$is_grouped = false;
|
|
@@ -304,6 +309,7 @@ final class Checkout_Data {
|
|
|
304
309
|
$product_id = $cart_item['product_id'];
|
|
305
310
|
$variation_id = $cart_item['variation_id'];
|
|
306
311
|
$amount = $cart_item['data']->get_price();
|
|
312
|
+
$quantity = max( 1, (int) ( $cart_item['quantity'] ?? 1 ) );
|
|
307
313
|
$referrer = $cart_item['referer'] ?? '';
|
|
308
314
|
} elseif ( $source instanceof \WC_Order ) {
|
|
309
315
|
// A subscription's purchase details normally live on the order it was
|
|
@@ -335,7 +341,8 @@ final class Checkout_Data {
|
|
|
335
341
|
}
|
|
336
342
|
$product_id = $order_item->get_product_id();
|
|
337
343
|
$variation_id = $order_item->get_variation_id();
|
|
338
|
-
$amount = $order_item->get_subtotal();
|
|
344
|
+
$amount = $order_item->get_subtotal(); // Already reflects quantity; do not multiply below.
|
|
345
|
+
$quantity = max( 1, (int) $order_item->get_quantity() );
|
|
339
346
|
$referrer = $items_source->get_meta( '_newspack_referer' );
|
|
340
347
|
}
|
|
341
348
|
|
|
@@ -376,9 +383,26 @@ final class Checkout_Data {
|
|
|
376
383
|
$data['is_grouped'] = true;
|
|
377
384
|
$data['child_ids'] = $children;
|
|
378
385
|
} else {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
386
|
+
// An order's amount is a line subtotal already scaled by quantity; a
|
|
387
|
+
// product's or cart item's is a per-unit price, so scale it here instead.
|
|
388
|
+
// The quantity-1 short circuit is load-bearing: get_price() returns a
|
|
389
|
+
// string, and multiplying would hand a float to consumers that have only
|
|
390
|
+
// ever seen the string.
|
|
391
|
+
$line_amount = ( $source instanceof \WC_Order || 1 === $quantity ) ? $amount : (float) $amount * $quantity;
|
|
392
|
+
$data['amount'] = $line_amount;
|
|
393
|
+
// Only a cart or order line item has a real seat count to report. For a
|
|
394
|
+
// bare product source the block's hidden field is the source of truth, so
|
|
395
|
+
// omitting the key — rather than hardcoding 1 — keeps getCheckoutData()'s
|
|
396
|
+
// JSON-wins merge in utils.js from overwriting it with a stale default.
|
|
397
|
+
if ( $source instanceof \WC_Cart || $source instanceof \WC_Order ) {
|
|
398
|
+
$data['quantity'] = $quantity;
|
|
399
|
+
}
|
|
400
|
+
// WooCommerce Subscriptions folds the sign-up fee into the unit price when
|
|
401
|
+
// it prices an order line, so its subtotal already carries the fee for
|
|
402
|
+
// every unit and the summary must not scale it a second time.
|
|
403
|
+
$summary_quantity = $source instanceof \WC_Order ? 1 : $quantity;
|
|
404
|
+
$data['price_summary'] = self::get_price_summary( $name, $line_amount, $recurrence, $variation_id ? $variation_id : $product_id, $summary_quantity );
|
|
405
|
+
$data['summary_template'] = self::get_price_summary( $name, '{{PRICE}}', $recurrence, $variation_id ? $variation_id : $product_id, $summary_quantity );
|
|
382
406
|
$data['recurrence'] = $recurrence;
|
|
383
407
|
}
|
|
384
408
|
if ( $variation_id ) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"translation-revision-date":"2024-08-30 08:45-0700","generator":"WP-CLI\/2.12.0","source":"src\/components\/query-controls.js","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"de","plural-forms":"nplurals=2; plural=(n != 1);"},"(no name)":["(kein Name)"],"Category":[""],"(no title)":["(kein Titel)"],"Deleted category":[""],"Deleted tag":[""],"Mode":[""],"The block will display only the specifically selected content.":[""],"The block will display content based on the filtering settings below.":[""],"Dynamic":[""],"Static":[""],"
|
|
1
|
+
{"translation-revision-date":"2024-08-30 08:45-0700","generator":"WP-CLI\/2.12.0","source":"src\/components\/query-controls.js","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"de","plural-forms":"nplurals=2; plural=(n != 1);"},"(no name)":["(kein Name)"],"Category":[""],"(no title)":["(kein Titel)"],"Unavailable content (%d)":[""],"Deleted category":[""],"Deleted tag":[""],"Mode":[""],"The block will display only the specifically selected content.":[""],"The block will display content based on the filtering settings below.":[""],"Dynamic":[""],"Static":[""],"is one of":[""],"is all of":[""],"Include subcategories":[""],"Tags":["Schlagw\u00f6rter"],"Authors":["Autoren"],"Excluded categories":[""],"Excluded tags":[""],"Excluded %s":[""],"Allow duplicate content":[""],"Exclude this block from the page's deduplication logic.":[""]}}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"translation-revision-date":"2024-08-30 08:45-0700","generator":"WP-CLI\/2.12.0","source":"dist\/editor.js","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"de","plural-forms":"nplurals=2; plural=(n != 1);"},"Cancel":[""],"Draft":[""],"Scheduled":[""],"per %s":[""],"Donate":["Spenden"],"Author Profile":[""],"More by %s":[""],"block style\u0004Default":["Standard"],"block style\u0004Centered":[""],"once":[""],"One-time":["Einmal"],"Monthly":["Monatlich"],"Annually":["J\u00e4hrlich"],"Donation amount":["Spendenbetrag"],"Other":["Andere"],"Load more posts":["Mehr Beitr\u00e4ge laden"],"Slide %s":[""],"Slide %1$s of %2$s":[""],"Image: %s,":[""],"Playing":[""],"Paused":[""],"Small":["Klein"],"S":["S"],"Medium":["Mittel"],"M":["M"],"Large":["Gro\u00df"],"L":["L"],"Extra Large":["Extra Gro\u00df"],"XL":["XL"],"Extra-large":["Extra Gro\u00df"],"Select a layout to start with:":[""],"No authors or guest authors found for ID %s.":[""],"Error fetching authors for this post.":[""],"Error fetching byline authors.":[""],"Display":[""],"Show authors without bio":[""],"Display author profiles even if their bio is empty.":[""],"Settings":[""],"Text size":[""],"Avatar":[""],"Display avatar":[""],"Hide default avatar":[""],"Size":[""],"Avatar size":[""],"Border radius":[""],"Avatar border radius":[""],"Fetching authors\u2026":[""],"Show avatar on left":[""],"Show avatar on right":[""],"Edit selection":[""],"Select a type:":[""],"Contextual":[""],"Specific":[""],"This block was created with a block theme and is not supported in the current theme. It will render using the classic layout on the frontend.":[""],"Author bio is hidden because Custom Byline is active on this post.":[""],"Search for an author to display":[""],"Begin typing name, click autocomplete result to select.":[""],"(no name)":["(kein Name)"],"author":[""],"authors":[""],"No authors found for this post.":[""],"Preview author":[""],"Previewing 1 of %d authors. All authors display on frontend.":[""],"Square":[""],"1:1":[""],"4:3":[""],"16:9":[""],"3:4":[""],"9:16":[""],"Hide Controls":[""],"Remove navigation indicators from view.":[""],"Autoplay":["Autoplay"],"Automatically advance through slides.":[""],"Transition delay":[""],"Set the waiting time between automatic slide transitions in seconds.":[""],"Slides per view":[""],"Choose how many slides appear on screen simultaneously.":[""],"Featured Image":[""],"Aspect ratio":[""],"All slides will share the same aspect ratio, for consistency.":[""],"Fit":[""],"The image will fill the entire slide and will be cropped if necessary.":[""],"The image will be resized to fit inside the slide without being cropped.":[""],"Cover":[""],"Contain":[""],"Show caption":[""],"Show credit":[""],"Post Meta":[""],"Show title":[""],"Show date":[""],"Show category":[""],"Show tag labels":[""],"Show author":[""],"Show avatar":[""],"Content Carousel":[""],"posts":["Beitr\u00e4ge"],"articles":["Artikel"],"latest":["letzten"],"query":[""],"An advanced block that displays content in a carousel format with customizable parameters and visual configurations.":[""],"The post content.":[""],"The post excerpt.":[""],"Full post content.":[""],"Post Subtitle":[""],"Post Title":[""],"by":["von"],"Category":[""],"Featured image caption":[""],"Biographical info":[""],"Social links":[""],"Email address":[""],"Link to author archive":[""],"Display the following fields:":[""],"Listings":[""],"Post Types":[""],"Additional Post Statuses":[""],"Selection here has effect only for editors, regular users will only see published posts.":[""],"(no title)":["(kein Titel)"],"Deleted category":[""],"Deleted tag":[""],"Mode":[""],"The block will display only the specifically selected content.":[""],"The block will display content based on the filtering settings below.":[""],"Dynamic":[""],"Static":[""],"Content":["Inhalt"],"Begin typing any word in a title. Click on an autocomplete result to select it.":[""],"is one of":[""],"is all of":[""],"Include subcategories":[""],"Tags":["Schlagw\u00f6rter"],"Authors":["Autoren"],"Excluded categories":[""],"Excluded tags":[""],"Excluded %s":[""],"Allow duplicate content":[""],"Exclude this block from the page's deduplication logic.":[""],"Post-Checkout Button":[""],"After a successful purchase, a button will be presented to finish the process.":[""],"Close the modal":[""],"Go to a custom URL":[""],"Go to the previous page":[""],"Button Label":[""],"Custom URL":[""],"https:\/\/example.com":[""],"post author\u0004and":[""],"Author List":[""],"profile":[""],"Display a list of author profile cards.":[""],"Error fetching authors.":[""],"Author List Settings":[""],"Author Type":[""],"%s will be displayed.":[""],"Both guest authors and WP users":[""],"%s only":[""],"Guest authors":[""],"WP users":[""],"All authors":[""],"Columns":["Spalten"],"WP User Roles":[""],"Display alphabetical separators":[""],"Chunk authors alphabetically.":[""],"Group authors by alphabet":[""],"Display each alphabetical chunk as a discrete section.":[""],"Exclude authors with 0 posts":[""],"Authors with no published posts will be %s.":[""],"hidden":[""],"displayed":[""],"Search by author name":[""],"Authors selected here will not be displayed.":[""],"Author Profile Settings":[""],"Text Size":[""],"List":[""],"Author Name":[""],"Job Title":[""],"Role":[""],"Employer":[""],"Biography":[""],"Avatar left":[""],"Avatar on the left, content on the right.":[""],"Avatar right":[""],"Content on the left, avatar on the right.":[""],"Centered":[""],"Large centered avatar with center-aligned text.":[""],"Compact":[""],"No avatar, vertical stack.":[""],"Display an author profile card.":[""],"Coupon":[""],"Edit":[""],"Remove":[""],"Coupons are not available":[""],"Type to search for a coupon\u2026":[""],"Width":[""],"Could not find a product with ID %s. Please select a different product.":[""],"Product":[""],"Change the selected product":[""],"Type to search for a product\u2026":[""],"Allow the reader to select the variation before checkout.":[""],"Variation":[""],"Select the product variation to be added to cart.":[""],"Donation products do not support coupons. \"%s\" will not be applied.":[""],"Donation products do not support coupons.":[""],"After purchase":[""],"Name Your Price":[""],"This product has \"Name Your Price\" toggled on. You can set the custom price for this checkout.":[""],"Suggested price:":[""],"Minimum price:":[""],"Maximum price:":[""],"Custom Price":[""],"per `Frequency`\u0004per %s":[""],"Frequency":[""],"Tiers":[""],"Heading\u2026":[""],"\u2026":[""],"Button text\u2026":[""],"Description\u2026":[""],"Thank you text\u2026":[""],"Low-tier":[""],"Mid-tier":[""],"High-tier":[""],"donate":["Spenden"],"memberships":["Mitgliedschaften"],"subscriptions":["Abonnements"],"Manually place a donation block on any post or page on your site.":[""],"Support reference":[""],"Could not fetch the donation settings.":[""],"Error":["Fehler"],"Go to checkout & payment settings to troubleshoot.":[""],"The Donate block will not be rendered.":[""],"The Reader Revenue platform is set to \"other\".":[""],"Go to checkout & payment settings to update the platform.":[""],"Grid View":[""],"List View":[""],"Warning: suggested donations should be at least the minimum donation amount (%1$s%2$d).":[""],"Layout":[""],"Tiers layout is disabled if the block is set to render untiered.":[""],"Default Tab":[""],"Suggested Donations":[""],"Configure manually":[""],"Tiered":[""],"Minimum donation":[""],"The Donate Block allows you to collect donations from readers. The fields are automatically defined based on your donation settings.":[""],"Edit donation settings":[""],"Styling":[""],"Button Color":[""],"News Revenue Hub Settings":[""],"Campaign ID":[""],"Alternate":[""],"Minimal":[""],"Modern":[""],"Grid":[""],"Show \"Load more posts\" button":[""],"This site is private, therefore this feature is not active.":[""],"Infinite scroll":[""],"Text":[""],"None":[""],"Excerpt":[""],"Full Post":[""],"Max number of words in excerpt":[""],"Keep reading":[""],"Show featured image":[""],"Stack on mobile":[""],"Minimum height":["Mindesth\u00f6he"],"Sets a minimum height for the block, using a percentage of the screen's current height.":[""],"Show subtitle":[""],"Color":[""],"Typography":[""],"Type Scale":[""],"Dimensions":[""],"Grid Spacing":[""],"List view":[""],"Grid view":[""],"Show media on top":[""],"Show media on left":[""],"Show media on right":[""],"Show media behind":[""],"Landscape image shape":[""],"portrait image shape":[""],"Square image shape":[""],"Uncropped":[""],"Write header\u2026":[""],"Sorry, no posts were found.":["Entschuldigung, wir konnten keine Beitr\u00e4ge finden."],"Content Loop":[""],"homepage":[""],"An advanced block that allows displaying content based on different parameters and visual configurations.":[""],"block style\u0004Borders":["Rahmen"],"Upload a document file (PDF, Word, Excel sheet, or a PPT), choose one from the media library, %s.":[""],"embed from a URL, or upload a .zip archive containing HTML assets":[""],"or embed from a URL":[""],"Update":[""],"Upload":[""],"Media Library":[""],"Update from URL":[""],"Embed from URL":[""],"Iframe":[""],"iframe":[""],"project iframe":[""],"Embed an iframe.":[""],"Hide iframe preview":[""],"Show iframe preview":[""],"Iframe Settings":[""],"Fullscreen":[""],"If enabled, the iframe will be full screen and hide all the post content.":[""],"Height":[""],"This block will take over the page content.":[""],"Newspack embedded iframe":[""],"An error occured when uploading the iframe archive.":[""],"An error occured when setting the iframe from the archive media.":[""],"No videos found":[""],"Consider using <a href=\"%1$s\">YouTube Playlists<\/a> instead, which can be <a href=\"%2$s\">embedded<\/a> into post or page content.":[""],"YouTube Video Playlist (DEPRECATED)":[""],"video":[""],"playlist":[""],"youtube":[""],"Embed a playlist of latest YouTube videos.":[""]}}}
|
|
1
|
+
{"translation-revision-date":"2024-08-30 08:45-0700","generator":"WP-CLI\/2.12.0","source":"dist\/editor.js","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"de","plural-forms":"nplurals=2; plural=(n != 1);"},"Close":[""],"%1$s: %2$s":[""],"Update":[""],"Cancel":[""],"Draft":[""],"Scheduled":[""],"per %s":[""],"Donate":["Spenden"],"Author Profile":[""],"More by %s":[""],"block style\u0004Default":["Standard"],"block style\u0004Centered":[""],"once":[""],"One-time":["Einmal"],"Monthly":["Monatlich"],"Annually":["J\u00e4hrlich"],"Donation amount":["Spendenbetrag"],"Other":["Andere"],"Load more posts":["Mehr Beitr\u00e4ge laden"],"Slide %s":[""],"Slide %1$s of %2$s":[""],"Image: %s,":[""],"Playing":[""],"Paused":[""],"Small":["Klein"],"S":["S"],"Medium":["Mittel"],"M":["M"],"Large":["Gro\u00df"],"L":["L"],"Extra Large":["Extra Gro\u00df"],"XL":["XL"],"Extra-large":["Extra Gro\u00df"],"Select a layout to start with:":[""],"No authors or guest authors found for ID %s.":[""],"Error fetching authors for this post.":[""],"Error fetching byline authors.":[""],"Display":[""],"Show authors without bio":[""],"Display author profiles even if their bio is empty.":[""],"Settings":[""],"Text size":[""],"Avatar":[""],"Display avatar":[""],"Hide default avatar":[""],"Size":[""],"Avatar size":[""],"Border radius":[""],"Avatar border radius":[""],"Fetching authors\u2026":[""],"Show avatar on left":[""],"Show avatar on right":[""],"Edit selection":[""],"Select a type:":[""],"Contextual":[""],"Specific":[""],"This block was created with a block theme and is not supported in the current theme. It will render using the classic layout on the frontend.":[""],"Author bio is hidden because Custom Byline is active on this post.":[""],"Search for an author to display":[""],"Begin typing name, click autocomplete result to select.":[""],"(no name)":["(kein Name)"],"author":[""],"authors":[""],"No authors found for this post.":[""],"Preview author":[""],"Previewing 1 of %d authors. All authors display on frontend.":[""],"Square":[""],"1:1":[""],"4:3":[""],"16:9":[""],"3:4":[""],"9:16":[""],"Hide Controls":[""],"Remove navigation indicators from view.":[""],"Autoplay":["Autoplay"],"Automatically advance through slides.":[""],"Transition delay":[""],"Set the waiting time between automatic slide transitions in seconds.":[""],"Slides per view":[""],"Choose how many slides appear on screen simultaneously.":[""],"Featured Image":[""],"Aspect ratio":[""],"All slides will share the same aspect ratio, for consistency.":[""],"Fit":[""],"The image will fill the entire slide and will be cropped if necessary.":[""],"The image will be resized to fit inside the slide without being cropped.":[""],"Cover":[""],"Contain":[""],"Show caption":[""],"Show credit":[""],"Post Meta":[""],"Show title":[""],"Show date":[""],"Show category":[""],"Show tag labels":[""],"Show author":[""],"Show avatar":[""],"Content Carousel":[""],"posts":["Beitr\u00e4ge"],"articles":["Artikel"],"latest":["letzten"],"query":[""],"An advanced block that displays content in a carousel format with customizable parameters and visual configurations.":[""],"The post content.":[""],"The post excerpt.":[""],"Full post content.":[""],"Post Subtitle":[""],"Post Title":[""],"by":["von"],"Category":[""],"Featured image caption":[""],"Biographical info":[""],"Social links":[""],"Email address":[""],"Link to author archive":[""],"Display the following fields:":[""],"Listings":[""],"Post Types":[""],"Additional Post Statuses":[""],"Selection here has effect only for editors, regular users will only see published posts.":[""],"Move Up":[""],"Move Down":[""],"(no title)":["(kein Titel)"],"%d item ready to reorder.":[""],"%1$s moved to position %2$d of %3$d.":[""],"Loading content\u2026":[""],"The content could not be loaded, so the order cannot be saved.":[""],"Save":[""],"Discard the new order?":[""],"Discard":[""],"Keep editing":[""],"The order you set will be lost.":[""],"Reorder Content":["Inhalt"],"Content":["Inhalt"],"Begin typing any word in a title. Click on an autocomplete result to select it.":[""],"Pick at least two items to reorder them.":[""],"Unavailable content (%d)":[""],"Deleted category":[""],"Deleted tag":[""],"Mode":[""],"The block will display only the specifically selected content.":[""],"The block will display content based on the filtering settings below.":[""],"Dynamic":[""],"Static":[""],"is one of":[""],"is all of":[""],"Include subcategories":[""],"Tags":["Schlagw\u00f6rter"],"Authors":["Autoren"],"Excluded categories":[""],"Excluded tags":[""],"Excluded %s":[""],"Allow duplicate content":[""],"Exclude this block from the page's deduplication logic.":[""],"Post-Checkout Button":[""],"After a successful purchase, a button will be presented to finish the process.":[""],"Close the modal":[""],"Go to a custom URL":[""],"Go to the previous page":[""],"Button Label":[""],"Custom URL":[""],"https:\/\/example.com":[""],"post author\u0004and":[""],"Author List":[""],"profile":[""],"Display a list of author profile cards.":[""],"Error fetching authors.":[""],"Author List Settings":[""],"Author Type":[""],"%s will be displayed.":[""],"Both guest authors and WP users":[""],"%s only":[""],"Guest authors":[""],"WP users":[""],"All authors":[""],"Columns":["Spalten"],"WP User Roles":[""],"Display alphabetical separators":[""],"Chunk authors alphabetically.":[""],"Group authors by alphabet":[""],"Display each alphabetical chunk as a discrete section.":[""],"Exclude authors with 0 posts":[""],"Authors with no published posts will be %s.":[""],"hidden":[""],"displayed":[""],"Search by author name":[""],"Authors selected here will not be displayed.":[""],"Author Profile Settings":[""],"Text Size":[""],"List":[""],"Author Name":[""],"Job Title":[""],"Role":[""],"Employer":[""],"Biography":[""],"Avatar left":[""],"Avatar on the left, content on the right.":[""],"Avatar right":[""],"Content on the left, avatar on the right.":[""],"Centered":[""],"Large centered avatar with center-aligned text.":[""],"Compact":[""],"No avatar, vertical stack.":[""],"Display an author profile card.":[""],"Coupon":[""],"Edit":[""],"Remove":[""],"Coupons are not available":[""],"Type to search for a coupon\u2026":[""],"Width":[""],"Could not find a product with ID %s. Please select a different product.":[""],"Product":[""],"Change the selected product":[""],"Type to search for a product\u2026":[""],"Allow the reader to select the variation before checkout.":[""],"Variation":[""],"Select the product variation to be added to cart.":[""],"Donation products do not support coupons. \"%s\" will not be applied.":[""],"Donation products do not support coupons.":[""],"After purchase":[""],"Name Your Price":[""],"This product has \"Name Your Price\" toggled on. You can set the custom price for this checkout.":[""],"Suggested price:":[""],"Minimum price:":[""],"Maximum price:":[""],"Custom Price":[""],"Seats":[""],"Optional. Pre-fill the number of seats. Readers can change it before paying.":[""],"Default seats":["Standard"],"per `Frequency`\u0004per %s":[""],"Frequency":[""],"Tiers":[""],"Heading\u2026":[""],"\u2026":[""],"Button text\u2026":[""],"Description\u2026":[""],"Thank you text\u2026":[""],"Low-tier":[""],"Mid-tier":[""],"High-tier":[""],"donate":["Spenden"],"memberships":["Mitgliedschaften"],"subscriptions":["Abonnements"],"Manually place a donation block on any post or page on your site.":[""],"Support reference":[""],"Could not fetch the donation settings.":[""],"Error":["Fehler"],"Go to checkout & payment settings to troubleshoot.":[""],"The Donate block will not be rendered.":[""],"The Reader Revenue platform is set to \"other\".":[""],"Go to checkout & payment settings to update the platform.":[""],"Grid View":[""],"List View":[""],"Warning: suggested donations should be at least the minimum donation amount (%1$s%2$d).":[""],"Layout":[""],"Tiers layout is disabled if the block is set to render untiered.":[""],"Default Tab":[""],"Suggested Donations":[""],"Configure manually":[""],"Tiered":[""],"Minimum donation":[""],"The Donate Block allows you to collect donations from readers. The fields are automatically defined based on your donation settings.":[""],"Edit donation settings":[""],"Styling":[""],"Button Color":[""],"News Revenue Hub Settings":[""],"Campaign ID":[""],"Alternate":[""],"Minimal":[""],"Modern":[""],"Grid":[""],"Show \"Load more posts\" button":[""],"This site is private, therefore this feature is not active.":[""],"Infinite scroll":[""],"Text":[""],"None":[""],"Excerpt":[""],"Full Post":[""],"Max number of words in excerpt":[""],"Keep reading":[""],"Show featured image":[""],"Stack on mobile":[""],"Minimum height":["Mindesth\u00f6he"],"Sets a minimum height for the block, using a percentage of the screen's current height.":[""],"Show subtitle":[""],"Color":[""],"Typography":[""],"Type Scale":[""],"Dimensions":[""],"Grid Spacing":[""],"List view":[""],"Grid view":[""],"Show media on top":[""],"Show media on left":[""],"Show media on right":[""],"Show media behind":[""],"Landscape image shape":[""],"portrait image shape":[""],"Square image shape":[""],"Uncropped":[""],"Write header\u2026":[""],"Sorry, no posts were found.":["Entschuldigung, wir konnten keine Beitr\u00e4ge finden."],"Content Loop":[""],"homepage":[""],"An advanced block that allows displaying content based on different parameters and visual configurations.":[""],"block style\u0004Borders":["Rahmen"],"Upload a document file (PDF, Word, Excel sheet, or a PPT), choose one from the media library, %s.":[""],"embed from a URL, or upload a .zip archive containing HTML assets":[""],"or embed from a URL":[""],"Upload":[""],"Media Library":[""],"Update from URL":[""],"Embed from URL":[""],"Iframe":[""],"iframe":[""],"project iframe":[""],"Embed an iframe.":[""],"Hide iframe preview":[""],"Show iframe preview":[""],"Iframe Settings":[""],"Fullscreen":[""],"If enabled, the iframe will be full screen and hide all the post content.":[""],"Height":[""],"This block will take over the page content.":[""],"Newspack embedded iframe":[""],"An error occured when uploading the iframe archive.":[""],"An error occured when setting the iframe from the archive media.":[""],"No videos found":[""],"Consider using <a href=\"%1$s\">YouTube Playlists<\/a> instead, which can be <a href=\"%2$s\">embedded<\/a> into post or page content.":[""],"YouTube Video Playlist (DEPRECATED)":[""],"video":[""],"playlist":[""],"youtube":[""],"Embed a playlist of latest YouTube videos.":[""]}}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"translation-revision-date":"2024-08-30 08:45-0700","generator":"WP-CLI\/2.12.0","source":"src\/components\/reorder-modal.js","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"de","plural-forms":"nplurals=2; plural=(n != 1);"},"Close":[""],"%1$s: %2$s":[""],"Cancel":[""],"Move Up":[""],"Move Down":[""],"(no title)":["(kein Titel)"],"%d item ready to reorder.":[""],"%1$s moved to position %2$d of %3$d.":[""],"Loading content\u2026":[""],"The content could not be loaded, so the order cannot be saved.":[""],"Save":[""],"Discard the new order?":[""],"Discard":[""],"Keep editing":[""],"The order you set will be lost.":[""]}}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"translation-revision-date":"2024-08-30 08:45-0700","generator":"WP-CLI\/2.12.0","source":"src\/blocks\/checkout-button\/edit.js","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"de","plural-forms":"nplurals=2; plural=(n != 1);"},"Cancel":[""],"Settings":[""],"Edit":[""],"Width":[""],"Could not find a product with ID %s. Please select a different product.":[""],"Product":[""],"Change the selected product":[""],"Type to search for a product\u2026":[""],"Allow the reader to select the variation before checkout.":[""],"Variation":[""],"Select the product variation to be added to cart.":[""],"Donation products do not support coupons. \"%s\" will not be applied.":[""],"Donation products do not support coupons.":[""],"After purchase":[""],"Name Your Price":[""],"This product has \"Name Your Price\" toggled on. You can set the custom price for this checkout.":[""],"Suggested price:":[""],"Minimum price:":[""],"Maximum price:":[""],"Custom Price":[""]}}}
|
|
1
|
+
{"translation-revision-date":"2024-08-30 08:45-0700","generator":"WP-CLI\/2.12.0","source":"src\/blocks\/checkout-button\/edit.js","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"de","plural-forms":"nplurals=2; plural=(n != 1);"},"Cancel":[""],"Settings":[""],"Edit":[""],"Width":[""],"Could not find a product with ID %s. Please select a different product.":[""],"Product":[""],"Change the selected product":[""],"Type to search for a product\u2026":[""],"Allow the reader to select the variation before checkout.":[""],"Variation":[""],"Select the product variation to be added to cart.":[""],"Donation products do not support coupons. \"%s\" will not be applied.":[""],"Donation products do not support coupons.":[""],"After purchase":[""],"Name Your Price":[""],"This product has \"Name Your Price\" toggled on. You can set the custom price for this checkout.":[""],"Suggested price:":[""],"Minimum price:":[""],"Maximum price:":[""],"Custom Price":[""],"Seats":[""],"Optional. Pre-fill the number of seats. Readers can change it before paying.":[""],"Default seats":["Standard"]}}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"translation-revision-date":"2024-08-30 08:45-0700","generator":"WP-CLI\/2.12.0","source":"src\/blocks\/iframe\/iframe-placeholder.js","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"de","plural-forms":"nplurals=2; plural=(n != 1);"},"Upload a document file (PDF, Word, Excel sheet, or a PPT), choose one from the media library, %s.":[""],"embed from a URL, or upload a .zip archive containing HTML assets":[""],"or embed from a URL":[""],"
|
|
1
|
+
{"translation-revision-date":"2024-08-30 08:45-0700","generator":"WP-CLI\/2.12.0","source":"src\/blocks\/iframe\/iframe-placeholder.js","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"de","plural-forms":"nplurals=2; plural=(n != 1);"},"Update":[""],"Upload a document file (PDF, Word, Excel sheet, or a PPT), choose one from the media library, %s.":[""],"embed from a URL, or upload a .zip archive containing HTML assets":[""],"or embed from a URL":[""],"Upload":[""],"Media Library":[""],"Update from URL":[""],"Embed from URL":[""]}}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"translation-revision-date":"2024-08-30 08:45-0700","generator":"WP-CLI\/2.12.0","source":"src\/components\/specific-posts-control.js","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"de","plural-forms":"nplurals=2; plural=(n != 1);"},"Reorder Content":["Inhalt"],"Content":["Inhalt"],"Begin typing any word in a title. Click on an autocomplete result to select it.":[""],"Pick at least two items to reorder them.":[""]}}}
|