Accordion
1 year ago
AddToCartWithOptions
1 week ago
OrderConfirmation
2 months ago
ProductCollection
3 weeks ago
Reviews
2 months ago
AbstractBlock.php
4 weeks ago
AbstractDynamicBlock.php
2 years ago
AbstractInnerBlock.php
1 year ago
AbstractProductGrid.php
3 months ago
ActiveFilters.php
2 years ago
AddToCartForm.php
4 weeks ago
AddToWishlistButton.php
4 weeks ago
AllProducts.php
2 years ago
AllReviews.php
2 years ago
AtomicBlock.php
2 years ago
AttributeFilter.php
1 year ago
Breadcrumbs.php
1 year ago
Cart.php
2 months ago
CartAcceptedPaymentMethodsBlock.php
2 years ago
CartCrossSellsBlock.php
2 years ago
CartCrossSellsProductsBlock.php
2 years ago
CartExpressPaymentBlock.php
1 year ago
CartItemsBlock.php
2 years ago
CartLineItemsBlock.php
2 years ago
CartLink.php
2 months ago
CartOrderSummaryBlock.php
2 years ago
CartOrderSummaryCouponFormBlock.php
2 years ago
CartOrderSummaryDiscountBlock.php
2 years ago
CartOrderSummaryFeeBlock.php
2 years ago
CartOrderSummaryHeadingBlock.php
2 years ago
CartOrderSummaryShippingBlock.php
2 years ago
CartOrderSummarySubtotalBlock.php
2 years ago
CartOrderSummaryTaxesBlock.php
2 years ago
CartOrderSummaryTotalsBlock.php
2 years ago
CartTotalsBlock.php
2 years ago
CatalogSorting.php
4 weeks ago
CategoryDescription.php
2 months ago
CategoryTitle.php
2 months ago
Checkout.php
2 months ago
CheckoutActionsBlock.php
1 year ago
CheckoutAdditionalInformationBlock.php
2 years ago
CheckoutBillingAddressBlock.php
2 years ago
CheckoutContactInformationBlock.php
2 years ago
CheckoutExpressPaymentBlock.php
9 months ago
CheckoutFieldsBlock.php
2 years ago
CheckoutOrderNoteBlock.php
2 years ago
CheckoutOrderSummaryBlock.php
2 years ago
CheckoutOrderSummaryCartItemsBlock.php
2 years ago
CheckoutOrderSummaryCouponFormBlock.php
2 years ago
CheckoutOrderSummaryDiscountBlock.php
2 years ago
CheckoutOrderSummaryFeeBlock.php
2 years ago
CheckoutOrderSummaryShippingBlock.php
2 years ago
CheckoutOrderSummarySubtotalBlock.php
2 years ago
CheckoutOrderSummaryTaxesBlock.php
2 years ago
CheckoutOrderSummaryTotalsBlock.php
2 years ago
CheckoutPaymentBlock.php
2 years ago
CheckoutPickupOptionsBlock.php
2 years ago
CheckoutShippingAddressBlock.php
2 years ago
CheckoutShippingMethodBlock.php
2 years ago
CheckoutShippingMethodsBlock.php
2 years ago
CheckoutTermsBlock.php
2 years ago
CheckoutTotalsBlock.php
2 years ago
ClassicShortcode.php
1 year ago
ClassicTemplate.php
4 months ago
ComingSoon.php
1 year ago
CouponCode.php
2 months ago
CustomerAccount.php
3 months ago
Dropdown.php
4 weeks ago
EmailContent.php
5 months ago
EmptyCartBlock.php
2 years ago
EmptyMiniCartContentsBlock.php
11 months ago
EnableBlockJsonAssetsTrait.php
1 year ago
FeaturedCategory.php
7 months ago
FeaturedItem.php
7 months ago
FeaturedProduct.php
7 months ago
FilledCartBlock.php
2 years ago
FilledMiniCartContentsBlock.php
4 months ago
FilterWrapper.php
2 years ago
HandpickedProducts.php
1 year ago
MiniCart.php
4 weeks ago
MiniCartCartButtonBlock.php
11 months ago
MiniCartCheckoutButtonBlock.php
11 months ago
MiniCartContents.php
3 months ago
MiniCartFooterBlock.php
4 weeks ago
MiniCartItemsBlock.php
11 months ago
MiniCartProductsTableBlock.php
4 weeks ago
MiniCartShoppingButtonBlock.php
9 months ago
MiniCartTitleBlock.php
11 months ago
MiniCartTitleItemsCounterBlock.php
11 months ago
MiniCartTitleLabelBlock.php
11 months ago
NextPreviousButtons.php
5 months ago
PageContentWrapper.php
2 years ago
PaymentMethodIcons.php
11 months ago
PriceFilter.php
1 year ago
ProceedToCheckoutBlock.php
2 years ago
ProductAverageRating.php
1 year ago
ProductBestSellers.php
2 years ago
ProductButton.php
2 months ago
ProductCategories.php
4 weeks ago
ProductCategory.php
2 years ago
ProductDescription.php
9 months ago
ProductDetails.php
5 months ago
ProductFilterActive.php
4 weeks ago
ProductFilterAttribute.php
4 weeks ago
ProductFilterCheckboxList.php
4 weeks ago
ProductFilterChips.php
4 weeks ago
ProductFilterClearButton.php
4 weeks ago
ProductFilterPrice.php
4 weeks ago
ProductFilterPriceSlider.php
4 weeks ago
ProductFilterRating.php
4 weeks ago
ProductFilterRemovableChips.php
4 weeks ago
ProductFilterStatus.php
4 weeks ago
ProductFilterTaxonomy.php
4 weeks ago
ProductFilters.php
3 weeks ago
ProductGallery.php
4 weeks ago
ProductGalleryLargeImage.php
4 weeks ago
ProductGalleryThumbnails.php
4 weeks ago
ProductImage.php
4 weeks ago
ProductImageGallery.php
7 months ago
ProductMeta.php
1 year ago
ProductNew.php
2 years ago
ProductOnSale.php
2 years ago
ProductPrice.php
2 months ago
ProductQuery.php
4 weeks ago
ProductRating.php
1 year ago
ProductRatingCounter.php
1 year ago
ProductRatingStars.php
1 year ago
ProductResultsCount.php
2 months ago
ProductSKU.php
2 months ago
ProductSaleBadge.php
1 year ago
ProductSearch.php
10 months ago
ProductSpecifications.php
2 months ago
ProductStockIndicator.php
2 months ago
ProductSummary.php
7 months ago
ProductTag.php
2 years ago
ProductTemplate.php
2 months ago
ProductTitle.php
1 year ago
ProductTopRated.php
2 years ago
ProductsByAttribute.php
2 years ago
RatingFilter.php
2 years ago
RelatedProducts.php
3 months ago
ReviewsByCategory.php
2 years ago
ReviewsByProduct.php
2 years ago
SavedForLater.php
4 weeks ago
SingleProduct.php
2 months ago
StockFilter.php
2 years ago
StoreNotices.php
1 year ago
Wishlist.php
4 weeks ago
Checkout.php
670 lines
| 1 | <?php |
| 2 | declare( strict_types = 1); |
| 3 | namespace Automattic\WooCommerce\Blocks\BlockTypes; |
| 4 | |
| 5 | use Automattic\Block_Scanner; |
| 6 | use Automattic\WooCommerce\StoreApi\Utilities\LocalPickupUtils; |
| 7 | use Automattic\WooCommerce\Blocks\Utils\CartCheckoutUtils; |
| 8 | use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields; |
| 9 | use Automattic\WooCommerce\Blocks\Package; |
| 10 | use Automattic\WooCommerce\StoreApi\Utilities\PaymentUtils; |
| 11 | use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFieldsSchema\Validation; |
| 12 | use Automattic\WooCommerce\Internal\AddressProvider\AddressProviderController; |
| 13 | |
| 14 | /** |
| 15 | * Checkout class. |
| 16 | * |
| 17 | * @internal |
| 18 | */ |
| 19 | class Checkout extends AbstractBlock { |
| 20 | /** |
| 21 | * Block name. |
| 22 | * |
| 23 | * @var string |
| 24 | */ |
| 25 | protected $block_name = 'checkout'; |
| 26 | |
| 27 | /** |
| 28 | * Chunks build folder. |
| 29 | * |
| 30 | * @var string |
| 31 | */ |
| 32 | protected $chunks_folder = 'checkout-blocks'; |
| 33 | |
| 34 | /** |
| 35 | * Initialize this block type. |
| 36 | * |
| 37 | * - Hook into WP lifecycle. |
| 38 | * - Register the block with WordPress. |
| 39 | */ |
| 40 | protected function initialize() { |
| 41 | parent::initialize(); |
| 42 | add_action( 'rest_api_init', array( $this, 'register_settings' ) ); |
| 43 | add_action( 'wp_loaded', array( $this, 'register_patterns' ) ); |
| 44 | add_action( 'wp', array( $this, 'disable_wp_emoji' ) ); |
| 45 | // This prevents the page redirecting when the cart is empty. This is so the editor still loads the page preview. |
| 46 | add_filter( |
| 47 | 'woocommerce_checkout_redirect_empty_cart', |
| 48 | function ( $redirect_empty_cart ) { |
| 49 | // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 50 | return isset( $_GET['_wp-find-template'] ) ? false : $redirect_empty_cart; |
| 51 | } |
| 52 | ); |
| 53 | |
| 54 | add_action( 'save_post', array( $this, 'update_local_pickup_title' ), 10, 2 ); |
| 55 | } |
| 56 | |
| 57 | /** |
| 58 | * Remove WordPress emoji detection script on pages containing this block. |
| 59 | * |
| 60 | * The wp-emoji MutationObserver converts emoji text nodes to <img> elements |
| 61 | * when React hydrates or re-renders, corrupting the DOM tree and crashing the block. |
| 62 | * The wp-exclude-emoji class on the wrapper only prevents the initial parse, not the |
| 63 | * MutationObserver, so the script must be removed entirely. |
| 64 | * |
| 65 | * @since 10.8.0 |
| 66 | * |
| 67 | * @return void |
| 68 | */ |
| 69 | public function disable_wp_emoji() { |
| 70 | if ( has_block( $this->get_full_block_name() ) ) { |
| 71 | remove_action( 'wp_head', 'print_emoji_detection_script', 7 ); |
| 72 | remove_action( 'wp_print_styles', 'print_emoji_styles' ); |
| 73 | } |
| 74 | } |
| 75 | |
| 76 | /** |
| 77 | * Dequeues the scripts added by WC Core to the Checkout page. |
| 78 | * |
| 79 | * @return void |
| 80 | */ |
| 81 | public function dequeue_woocommerce_core_scripts() { |
| 82 | wp_dequeue_script( 'wc-checkout' ); |
| 83 | wp_dequeue_script( 'wc-address-autocomplete' ); |
| 84 | wp_dequeue_style( 'wc-address-autocomplete' ); |
| 85 | wp_dequeue_script( 'wc-password-strength-meter' ); |
| 86 | wp_dequeue_script( 'selectWoo' ); |
| 87 | wp_dequeue_style( 'select2' ); |
| 88 | } |
| 89 | |
| 90 | /** |
| 91 | * Exposes settings exposed by the checkout block. |
| 92 | */ |
| 93 | public function register_settings() { |
| 94 | register_setting( |
| 95 | 'options', |
| 96 | 'woocommerce_checkout_phone_field', |
| 97 | array( |
| 98 | 'type' => 'object', |
| 99 | 'description' => __( 'Controls the display of the phone field in checkout.', 'woocommerce' ), |
| 100 | 'label' => __( 'Phone number', 'woocommerce' ), |
| 101 | 'show_in_rest' => array( |
| 102 | 'name' => 'woocommerce_checkout_phone_field', |
| 103 | 'schema' => array( |
| 104 | 'type' => 'string', |
| 105 | 'enum' => array( 'optional', 'required', 'hidden' ), |
| 106 | ), |
| 107 | ), |
| 108 | 'default' => CartCheckoutUtils::get_phone_field_visibility(), |
| 109 | ) |
| 110 | ); |
| 111 | register_setting( |
| 112 | 'options', |
| 113 | 'woocommerce_checkout_company_field', |
| 114 | array( |
| 115 | 'type' => 'object', |
| 116 | 'description' => __( 'Controls the display of the company field in checkout.', 'woocommerce' ), |
| 117 | 'label' => __( 'Company', 'woocommerce' ), |
| 118 | 'show_in_rest' => array( |
| 119 | 'name' => 'woocommerce_checkout_company_field', |
| 120 | 'schema' => array( |
| 121 | 'type' => 'string', |
| 122 | 'enum' => array( 'optional', 'required', 'hidden' ), |
| 123 | ), |
| 124 | ), |
| 125 | 'default' => CartCheckoutUtils::get_company_field_visibility(), |
| 126 | ) |
| 127 | ); |
| 128 | register_setting( |
| 129 | 'options', |
| 130 | 'woocommerce_checkout_address_2_field', |
| 131 | array( |
| 132 | 'type' => 'object', |
| 133 | 'description' => __( 'Controls the display of the apartment (address_2) field in checkout.', 'woocommerce' ), |
| 134 | 'label' => __( 'Address Line 2', 'woocommerce' ), |
| 135 | 'show_in_rest' => array( |
| 136 | 'name' => 'woocommerce_checkout_address_2_field', |
| 137 | 'schema' => array( |
| 138 | 'type' => 'string', |
| 139 | 'enum' => array( 'optional', 'required', 'hidden' ), |
| 140 | ), |
| 141 | ), |
| 142 | 'default' => CartCheckoutUtils::get_address_2_field_visibility(), |
| 143 | ) |
| 144 | ); |
| 145 | } |
| 146 | |
| 147 | /** |
| 148 | * Register block pattern for Empty Cart Message to make it translatable. |
| 149 | */ |
| 150 | public function register_patterns() { |
| 151 | register_block_pattern( |
| 152 | 'woocommerce/checkout-heading', |
| 153 | array( |
| 154 | 'title' => '', |
| 155 | 'inserter' => false, |
| 156 | 'content' => '<!-- wp:heading {"align":"wide", "level":1} --><h1 class="wp-block-heading alignwide">' . esc_html__( 'Checkout', 'woocommerce' ) . '</h1><!-- /wp:heading -->', |
| 157 | ) |
| 158 | ); |
| 159 | } |
| 160 | |
| 161 | /** |
| 162 | * Get the editor script handle for this block type. |
| 163 | * |
| 164 | * @param string $key Data to get, or default to everything. |
| 165 | * @return array|string; |
| 166 | */ |
| 167 | protected function get_block_type_editor_script( $key = null ) { |
| 168 | $script = [ |
| 169 | 'handle' => 'wc-' . $this->block_name . '-block', |
| 170 | 'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ), |
| 171 | 'dependencies' => [ 'wc-blocks' ], |
| 172 | ]; |
| 173 | return $key ? $script[ $key ] : $script; |
| 174 | } |
| 175 | |
| 176 | /** |
| 177 | * Get the frontend script handle for this block type. |
| 178 | * |
| 179 | * @see $this->register_block_type() |
| 180 | * @param string $key Data to get, or default to everything. |
| 181 | * @return array|string |
| 182 | */ |
| 183 | protected function get_block_type_script( $key = null ) { |
| 184 | $dependencies = []; |
| 185 | |
| 186 | // Load password strength meter script asynchronously if needed. |
| 187 | if ( ! is_user_logged_in() && 'no' === get_option( 'woocommerce_registration_generate_password' ) ) { |
| 188 | $dependencies[] = 'zxcvbn-async'; |
| 189 | } |
| 190 | |
| 191 | $checkout_fields = Package::container()->get( CheckoutFields::class ); |
| 192 | // Load schema parser asynchronously if we need it. |
| 193 | if ( Validation::has_field_schema( $checkout_fields->get_additional_fields() ) ) { |
| 194 | $dependencies[] = 'wc-schema-parser'; |
| 195 | } |
| 196 | |
| 197 | $script = [ |
| 198 | 'handle' => 'wc-' . $this->block_name . '-block-frontend', |
| 199 | 'path' => $this->asset_api->get_block_asset_build_path( $this->block_name . '-frontend' ), |
| 200 | 'dependencies' => $dependencies, |
| 201 | ]; |
| 202 | return $key ? $script[ $key ] : $script; |
| 203 | } |
| 204 | |
| 205 | /** |
| 206 | * Get the frontend style handle for this block type. |
| 207 | * |
| 208 | * @return string[] |
| 209 | */ |
| 210 | protected function get_block_type_style() { |
| 211 | return array_merge( parent::get_block_type_style(), [ 'wc-blocks-packages-style' ] ); |
| 212 | } |
| 213 | |
| 214 | /** |
| 215 | * Enqueue frontend assets for this block, just in time for rendering. |
| 216 | * |
| 217 | * @param array $attributes Any attributes that currently are available from the block. |
| 218 | * @param string $content The block content. |
| 219 | * @param WP_Block $block The block object. |
| 220 | */ |
| 221 | protected function enqueue_assets( array $attributes, $content, $block ) { |
| 222 | /** |
| 223 | * Fires before checkout block scripts are enqueued. |
| 224 | * |
| 225 | * @since 4.6.0 |
| 226 | */ |
| 227 | do_action( 'woocommerce_blocks_enqueue_checkout_block_scripts_before' ); |
| 228 | parent::enqueue_assets( $attributes, $content, $block ); |
| 229 | /** |
| 230 | * Fires after checkout block scripts are enqueued. |
| 231 | * |
| 232 | * @since 4.6.0 |
| 233 | */ |
| 234 | do_action( 'woocommerce_blocks_enqueue_checkout_block_scripts_after' ); |
| 235 | } |
| 236 | |
| 237 | /** |
| 238 | * Append frontend scripts when rendering the block. |
| 239 | * |
| 240 | * @param array $attributes Block attributes. |
| 241 | * @param string $content Block content. |
| 242 | * @param WP_Block $block Block instance. |
| 243 | * @return string Rendered block type output. |
| 244 | */ |
| 245 | protected function render( $attributes, $content, $block ) { |
| 246 | |
| 247 | if ( $this->is_checkout_endpoint() ) { |
| 248 | // Note: Currently the block only takes care of the main checkout form -- if an endpoint is set, refer to the |
| 249 | // legacy shortcode instead and do not render block. |
| 250 | return wp_is_block_theme() ? do_shortcode( '[woocommerce_checkout]' ) : '[woocommerce_checkout]'; |
| 251 | } |
| 252 | |
| 253 | // Dequeue the core scripts when rendering this block. |
| 254 | add_action( 'wp_enqueue_scripts', array( $this, 'dequeue_woocommerce_core_scripts' ), 20 ); |
| 255 | |
| 256 | /** |
| 257 | * We need to check if $content has any templates from prior iterations of the block, in order to update to the latest iteration. |
| 258 | * We test the iteration version by searching for new blocks brought in by it. |
| 259 | * The blocks used for testing should be always available in the block (not removable by the user). |
| 260 | * Checkout i1's content was returning an empty div, with no data-block-name attribute |
| 261 | */ |
| 262 | $regex_for_empty_block = '/<div class="[a-zA-Z0-9_\- ]*wp-block-woocommerce-checkout[a-zA-Z0-9_\- ]*"><\/div>/mi'; |
| 263 | $has_i1_template = preg_match( $regex_for_empty_block, $content ); |
| 264 | |
| 265 | if ( $has_i1_template ) { |
| 266 | // This fallback needs to match the default templates defined in our Blocks. |
| 267 | $inner_blocks_html = ' |
| 268 | <div data-block-name="woocommerce/checkout-fields-block" class="wp-block-woocommerce-checkout-fields-block"> |
| 269 | <div data-block-name="woocommerce/checkout-express-payment-block" class="wp-block-woocommerce-checkout-express-payment-block"></div> |
| 270 | <div data-block-name="woocommerce/checkout-contact-information-block" class="wp-block-woocommerce-checkout-contact-information-block"></div> |
| 271 | <div data-block-name="woocommerce/checkout-shipping-address-block" class="wp-block-woocommerce-checkout-shipping-address-block"></div> |
| 272 | <div data-block-name="woocommerce/checkout-billing-address-block" class="wp-block-woocommerce-checkout-billing-address-block"></div> |
| 273 | <div data-block-name="woocommerce/checkout-shipping-methods-block" class="wp-block-woocommerce-checkout-shipping-methods-block"></div> |
| 274 | <div data-block-name="woocommerce/checkout-payment-block" class="wp-block-woocommerce-checkout-payment-block"></div> |
| 275 | <div data-block-name="woocommerce/checkout-additional-information-block" class="wp-block-woocommerce-checkout-additional-information-block"></div>' . |
| 276 | ( isset( $attributes['showOrderNotes'] ) && false === $attributes['showOrderNotes'] ? '' : '<div data-block-name="woocommerce/checkout-order-note-block" class="wp-block-woocommerce-checkout-order-note-block"></div>' ) . |
| 277 | ( isset( $attributes['showPolicyLinks'] ) && false === $attributes['showPolicyLinks'] ? '' : '<div data-block-name="woocommerce/checkout-terms-block" class="wp-block-woocommerce-checkout-terms-block"></div>' ) . |
| 278 | '<div data-block-name="woocommerce/checkout-actions-block" class="wp-block-woocommerce-checkout-actions-block"></div> |
| 279 | </div> |
| 280 | <div data-block-name="woocommerce/checkout-totals-block" class="wp-block-woocommerce-checkout-totals-block"> |
| 281 | <div data-block-name="woocommerce/checkout-order-summary-block" class="wp-block-woocommerce-checkout-order-summary-block"></div> |
| 282 | </div> |
| 283 | '; |
| 284 | |
| 285 | $content = str_replace( '</div>', $inner_blocks_html . '</div>', $content ); |
| 286 | } |
| 287 | |
| 288 | /** |
| 289 | * Checkout i3 added inner blocks for Order summary. |
| 290 | * We need to add them to Checkout i2 templates. |
| 291 | * The order needs to match the order in which these blocks were registered. |
| 292 | */ |
| 293 | $order_summary_with_inner_blocks = '$0 |
| 294 | <div data-block-name="woocommerce/checkout-order-summary-cart-items-block" class="wp-block-woocommerce-checkout-order-summary-cart-items-block"></div> |
| 295 | <div data-block-name="woocommerce/checkout-order-summary-subtotal-block" class="wp-block-woocommerce-checkout-order-summary-subtotal-block"></div> |
| 296 | <div data-block-name="woocommerce/checkout-order-summary-fee-block" class="wp-block-woocommerce-checkout-order-summary-fee-block"></div> |
| 297 | <div data-block-name="woocommerce/checkout-order-summary-discount-block" class="wp-block-woocommerce-checkout-order-summary-discount-block"></div> |
| 298 | <div data-block-name="woocommerce/checkout-order-summary-coupon-form-block" class="wp-block-woocommerce-checkout-order-summary-coupon-form-block"></div> |
| 299 | <div data-block-name="woocommerce/checkout-order-summary-shipping-block" class="wp-block-woocommerce-checkout-order-summary-shipping-block"></div> |
| 300 | <div data-block-name="woocommerce/checkout-order-summary-taxes-block" class="wp-block-woocommerce-checkout-order-summary-taxes-block"></div> |
| 301 | '; |
| 302 | // Order summary subtotal block was added in i3, so we search for it to see if we have a Checkout i2 template. |
| 303 | $regex_for_order_summary_subtotal = '/<div[^<]*?data-block-name="woocommerce\/checkout-order-summary-subtotal-block"[^>]*?>/mi'; |
| 304 | $regex_for_order_summary = '/<div[^<]*?data-block-name="woocommerce\/checkout-order-summary-block"[^>]*?>/mi'; |
| 305 | $has_i2_template = ! preg_match( $regex_for_order_summary_subtotal, $content ); |
| 306 | |
| 307 | if ( $has_i2_template ) { |
| 308 | $content = preg_replace( $regex_for_order_summary, $order_summary_with_inner_blocks, $content ); |
| 309 | } |
| 310 | |
| 311 | /** |
| 312 | * Add the Local Pickup toggle to checkouts missing this forced template. |
| 313 | */ |
| 314 | $local_pickup_inner_blocks = '<div data-block-name="woocommerce/checkout-shipping-method-block" class="wp-block-woocommerce-checkout-shipping-method-block"></div>' . PHP_EOL . PHP_EOL . '<div data-block-name="woocommerce/checkout-pickup-options-block" class="wp-block-woocommerce-checkout-pickup-options-block"></div>' . PHP_EOL . PHP_EOL . '$0'; |
| 315 | $has_local_pickup_regex = '/<div[^<]*?data-block-name="woocommerce\/checkout-shipping-method-block"[^>]*?>/mi'; |
| 316 | $has_local_pickup = preg_match( $has_local_pickup_regex, $content ); |
| 317 | |
| 318 | if ( ! $has_local_pickup ) { |
| 319 | $shipping_address_block_regex = '/<div[^<]*?data-block-name="woocommerce\/checkout-shipping-address-block" class="wp-block-woocommerce-checkout-shipping-address-block"[^>]*?><\/div>/mi'; |
| 320 | $content = preg_replace( $shipping_address_block_regex, $local_pickup_inner_blocks, $content ); |
| 321 | } |
| 322 | |
| 323 | /** |
| 324 | * Add the Additional Information block to checkouts missing it. |
| 325 | */ |
| 326 | $additional_information_inner_blocks = '$0' . PHP_EOL . PHP_EOL . '<div data-block-name="woocommerce/checkout-additional-information-block" class="wp-block-woocommerce-checkout-additional-information-block"></div>' . PHP_EOL . PHP_EOL; |
| 327 | $has_additional_information_regex = '/<div[^<]*?data-block-name="woocommerce\/checkout-additional-information-block"[^>]*?>/mi'; |
| 328 | $has_additional_information_block = preg_match( $has_additional_information_regex, $content ); |
| 329 | |
| 330 | if ( ! $has_additional_information_block ) { |
| 331 | $payment_block_regex = '/<div[^<]*?data-block-name="woocommerce\/checkout-payment-block" class="wp-block-woocommerce-checkout-payment-block"[^>]*?><\/div>/mi'; |
| 332 | $content = preg_replace( $payment_block_regex, $additional_information_inner_blocks, $content ); |
| 333 | } |
| 334 | |
| 335 | return $content; |
| 336 | } |
| 337 | |
| 338 | /** |
| 339 | * Check if we're viewing a checkout page endpoint, rather than the main checkout page itself. |
| 340 | * |
| 341 | * @return boolean |
| 342 | */ |
| 343 | protected function is_checkout_endpoint() { |
| 344 | return is_wc_endpoint_url( 'order-pay' ) || is_wc_endpoint_url( 'order-received' ); |
| 345 | } |
| 346 | |
| 347 | /** |
| 348 | * Update the local pickup title in WooCommerce Settings when the checkout page containing a Checkout block is saved. |
| 349 | * |
| 350 | * @param int $post_id The post ID. |
| 351 | * @param \WP_Post $post The post object. |
| 352 | * @return void |
| 353 | */ |
| 354 | public function update_local_pickup_title( $post_id, $post ) { |
| 355 | |
| 356 | // This is not a proper save action, maybe an autosave, so don't continue. |
| 357 | if ( empty( $post->post_status ) || 'inherit' === $post->post_status ) { |
| 358 | return; |
| 359 | } |
| 360 | |
| 361 | // Check if we are editing the checkout page and that it contains a Checkout block. |
| 362 | // Cast to string for Checkout page ID comparison because get_option can return it as a string, so better to compare both values as strings. |
| 363 | if ( ! empty( $post->post_type ) && 'wp_template' !== $post->post_type && ( false === has_block( 'woocommerce/checkout', $post ) || (string) get_option( 'woocommerce_checkout_page_id' ) !== (string) $post_id ) ) { |
| 364 | return; |
| 365 | } |
| 366 | |
| 367 | if ( ( ! empty( $post->post_type ) && ! empty( $post->post_name ) && 'page-checkout' !== $post->post_name && 'wp_template' === $post->post_type ) || false === has_block( 'woocommerce/checkout', $post ) ) { |
| 368 | return; |
| 369 | } |
| 370 | $pickup_location_settings = LocalPickupUtils::get_local_pickup_settings( 'edit' ); |
| 371 | |
| 372 | if ( ! isset( $pickup_location_settings['title'] ) ) { |
| 373 | return; |
| 374 | } |
| 375 | |
| 376 | if ( empty( $post->post_content ) ) { |
| 377 | return; |
| 378 | } |
| 379 | |
| 380 | $title = $this->find_local_pickup_text_in_checkout_block( $post->post_content ); |
| 381 | |
| 382 | // Set the title to be an empty string if it isn't a string. This will make it fall back to the default value of "Pickup". |
| 383 | if ( ! is_string( $title ) ) { |
| 384 | $title = ''; |
| 385 | } |
| 386 | |
| 387 | $pickup_location_settings['title'] = $title; |
| 388 | update_option( 'woocommerce_pickup_location_settings', $pickup_location_settings ); |
| 389 | } |
| 390 | |
| 391 | /** |
| 392 | * Find the shipping methods block, then get the value of the localPickupText attribute from it. |
| 393 | * |
| 394 | * @param string $post_content The post content to search through. |
| 395 | * @return null|string The local pickup text if found, otherwise null. |
| 396 | */ |
| 397 | private function find_local_pickup_text_in_checkout_block( $post_content ) { |
| 398 | $scanner = Block_Scanner::create( $post_content ); |
| 399 | |
| 400 | while ( $scanner->next_delimiter() ) { |
| 401 | if ( $scanner->opens_block( 'woocommerce/checkout-shipping-method-block' ) ) { |
| 402 | $attributes = $scanner->allocate_and_return_parsed_attributes(); |
| 403 | if ( isset( $attributes['localPickupText'] ) ) { |
| 404 | return $attributes['localPickupText']; |
| 405 | } |
| 406 | } |
| 407 | } |
| 408 | |
| 409 | return null; |
| 410 | } |
| 411 | |
| 412 | /** |
| 413 | * Extra data passed through from server to client for block. |
| 414 | * |
| 415 | * @param array $attributes Any attributes that currently are available from the block. |
| 416 | * Note, this will be empty in the editor context when the block is |
| 417 | * not in the post content on editor load. |
| 418 | */ |
| 419 | protected function enqueue_data( array $attributes = [] ) { |
| 420 | parent::enqueue_data( $attributes ); |
| 421 | |
| 422 | $country_data = CartCheckoutUtils::get_country_data(); |
| 423 | $address_formats = WC()->countries->get_address_formats(); |
| 424 | |
| 425 | // Move the address format into the 'countryData' setting. |
| 426 | // We need to skip 'default' because that's not a valid country. |
| 427 | foreach ( $address_formats as $country_code => $format ) { |
| 428 | if ( 'default' === $country_code ) { |
| 429 | continue; |
| 430 | } |
| 431 | $country_data[ $country_code ]['format'] = $format; |
| 432 | } |
| 433 | |
| 434 | $providers_payload = []; |
| 435 | if ( class_exists( AddressProviderController::class ) && 'no' !== get_option( 'woocommerce_address_autocomplete_enabled', 'no' ) ) { |
| 436 | $controller = wc_get_container()->get( AddressProviderController::class ); |
| 437 | $providers = $controller->get_providers(); |
| 438 | $providers_payload = array_map( |
| 439 | static function ( $provider ) { |
| 440 | return array( |
| 441 | 'id' => (string) $provider->id, |
| 442 | 'name' => sanitize_text_field( (string) $provider->name ), |
| 443 | 'branding_html' => wp_kses_post( (string) $provider->branding_html ), |
| 444 | ); |
| 445 | }, |
| 446 | (array) $providers |
| 447 | ); |
| 448 | } |
| 449 | $this->asset_data_registry->add( 'addressAutocompleteProviders', $providers_payload ); |
| 450 | $this->asset_data_registry->add( 'countryData', $country_data ); |
| 451 | $this->asset_data_registry->add( 'defaultAddressFormat', $address_formats['default'] ); |
| 452 | |
| 453 | // Prime caches to reduce future queries. |
| 454 | wp_prime_option_caches( |
| 455 | array( |
| 456 | 'woocommerce_enable_guest_checkout', |
| 457 | 'woocommerce_enable_signup_and_login_from_checkout', |
| 458 | 'woocommerce_enable_checkout_login_reminder', |
| 459 | 'woocommerce_tax_display_cart', // This one is autoloaded, but we add it here for clarity. |
| 460 | 'woocommerce_tax_total_display', |
| 461 | 'woocommerce_ship_to_destination', |
| 462 | 'woocommerce_registration_generate_password', |
| 463 | 'pickup_location_pickup_locations', |
| 464 | ) |
| 465 | ); |
| 466 | |
| 467 | $this->asset_data_registry->add( |
| 468 | 'checkoutAllowsGuest', |
| 469 | false === filter_var( |
| 470 | wc()->checkout()->is_registration_required(), |
| 471 | FILTER_VALIDATE_BOOLEAN |
| 472 | ) |
| 473 | ); |
| 474 | $this->asset_data_registry->add( |
| 475 | 'checkoutAllowsSignup', |
| 476 | filter_var( |
| 477 | wc()->checkout()->is_registration_enabled(), |
| 478 | FILTER_VALIDATE_BOOLEAN |
| 479 | ) |
| 480 | ); |
| 481 | $this->asset_data_registry->add( 'checkoutShowLoginReminder', filter_var( get_option( 'woocommerce_enable_checkout_login_reminder' ), FILTER_VALIDATE_BOOLEAN ) ); |
| 482 | $this->asset_data_registry->add( 'displayCartPricesIncludingTax', 'incl' === get_option( 'woocommerce_tax_display_cart' ) ); |
| 483 | $this->asset_data_registry->add( 'displayItemizedTaxes', 'itemized' === get_option( 'woocommerce_tax_total_display' ) ); |
| 484 | $this->asset_data_registry->add( 'forcedBillingAddress', 'billing_only' === get_option( 'woocommerce_ship_to_destination' ) ); |
| 485 | $this->asset_data_registry->add( 'generatePassword', filter_var( get_option( 'woocommerce_registration_generate_password' ), FILTER_VALIDATE_BOOLEAN ) ); |
| 486 | $this->asset_data_registry->add( 'taxesEnabled', wc_tax_enabled() ); |
| 487 | $this->asset_data_registry->add( 'couponsEnabled', wc_coupons_enabled() ); |
| 488 | $this->asset_data_registry->add( 'shippingEnabled', wc_shipping_enabled() ); |
| 489 | $this->asset_data_registry->add( 'hasDarkEditorStyleSupport', current_theme_supports( 'dark-editor-style' ) ); |
| 490 | $this->asset_data_registry->register_page_id( isset( $attributes['cartPageId'] ) ? $attributes['cartPageId'] : 0 ); |
| 491 | $this->asset_data_registry->add( 'isBlockTheme', wp_is_block_theme() ); |
| 492 | $this->asset_data_registry->add( 'isCheckoutBlock', true ); |
| 493 | |
| 494 | $pickup_location_settings = LocalPickupUtils::get_local_pickup_settings(); |
| 495 | $local_pickup_method_ids = LocalPickupUtils::get_local_pickup_method_ids(); |
| 496 | |
| 497 | $this->asset_data_registry->add( 'localPickupEnabled', $pickup_location_settings['enabled'] ); |
| 498 | $this->asset_data_registry->add( 'localPickupText', $pickup_location_settings['title'] ); |
| 499 | $this->asset_data_registry->add( 'localPickupCost', $pickup_location_settings['cost'] ); |
| 500 | $this->asset_data_registry->add( 'collectableMethodIds', $local_pickup_method_ids ); |
| 501 | $this->asset_data_registry->add( 'shippingMethodsExist', CartCheckoutUtils::shipping_methods_exist() ); |
| 502 | |
| 503 | $is_block_editor = $this->is_block_editor(); |
| 504 | |
| 505 | if ( $is_block_editor ) { |
| 506 | $this->asset_data_registry->add( |
| 507 | 'localPickupLocations', |
| 508 | array_filter( |
| 509 | array_map( |
| 510 | function ( $location ) { |
| 511 | if ( ! wc_string_to_bool( $location['enabled'] ) ) { |
| 512 | return null; |
| 513 | } |
| 514 | $location['formatted_address'] = wc()->countries->get_formatted_address( $location['address'], ', ' ); |
| 515 | return $location; |
| 516 | }, |
| 517 | LocalPickupUtils::get_local_pickup_method_locations() |
| 518 | ) |
| 519 | ), |
| 520 | ); |
| 521 | } |
| 522 | |
| 523 | if ( $is_block_editor && ! $this->asset_data_registry->exists( 'globalShippingMethods' ) ) { |
| 524 | $shipping_methods = WC()->shipping()->get_shipping_methods(); |
| 525 | $formatted_shipping_methods = array_reduce( |
| 526 | $shipping_methods, |
| 527 | function ( array $acc, $method ) use ( $local_pickup_method_ids ) { |
| 528 | if ( in_array( $method->id, $local_pickup_method_ids, true ) ) { |
| 529 | return $acc; |
| 530 | } |
| 531 | if ( $method->supports( 'settings' ) ) { |
| 532 | $acc[] = [ |
| 533 | 'id' => $method->id, |
| 534 | 'title' => $method->method_title, |
| 535 | 'description' => $method->method_description, |
| 536 | ]; |
| 537 | } |
| 538 | return $acc; |
| 539 | }, |
| 540 | [] |
| 541 | ); |
| 542 | $this->asset_data_registry->add( 'globalShippingMethods', $formatted_shipping_methods ); |
| 543 | } |
| 544 | |
| 545 | if ( $is_block_editor && ! $this->asset_data_registry->exists( 'activeShippingZones' ) && class_exists( '\WC_Shipping_Zones' ) ) { |
| 546 | $this->asset_data_registry->add( 'activeShippingZones', CartCheckoutUtils::get_shipping_zones() ); |
| 547 | } |
| 548 | |
| 549 | if ( $is_block_editor && ! $this->asset_data_registry->exists( 'globalPaymentMethods' ) ) { |
| 550 | // These are used to show options in the sidebar. We want to get the full list of enabled payment methods, |
| 551 | // not just the ones that are available for the current cart (which may not exist yet). |
| 552 | $payment_methods = PaymentUtils::get_enabled_payment_gateways(); |
| 553 | $formatted_payment_methods = array_reduce( |
| 554 | $payment_methods, |
| 555 | function ( array $acc, $method ) { |
| 556 | $acc[] = [ |
| 557 | 'id' => $method->id, |
| 558 | 'title' => $method->get_method_title() !== '' ? $method->get_method_title() : $method->get_title(), |
| 559 | 'description' => $method->get_method_description() !== '' ? $method->get_method_description() : $method->get_description(), |
| 560 | ]; |
| 561 | return $acc; |
| 562 | }, |
| 563 | [] |
| 564 | ); |
| 565 | $this->asset_data_registry->add( 'globalPaymentMethods', $formatted_payment_methods ); |
| 566 | } |
| 567 | |
| 568 | // Check `current_user_can` so we can show notices about incompatible extensions in the front-end to admins too. |
| 569 | if ( ( $is_block_editor || current_user_can( 'manage_woocommerce' ) ) && ! $this->asset_data_registry->exists( 'incompatibleExtensions' ) ) { |
| 570 | if ( ! class_exists( '\Automattic\WooCommerce\Utilities\FeaturesUtil' ) || ! function_exists( 'get_plugins' ) ) { |
| 571 | return; |
| 572 | } |
| 573 | |
| 574 | $declared_extensions = \Automattic\WooCommerce\Utilities\FeaturesUtil::get_compatible_plugins_for_feature( 'cart_checkout_blocks' ); |
| 575 | $all_plugins = \get_plugins(); // Note that `get_compatible_plugins_for_feature` calls `get_plugins` internally, so this is already in cache. |
| 576 | $incompatible_extensions = array_reduce( |
| 577 | $declared_extensions['incompatible'], |
| 578 | function ( array $acc, $item ) use ( $all_plugins ) { |
| 579 | $plugin = $all_plugins[ $item ] ?? null; |
| 580 | $plugin_id = $plugin['TextDomain'] ?? dirname( $item ); |
| 581 | $plugin_name = $plugin['Name'] ?? $plugin_id; |
| 582 | $acc[] = [ |
| 583 | 'id' => $plugin_id, |
| 584 | 'title' => $plugin_name, |
| 585 | ]; |
| 586 | return $acc; |
| 587 | }, |
| 588 | [] |
| 589 | ); |
| 590 | $this->asset_data_registry->add( 'incompatibleExtensions', $incompatible_extensions ); |
| 591 | } |
| 592 | |
| 593 | if ( ! is_admin() && ! WC()->is_rest_api_request() ) { |
| 594 | $this->asset_data_registry->hydrate_api_request( '/wc/store/v1/cart' ); |
| 595 | $this->asset_data_registry->hydrate_data_from_api_request( 'checkoutData', '/wc/store/v1/checkout' ); |
| 596 | $this->hydrate_customer_payment_methods(); |
| 597 | } |
| 598 | |
| 599 | /** |
| 600 | * Fires after checkout block data is registered. |
| 601 | * |
| 602 | * @since 2.6.0 |
| 603 | */ |
| 604 | do_action( 'woocommerce_blocks_checkout_enqueue_data' ); |
| 605 | } |
| 606 | |
| 607 | /** |
| 608 | * Get saved customer payment methods for use in checkout. |
| 609 | */ |
| 610 | protected function hydrate_customer_payment_methods() { |
| 611 | $payment_methods = PaymentUtils::get_saved_payment_methods(); |
| 612 | |
| 613 | if ( ! $payment_methods || $this->asset_data_registry->exists( 'customerPaymentMethods' ) ) { |
| 614 | return; |
| 615 | } |
| 616 | |
| 617 | $this->asset_data_registry->add( |
| 618 | 'customerPaymentMethods', |
| 619 | is_array( $payment_methods ) ? $payment_methods['enabled'] : null |
| 620 | ); |
| 621 | } |
| 622 | |
| 623 | /** |
| 624 | * Register script and style assets for the block type before it is registered. |
| 625 | * |
| 626 | * This registers the scripts; it does not enqueue them. |
| 627 | */ |
| 628 | protected function register_block_type_assets() { |
| 629 | parent::register_block_type_assets(); |
| 630 | $chunks = $this->get_chunks_paths( $this->chunks_folder ); |
| 631 | $vendor_chunks = $this->get_chunks_paths( 'vendors--checkout-blocks' ); |
| 632 | $shared_chunks = [ 'cart-blocks/cart-express-payment--checkout-blocks/express-payment-frontend' ]; |
| 633 | $this->register_chunk_translations( array_merge( $chunks, $vendor_chunks, $shared_chunks ) ); |
| 634 | } |
| 635 | |
| 636 | /** |
| 637 | * Get list of Checkout block & its inner-block types. |
| 638 | * |
| 639 | * @return array; |
| 640 | */ |
| 641 | public static function get_checkout_block_types() { |
| 642 | return [ |
| 643 | 'Checkout', |
| 644 | 'CheckoutActionsBlock', |
| 645 | 'CheckoutAdditionalInformationBlock', |
| 646 | 'CheckoutBillingAddressBlock', |
| 647 | 'CheckoutContactInformationBlock', |
| 648 | 'CheckoutExpressPaymentBlock', |
| 649 | 'CheckoutFieldsBlock', |
| 650 | 'CheckoutOrderNoteBlock', |
| 651 | 'CheckoutOrderSummaryBlock', |
| 652 | 'CheckoutOrderSummaryCartItemsBlock', |
| 653 | 'CheckoutOrderSummaryCouponFormBlock', |
| 654 | 'CheckoutOrderSummaryDiscountBlock', |
| 655 | 'CheckoutOrderSummaryFeeBlock', |
| 656 | 'CheckoutOrderSummaryShippingBlock', |
| 657 | 'CheckoutOrderSummarySubtotalBlock', |
| 658 | 'CheckoutOrderSummaryTaxesBlock', |
| 659 | 'CheckoutOrderSummaryTotalsBlock', |
| 660 | 'CheckoutPaymentBlock', |
| 661 | 'CheckoutShippingAddressBlock', |
| 662 | 'CheckoutShippingMethodsBlock', |
| 663 | 'CheckoutShippingMethodBlock', |
| 664 | 'CheckoutPickupOptionsBlock', |
| 665 | 'CheckoutTermsBlock', |
| 666 | 'CheckoutTotalsBlock', |
| 667 | ]; |
| 668 | } |
| 669 | } |
| 670 |