plugin updates
This commit is contained in:
@@ -8,6 +8,8 @@ use Automattic\Jetpack\Connection\Utils;
|
||||
|
||||
/**
|
||||
* Class Configuration
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Configuration {
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ use WpOrg\Requests\Requests;
|
||||
|
||||
/**
|
||||
* Class Connection
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Connection {
|
||||
const TEXT_COMPLETION_API_URL = 'https://public-api.wordpress.com/wpcom/v2/text-completion';
|
||||
|
||||
@@ -10,6 +10,8 @@ use WP_Error;
|
||||
* ContentProcessor class.
|
||||
*
|
||||
* Process images for content
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ContentProcessor {
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ namespace Automattic\WooCommerce\Blocks\AIContent;
|
||||
|
||||
/**
|
||||
* Patterns Dictionary class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class PatternsDictionary {
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,8 @@ use WP_Error;
|
||||
|
||||
/**
|
||||
* Patterns Helper class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class PatternsHelper {
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,8 @@ use WP_Error;
|
||||
|
||||
/**
|
||||
* Pattern Images class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class UpdatePatterns {
|
||||
|
||||
|
||||
@@ -4,8 +4,11 @@ namespace Automattic\WooCommerce\Blocks\AIContent;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\AI\Connection;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Pattern Images class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class UpdateProducts {
|
||||
|
||||
@@ -471,11 +474,11 @@ class UpdateProducts {
|
||||
/**
|
||||
* Update the product with the new content.
|
||||
*
|
||||
* @param \WC_Product $product The product.
|
||||
* @param int $product_image_id The product image ID.
|
||||
* @param string $product_title The product title.
|
||||
* @param string $product_description The product description.
|
||||
* @param int $product_price The product price.
|
||||
* @param \WC_Product $product The product.
|
||||
* @param int|string|WP_Error $product_image_id The product image ID.
|
||||
* @param string $product_title The product title.
|
||||
* @param string $product_description The product description.
|
||||
* @param int $product_price The product price.
|
||||
*
|
||||
* @return int|\WP_Error
|
||||
*/
|
||||
|
||||
@@ -41,6 +41,13 @@ class Api {
|
||||
*/
|
||||
private $script_data = null;
|
||||
|
||||
/**
|
||||
* Tracks whether script_data was modified during the current request.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
private $script_data_modified = false;
|
||||
|
||||
/**
|
||||
* Stores the hash for the script data, made up of the site url, plugin version and package path.
|
||||
*
|
||||
@@ -171,6 +178,9 @@ class Api {
|
||||
if ( is_null( $this->script_data ) || $this->disable_cache ) {
|
||||
return;
|
||||
}
|
||||
if ( ! $this->script_data_modified ) {
|
||||
return;
|
||||
}
|
||||
set_transient(
|
||||
$this->script_data_transient_key,
|
||||
wp_json_encode(
|
||||
@@ -216,6 +226,7 @@ class Api {
|
||||
'version' => ! empty( $asset['version'] ) ? $asset['version'] : $this->get_file_version( $relative_src ),
|
||||
'dependencies' => ! empty( $asset['dependencies'] ) ? $asset['dependencies'] : [],
|
||||
);
|
||||
$this->script_data_modified = true;
|
||||
}
|
||||
|
||||
// Return asset details as well as the requested dependencies array.
|
||||
|
||||
@@ -80,8 +80,6 @@ class BlockPatterns {
|
||||
$this->pattern_registry = $pattern_registry;
|
||||
$this->ptk_patterns_store = $ptk_patterns_store;
|
||||
|
||||
$this->dictionary = PatternsHelper::get_patterns_dictionary();
|
||||
|
||||
add_action( 'init', array( $this, 'register_block_patterns' ) );
|
||||
|
||||
if ( Features::is_enabled( 'pattern-toolkit-full-composability' ) ) {
|
||||
@@ -89,6 +87,19 @@ class BlockPatterns {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Patterns dictionary.
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
private function get_patterns_dictionary() {
|
||||
if ( null === $this->dictionary ) {
|
||||
$this->dictionary = PatternsHelper::get_patterns_dictionary();
|
||||
}
|
||||
|
||||
return $this->dictionary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register block patterns from core.
|
||||
*
|
||||
@@ -123,7 +134,7 @@ class BlockPatterns {
|
||||
foreach ( $files as $file ) {
|
||||
$pattern_data = get_file_data( $file, $default_headers );
|
||||
|
||||
$this->pattern_registry->register_block_pattern( $file, $pattern_data, $this->dictionary );
|
||||
$this->pattern_registry->register_block_pattern( $file, $pattern_data, $this->get_patterns_dictionary() );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,11 +150,20 @@ class BlockPatterns {
|
||||
return;
|
||||
}
|
||||
|
||||
// The most efficient way to check for an existing action is to use `as_has_scheduled_action`, but in unusual
|
||||
// cases where another plugin has loaded a very old version of Action Scheduler, it may not be available to us.
|
||||
$has_scheduled_action = function_exists( 'as_has_scheduled_action' ) ? 'as_has_scheduled_action' : 'as_next_scheduled_action';
|
||||
|
||||
$patterns = $this->ptk_patterns_store->get_patterns();
|
||||
if ( empty( $patterns ) ) {
|
||||
wc_get_logger()->warning(
|
||||
__( 'Empty patterns received from the PTK Pattern Store', 'woocommerce' ),
|
||||
);
|
||||
// By only logging when patterns are empty and no fetch is scheduled,
|
||||
// we ensure that warnings are only generated in genuinely problematic situations,
|
||||
// such as when the pattern fetching mechanism has failed entirely.
|
||||
if ( ! call_user_func( $has_scheduled_action, 'fetch_patterns' ) ) {
|
||||
wc_get_logger()->warning(
|
||||
__( 'Empty patterns received from the PTK Pattern Store', 'woocommerce' ),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -153,7 +173,7 @@ class BlockPatterns {
|
||||
$pattern['slug'] = $pattern['name'];
|
||||
$pattern['content'] = $pattern['html'];
|
||||
|
||||
$this->pattern_registry->register_block_pattern( $pattern['ID'], $pattern, $this->dictionary );
|
||||
$this->pattern_registry->register_block_pattern( $pattern['ID'], $pattern, $this->get_patterns_dictionary() );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,14 +34,16 @@ class Breadcrumbs extends AbstractBlock {
|
||||
return;
|
||||
}
|
||||
|
||||
$classname = $attributes['className'] ?? '';
|
||||
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
|
||||
|
||||
return sprintf(
|
||||
'<div class="woocommerce wc-block-breadcrumbs %1$s %2$s" style="%3$s">%4$s</div>',
|
||||
esc_attr( $classes_and_styles['classes'] ),
|
||||
esc_attr( $classname ),
|
||||
esc_attr( $classes_and_styles['styles'] ),
|
||||
'<div %1$s>%2$s</div>',
|
||||
get_block_wrapper_attributes(
|
||||
array(
|
||||
'class' => 'wc-block-breadcrumbs woocommerce ' . esc_attr( $classes_and_styles['classes'] ),
|
||||
'style' => $classes_and_styles['styles'],
|
||||
)
|
||||
),
|
||||
$breadcrumb
|
||||
);
|
||||
}
|
||||
|
||||
@@ -245,8 +245,12 @@ class Cart extends AbstractBlock {
|
||||
$this->asset_data_registry->add( 'hasDarkEditorStyleSupport', current_theme_supports( 'dark-editor-style' ) );
|
||||
$this->asset_data_registry->register_page_id( isset( $attributes['checkoutPageId'] ) ? $attributes['checkoutPageId'] : 0 );
|
||||
$this->asset_data_registry->add( 'isBlockTheme', wc_current_theme_is_fse_theme() );
|
||||
|
||||
$pickup_location_settings = LocalPickupUtils::get_local_pickup_settings();
|
||||
$local_pickup_method_ids = LocalPickupUtils::get_local_pickup_method_ids();
|
||||
|
||||
$this->asset_data_registry->add( 'localPickupEnabled', $pickup_location_settings['enabled'] );
|
||||
$this->asset_data_registry->add( 'collectableMethodIds', $local_pickup_method_ids );
|
||||
|
||||
// Hydrate the following data depending on admin or frontend context.
|
||||
if ( ! is_admin() && ! WC()->is_rest_api_request() ) {
|
||||
|
||||
@@ -11,4 +11,18 @@ class CartExpressPaymentBlock extends AbstractInnerBlock {
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-express-payment-block';
|
||||
|
||||
/**
|
||||
* Uniform default_styles for the express payment buttons
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $default_styles = null;
|
||||
|
||||
/**
|
||||
* Current styles for the express payment buttons
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $current_styles = null;
|
||||
}
|
||||
|
||||
@@ -370,8 +370,11 @@ class Checkout extends AbstractBlock {
|
||||
$this->asset_data_registry->add( 'isBlockTheme', wc_current_theme_is_fse_theme() );
|
||||
|
||||
$pickup_location_settings = LocalPickupUtils::get_local_pickup_settings();
|
||||
$local_pickup_method_ids = LocalPickupUtils::get_local_pickup_method_ids();
|
||||
|
||||
$this->asset_data_registry->add( 'localPickupEnabled', $pickup_location_settings['enabled'] );
|
||||
$this->asset_data_registry->add( 'localPickupText', $pickup_location_settings['title'] );
|
||||
$this->asset_data_registry->add( 'collectableMethodIds', $local_pickup_method_ids );
|
||||
|
||||
$is_block_editor = $this->is_block_editor();
|
||||
|
||||
@@ -385,8 +388,8 @@ class Checkout extends AbstractBlock {
|
||||
$shipping_methods = WC()->shipping()->get_shipping_methods();
|
||||
$formatted_shipping_methods = array_reduce(
|
||||
$shipping_methods,
|
||||
function ( $acc, $method ) {
|
||||
if ( in_array( $method->id, LocalPickupUtils::get_local_pickup_method_ids(), true ) ) {
|
||||
function ( $acc, $method ) use ( $local_pickup_method_ids ) {
|
||||
if ( in_array( $method->id, $local_pickup_method_ids, true ) ) {
|
||||
return $acc;
|
||||
}
|
||||
if ( $method->supports( 'settings' ) ) {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Utils\CartCheckoutUtils;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* CheckoutExpressPaymentBlock class.
|
||||
*/
|
||||
@@ -11,4 +14,128 @@ class CheckoutExpressPaymentBlock extends AbstractInnerBlock {
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-express-payment-block';
|
||||
|
||||
/**
|
||||
* Default styles for the express payment buttons
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $default_styles = null;
|
||||
|
||||
/**
|
||||
* Current styles for the express payment buttons
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $current_styles = null;
|
||||
|
||||
/**
|
||||
* Initialise the block
|
||||
*/
|
||||
protected function initialize() {
|
||||
parent::initialize();
|
||||
|
||||
$this->default_styles = array(
|
||||
'showButtonStyles' => false,
|
||||
'buttonHeight' => '48',
|
||||
'buttonBorderRadius' => '4',
|
||||
);
|
||||
|
||||
add_action( 'save_post', array( $this, 'sync_express_payment_attrs' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchorize the express payment attributes between the Cart and Checkout pages.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
* @param WP_Post $post Post object.
|
||||
*/
|
||||
public function sync_express_payment_attrs( $post_id, $post ) {
|
||||
if ( wc_get_page_id( 'cart' ) === $post_id ) {
|
||||
$cart_or_checkout = 'cart';
|
||||
} elseif ( wc_get_page_id( 'checkout' ) === $post_id ) {
|
||||
$cart_or_checkout = 'checkout';
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// This is not a proper save action, maybe an autosave, so don't continue.
|
||||
if ( empty( $post->post_status ) || 'inherit' === $post->post_status ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$block_name = 'woocommerce/' . $cart_or_checkout;
|
||||
$page_id = 'woocommerce_' . $cart_or_checkout . '_page_id';
|
||||
$template_name = 'page-' . $cart_or_checkout;
|
||||
|
||||
// Check if we are editing the cart/checkout page and that it contains a Cart/Checkout block.
|
||||
// Cast to string for Cart/Checkout page ID comparison because get_option can return it as a string, so better to compare both values as strings.
|
||||
if ( ! empty( $post->post_type ) && 'wp_template' !== $post->post_type && ( false === has_block( $block_name, $post ) || (string) get_option( $page_id ) !== (string) $post_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we are editing the Cart/Checkout template and that it contains a Cart/Checkout block.
|
||||
if ( ( ! empty( $post->post_type ) && ! empty( $post->post_name ) && $template_name !== $post->post_name && 'wp_template' === $post->post_type ) || false === has_block( $block_name, $post ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( empty( $post->post_content ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Parse the post content to get the express payment attributes of the current page.
|
||||
$blocks = parse_blocks( $post->post_content );
|
||||
$attrs = CartCheckoutUtils::find_express_checkout_attributes( $blocks, $cart_or_checkout );
|
||||
|
||||
if ( ! is_array( $attrs ) ) {
|
||||
return;
|
||||
}
|
||||
$updated_attrs = array_merge( $this->default_styles, $attrs );
|
||||
|
||||
// We need to sync the attributes between the Cart and Checkout pages.
|
||||
$other_page = 'cart' === $cart_or_checkout ? 'checkout' : 'cart';
|
||||
|
||||
$this->update_other_page_with_express_payment_attrs( $other_page, $updated_attrs );
|
||||
} catch ( Exception $e ) {
|
||||
wc_get_logger()->log( 'error', 'Error updating express payment attributes: ' . $e->getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the express payment attributes in the other page (Cart or Checkout).
|
||||
*
|
||||
* @param string $cart_or_checkout The page to update.
|
||||
* @param array $updated_attrs The updated attributes.
|
||||
*/
|
||||
private function update_other_page_with_express_payment_attrs( $cart_or_checkout, $updated_attrs ) {
|
||||
$page_id = 'cart' === $cart_or_checkout ? wc_get_page_id( 'cart' ) : wc_get_page_id( 'checkout' );
|
||||
|
||||
if ( -1 === $page_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$post = get_post( $page_id );
|
||||
|
||||
if ( empty( $post->post_content ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$blocks = parse_blocks( $post->post_content );
|
||||
CartCheckoutUtils::update_blocks_with_new_attrs( $blocks, $cart_or_checkout, $updated_attrs );
|
||||
|
||||
$updated_content = serialize_blocks( $blocks );
|
||||
remove_action( 'save_post', array( $this, 'sync_express_payment_attrs' ), 10, 2 );
|
||||
|
||||
wp_update_post(
|
||||
array(
|
||||
'ID' => $page_id,
|
||||
'post_content' => $updated_content,
|
||||
),
|
||||
false,
|
||||
false
|
||||
);
|
||||
|
||||
add_action( 'save_post', array( $this, 'sync_express_payment_attrs' ), 10, 2 );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ class CustomerAccount extends AbstractBlock {
|
||||
public function modify_hooked_block_attributes( $parsed_hooked_block, $hooked_block_type, $relative_position, $parsed_anchor_block, $context ) {
|
||||
$parsed_hooked_block['attrs']['displayStyle'] = 'icon_only';
|
||||
$parsed_hooked_block['attrs']['iconStyle'] = 'line';
|
||||
$parsed_hooked_block['attrs']['iconClass'] = 'wc-block-customer-account__account-icon';
|
||||
|
||||
/*
|
||||
* The Mini Cart block (which is hooked into the header) has a margin of 0.5em on the left side.
|
||||
|
||||
@@ -55,11 +55,12 @@ abstract class AbstractOrderConfirmationBlock extends AbstractBlock {
|
||||
}
|
||||
|
||||
return $block_content ? sprintf(
|
||||
'<div class="wc-block-%4$s %1$s" style="%2$s">%3$s</div>',
|
||||
'<div class="wp-block-%5$s-%4$s wc-block-%4$s %1$s" style="%2$s">%3$s</div>',
|
||||
esc_attr( trim( $classname ) ),
|
||||
esc_attr( $classes_and_styles['styles'] ),
|
||||
$block_content,
|
||||
esc_attr( $this->block_name )
|
||||
esc_attr( $this->block_name ),
|
||||
esc_attr( $this->namespace )
|
||||
) : '';
|
||||
}
|
||||
|
||||
@@ -186,7 +187,13 @@ abstract class AbstractOrderConfirmationBlock extends AbstractBlock {
|
||||
*/
|
||||
protected function is_email_verified( $order ) {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
|
||||
if ( empty( $_POST ) || ! isset( $_POST['email'] ) || ! wp_verify_nonce( $_POST['check_submission'] ?? '', 'wc_verify_email' ) ) {
|
||||
if ( empty( $_POST ) || ! isset( $_POST['email'], $_POST['_wpnonce'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$nonce_value = sanitize_key( wp_unslash( $_POST['_wpnonce'] ?? '' ) );
|
||||
|
||||
if ( ! wp_verify_nonce( $nonce_value, 'wc_verify_email' ) && ! wp_verify_nonce( $nonce_value, 'wc_create_account' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;
|
||||
|
||||
use Automattic\WooCommerce\StoreApi\Utilities\OrderController;
|
||||
|
||||
/**
|
||||
* CreateAccount class.
|
||||
*/
|
||||
class CreateAccount extends AbstractOrderConfirmationBlock {
|
||||
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'order-confirmation-create-account';
|
||||
|
||||
/**
|
||||
* Get the frontend script handle for this block type.
|
||||
*
|
||||
* @see $this->register_block_type()
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return array|string
|
||||
*/
|
||||
protected function get_block_type_script( $key = null ) {
|
||||
$script = [
|
||||
'handle' => 'wc-order-confirmation-create-account-block-frontend',
|
||||
'path' => $this->asset_api->get_block_asset_build_path( 'order-confirmation-create-account-frontend' ),
|
||||
'dependencies' => [],
|
||||
];
|
||||
return $key ? $script[ $key ] : $script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process posted account form.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @return \WP_Error|int
|
||||
*/
|
||||
protected function process_form_post( $order ) {
|
||||
if ( ! isset( $_POST['create-account'], $_POST['email'], $_POST['password'], $_POST['_wpnonce'] ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ( ! wp_verify_nonce( sanitize_key( wp_unslash( $_POST['_wpnonce'] ?? '' ) ), 'wc_create_account' ) ) {
|
||||
return new \WP_Error( 'invalid_nonce', __( 'Unable to create account. Please try again.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$user_email = sanitize_email( wp_unslash( $_POST['email'] ) );
|
||||
$password = wp_unslash( $_POST['password'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
|
||||
// Does order already have user?
|
||||
if ( $order->get_customer_id() ) {
|
||||
return new \WP_Error( 'order_already_has_user', __( 'This order is already linked to a user account.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
// Check given details match the current viewed order.
|
||||
if ( $order->get_billing_email() !== $user_email ) {
|
||||
return new \WP_Error( 'email_mismatch', __( 'The email address provided does not match the email address on this order.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
if ( empty( $password ) || strlen( $password ) < 8 ) {
|
||||
return new \WP_Error( 'password_too_short', __( 'Password must be at least 8 characters.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$customer_id = wc_create_new_customer(
|
||||
$user_email,
|
||||
'',
|
||||
$password,
|
||||
[
|
||||
'first_name' => $order->get_billing_first_name(),
|
||||
'last_name' => $order->get_billing_last_name(),
|
||||
'source' => 'delayed-account-creation',
|
||||
]
|
||||
);
|
||||
|
||||
if ( is_wp_error( $customer_id ) ) {
|
||||
return $customer_id;
|
||||
}
|
||||
|
||||
// Associate customer with the order.
|
||||
$order->set_customer_id( $customer_id );
|
||||
$order->save();
|
||||
|
||||
// Associate addresses from the order with the customer.
|
||||
$order_controller = new OrderController();
|
||||
$order_controller->sync_customer_data_with_order( $order );
|
||||
|
||||
// Set the customer auth cookie.
|
||||
wc_set_customer_auth_cookie( $customer_id );
|
||||
|
||||
return $customer_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* This renders the content of the block within the wrapper.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @param string|false $permission If the current user can view the order details or not.
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Original block content.
|
||||
* @return string
|
||||
*/
|
||||
protected function render_content( $order, $permission = false, $attributes = [], $content = '' ) {
|
||||
if ( ! $permission ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Check registration is possible for this order/customer, and if not, return early.
|
||||
if ( is_user_logged_in() || email_exists( $order->get_billing_email() ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$result = $this->process_form_post( $order );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
$notice = wc_print_notice( $result->get_error_message(), 'error', [], true );
|
||||
} elseif ( $result ) {
|
||||
return $this->render_confirmation();
|
||||
}
|
||||
|
||||
$processor = new \WP_HTML_Tag_Processor(
|
||||
$content .
|
||||
'<div class="woocommerce-order-confirmation-create-account-form-wrapper">' .
|
||||
$notice .
|
||||
'<div class="woocommerce-order-confirmation-create-account-form"></div>' .
|
||||
'</div>'
|
||||
);
|
||||
|
||||
if ( ! $processor->next_tag( array( 'class_name' => 'wp-block-woocommerce-order-confirmation-create-account' ) ) ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$processor->set_attribute( 'class', '' );
|
||||
$processor->set_attribute( 'style', '' );
|
||||
$processor->add_class( 'woocommerce-order-confirmation-create-account-content' );
|
||||
|
||||
if ( ! $processor->next_tag( array( 'class_name' => 'woocommerce-order-confirmation-create-account-form' ) ) ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$processor->set_attribute( 'data-customer-email', $order->get_billing_email() );
|
||||
$processor->set_attribute( 'data-nonce-token', wp_create_nonce( 'wc_create_account' ) );
|
||||
|
||||
if ( ! empty( $attributes['hasDarkControls'] ) ) {
|
||||
$processor->add_class( 'has-dark-controls' );
|
||||
}
|
||||
|
||||
return $processor->get_updated_html();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the block when an account has been registered.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function render_confirmation() {
|
||||
$content = '<div class="woocommerce-order-confirmation-create-account-success" id="create-account">';
|
||||
$content .= '<h3>' . esc_html__( 'Your account has been successfully created', 'woocommerce' ) . '</h3>';
|
||||
$content .= '<p>' . sprintf(
|
||||
/* translators: 1: link to my account page, 2: link to shipping and billing addresses, 3: link to account details, 4: closing tag */
|
||||
esc_html__( 'You can now %1$sview your recent orders%4$s, manage your %2$sshipping and billing addresses%4$s, and edit your %3$spassword and account details%4$s.', 'woocommerce' ),
|
||||
'<a href="' . esc_url( wc_get_endpoint_url( 'orders', '', wc_get_page_permalink( 'myaccount' ) ) ) . '">',
|
||||
'<a href="' . esc_url( wc_get_endpoint_url( 'edit-address', '', wc_get_page_permalink( 'myaccount' ) ) ) . '">',
|
||||
'<a href="' . esc_url( wc_get_endpoint_url( 'edit-account', '', wc_get_page_permalink( 'myaccount' ) ) ) . '">',
|
||||
'</a>'
|
||||
) . '</p>';
|
||||
$content .= '</div>';
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
|
||||
|
||||
/**
|
||||
* Status class.
|
||||
*/
|
||||
@@ -265,7 +263,7 @@ class Status extends AbstractOrderConfirmationBlock {
|
||||
</p>',
|
||||
esc_attr( 'verify-email-submit' ),
|
||||
esc_html__( 'Confirm email and view order', 'woocommerce' ),
|
||||
wp_nonce_field( 'wc_verify_email', 'check_submission', true, false ),
|
||||
wp_nonce_field( 'wc_verify_email', '_wpnonce', true, false ),
|
||||
esc_attr( wc_wp_theme_get_element_class_name( 'button' ) )
|
||||
) .
|
||||
'</form>';
|
||||
|
||||
@@ -29,11 +29,11 @@ class Summary extends AbstractOrderConfirmationBlock {
|
||||
}
|
||||
|
||||
$content = '<ul class="wc-block-order-confirmation-summary-list">';
|
||||
$content .= $this->render_summary_row( __( 'Order number:', 'woocommerce' ), $order->get_order_number() );
|
||||
$content .= $this->render_summary_row( __( 'Order #:', 'woocommerce' ), $order->get_order_number() );
|
||||
$content .= $this->render_summary_row( __( 'Date:', 'woocommerce' ), wc_format_datetime( $order->get_date_created() ) );
|
||||
$content .= $this->render_summary_row( __( 'Total:', 'woocommerce' ), $order->get_formatted_order_total() );
|
||||
$content .= $this->render_summary_row( __( 'Email:', 'woocommerce' ), $order->get_billing_email() );
|
||||
$content .= $this->render_summary_row( __( 'Payment method:', 'woocommerce' ), $order->get_payment_method_title() );
|
||||
$content .= $this->render_summary_row( __( 'Payment:', 'woocommerce' ), $order->get_payment_method_title() );
|
||||
$content .= '</ul>';
|
||||
|
||||
return $content;
|
||||
|
||||
@@ -176,6 +176,10 @@ class ProductButton extends AbstractBlock {
|
||||
data-wc-class--loading="context.isLoading"
|
||||
';
|
||||
|
||||
$anchor_directive = '
|
||||
data-wc-on--click="woocommerce/product-collection::actions.viewProduct"
|
||||
';
|
||||
|
||||
$span_button_directives = '
|
||||
data-wc-text="state.addToCartText"
|
||||
data-wc-class--wc-block-slide-in="state.slideInAnimation"
|
||||
@@ -219,7 +223,7 @@ class ProductButton extends AbstractBlock {
|
||||
'{attributes}' => isset( $args['attributes'] ) ? wc_implode_html_attributes( $args['attributes'] ) : '',
|
||||
'{add_to_cart_text}' => esc_html( $initial_product_text ),
|
||||
'{div_directives}' => $is_ajax_button ? $div_directives : '',
|
||||
'{button_directives}' => $is_ajax_button ? $button_directives : '',
|
||||
'{button_directives}' => $is_ajax_button ? $button_directives : $anchor_directive,
|
||||
'{span_button_directives}' => $is_ajax_button ? $span_button_directives : '',
|
||||
'{view_cart_html}' => $is_ajax_button ? $this->get_view_cart_html() : '',
|
||||
)
|
||||
|
||||
@@ -47,6 +47,18 @@ class ProductCollection extends AbstractBlock {
|
||||
protected $custom_order_opts = array( 'popularity', 'rating' );
|
||||
|
||||
|
||||
/**
|
||||
* The render state of the product collection block.
|
||||
*
|
||||
* These props are runtime-based and reinitialize for every block on a page.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $render_state = array(
|
||||
'has_results' => false,
|
||||
'has_no_results_block' => false,
|
||||
);
|
||||
|
||||
/**
|
||||
* Initialize this block type.
|
||||
*
|
||||
@@ -80,9 +92,32 @@ class ProductCollection extends AbstractBlock {
|
||||
// Provide location context into block's context.
|
||||
add_filter( 'render_block_context', array( $this, 'provide_location_context_for_inner_blocks' ), 11, 1 );
|
||||
|
||||
// Disable block render if the ProductTemplate block is empty.
|
||||
add_filter(
|
||||
'render_block_woocommerce/product-template',
|
||||
function ( $html ) {
|
||||
$this->render_state['has_results'] = ! empty( $html );
|
||||
return $html;
|
||||
},
|
||||
100,
|
||||
1
|
||||
);
|
||||
|
||||
// Enable block render if the ProductCollectionNoResults block is rendered.
|
||||
add_filter(
|
||||
'render_block_woocommerce/product-collection-no-results',
|
||||
function ( $html ) {
|
||||
$this->render_state['has_no_results_block'] = ! empty( $html );
|
||||
return $html;
|
||||
},
|
||||
100,
|
||||
1
|
||||
);
|
||||
|
||||
// Interactivity API: Add navigation directives to the product collection block.
|
||||
add_filter( 'render_block_woocommerce/product-collection', array( $this, 'enhance_product_collection_with_interactivity' ), 10, 2 );
|
||||
add_filter( 'render_block_woocommerce/product-collection', array( $this, 'handle_rendering' ), 10, 2 );
|
||||
add_filter( 'render_block_core/query-pagination', array( $this, 'add_navigation_link_directives' ), 10, 3 );
|
||||
add_filter( 'render_block_core/post-title', array( $this, 'add_product_title_click_event_directives' ), 10, 3 );
|
||||
|
||||
add_filter( 'posts_clauses', array( $this, 'add_price_range_filter_posts_clauses' ), 10, 2 );
|
||||
|
||||
@@ -90,6 +125,46 @@ class ProductCollection extends AbstractBlock {
|
||||
add_filter( 'render_block_data', array( $this, 'disable_enhanced_pagination' ), 10, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the rendering of the block.
|
||||
*
|
||||
* @param string $block_content The block content about to be rendered.
|
||||
* @param array $block The block being rendered.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function handle_rendering( $block_content, $block ) {
|
||||
if ( $this->should_prevent_render() ) {
|
||||
return ''; // Prevent rendering.
|
||||
}
|
||||
|
||||
// Reset the render state for the next render.
|
||||
$this->reset_render_state();
|
||||
|
||||
return $this->enhance_product_collection_with_interactivity( $block_content, $block );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the block should be prevented from rendering.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function should_prevent_render() {
|
||||
return ! $this->render_state['has_results'] && ! $this->render_state['has_no_results_block'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the render state.
|
||||
*/
|
||||
private function reset_render_state() {
|
||||
$this->render_state = array(
|
||||
'has_results' => false,
|
||||
'has_no_results_block' => false,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Provides the location context to each inner block of the product collection block.
|
||||
* Hint: Only blocks using the 'query' context will be affected.
|
||||
@@ -326,7 +401,7 @@ class ProductCollection extends AbstractBlock {
|
||||
$is_enhanced_pagination_enabled = ! ( $this->parsed_block['attrs']['forcePageReload'] ?? false );
|
||||
|
||||
// Only proceed if the block is a product collection block,
|
||||
// enhaced pagination is enabled and query IDs match.
|
||||
// enhanced pagination is enabled and query IDs match.
|
||||
if ( $is_product_collection_block && $is_enhanced_pagination_enabled && $query_id === $parsed_query_id ) {
|
||||
$block_content = $this->process_pagination_links( $block_content );
|
||||
}
|
||||
@@ -334,6 +409,36 @@ class ProductCollection extends AbstractBlock {
|
||||
return $block_content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add interactivity to the Product Title block within Product Collection.
|
||||
* This enables the triggering of a custom event when the product title is clicked.
|
||||
*
|
||||
* @param string $block_content The block content.
|
||||
* @param array $block The full block, including name and attributes.
|
||||
* @param \WP_Block $instance The block instance.
|
||||
* @return string Modified block content with added interactivity.
|
||||
*/
|
||||
public function add_product_title_click_event_directives( $block_content, $block, $instance ) {
|
||||
$namespace = $instance->attributes['__woocommerceNamespace'] ?? '';
|
||||
$is_product_title_block = 'woocommerce/product-collection/product-title' === $namespace;
|
||||
$is_link = $instance->attributes['isLink'] ?? false;
|
||||
|
||||
// Only proceed if the block is a Product Title (Post Title variation) block.
|
||||
if ( $is_product_title_block && $is_link ) {
|
||||
$p = new \WP_HTML_Tag_Processor( $block_content );
|
||||
$p->next_tag( array( 'class_name' => 'wp-block-post-title' ) );
|
||||
$is_anchor = $p->next_tag( array( 'tag_name' => 'a' ) );
|
||||
|
||||
if ( $is_anchor ) {
|
||||
$p->set_attribute( 'data-wc-on--click', 'woocommerce/product-collection::actions.viewProduct' );
|
||||
|
||||
$block_content = $p->get_updated_html();
|
||||
}
|
||||
}
|
||||
|
||||
return $block_content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process pagination links within the block content.
|
||||
*
|
||||
@@ -394,11 +499,18 @@ class ProductCollection extends AbstractBlock {
|
||||
*/
|
||||
private function is_block_compatible( $block_name ) {
|
||||
// Check for explicitly unsupported blocks.
|
||||
if (
|
||||
'core/post-content' === $block_name ||
|
||||
'woocommerce/mini-cart' === $block_name ||
|
||||
'woocommerce/featured-product' === $block_name
|
||||
) {
|
||||
$unsupported_blocks = array(
|
||||
'core/post-content',
|
||||
'woocommerce/mini-cart',
|
||||
'woocommerce/featured-product',
|
||||
'woocommerce/active-filters',
|
||||
'woocommerce/price-filter',
|
||||
'woocommerce/stock-filter',
|
||||
'woocommerce/attribute-filter',
|
||||
'woocommerce/rating-filter',
|
||||
);
|
||||
|
||||
if ( in_array( $block_name, $unsupported_blocks, true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -416,8 +528,8 @@ class ProductCollection extends AbstractBlock {
|
||||
|
||||
/**
|
||||
* Check inner blocks of Product Collection block if there's one
|
||||
* incompatible with Interactivity API and if so, disable client-side
|
||||
* naviagtion.
|
||||
* incompatible with the Interactivity API and if so, disable client-side
|
||||
* navigation.
|
||||
*
|
||||
* @param array $parsed_block The block being rendered.
|
||||
* @return string Returns the parsed block, unmodified.
|
||||
@@ -869,7 +981,7 @@ class ProductCollection extends AbstractBlock {
|
||||
* - For array items with numeric keys, we merge them as normal.
|
||||
* - For array items with string keys:
|
||||
*
|
||||
* - If the value isn't array, we'll use the value comming from the merge array.
|
||||
* - If the value isn't array, we'll use the value coming from the merge array.
|
||||
* $base = ['orderby' => 'date']
|
||||
* $new = ['orderby' => 'meta_value_num']
|
||||
* Result: ['orderby' => 'meta_value_num']
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use WP_HTML_Tag_Processor;
|
||||
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
|
||||
|
||||
/**
|
||||
@@ -50,8 +51,31 @@ class ProductDetails extends AbstractBlock {
|
||||
* @return string Rendered block output.
|
||||
*/
|
||||
protected function render( $attributes, $content, $block ) {
|
||||
$hide_tab_title = isset( $attributes['hideTabTitle'] ) ? $attributes['hideTabTitle'] : false;
|
||||
|
||||
if ( $hide_tab_title ) {
|
||||
add_filter( 'woocommerce_product_description_heading', '__return_empty_string' );
|
||||
add_filter( 'woocommerce_product_additional_information_heading', '__return_empty_string' );
|
||||
add_filter( 'woocommerce_reviews_title', '__return_empty_string' );
|
||||
}
|
||||
|
||||
$tabs = $this->render_tabs();
|
||||
|
||||
if ( $hide_tab_title ) {
|
||||
remove_filter( 'woocommerce_product_description_heading', '__return_empty_string' );
|
||||
remove_filter( 'woocommerce_product_additional_information_heading', '__return_empty_string' );
|
||||
remove_filter( 'woocommerce_reviews_title', '__return_empty_string' );
|
||||
|
||||
// Remove the first `h2` of every `.wc-tab`. This is required for the Reviews tabs when there are no reviews and for plugin tabs.
|
||||
$tabs_html = new WP_HTML_Tag_Processor( $tabs );
|
||||
while ( $tabs_html->next_tag( array( 'class_name' => 'wc-tab' ) ) ) {
|
||||
if ( $tabs_html->next_tag( 'h2' ) ) {
|
||||
$tabs_html->set_attribute( 'hidden', 'true' );
|
||||
}
|
||||
}
|
||||
$tabs = $tabs_html->get_updated_html();
|
||||
}
|
||||
|
||||
$classname = $attributes['className'] ?? '';
|
||||
|
||||
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\QueryFilters;
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use WP_HTML_Tag_Processor;
|
||||
|
||||
/**
|
||||
* Product Filter Block.
|
||||
*/
|
||||
final class ProductFilter extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'product-filter';
|
||||
|
||||
/**
|
||||
* Get the frontend style handle for this block type.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
protected function get_block_type_style() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend script handle for this block type.
|
||||
*
|
||||
* @see $this->register_block_type()
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return array|string|null
|
||||
*/
|
||||
protected function get_block_type_script( $key = null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra data passed through from server to client for block.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* Note, this will be empty in the editor context when the block is
|
||||
* not in the post content on editor load.
|
||||
*/
|
||||
protected function enqueue_data( array $attributes = [] ) {
|
||||
global $pagenow;
|
||||
parent::enqueue_data( $attributes );
|
||||
|
||||
$this->asset_data_registry->add( 'isBlockTheme', wc_current_theme_is_fse_theme() );
|
||||
$this->asset_data_registry->add( 'isProductArchive', is_shop() || is_product_taxonomy() );
|
||||
$this->asset_data_registry->add( 'isSiteEditor', 'site-editor.php' === $pagenow );
|
||||
$this->asset_data_registry->add( 'isWidgetEditor', 'widgets.php' === $pagenow || 'customize.php' === $pagenow );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check array for checked item.
|
||||
*
|
||||
* @param array $items Items to check.
|
||||
*/
|
||||
private function hasSelectedFilter( $items ) {
|
||||
foreach ( $items as $key => $value ) {
|
||||
if ( 'checked' === $key && true === $value ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( is_array( $value ) && $this->hasSelectedFilter( $value ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the block.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Block content.
|
||||
* @param WP_Block $block Block instance.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render( $attributes, $content, $block ) {
|
||||
if ( is_admin() ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$tags = new WP_HTML_Tag_Processor( $content );
|
||||
$has_selected_filter = false;
|
||||
|
||||
while ( $tags->next_tag( 'div' ) ) {
|
||||
$items = $tags->get_attribute( 'data-wc-context' ) ? json_decode( $tags->get_attribute( 'data-wc-context' ), true ) : null;
|
||||
|
||||
// For checked box filters.
|
||||
if ( $items && array_key_exists( 'items', $items ) ) {
|
||||
$has_selected_filter = $this->hasSelectedFilter( $items['items'] );
|
||||
break;
|
||||
}
|
||||
|
||||
// For price range filter.
|
||||
if ( $items && array_key_exists( 'minPrice', $items ) ) {
|
||||
if ( $items['minPrice'] > $items['minRange'] || $items['maxPrice'] < $items['maxRange'] ) {
|
||||
$has_selected_filter = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// For dropdown filters.
|
||||
if ( $items && array_key_exists( 'selectedItems', $items ) ) {
|
||||
if ( count( $items['selectedItems'] ) > 0 ) {
|
||||
$has_selected_filter = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$attributes_data = array(
|
||||
'data-wc-interactive' => wp_json_encode( array( 'namespace' => $this->get_full_block_name() ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ),
|
||||
'data-wc-context' => wp_json_encode( array( 'hasSelectedFilter' => $has_selected_filter ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ),
|
||||
'class' => 'wc-block-product-filters',
|
||||
);
|
||||
|
||||
if ( ! isset( $block->context['queryId'] ) ) {
|
||||
$attributes_data['data-wc-navigation-id'] = $this->generate_navigation_id( $block );
|
||||
}
|
||||
|
||||
$tags = new WP_HTML_Tag_Processor( $content );
|
||||
|
||||
while ( $tags->next_tag( 'div' ) ) {
|
||||
if ( 'yes' === $tags->get_attribute( 'data-has-filter' ) ) {
|
||||
return sprintf(
|
||||
'<nav %1$s>%2$s</nav>',
|
||||
get_block_wrapper_attributes( $attributes_data ),
|
||||
$content
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'<nav %1$s></nav>',
|
||||
get_block_wrapper_attributes( $attributes_data ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a unique navigation ID for the block.
|
||||
*
|
||||
* @param mixed $block - Block instance.
|
||||
* @return string - Unique navigation ID.
|
||||
*/
|
||||
private function generate_navigation_id( $block ) {
|
||||
return sprintf(
|
||||
'wc-product-filter-%s',
|
||||
md5( wp_json_encode( $block->parsed_block['innerBlocks'] ) )
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -48,16 +48,9 @@ final class ProductFilterActive extends AbstractBlock {
|
||||
*/
|
||||
$active_filters = apply_filters( 'collection_active_filters_data', array(), $this->get_filter_query_params( $query_id ) );
|
||||
|
||||
$context = array(
|
||||
'queryId' => $query_id,
|
||||
'params' => array_keys( $this->get_filter_query_params( $query_id ) ),
|
||||
);
|
||||
|
||||
$wrapper_attributes = get_block_wrapper_attributes(
|
||||
array(
|
||||
'data-wc-interactive' => wp_json_encode( array( 'namespace' => $this->get_full_block_name() ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ),
|
||||
'data-wc-context' => wp_json_encode( $context, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ),
|
||||
'data-has-filter' => empty( $active_filters ) ? 'no' : 'yes',
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\QueryFilters;
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use Automattic\WooCommerce\Blocks\InteractivityComponents\Dropdown;
|
||||
use Automattic\WooCommerce\Blocks\InteractivityComponents\CheckboxList;
|
||||
use Automattic\WooCommerce\Blocks\Utils\ProductCollectionUtils;
|
||||
|
||||
/**
|
||||
@@ -26,6 +26,7 @@ final class ProductFilterAttribute extends AbstractBlock {
|
||||
* - Register the block with WordPress.
|
||||
*/
|
||||
protected function initialize() {
|
||||
add_filter( 'block_type_metadata_settings', array( $this, 'add_block_type_metadata_settings' ), 10, 2 );
|
||||
parent::initialize();
|
||||
|
||||
add_filter( 'collection_filter_query_param_keys', array( $this, 'get_filter_query_param_keys' ), 10, 2 );
|
||||
@@ -129,10 +130,10 @@ final class ProductFilterAttribute extends AbstractBlock {
|
||||
return array(
|
||||
'title' => $term_object->name,
|
||||
'attributes' => array(
|
||||
'data-wc-on--click' => "$action_namespace::actions.removeFilter",
|
||||
'value' => $term,
|
||||
'data-wc-on--click' => "$action_namespace::actions.toggleFilter",
|
||||
'data-wc-context' => "$action_namespace::" . wp_json_encode(
|
||||
array(
|
||||
'value' => $term,
|
||||
'attributeSlug' => $product_attribute,
|
||||
'queryType' => get_query_var( "query_type_{$product_attribute}" ),
|
||||
),
|
||||
@@ -156,24 +157,24 @@ final class ProductFilterAttribute extends AbstractBlock {
|
||||
/**
|
||||
* Render the block.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Block content.
|
||||
* @param WP_Block $block Block instance.
|
||||
* @param array $block_attributes Block attributes.
|
||||
* @param string $content Block content.
|
||||
* @param WP_Block $block Block instance.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render( $attributes, $content, $block ) {
|
||||
if ( empty( $attributes['attributeId'] ) ) {
|
||||
$default_product_attribute = $this->get_default_product_attribute();
|
||||
$attributes['attributeId'] = $default_product_attribute->attribute_id;
|
||||
protected function render( $block_attributes, $content, $block ) {
|
||||
if ( empty( $block_attributes['attributeId'] ) ) {
|
||||
$default_product_attribute = $this->get_default_product_attribute();
|
||||
$block_attributes['attributeId'] = $default_product_attribute->attribute_id;
|
||||
}
|
||||
|
||||
// don't render if its admin, or ajax in progress.
|
||||
if ( is_admin() || wp_doing_ajax() || empty( $attributes['attributeId'] ) ) {
|
||||
if ( is_admin() || wp_doing_ajax() || empty( $block_attributes['attributeId'] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$product_attribute = wc_get_attribute( $attributes['attributeId'] );
|
||||
$attribute_counts = $this->get_attribute_counts( $block, $product_attribute->slug, $attributes['queryType'] );
|
||||
$product_attribute = wc_get_attribute( $block_attributes['attributeId'] );
|
||||
$attribute_counts = $this->get_attribute_counts( $block, $product_attribute->slug, $block_attributes['queryType'] );
|
||||
|
||||
if ( empty( $attribute_counts ) ) {
|
||||
return sprintf(
|
||||
@@ -181,7 +182,6 @@ final class ProductFilterAttribute extends AbstractBlock {
|
||||
get_block_wrapper_attributes(
|
||||
array(
|
||||
'data-wc-interactive' => wp_json_encode( array( 'namespace' => $this->get_full_block_name() ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ),
|
||||
'data-has-filter' => 'no',
|
||||
)
|
||||
),
|
||||
);
|
||||
@@ -202,118 +202,56 @@ final class ProductFilterAttribute extends AbstractBlock {
|
||||
);
|
||||
|
||||
$attribute_options = array_map(
|
||||
function ( $term ) use ( $attribute_counts, $selected_terms ) {
|
||||
function ( $term ) use ( $block_attributes, $attribute_counts, $selected_terms ) {
|
||||
$term = (array) $term;
|
||||
$term['count'] = $attribute_counts[ $term['term_id'] ];
|
||||
$term['selected'] = in_array( $term['slug'], $selected_terms, true );
|
||||
return $term;
|
||||
return array(
|
||||
'label' => $block_attributes['showCounts'] ? sprintf( '%1$s (%2$d)', $term['name'], $term['count'] ) : $term['name'],
|
||||
'value' => $term['slug'],
|
||||
'selected' => $term['selected'],
|
||||
'rawData' => $term,
|
||||
);
|
||||
},
|
||||
$attribute_terms
|
||||
);
|
||||
|
||||
$filtered_options = array_filter(
|
||||
$attribute_options,
|
||||
function ( $option ) {
|
||||
return $option['count'] > 0;
|
||||
function ( $option ) use ( $block_attributes ) {
|
||||
$hide_empty = $block_attributes['hideEmpty'] ?? true;
|
||||
if ( $hide_empty ) {
|
||||
return $option['rawData']['count'] > 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
$filter_content = 'dropdown' === $attributes['displayStyle'] ?
|
||||
$this->render_attribute_dropdown( $filtered_options, $attributes ) :
|
||||
$this->render_attribute_checkbox_list( $filtered_options, $attributes );
|
||||
$filter_context = array(
|
||||
'action' => "{$this->get_full_block_name()}::actions.toggleFilter",
|
||||
'items' => $filtered_options,
|
||||
);
|
||||
|
||||
foreach ( $block->parsed_block['innerBlocks'] as $inner_block ) {
|
||||
$content .= ( new \WP_Block( $inner_block, array( 'filterData' => $filter_context ) ) )->render();
|
||||
}
|
||||
|
||||
$context = array(
|
||||
'attributeSlug' => str_replace( 'pa_', '', $product_attribute->slug ),
|
||||
'queryType' => $attributes['queryType'],
|
||||
'selectType' => 'multiple',
|
||||
'attributeSlug' => str_replace( 'pa_', '', $product_attribute->slug ),
|
||||
'queryType' => $block_attributes['queryType'],
|
||||
'selectType' => 'multiple',
|
||||
'hasSelectedFilters' => count( $selected_terms ) > 0,
|
||||
);
|
||||
|
||||
return sprintf(
|
||||
'<div %1$s>%2$s%3$s</div>',
|
||||
'<div %1$s>%2$s</div>',
|
||||
get_block_wrapper_attributes(
|
||||
array(
|
||||
'data-wc-context' => wp_json_encode( $context, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ),
|
||||
'data-wc-interactive' => wp_json_encode( array( 'namespace' => $this->get_full_block_name() ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ),
|
||||
'data-has-filter' => 'yes',
|
||||
)
|
||||
),
|
||||
$content,
|
||||
$filter_content
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the dropdown.
|
||||
*
|
||||
* @param array $options Data to render the dropdown.
|
||||
* @param bool $attributes Block attributes.
|
||||
*/
|
||||
private function render_attribute_dropdown( $options, $attributes ) {
|
||||
if ( empty( $options ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$list_items = array();
|
||||
$selected_items = array();
|
||||
|
||||
$product_attribute = wc_get_attribute( $attributes['attributeId'] );
|
||||
|
||||
foreach ( $options as $option ) {
|
||||
$item = array(
|
||||
'label' => $attributes['showCounts'] ? sprintf( '%1$s (%2$d)', $option['name'], $option['count'] ) : $option['name'],
|
||||
'value' => $option['slug'],
|
||||
);
|
||||
|
||||
$list_items[] = $item;
|
||||
|
||||
if ( $option['selected'] ) {
|
||||
$selected_items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
return Dropdown::render(
|
||||
array(
|
||||
'items' => $list_items,
|
||||
'action' => "{$this->get_full_block_name()}::actions.navigate",
|
||||
'selected_items' => $selected_items,
|
||||
'select_type' => 'multiple',
|
||||
// translators: %s is a product attribute name.
|
||||
'placeholder' => sprintf( __( 'Select %s', 'woocommerce' ), $product_attribute->name ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the attribute filter checkbox list.
|
||||
*
|
||||
* @param mixed $options Attribute filter options to render in the checkbox list.
|
||||
* @param mixed $attributes Block attributes.
|
||||
* @return string
|
||||
*/
|
||||
private function render_attribute_checkbox_list( $options, $attributes ) {
|
||||
if ( empty( $options ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$show_counts = $attributes['showCounts'] ?? false;
|
||||
|
||||
$list_options = array_map(
|
||||
function ( $option ) use ( $show_counts ) {
|
||||
return array(
|
||||
'id' => $option['slug'] . '-' . $option['term_id'],
|
||||
'checked' => $option['selected'],
|
||||
'label' => $show_counts ? sprintf( '%1$s (%2$d)', $option['name'], $option['count'] ) : $option['name'],
|
||||
'value' => $option['slug'],
|
||||
);
|
||||
},
|
||||
$options
|
||||
);
|
||||
|
||||
return CheckboxList::render(
|
||||
array(
|
||||
'items' => $list_options,
|
||||
'on_change' => "{$this->get_full_block_name()}::actions.updateProducts",
|
||||
)
|
||||
$content
|
||||
);
|
||||
}
|
||||
|
||||
@@ -380,7 +318,16 @@ final class ProductFilterAttribute extends AbstractBlock {
|
||||
|
||||
$cached = get_transient( 'wc_block_product_filter_attribute_default_attribute' );
|
||||
|
||||
if ( $cached ) {
|
||||
if (
|
||||
$cached &&
|
||||
isset( $cached->attribute_id ) &&
|
||||
isset( $cached->attribute_name ) &&
|
||||
isset( $cached->attribute_label ) &&
|
||||
isset( $cached->attribute_type ) &&
|
||||
isset( $cached->attribute_orderby ) &&
|
||||
isset( $cached->attribute_public ) &&
|
||||
'0' !== $cached->attribute_id
|
||||
) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
@@ -428,10 +375,9 @@ final class ProductFilterAttribute extends AbstractBlock {
|
||||
|
||||
if ( $attribute_id ) {
|
||||
$default_attribute = $attributes[ $attribute_id ];
|
||||
set_transient( 'wc_block_product_filter_attribute_default_attribute', $default_attribute, DAY_IN_SECONDS );
|
||||
}
|
||||
|
||||
set_transient( 'wc_block_product_filter_attribute_default_attribute', $default_attribute );
|
||||
|
||||
return $default_attribute;
|
||||
}
|
||||
|
||||
@@ -447,32 +393,29 @@ final class ProductFilterAttribute extends AbstractBlock {
|
||||
'inserter' => false,
|
||||
'content' => strtr(
|
||||
'
|
||||
<!-- wp:woocommerce/product-filter {"filterType":"attribute-filter","attributeId":{{attribute_id}}} -->
|
||||
<!-- wp:group {"metadata":{"name":"Header"},"style":{"spacing":{"blockGap":"0"}},"layout":{"type":"flex","flexWrap":"nowrap"}} -->
|
||||
<div class="wp-block-group">
|
||||
<!-- wp:heading {"level":3} -->
|
||||
<h3 class="wp-block-heading">{{attribute_label}}</h3>
|
||||
<!-- /wp:heading -->
|
||||
<!-- wp:woocommerce/product-filter-attribute {"attributeId":{{attribute_id}}} -->
|
||||
<div class="wp-block-woocommerce-product-filter-attribute">
|
||||
<!-- wp:group {"metadata":{"name":"Header"},"style":{"spacing":{"blockGap":"0"}},"layout":{"type":"flex","flexWrap":"nowrap"}} -->
|
||||
<div class="wp-block-group">
|
||||
<!-- wp:heading {"level":3} -->
|
||||
<h3 class="wp-block-heading">{{attribute_label}}</h3>
|
||||
<!-- /wp:heading -->
|
||||
|
||||
<!-- wp:woocommerce/product-filter-clear-button {"lock":{"remove":true}} -->
|
||||
<!-- wp:buttons {"layout":{"type":"flex"}} -->
|
||||
<div class="wp-block-buttons"><!-- wp:button {"className":"wc-block-product-filter-clear-button is-style-outline","style":{"border":{"width":"0px","style":"none"},"typography":{"textDecoration":"underline"},"outline":"none","fontSize":"medium"}} -->
|
||||
<div class="wp-block-button wc-block-product-filter-clear-button is-style-outline" style="text-decoration:underline"><a class="wp-block-button__link wp-element-button" style="border-style:none;border-width:0px">Clear</a></div>
|
||||
<!-- /wp:button --></div>
|
||||
<!-- /wp:buttons -->
|
||||
<!-- /wp:woocommerce/product-filter-clear-button --></div>
|
||||
<!-- /wp:group -->
|
||||
|
||||
<!-- wp:woocommerce/product-filter-checkbox-list {"lock":{"remove":true}} -->
|
||||
<div class="wp-block-woocommerce-product-filter-checkbox-list wc-block-product-filter-checkbox-list"></div>
|
||||
<!-- /wp:woocommerce/product-filter-checkbox-list -->
|
||||
|
||||
<!-- wp:woocommerce/product-filter-clear-button {"lock":{"remove":true,"move":false}} -->
|
||||
<!-- wp:buttons {"layout":{"type":"flex"}} -->
|
||||
<div class="wp-block-buttons">
|
||||
<!-- wp:button {"className":"wc-block-product-filter-clear-button is-style-outline","style":{"border":{"width":"0px","style":"none"},"typography":{"textDecoration":"underline"},"outline":"none","fontSize":"medium"}} -->
|
||||
<div
|
||||
class="wp-block-button wc-block-product-filter-clear-button is-style-outline"
|
||||
style="text-decoration: underline"
|
||||
>
|
||||
<a class="wp-block-button__link wp-element-button" style="border-style: none; border-width: 0px">Clear</a>
|
||||
</div>
|
||||
<!-- /wp:button -->
|
||||
</div>
|
||||
<!-- /wp:buttons -->
|
||||
<!-- /wp:woocommerce/product-filter-clear-button -->
|
||||
</div>
|
||||
<!-- /wp:group -->
|
||||
|
||||
<!-- wp:woocommerce/product-filter-attribute {"attributeId":{{attribute_id}},"lock":{"remove":true}} /-->
|
||||
<!-- /wp:woocommerce/product-filter -->
|
||||
<!-- /wp:woocommerce/product-filter-attribute -->
|
||||
',
|
||||
array(
|
||||
'{{attribute_id}}' => intval( $default_attribute->attribute_id ),
|
||||
@@ -482,4 +425,18 @@ final class ProductFilterAttribute extends AbstractBlock {
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip default rendering routine for inner blocks.
|
||||
*
|
||||
* @param array $settings Array of determined settings for registering a block type.
|
||||
* @param array $metadata Metadata provided for registering a block type.
|
||||
* @return array
|
||||
*/
|
||||
public function add_block_type_metadata_settings( $settings, $metadata ) {
|
||||
if ( ! empty( $metadata['name'] ) && "woocommerce/{$this->block_name}" === $metadata['name'] ) {
|
||||
$settings['skip_inner_blocks'] = true;
|
||||
}
|
||||
return $settings;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* Product Filter: Checkbox List Block.
|
||||
*/
|
||||
final class ProductFilterCheckboxList extends AbstractBlock {
|
||||
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'product-filter-checkbox-list';
|
||||
|
||||
/**
|
||||
* Render the block.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Block content.
|
||||
* @param WP_Block $block Block instance.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render( $attributes, $content, $block ) {
|
||||
$context = $block->context['filterData'];
|
||||
$items = $context['items'] ?? array();
|
||||
$checkbox_list_context = array( 'items' => $items );
|
||||
$action = $context['action'] ?? '';
|
||||
$namespace = wp_json_encode( array( 'namespace' => 'woocommerce/product-filter-checkbox-list' ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP );
|
||||
$classes = '';
|
||||
$style = '';
|
||||
|
||||
$tags = new \WP_HTML_Tag_Processor( $content );
|
||||
if ( $tags->next_tag( array( 'class_name' => 'wc-block-product-filter-checkbox-list' ) ) ) {
|
||||
$classes = $tags->get_attribute( 'class' );
|
||||
$style = $tags->get_attribute( 'style' );
|
||||
}
|
||||
|
||||
$checked_items = array_filter(
|
||||
$items,
|
||||
function ( $item ) {
|
||||
return $item['selected'];
|
||||
}
|
||||
);
|
||||
$show_initially = $context['show_initially'] ?? 15;
|
||||
$remaining_initial_unchecked = count( $checked_items ) > $show_initially ? count( $checked_items ) : $show_initially - count( $checked_items );
|
||||
$count = 0;
|
||||
|
||||
$wrapper_attributes = array(
|
||||
'data-wc-interactive' => esc_attr( $namespace ),
|
||||
'data-wc-context' => wp_json_encode( $checkbox_list_context, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ),
|
||||
'class' => esc_attr( $classes ),
|
||||
'style' => esc_attr( $style ),
|
||||
);
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div <?php echo get_block_wrapper_attributes( $wrapper_attributes ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
|
||||
<ul class="wc-block-product-filter-checkbox-list__list" aria-label="<?php echo esc_attr__( 'Filter Options', 'woocommerce' ); ?>">
|
||||
<?php foreach ( $items as $item ) { ?>
|
||||
<?php
|
||||
$item['id'] = $item['id'] ?? uniqid( 'checkbox-' );
|
||||
// translators: %s: checkbox label.
|
||||
$i18n_label = sprintf( __( 'Checkbox: %s', 'woocommerce' ), $item['aria_label'] ?? '' );
|
||||
?>
|
||||
<li
|
||||
data-wc-key="<?php echo esc_attr( $item['id'] ); ?>"
|
||||
<?php
|
||||
if ( ! $item['selected'] ) :
|
||||
if ( $count >= $remaining_initial_unchecked ) :
|
||||
?>
|
||||
class="wc-block-product-filter-checkbox-list__item"
|
||||
data-wc-bind--hidden="!context.showAll"
|
||||
hidden
|
||||
<?php else : ?>
|
||||
<?php ++$count; ?>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
class="wc-block-product-filter-checkbox-list__item"
|
||||
>
|
||||
<label
|
||||
class="wc-block-product-filter-checkbox-list__label"
|
||||
for="<?php echo esc_attr( $item['id'] ); ?>"
|
||||
>
|
||||
<span class="wc-block-product-filter-checkbox-list__input-wrapper">
|
||||
<input
|
||||
id="<?php echo esc_attr( $item['id'] ); ?>"
|
||||
class="wc-block-product-filter-checkbox-list__input"
|
||||
type="checkbox"
|
||||
aria-invalid="false"
|
||||
aria-label="<?php echo esc_attr( $i18n_label ); ?>"
|
||||
data-wc-on--change--select-item="actions.selectCheckboxItem"
|
||||
data-wc-on--change--parent-action="<?php echo esc_attr( $action ); ?>"
|
||||
value="<?php echo esc_attr( $item['value'] ); ?>"
|
||||
<?php checked( $item['selected'], 1 ); ?>
|
||||
>
|
||||
<svg class="wc-block-product-filter-checkbox-list__mark" viewBox="0 0 10 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.25 1.19922L3.75 6.69922L1 3.94922" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</span>
|
||||
<span class="wc-block-product-filter-checkbox-list__text">
|
||||
<?php echo wp_kses_post( $item['label'] ); ?>
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
<?php } ?>
|
||||
</ul>
|
||||
<?php if ( count( $items ) > $show_initially ) : ?>
|
||||
<button
|
||||
class="wc-block-product-filter-checkbox-list__show-more"
|
||||
data-wc-bind--hidden="context.showAll"
|
||||
data-wc-on--click="actions.showAllItems"
|
||||
hidden
|
||||
>
|
||||
<?php echo esc_html__( 'Show more...', 'woocommerce' ); ?>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* Product Filter: Chips Block.
|
||||
*/
|
||||
final class ProductFilterChips extends AbstractBlock {
|
||||
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'product-filter-chips';
|
||||
|
||||
/**
|
||||
* Render the block.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Block content.
|
||||
* @param WP_Block $block Block instance.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render( $attributes, $content, $block ) {
|
||||
$classes = '';
|
||||
$style = '';
|
||||
$context = $block->context['filterData'];
|
||||
$items = $context['items'] ?? array();
|
||||
$checkbox_list_context = array( 'items' => $items );
|
||||
$action = $context['action'] ?? '';
|
||||
$namespace = wp_json_encode( array( 'namespace' => 'woocommerce/product-filter-chips' ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP );
|
||||
|
||||
$tags = new \WP_HTML_Tag_Processor( $content );
|
||||
if ( $tags->next_tag( array( 'class_name' => 'wc-block-product-filter-chips' ) ) ) {
|
||||
$classes = $tags->get_attribute( 'class' );
|
||||
$style = $tags->get_attribute( 'style' );
|
||||
}
|
||||
|
||||
$checked_items = array_filter(
|
||||
$items,
|
||||
function ( $item ) {
|
||||
return $item['selected'];
|
||||
}
|
||||
);
|
||||
$show_initially = $context['show_initially'] ?? 15;
|
||||
$remaining_initial_unchecked = count( $checked_items ) > $show_initially ? count( $checked_items ) : $show_initially - count( $checked_items );
|
||||
$count = 0;
|
||||
|
||||
$wrapper_attributes = array(
|
||||
'data-wc-interactive' => esc_attr( $namespace ),
|
||||
'data-wc-context' => wp_json_encode( $checkbox_list_context, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ),
|
||||
'class' => esc_attr( $classes ),
|
||||
'style' => esc_attr( $style ),
|
||||
);
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div <?php echo get_block_wrapper_attributes( $wrapper_attributes ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
|
||||
<div class="wc-block-product-filter-chips__items" aria-label="<?php echo esc_attr__( 'Filter Options', 'woocommerce' ); ?>">
|
||||
<?php foreach ( $items as $item ) { ?>
|
||||
<?php $item['id'] = $item['id'] ?? uniqid( 'chips-' ); ?>
|
||||
<button
|
||||
data-wc-key="<?php echo esc_attr( $item['id'] ); ?>"
|
||||
<?php
|
||||
if ( ! $item['selected'] ) :
|
||||
if ( $count >= $remaining_initial_unchecked ) :
|
||||
?>
|
||||
class="wc-block-product-filter-chips__item"
|
||||
data-wc-bind--hidden="!context.showAll"
|
||||
hidden
|
||||
<?php else : ?>
|
||||
<?php ++$count; ?>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
class="wc-block-product-filter-chips__item"
|
||||
data-wc-on--click--select-item="actions.selectItem"
|
||||
data-wc-on--click--parent-action="<?php echo esc_attr( $action ); ?>"
|
||||
value="<?php echo esc_attr( $item['value'] ); ?>"
|
||||
aria-checked="<?php echo $item['selected'] ? 'true' : 'false'; ?>"
|
||||
>
|
||||
<span class="wc-block-product-filter-chips__label">
|
||||
<?php echo wp_kses_post( $item['label'] ); ?>
|
||||
</span>
|
||||
</button>
|
||||
<?php } ?>
|
||||
</div>
|
||||
<?php if ( count( $items ) > $show_initially ) : ?>
|
||||
<button
|
||||
class="wc-block-product-filter-chips__show-more"
|
||||
data-wc-bind--hidden="context.showAll"
|
||||
data-wc-on--click="actions.showAllItems"
|
||||
hidden
|
||||
>
|
||||
<?php echo esc_html__( 'Show more...', 'woocommerce' ); ?>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
}
|
||||
@@ -38,14 +38,14 @@ final class ProductFilterClearButton extends AbstractBlock {
|
||||
|
||||
$wrapper_attributes = get_block_wrapper_attributes(
|
||||
array(
|
||||
'data-wc-bind--hidden' => '!context.hasSelectedFilter',
|
||||
'data-wc-bind--hidden' => '!context.hasSelectedFilters',
|
||||
)
|
||||
);
|
||||
|
||||
$p = new \WP_HTML_Tag_Processor( $content );
|
||||
|
||||
if ( $p->next_tag( array( 'class_name' => 'wp-block-button__link' ) ) ) {
|
||||
$p->set_attribute( 'data-wc-on--click', 'actions.clear' );
|
||||
$p->set_attribute( 'data-wc-on--click', 'actions.clearFilters' );
|
||||
|
||||
$style = $p->get_attribute( 'style' );
|
||||
$p->set_attribute( 'style', 'outline:none;' . $style );
|
||||
|
||||
@@ -234,6 +234,7 @@ final class ProductFilterPrice extends AbstractBlock {
|
||||
data-wc-bind--max="context.maxRange"
|
||||
data-wc-bind--value="context.minPrice"
|
||||
data-wc-on--change="actions.updateProducts"
|
||||
data-wc-on--input="actions.updateRange"
|
||||
>
|
||||
<input
|
||||
type="range"
|
||||
@@ -246,6 +247,7 @@ final class ProductFilterPrice extends AbstractBlock {
|
||||
data-wc-bind--max="context.maxRange"
|
||||
data-wc-bind--value="context.maxPrice"
|
||||
data-wc-on--change="actions.updateProducts"
|
||||
data-wc-on--input="actions.updateRange"
|
||||
>
|
||||
</div>
|
||||
<div class="wp-block-woocommerce-product-filter-price-content-right-input text">
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
|
||||
|
||||
/**
|
||||
* ProductFilters class.
|
||||
*/
|
||||
@@ -18,16 +20,108 @@ class ProductFilters extends AbstractBlock {
|
||||
* @return string[]
|
||||
*/
|
||||
protected function get_block_type_uses_context() {
|
||||
return [ 'postId' ];
|
||||
return array( 'postId' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend style handle for this block type.
|
||||
* Extra data passed through from server to client for block.
|
||||
*
|
||||
* @return null
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* Note, this will be empty in the editor context when the block is
|
||||
* not in the post content on editor load.
|
||||
*/
|
||||
protected function get_block_type_style() {
|
||||
return null;
|
||||
protected function enqueue_data( array $attributes = array() ) {
|
||||
global $pagenow;
|
||||
parent::enqueue_data( $attributes );
|
||||
|
||||
$this->asset_data_registry->add( 'isBlockTheme', wc_current_theme_is_fse_theme() );
|
||||
$this->asset_data_registry->add( 'isProductArchive', is_shop() || is_product_taxonomy() );
|
||||
$this->asset_data_registry->add( 'isSiteEditor', 'site-editor.php' === $pagenow );
|
||||
$this->asset_data_registry->add( 'isWidgetEditor', 'widgets.php' === $pagenow || 'customize.php' === $pagenow );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the dialog content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function render_dialog() {
|
||||
$template_part = BlockTemplateUtils::get_template_part( 'product-filters-overlay' );
|
||||
|
||||
$html = $this->render_template_part( $template_part );
|
||||
|
||||
$html = strtr(
|
||||
'<dialog hidden role="dialog" aria-modal="true">
|
||||
{{html}}
|
||||
</dialog>',
|
||||
array(
|
||||
'{{html}}' => $html,
|
||||
)
|
||||
);
|
||||
|
||||
$p = new \WP_HTML_Tag_Processor( $html );
|
||||
if ( $p->next_tag() ) {
|
||||
$p->set_attribute( 'data-wc-interactive', wp_json_encode( array( 'namespace' => 'woocommerce/product-filters' ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ) );
|
||||
$p->set_attribute( 'data-wc-bind--hidden', '!state.isDialogOpen' );
|
||||
$p->set_attribute( 'data-wc-class--wc-block-product-filters--dialog-open', 'state.isDialogOpen' );
|
||||
$p->set_attribute( 'data-wc-class--wc-block-product-filters--with-admin-bar', 'context.hasPageWithWordPressAdminBar' );
|
||||
$html = $p->get_updated_html();
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to render the template part. For each template part, we parse the blocks and render them.
|
||||
*
|
||||
* @param string $template_part The template part to render.
|
||||
* @return string The rendered template part.
|
||||
*/
|
||||
protected function render_template_part( $template_part ) {
|
||||
$parsed_blocks = parse_blocks( $template_part );
|
||||
$wrapper_template_part_block = $parsed_blocks[0];
|
||||
$html = $wrapper_template_part_block['innerHTML'];
|
||||
$target_div = '</div>';
|
||||
|
||||
$template_part_content_html = array_reduce(
|
||||
$wrapper_template_part_block['innerBlocks'],
|
||||
function ( $carry, $item ) {
|
||||
if ( 'core/template-part' === $item['blockName'] ) {
|
||||
$inner_template_part = BlockTemplateUtils::get_template_part( $item['attrs']['slug'] );
|
||||
$inner_template_part_content_html = $this->render_template_part( $inner_template_part );
|
||||
|
||||
return $carry . $inner_template_part_content_html;
|
||||
}
|
||||
return $carry . render_block( $item );
|
||||
},
|
||||
''
|
||||
);
|
||||
|
||||
$html = str_replace( $target_div, $template_part_content_html . $target_div, $html );
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject dialog into the product filters HTML.
|
||||
*
|
||||
* @param string $product_filters_html The Product Filters HTML.
|
||||
* @param string $dialog_html The dialog HTML.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function inject_dialog( $product_filters_html, $dialog_html ) {
|
||||
// Find the position of the last </div>.
|
||||
$pos = strrpos( $product_filters_html, '</div>' );
|
||||
|
||||
if ( $pos ) {
|
||||
// Inject the dialog_html at the correct position.
|
||||
$html = substr_replace( $product_filters_html, $dialog_html, $pos, 0 );
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
return $product_filters_html;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,6 +133,86 @@ class ProductFilters extends AbstractBlock {
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render( $attributes, $content, $block ) {
|
||||
return $content;
|
||||
$tags = new \WP_HTML_Tag_Processor( $content );
|
||||
if ( $tags->next_tag() ) {
|
||||
$tags->set_attribute( 'data-wc-interactive', wp_json_encode( array( 'namespace' => 'woocommerce/' . $this->block_name ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ) );
|
||||
$tags->set_attribute(
|
||||
'data-wc-context',
|
||||
wp_json_encode(
|
||||
array(
|
||||
'isDialogOpen' => false,
|
||||
'hasPageWithWordPressAdminBar' => false,
|
||||
'params' => $this->get_filter_query_params( 0 ),
|
||||
'originalParams' => $this->get_filter_query_params( 0 ),
|
||||
),
|
||||
JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
|
||||
)
|
||||
);
|
||||
$tags->set_attribute( 'data-wc-navigation-id', $this->generate_navigation_id( $block ) );
|
||||
$tags->set_attribute( 'data-wc-watch', 'callbacks.maybeNavigate' );
|
||||
|
||||
if (
|
||||
'always' === $attributes['overlay'] ||
|
||||
( 'mobile' === $attributes['overlay'] && wp_is_mobile() )
|
||||
) {
|
||||
return $this->inject_dialog( $tags->get_updated_html(), $this->render_dialog() );
|
||||
}
|
||||
|
||||
return $tags->get_updated_html();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a unique navigation ID for the block.
|
||||
*
|
||||
* @param mixed $block - Block instance.
|
||||
* @return string - Unique navigation ID.
|
||||
*/
|
||||
private function generate_navigation_id( $block ) {
|
||||
return sprintf(
|
||||
'wc-product-filters-%s',
|
||||
md5( wp_json_encode( $block->parsed_block['innerBlocks'] ) )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the filter parameters from the URL.
|
||||
* For now we only get the global query params from the URL. In the future,
|
||||
* we should get the query params based on $query_id.
|
||||
*
|
||||
* @param int $query_id Query ID.
|
||||
* @return array Parsed filter params.
|
||||
*/
|
||||
private function get_filter_query_params( $query_id ) {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
$request_uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '';
|
||||
|
||||
$parsed_url = wp_parse_url( esc_url_raw( $request_uri ) );
|
||||
|
||||
if ( empty( $parsed_url['query'] ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
parse_str( $parsed_url['query'], $url_query_params );
|
||||
|
||||
/**
|
||||
* Filters the active filter data provided by filter blocks.
|
||||
*
|
||||
* @since 11.7.0
|
||||
*
|
||||
* @param array $filter_param_keys The active filters data
|
||||
* @param array $url_param_keys The query param parsed from the URL.
|
||||
*
|
||||
* @return array Active filters params.
|
||||
*/
|
||||
$filter_param_keys = array_unique( apply_filters( 'collection_filter_query_param_keys', array(), array_keys( $url_query_params ) ) );
|
||||
|
||||
return array_filter(
|
||||
$url_query_params,
|
||||
function ( $key ) use ( $filter_param_keys ) {
|
||||
return in_array( $key, $filter_param_keys, true );
|
||||
},
|
||||
ARRAY_FILTER_USE_KEY
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,17 +21,6 @@ class ProductFiltersOverlayNavigation extends AbstractBlock {
|
||||
return [ 'woocommerce/product-filters/overlay' ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend script handle for this block type.
|
||||
*
|
||||
* @see $this->register_block_type()
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return array|string|null
|
||||
*/
|
||||
protected function get_block_type_script( $key = null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Include and render the block.
|
||||
*
|
||||
@@ -46,13 +35,13 @@ class ProductFiltersOverlayNavigation extends AbstractBlock {
|
||||
'class' => 'wc-block-product-filters-overlay-navigation',
|
||||
)
|
||||
);
|
||||
$overlay_mode = $block->context['woocommerce/product-filters/overlay'];
|
||||
$overlay_mode = isset( $block->context['woocommerce/product-filters/overlay'] ) ? $block->context['woocommerce/product-filters/overlay'] : 'never';
|
||||
|
||||
if ( 'never' === $overlay_mode || ( ! wp_is_mobile() && 'mobile' === $overlay_mode ) ) {
|
||||
if ( 'open-overlay' === $attributes['triggerType'] && ( 'never' === $overlay_mode || ( ! wp_is_mobile() && 'mobile' === $overlay_mode ) ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$html_content = strtr(
|
||||
$html = strtr(
|
||||
'<div {{wrapper_attributes}}>
|
||||
{{primary_content}}
|
||||
{{secondary_content}}
|
||||
@@ -63,7 +52,20 @@ class ProductFiltersOverlayNavigation extends AbstractBlock {
|
||||
'{{secondary_content}}' => 'open-overlay' === $attributes['triggerType'] ? $this->render_label( $attributes ) : $this->render_icon( $attributes ),
|
||||
)
|
||||
);
|
||||
return $html_content;
|
||||
|
||||
$p = new \WP_HTML_Tag_Processor( $html );
|
||||
|
||||
if ( $p->next_tag() ) {
|
||||
$p->set_attribute( 'data-wc-interactive', wp_json_encode( array( 'namespace' => 'woocommerce/product-filters' ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ) );
|
||||
$p->set_attribute(
|
||||
'data-wc-on--click',
|
||||
'open-overlay' === $attributes['triggerType'] ? 'actions.openDialog' : 'actions.closeDialog'
|
||||
);
|
||||
$p->set_attribute( 'data-wc-class--hidden', 'open-overlay' === $attributes['triggerType'] ? 'state.isDialogOpen' : '!state.isDialogOpen' );
|
||||
$html = $p->get_updated_html();
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -110,11 +110,14 @@ class ProductGallery extends AbstractBlock {
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render( $attributes, $content, $block ) {
|
||||
$post_id = $block->context['postId'] ?? '';
|
||||
$post_id = $block->context['postId'] ?? '';
|
||||
$product = wc_get_product( $post_id );
|
||||
if ( ! $product instanceof \WC_Product ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$product_gallery_images = ProductGalleryUtils::get_product_gallery_images( $post_id, 'thumbnail', array() );
|
||||
$classname_single_image = '';
|
||||
// This is a temporary solution. We have to refactor this code when the block will have to be addable on every page/post https://github.com/woocommerce/woocommerce-blocks/issues/10882.
|
||||
global $product;
|
||||
|
||||
if ( count( $product_gallery_images ) < 2 ) {
|
||||
// The gallery consists of a single image.
|
||||
@@ -124,8 +127,6 @@ class ProductGallery extends AbstractBlock {
|
||||
$number_of_thumbnails = $block->attributes['thumbnailsNumberOfThumbnails'] ?? 0;
|
||||
$classname = $attributes['className'] ?? '';
|
||||
$dialog = isset( $attributes['mode'] ) && 'full' !== $attributes['mode'] ? $this->render_dialog() : '';
|
||||
$post_id = $block->context['postId'] ?? '';
|
||||
$product = wc_get_product( $post_id );
|
||||
$product_gallery_first_image = ProductGalleryUtils::get_product_gallery_image_ids( $product, 1 );
|
||||
$product_gallery_first_image_id = reset( $product_gallery_first_image );
|
||||
$product_id = strval( $product->get_id() );
|
||||
|
||||
@@ -125,12 +125,15 @@ class ProductImage extends AbstractBlock {
|
||||
private function render_anchor( $product, $on_sale_badge, $product_image, $attributes ) {
|
||||
$product_permalink = $product->get_permalink();
|
||||
|
||||
$pointer_events = false === $attributes['showProductLink'] ? 'pointer-events: none;' : '';
|
||||
$is_link = true === $attributes['showProductLink'];
|
||||
$pointer_events = $is_link ? '' : 'pointer-events: none;';
|
||||
$directive = $is_link ? 'data-wc-on--click="woocommerce/product-collection::actions.viewProduct"' : '';
|
||||
|
||||
return sprintf(
|
||||
'<a href="%1$s" style="%2$s">%3$s %4$s</a>',
|
||||
'<a href="%1$s" style="%2$s" %3$s>%4$s %5$s</a>',
|
||||
$product_permalink,
|
||||
$pointer_events,
|
||||
$directive,
|
||||
$on_sale_badge,
|
||||
$product_image
|
||||
);
|
||||
|
||||
@@ -778,7 +778,7 @@ class ProductQuery extends AbstractBlock {
|
||||
* - For array items with numeric keys, we merge them as normal.
|
||||
* - For array items with string keys:
|
||||
*
|
||||
* - If the value isn't array, we'll use the value comming from the merge array.
|
||||
* - If the value isn't array, we'll use the value coming from the merge array.
|
||||
* $base = ['orderby' => 'date']
|
||||
* $new = ['orderby' => 'meta_value_num']
|
||||
* Result: ['orderby' => 'meta_value_num']
|
||||
|
||||
@@ -114,7 +114,9 @@ class ProductRating extends AbstractBlock {
|
||||
$post_id = isset( $block->context['postId'] ) ? $block->context['postId'] : '';
|
||||
$product = wc_get_product( $post_id );
|
||||
|
||||
if ( $product && $product->get_review_count() > 0 ) {
|
||||
if ( $product && $product->get_review_count() > 0
|
||||
&& $product->get_reviews_allowed()
|
||||
&& wc_reviews_enabled() ) {
|
||||
$product_reviews_count = $product->get_review_count();
|
||||
$product_rating = $product->get_average_rating();
|
||||
$parsed_attributes = $this->parse_attributes( $attributes );
|
||||
|
||||
@@ -69,14 +69,27 @@ class ProductSKU extends AbstractBlock {
|
||||
|
||||
$styles_and_classes = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
|
||||
|
||||
$prefix = isset( $attributes['prefix'] ) ? wp_kses_post( ( $attributes['prefix'] ) ) : __( 'SKU: ', 'woocommerce' );
|
||||
if ( ! empty( $prefix ) ) {
|
||||
$prefix = sprintf( '<span class="prefix">%s</span>', $prefix );
|
||||
}
|
||||
|
||||
$suffix = isset( $attributes['suffix'] ) ? wp_kses_post( ( $attributes['suffix'] ) ) : '';
|
||||
if ( ! empty( $suffix ) ) {
|
||||
$suffix = sprintf( '<span class="suffix">%s</span>', $suffix );
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'<div class="wc-block-components-product-sku wc-block-grid__product-sku wp-block-woocommerce-product-sku product_meta %1$s" style="%2$s">
|
||||
SKU:
|
||||
<strong class="sku">%3$s</strong>
|
||||
%3$s
|
||||
<strong class="sku">%4$s</strong>
|
||||
%5$s
|
||||
</div>',
|
||||
esc_attr( $styles_and_classes['classes'] ),
|
||||
esc_attr( $styles_and_classes['styles'] ?? '' ),
|
||||
$product_sku
|
||||
$prefix,
|
||||
$product_sku,
|
||||
$suffix
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +85,7 @@ class ProductTemplate extends AbstractBlock {
|
||||
|
||||
// Get an instance of the current Post Template block.
|
||||
$block_instance = $block->parsed_block;
|
||||
$product_id = get_the_ID();
|
||||
|
||||
// Set the block name to one that does not correspond to an existing registered block.
|
||||
// This ensures that for the inner instances of the Post Template block, we do not render any block supports.
|
||||
@@ -97,14 +98,39 @@ class ProductTemplate extends AbstractBlock {
|
||||
$block_instance,
|
||||
array(
|
||||
'postType' => get_post_type(),
|
||||
'postId' => get_the_ID(),
|
||||
'postId' => $product_id,
|
||||
)
|
||||
)
|
||||
)->render( array( 'dynamic' => false ) );
|
||||
|
||||
$interactive = array(
|
||||
'namespace' => 'woocommerce/product-collection',
|
||||
);
|
||||
|
||||
$context = array(
|
||||
'productId' => $product_id,
|
||||
);
|
||||
|
||||
$li_directives = '
|
||||
data-wc-interactive=\'' . wp_json_encode( $interactive, JSON_NUMERIC_CHECK | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ) . '\'
|
||||
data-wc-context=\'' . wp_json_encode( $context, JSON_NUMERIC_CHECK | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ) . '\'
|
||||
data-wc-key="product-item-' . $product_id . '"
|
||||
';
|
||||
|
||||
// Wrap the render inner blocks in a `li` element with the appropriate post classes.
|
||||
$post_classes = implode( ' ', get_post_class( 'wc-block-product' ) );
|
||||
$content .= '<li data-wc-key="product-item-' . get_the_ID() . '" class="' . esc_attr( $post_classes ) . '">' . $block_content . '</li>';
|
||||
$content .= strtr(
|
||||
'<li class="{classes}"
|
||||
{li_directives}
|
||||
>
|
||||
{content}
|
||||
</li>',
|
||||
array(
|
||||
'{classes}' => esc_attr( $post_classes ),
|
||||
'{li_directives}' => $li_directives,
|
||||
'{content}' => $block_content,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -45,17 +45,15 @@ class StoreNotices extends AbstractBlock {
|
||||
return;
|
||||
}
|
||||
|
||||
$classname = isset( $attributes['className'] ) ? $attributes['className'] : '';
|
||||
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
|
||||
|
||||
if ( isset( $attributes['align'] ) ) {
|
||||
$classname .= " align{$attributes['align']}";
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'<div class="woocommerce wc-block-store-notices %1$s %2$s">%3$s</div>',
|
||||
esc_attr( $classes_and_styles['classes'] ),
|
||||
esc_attr( $classname ),
|
||||
'<div %1$s>%2$s</div>',
|
||||
get_block_wrapper_attributes(
|
||||
array(
|
||||
'class' => 'wc-block-store-notices woocommerce ' . esc_attr( $classes_and_styles['classes'] ),
|
||||
)
|
||||
),
|
||||
wc_kses_notice( $notices )
|
||||
);
|
||||
}
|
||||
|
||||
@@ -285,7 +285,7 @@ final class BlockTypesController {
|
||||
* and prevent them from showing as an option in the Legacy Widget block.
|
||||
*
|
||||
* @param array $widget_types An array of widgets hidden in core.
|
||||
* @return array $widget_types An array inluding the WooCommerce widgets to hide.
|
||||
* @return array $widget_types An array including the WooCommerce widgets to hide.
|
||||
*/
|
||||
public function hide_legacy_widgets_with_block_equivalent( $widget_types ) {
|
||||
array_push(
|
||||
@@ -404,7 +404,6 @@ final class BlockTypesController {
|
||||
// Update plugins/woocommerce-blocks/docs/internal-developers/blocks/feature-flags-and-experimental-interfaces.md
|
||||
// when modifying this list.
|
||||
if ( Features::is_enabled( 'experimental-blocks' ) ) {
|
||||
$block_types[] = 'ProductFilter';
|
||||
$block_types[] = 'ProductFilters';
|
||||
$block_types[] = 'ProductFiltersOverlay';
|
||||
$block_types[] = 'ProductFiltersOverlayNavigation';
|
||||
@@ -414,6 +413,9 @@ final class BlockTypesController {
|
||||
$block_types[] = 'ProductFilterRating';
|
||||
$block_types[] = 'ProductFilterActive';
|
||||
$block_types[] = 'ProductFilterClearButton';
|
||||
$block_types[] = 'ProductFilterCheckboxList';
|
||||
$block_types[] = 'ProductFilterChips';
|
||||
$block_types[] = 'OrderConfirmation\CreateAccount';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -169,8 +169,6 @@ class Bootstrap {
|
||||
$this->container->get( BlockPatterns::class );
|
||||
$this->container->get( BlockTypesController::class );
|
||||
$this->container->get( ClassicTemplatesCompatibility::class );
|
||||
$this->container->get( ArchiveProductTemplatesCompatibility::class )->init();
|
||||
$this->container->get( SingleProductTemplateCompatibility::class )->init();
|
||||
$this->container->get( Notices::class )->init();
|
||||
$this->container->get( PTKPatternsStore::class );
|
||||
$this->container->get( TemplateOptions::class )->init();
|
||||
@@ -276,18 +274,6 @@ class Bootstrap {
|
||||
return new ClassicTemplatesCompatibility( $asset_data_registry );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
ArchiveProductTemplatesCompatibility::class,
|
||||
function () {
|
||||
return new ArchiveProductTemplatesCompatibility();
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
SingleProductTemplateCompatibility::class,
|
||||
function () {
|
||||
return new SingleProductTemplateCompatibility();
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
DraftOrders::class,
|
||||
function ( Container $container ) {
|
||||
|
||||
@@ -8,6 +8,8 @@ use Automattic\WooCommerce\Blocks\Images\Pexels;
|
||||
|
||||
/**
|
||||
* AIPatterns class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class AIPatterns {
|
||||
const PATTERNS_AI_DATA_POST_TYPE = 'patterns_ai_data';
|
||||
|
||||
@@ -5,6 +5,8 @@ use WP_Error;
|
||||
|
||||
/**
|
||||
* PatternsToolkit class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class PTKClient {
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,8 @@ use WP_Upgrader;
|
||||
|
||||
/**
|
||||
* PTKPatterns class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class PTKPatternsStore {
|
||||
const TRANSIENT_NAME = 'ptk_patterns';
|
||||
@@ -92,7 +94,11 @@ class PTKPatternsStore {
|
||||
*/
|
||||
private function schedule_action_if_not_pending( $action ) {
|
||||
$last_request = get_transient( 'last_fetch_patterns_request' );
|
||||
if ( as_has_scheduled_action( $action ) || false !== $last_request ) {
|
||||
// The most efficient way to check for an existing action is to use `as_has_scheduled_action`, but in unusual
|
||||
// cases where another plugin has loaded a very old version of Action Scheduler, it may not be available to us.
|
||||
|
||||
$has_scheduled_action = function_exists( 'as_has_scheduled_action' ) ? 'as_has_scheduled_action' : 'as_next_scheduled_action';
|
||||
if ( call_user_func( $has_scheduled_action, $action ) || false !== $last_request ) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,25 +5,22 @@ use Automattic\WooCommerce\Admin\Features\Features;
|
||||
|
||||
/**
|
||||
* PatternRegistry class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class PatternRegistry {
|
||||
const SLUG_REGEX = '/^[A-z0-9\/_-]+$/';
|
||||
const COMMA_SEPARATED_REGEX = '/[\s,]+/';
|
||||
|
||||
|
||||
/**
|
||||
* Associates pattern slugs with their localized labels for categorization.
|
||||
* Returns pattern slugs with their localized labels for categorization.
|
||||
*
|
||||
* Each key represents a unique pattern slug, while the value is the localized label.
|
||||
*
|
||||
* @var array $category_labels
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private $category_labels;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->category_labels = [
|
||||
private function get_category_labels() {
|
||||
return [
|
||||
'woo-commerce' => __( 'WooCommerce', 'woocommerce' ),
|
||||
'intro' => __( 'Intro', 'woocommerce' ),
|
||||
'featured-selling' => __( 'Featured Selling', 'woocommerce' ),
|
||||
@@ -146,10 +143,10 @@ class PatternRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.LowLevelTranslationFunction
|
||||
// phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.LowLevelTranslationFunction
|
||||
$pattern_data['title'] = translate_with_gettext_context( $pattern_data['title'], 'Pattern title', 'woocommerce' );
|
||||
if ( ! empty( $pattern_data['description'] ) ) {
|
||||
// phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.LowLevelTranslationFunction
|
||||
// phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.LowLevelTranslationFunction
|
||||
$pattern_data['description'] = translate_with_gettext_context( $pattern_data['description'], 'Pattern description', 'woocommerce' );
|
||||
}
|
||||
|
||||
@@ -184,13 +181,15 @@ class PatternRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
$category_labels = $this->get_category_labels();
|
||||
|
||||
if ( ! empty( $pattern_data['categories'] ) ) {
|
||||
foreach ( $pattern_data['categories'] as $key => $category ) {
|
||||
$category_slug = _wp_to_kebab_case( $category );
|
||||
|
||||
$pattern_data['categories'][ $key ] = $category_slug;
|
||||
|
||||
$label = isset( $this->category_labels[ $category_slug ] ) ? $this->category_labels[ $category_slug ] : self::kebab_to_capital_case( $category_slug );
|
||||
$label = $category_labels[ $category_slug ] ?? self::kebab_to_capital_case( $category_slug );
|
||||
|
||||
register_block_pattern_category(
|
||||
$category_slug,
|
||||
|
||||
@@ -18,7 +18,7 @@ final class QueryFilters {
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the posts clauses of the main query to suport global filters.
|
||||
* Filter the posts clauses of the main query to support global filters.
|
||||
*
|
||||
* @param array $args Query args.
|
||||
* @param \WP_Query $wp_query WP_Query object.
|
||||
|
||||
@@ -60,8 +60,6 @@ class ShippingController {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
$this->asset_data_registry->add( 'collectableMethodIds', array( 'Automattic\WooCommerce\StoreApi\Utilities\LocalPickupUtils', 'get_local_pickup_method_ids' ) );
|
||||
$this->asset_data_registry->add( 'shippingCostRequiresAddress', get_option( 'woocommerce_shipping_cost_requires_address', false ) === 'yes' );
|
||||
add_action( 'rest_api_init', array( $this, 'register_settings' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts' ) );
|
||||
|
||||
@@ -21,10 +21,6 @@ abstract class AbstractTemplateCompatibility {
|
||||
* Initialization method.
|
||||
*/
|
||||
public function init() {
|
||||
if ( ! wc_current_theme_is_fse_theme() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set_hook_data();
|
||||
|
||||
add_filter(
|
||||
@@ -61,9 +57,9 @@ abstract class AbstractTemplateCompatibility {
|
||||
* @since 7.6.0
|
||||
* @param boolean.
|
||||
*/
|
||||
$is_disabled_compatility_layer = apply_filters( 'woocommerce_disable_compatibility_layer', false );
|
||||
$is_disabled_compatibility_layer = apply_filters( 'woocommerce_disable_compatibility_layer', false );
|
||||
|
||||
if ( $is_disabled_compatility_layer ) {
|
||||
if ( $is_disabled_compatibility_layer ) {
|
||||
return $block_content;
|
||||
}
|
||||
|
||||
|
||||
@@ -320,7 +320,7 @@ class ArchiveProductTemplatesCompatibility extends AbstractTemplateCompatibility
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if block is within the product-query namespace
|
||||
* Check whether block is within the product-query namespace.
|
||||
*
|
||||
* @param array $block Parsed block data.
|
||||
*/
|
||||
@@ -331,7 +331,7 @@ class ArchiveProductTemplatesCompatibility extends AbstractTemplateCompatibility
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if block has isInherited attribute asigned
|
||||
* Check whether block has isInherited attribute assigned.
|
||||
*
|
||||
* @param array $block Parsed block data.
|
||||
*/
|
||||
@@ -357,7 +357,7 @@ class ArchiveProductTemplatesCompatibility extends AbstractTemplateCompatibility
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if block is a Post template
|
||||
* Check whether block is a Post template.
|
||||
*
|
||||
* @param string $block_name Block name.
|
||||
*/
|
||||
@@ -366,7 +366,7 @@ class ArchiveProductTemplatesCompatibility extends AbstractTemplateCompatibility
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if block is a Product Template
|
||||
* Check whether block is a Product Template.
|
||||
*
|
||||
* @param string $block_name Block name.
|
||||
*/
|
||||
@@ -375,7 +375,7 @@ class ArchiveProductTemplatesCompatibility extends AbstractTemplateCompatibility
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if block is eaither a Post template or Product Template
|
||||
* Check if block is either a Post template or a Product Template
|
||||
*
|
||||
* @param string $block_name Block name.
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\Templates;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Templates\ArchiveProductTemplatesCompatibility;
|
||||
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
|
||||
|
||||
/**
|
||||
@@ -61,6 +62,9 @@ class ProductAttributeTemplate extends AbstractTemplate {
|
||||
}
|
||||
|
||||
if ( isset( $queried_object->taxonomy ) && taxonomy_is_product_attribute( $queried_object->taxonomy ) ) {
|
||||
$compatibility_layer = new ArchiveProductTemplatesCompatibility();
|
||||
$compatibility_layer->init();
|
||||
|
||||
$templates = get_block_templates( array( 'slug__in' => array( self::SLUG ) ) );
|
||||
|
||||
if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\Templates;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Templates\ArchiveProductTemplatesCompatibility;
|
||||
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
|
||||
|
||||
/**
|
||||
@@ -49,6 +50,9 @@ class ProductCatalogTemplate extends AbstractTemplate {
|
||||
*/
|
||||
public function render_block_template() {
|
||||
if ( ! is_embed() && ( is_post_type_archive( 'product' ) || is_page( wc_get_page_id( 'shop' ) ) ) ) {
|
||||
$compatibility_layer = new ArchiveProductTemplatesCompatibility();
|
||||
$compatibility_layer->init();
|
||||
|
||||
$templates = get_block_templates( array( 'slug__in' => array( self::SLUG ) ) );
|
||||
|
||||
if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\Templates;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Templates\ArchiveProductTemplatesCompatibility;
|
||||
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
|
||||
|
||||
/**
|
||||
@@ -55,6 +56,9 @@ class ProductCategoryTemplate extends AbstractTemplate {
|
||||
*/
|
||||
public function render_block_template() {
|
||||
if ( ! is_embed() && is_product_taxonomy() && is_tax( 'product_cat' ) ) {
|
||||
$compatibility_layer = new ArchiveProductTemplatesCompatibility();
|
||||
$compatibility_layer->init();
|
||||
|
||||
$templates = get_block_templates( array( 'slug__in' => array( self::SLUG ) ) );
|
||||
|
||||
if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Templates;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Templates\ArchiveProductTemplatesCompatibility;
|
||||
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
|
||||
|
||||
/**
|
||||
@@ -48,6 +49,9 @@ class ProductSearchResultsTemplate extends AbstractTemplate {
|
||||
*/
|
||||
public function render_block_template() {
|
||||
if ( ! is_embed() && is_post_type_archive( 'product' ) && is_search() ) {
|
||||
$compatibility_layer = new ArchiveProductTemplatesCompatibility();
|
||||
$compatibility_layer->init();
|
||||
|
||||
$templates = get_block_templates( array( 'slug__in' => array( self::SLUG ) ) );
|
||||
|
||||
if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\Templates;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Templates\ArchiveProductTemplatesCompatibility;
|
||||
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
|
||||
|
||||
/**
|
||||
@@ -55,6 +56,9 @@ class ProductTagTemplate extends AbstractTemplate {
|
||||
*/
|
||||
public function render_block_template() {
|
||||
if ( ! is_embed() && is_product_taxonomy() && is_tax( 'product_tag' ) ) {
|
||||
$compatibility_layer = new ArchiveProductTemplatesCompatibility();
|
||||
$compatibility_layer->init();
|
||||
|
||||
$templates = get_block_templates( array( 'slug__in' => array( self::SLUG ) ) );
|
||||
|
||||
if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
|
||||
|
||||
@@ -23,7 +23,7 @@ class SingleProductTemplate extends AbstractTemplate {
|
||||
*/
|
||||
public function init() {
|
||||
add_action( 'template_redirect', array( $this, 'render_block_template' ) );
|
||||
add_filter( 'get_block_templates', array( $this, 'update_single_product_content' ), 11, 3 );
|
||||
add_filter( 'get_block_templates', array( $this, 'update_single_product_content' ), 11, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,13 +51,34 @@ class SingleProductTemplate extends AbstractTemplate {
|
||||
if ( ! is_embed() && is_singular( 'product' ) ) {
|
||||
global $post;
|
||||
|
||||
$valid_slugs = array( self::SLUG );
|
||||
if ( 'product' === $post->post_type && $post->post_name ) {
|
||||
$compatibility_layer = new SingleProductTemplateCompatibility();
|
||||
$compatibility_layer->init();
|
||||
|
||||
$valid_slugs = array( self::SLUG );
|
||||
$single_product_slug = 'product' === $post->post_type && $post->post_name ? 'single-product-' . $post->post_name : '';
|
||||
if ( $single_product_slug ) {
|
||||
$valid_slugs[] = 'single-product-' . $post->post_name;
|
||||
}
|
||||
$templates = get_block_templates( array( 'slug__in' => $valid_slugs ) );
|
||||
|
||||
if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
|
||||
if ( count( $templates ) === 0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the first template by default.
|
||||
$template = $templates[0];
|
||||
|
||||
// Check if there is a template matching the slug `single-product-{post_name}`.
|
||||
if ( count( $valid_slugs ) > 1 && count( $templates ) > 1 ) {
|
||||
foreach ( $templates as $t ) {
|
||||
if ( $single_product_slug === $t->slug ) {
|
||||
$template = $t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $template ) && BlockTemplateUtils::template_has_legacy_template_block( $template ) ) {
|
||||
add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
|
||||
}
|
||||
|
||||
@@ -68,12 +89,10 @@ class SingleProductTemplate extends AbstractTemplate {
|
||||
/**
|
||||
* Add the block template objects to be used.
|
||||
*
|
||||
* @param array $query_result Array of template objects.
|
||||
* @param array $query Optional. Arguments to retrieve templates.
|
||||
* @param string $template_type wp_template or wp_template_part.
|
||||
* @param array $query_result Array of template objects.
|
||||
* @return array
|
||||
*/
|
||||
public function update_single_product_content( $query_result, $query, $template_type ) {
|
||||
public function update_single_product_content( $query_result ) {
|
||||
$query_result = array_map(
|
||||
function ( $template ) {
|
||||
if ( str_contains( $template->slug, self::SLUG ) ) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Templates;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
|
||||
|
||||
/**
|
||||
* SingleProductTemplateCompatibility class.
|
||||
*
|
||||
@@ -12,7 +14,6 @@ class SingleProductTemplateCompatibility extends AbstractTemplateCompatibility {
|
||||
const IS_FIRST_BLOCK = '__wooCommerceIsFirstBlock';
|
||||
const IS_LAST_BLOCK = '__wooCommerceIsLastBlock';
|
||||
|
||||
|
||||
/**
|
||||
* Inject hooks to rendered content of corresponding blocks.
|
||||
*
|
||||
@@ -257,15 +258,11 @@ class SingleProductTemplateCompatibility extends AbstractTemplateCompatibility {
|
||||
* @return string
|
||||
*/
|
||||
public static function add_compatibility_layer( $template_content ) {
|
||||
$parsed_blocks = parse_blocks( $template_content );
|
||||
|
||||
if ( ! self::has_single_product_template_blocks( $parsed_blocks ) ) {
|
||||
$template = self::inject_custom_attributes_to_first_and_last_block_single_product_template( $parsed_blocks );
|
||||
return self::serialize_blocks( $template );
|
||||
$blocks = parse_blocks( $template_content );
|
||||
if ( self::has_single_product_template_blocks( $blocks ) ) {
|
||||
$blocks = self::wrap_single_product_template( $template_content );
|
||||
}
|
||||
|
||||
$wrapped_blocks = self::wrap_single_product_template( $template_content );
|
||||
$template = self::inject_custom_attributes_to_first_and_last_block_single_product_template( $wrapped_blocks );
|
||||
$template = self::inject_custom_attributes_to_first_and_last_block_single_product_template( $blocks );
|
||||
return self::serialize_blocks( $template );
|
||||
}
|
||||
|
||||
@@ -385,7 +382,7 @@ class SingleProductTemplateCompatibility extends AbstractTemplateCompatibility {
|
||||
|
||||
/**
|
||||
* Check if the Single Product template has a single product template block:
|
||||
* woocommerce/product-gallery-image, woocommerce/product-details, woocommerce/add-to-cart-form]
|
||||
* woocommerce/product-gallery-image, woocommerce/product-details, woocommerce/add-to-cart-form, etc.
|
||||
*
|
||||
* @param array $parsed_blocks Array of parsed block objects.
|
||||
* @return bool True if the template has a single product template block, false otherwise.
|
||||
@@ -393,19 +390,7 @@ class SingleProductTemplateCompatibility extends AbstractTemplateCompatibility {
|
||||
private static function has_single_product_template_blocks( $parsed_blocks ) {
|
||||
$single_product_template_blocks = array( 'woocommerce/product-image-gallery', 'woocommerce/product-details', 'woocommerce/add-to-cart-form', 'woocommerce/product-meta', 'woocommerce/product-price', 'woocommerce/breadcrumbs' );
|
||||
|
||||
$found = false;
|
||||
|
||||
foreach ( $parsed_blocks as $block ) {
|
||||
if ( isset( $block['blockName'] ) && in_array( $block['blockName'], $single_product_template_blocks, true ) ) {
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
$found = self::has_single_product_template_blocks( $block['innerBlocks'], $single_product_template_blocks );
|
||||
if ( $found ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $found;
|
||||
return BlockTemplateUtils::has_block_including_patterns( $single_product_template_blocks, $parsed_blocks );
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Utils;
|
||||
|
||||
use WP_Block_Patterns_Registry;
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Blocks\Options;
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
@@ -480,7 +481,7 @@ class BlockTemplateUtils {
|
||||
* @return boolean
|
||||
*/
|
||||
public static function theme_has_template( $template_name ) {
|
||||
return ! ! self::get_theme_template_path( $template_name, 'wp_template' );
|
||||
return (bool) self::get_theme_template_path( $template_name, 'wp_template' );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -490,7 +491,7 @@ class BlockTemplateUtils {
|
||||
* @return boolean
|
||||
*/
|
||||
public static function theme_has_template_part( $template_name ) {
|
||||
return ! ! self::get_theme_template_path( $template_name, 'wp_template_part' );
|
||||
return (bool) self::get_theme_template_path( $template_name, 'wp_template_part' );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -696,6 +697,38 @@ class BlockTemplateUtils {
|
||||
return wc_string_to_bool( $use_blockified_templates );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the provided $blocks contains any of the $block_names,
|
||||
* or if they contain a pattern that contains any of the $block_names.
|
||||
*
|
||||
* @param string[] $block_names Full block types to look for.
|
||||
* @param WP_Block[] $blocks Array of block objects.
|
||||
* @return bool Whether the content contains the specified block.
|
||||
*/
|
||||
public static function has_block_including_patterns( $block_names, $blocks ) {
|
||||
$flattened_blocks = self::flatten_blocks( $blocks );
|
||||
|
||||
foreach ( $flattened_blocks as &$block ) {
|
||||
if ( isset( $block['blockName'] ) && in_array( $block['blockName'], $block_names, true ) ) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
'core/pattern' === $block['blockName'] &&
|
||||
isset( $block['attrs']['slug'] )
|
||||
) {
|
||||
$registry = WP_Block_Patterns_Registry::get_instance();
|
||||
$pattern = $registry->get_registered( $block['attrs']['slug'] );
|
||||
$pattern_blocks = parse_blocks( $pattern['content'] );
|
||||
|
||||
if ( self::has_block_including_patterns( $block_names, $pattern_blocks ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the passed `$template` has the legacy template block.
|
||||
*
|
||||
@@ -703,7 +736,13 @@ class BlockTemplateUtils {
|
||||
* @return boolean
|
||||
*/
|
||||
public static function template_has_legacy_template_block( $template ) {
|
||||
return has_block( 'woocommerce/legacy-template', $template->content );
|
||||
if ( has_block( 'woocommerce/legacy-template', $template->content ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$blocks = parse_blocks( $template->content );
|
||||
|
||||
return self::has_block_including_patterns( array( 'woocommerce/legacy-template' ), $blocks );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -108,7 +108,7 @@ class CartCheckoutUtils {
|
||||
}
|
||||
|
||||
$array_without_accents = array_map(
|
||||
function( $value ) {
|
||||
function ( $value ) {
|
||||
return is_array( $value )
|
||||
? self::deep_sort_with_accents( $value )
|
||||
: remove_accents( wc_strtolower( html_entity_decode( $value ) ) );
|
||||
@@ -129,7 +129,7 @@ class CartCheckoutUtils {
|
||||
$shipping_zones = \WC_Shipping_Zones::get_zones();
|
||||
$formatted_shipping_zones = array_reduce(
|
||||
$shipping_zones,
|
||||
function( $acc, $zone ) {
|
||||
function ( $acc, $zone ) {
|
||||
$acc[] = [
|
||||
'id' => $zone['id'],
|
||||
'title' => $zone['zone_name'],
|
||||
@@ -146,4 +146,47 @@ class CartCheckoutUtils {
|
||||
];
|
||||
return $formatted_shipping_zones;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively search the checkout block to find the express checkout block and
|
||||
* get the button style attributes
|
||||
*
|
||||
* @param array $blocks Blocks to search.
|
||||
* @param string $cart_or_checkout The block type to check.
|
||||
*/
|
||||
public static function find_express_checkout_attributes( $blocks, $cart_or_checkout ) {
|
||||
$express_block_name = 'woocommerce/' . $cart_or_checkout . '-express-payment-block';
|
||||
foreach ( $blocks as $block ) {
|
||||
if ( ! empty( $block['blockName'] ) && $express_block_name === $block['blockName'] && ! empty( $block['attrs'] ) ) {
|
||||
return $block['attrs'];
|
||||
}
|
||||
|
||||
if ( ! empty( $block['innerBlocks'] ) ) {
|
||||
$answer = self::find_express_checkout_attributes( $block['innerBlocks'], $cart_or_checkout );
|
||||
if ( $answer ) {
|
||||
return $answer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array of blocks, find the express payment block and update its attributes.
|
||||
*
|
||||
* @param array $blocks Blocks to search.
|
||||
* @param string $cart_or_checkout The block type to check.
|
||||
* @param array $updated_attrs The new attributes to set.
|
||||
*/
|
||||
public static function update_blocks_with_new_attrs( &$blocks, $cart_or_checkout, $updated_attrs ) {
|
||||
$express_block_name = 'woocommerce/' . $cart_or_checkout . '-express-payment-block';
|
||||
foreach ( $blocks as $key => &$block ) {
|
||||
if ( ! empty( $block['blockName'] ) && $express_block_name === $block['blockName'] ) {
|
||||
$blocks[ $key ]['attrs'] = $updated_attrs;
|
||||
}
|
||||
|
||||
if ( ! empty( $block['innerBlocks'] ) ) {
|
||||
self::update_blocks_with_new_attrs( $block['innerBlocks'], $cart_or_checkout, $updated_attrs );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ class ProductGalleryUtils {
|
||||
$product_gallery_images = array();
|
||||
$product = wc_get_product( $post_id );
|
||||
|
||||
if ( $product ) {
|
||||
if ( $product instanceof \WC_Product ) {
|
||||
$all_product_gallery_image_ids = self::get_product_gallery_image_ids( $product );
|
||||
|
||||
if ( 'full' === $size || 'full' !== $size && count( $all_product_gallery_image_ids ) > 1 ) {
|
||||
|
||||
Reference in New Issue
Block a user