Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Product Collection: Parse front-end context #44145

Open
wants to merge 17 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions plugins/woocommerce/changelog/add-44144-front-end-context
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: enhancement

Provide the location context within the Product Collection block context
66 changes: 66 additions & 0 deletions plugins/woocommerce/src/Blocks/BlockTypes/ProductCollection.php
xristos3490 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\ProductCollectionUtils;
use WP_Query;
use WC_Tax;

Expand Down Expand Up @@ -76,13 +77,77 @@ protected function initialize() {
// Extend allowed `collection_params` for the REST API.
add_filter( 'rest_product_collection_params', array( $this, 'extend_rest_query_allowed_params' ), 10, 1 );

// Provide location context into block's context.
add_filter( 'render_block_context', array( $this, 'provide_location_context' ), 10, 3 );
add_filter( 'render_block_woocommerce/product-template', array( $this, 'test' ), 0, 3 );

// 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_core/query-pagination', array( $this, 'add_navigation_link_directives' ), 10, 3 );

add_filter( 'posts_clauses', array( $this, 'add_price_range_filter_posts_clauses' ), 10, 2 );
}

// TODO: Remove this method.
public function test( $block_content, $block, $instance ) {
$location = $instance->context['location'] ?? array();
$block_content = print_r( $location, true ) . $block_content;
return $block_content;
}

/**
* Provides the location context into the block's context.
*
* The location context's schema is an array with the following:
* - type: The context type. Possible values are 'site', 'order', 'cart', 'archive', 'product'.
* - sourceData: The context data.
*
* The sourceData structure depends on the context type as follows:
* - site: []
* - order: ['orderId' => int]
* - cart: ['productIds' => array]
* - archive: ['taxonomy' => string, 'termId' => int]
* - product: ['productId' => int]
*
* @param array $context The block context.
* @param array $parsed_block The parsed block.
* @param WP_Block $parent_block The parent block.
*
* @return array The block's context including the location context.
*/
public function provide_location_context( $context, $parsed_block, $parent_block ) {

// Run only on frontend.
// This is needed to avoid SSR renders within patterns. @see https://github.com/woocommerce/woocommerce/issues/45181
if ( is_admin() || \WC()->is_rest_api_request() ) {
return $context;
}

// Only on Product Collection's children blocks.
if ( ! is_a( $parent_block, 'WP_Block' ) || 'woocommerce/product-collection' !== $parent_block->name ) {
xristos3490 marked this conversation as resolved.
Show resolved Hide resolved
return $context;
}

// Parse ancestor-level context.
// @see Automattic\WooCommerce\Blocks\BlockTypes\SingleProduct::update_context()
$is_in_single_product = ! empty( $parent_block->context['singleProduct'] ) && $parent_block->context['singleProduct'] && ! empty( $parent_block->context['postId'] );
xristos3490 marked this conversation as resolved.
Show resolved Hide resolved
if ( $is_in_single_product ) {

$context['location'] = array(
'type' => 'product',
'sourceData' => array(
'productId' => absint( $parent_block->context['postId'] )
)
);

return $context;
}

// Parse global context.
$context['location'] = ProductCollectionUtils::parse_global_location_context();
return $context;
}

/**
* Enhances the Product Collection block with client-side pagination.
*
Expand Down Expand Up @@ -324,6 +389,7 @@ public function add_support_for_filter_blocks( $pre_render, $parsed_block ) {
* @return array
*/
public function build_frontend_query( $query, $block, $page ) {

xristos3490 marked this conversation as resolved.
Show resolved Hide resolved
// If not in context of product collection block, return the query as is.
$is_product_collection_block = $block->context['query']['isProductCollectionBlock'] ?? false;
if ( ! $is_product_collection_block ) {
Expand Down
78 changes: 78 additions & 0 deletions plugins/woocommerce/src/Blocks/Utils/ProductCollectionUtils.php
xristos3490 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,84 @@
* {@internal This class and its methods are not intended for public use.}
*/
class ProductCollectionUtils {

/**
* Parse WP Query's global context for the Product Collection block.
*
* The return schema is:
* - type: The context type. Possible values are 'site', 'order', 'cart', 'archive', 'product'.
* - sourceData: The context data.
*
* The sourceData structure depends on the context type as follows:
* - site: []
* - order: ['orderId' => int]
* - cart: ['productIds' => array]
* - archive: ['taxonomy' => string, 'termId' => int]
* - product: ['productId' => int]
*
* @return array Parsed context.
*/
public static function parse_global_location_context() {
global $wp_query;

// Default context.
// Hint: The Shop page uses the default context.
$type = 'site';
$source_data = array();

if ( ! ( $wp_query instanceof WP_Query ) ) {

return array(
'type' => $type,
'sourceData' => $source_data,
);
}

// As more areas are blockified, expected future contexts include:
// - is_checkout_pay_page()
// - is_view_order_page()
if ( is_order_received_page() ) {

$type = 'order';
$source_data = array( 'orderId' => absint( $wp_query->query_vars['order-received'] ) );

} elseif ( ( is_cart() || is_checkout() ) && isset( WC()->cart ) && is_a( WC()->cart, 'WC_Cart' ) ) {

$type = 'cart';
$items = array();
foreach ( WC()->cart->get_cart() as $cart_item ) {
$items[] = absint( $cart_item[ 'productId' ] );
}
$items = array_unique( array_filter( $items ) );
$source_data = array( 'productIds' => $items );

} elseif ( is_product_taxonomy() ) {

$source = $wp_query->get_queried_object();
$taxonomy = is_a( $source, 'WP_Term' ) ? $source->taxonomy : '';
$term_id = is_a( $source, 'WP_Term' ) ? $source->term_id : '';
$type = 'archive';
$source_data = array(
'taxonomy' => wc_clean( $taxonomy ),
'termId' => absint( $term_id ),
);

} elseif ( is_product() ) {

$source = $wp_query->get_queried_object();
$product_id = is_a( $source, 'WP_Post' ) ? absint( $source->ID ) : 0;
$type = 'product';
$source_data = array( 'productId' => $product_id );
}

$context = array(
'type' => $type,
'sourceData' => $source_data,
);

return $context;
}

/**
* Prepare and execute a query for the Product Collection block.
* This method is used by the Product Collection block and the No Results block.
Expand Down