Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Product Collection - New 'No Results' block with default UI #11783

Merged
merged 10 commits into from
Nov 20, 2023
Merged
2 changes: 1 addition & 1 deletion assets/js/blocks/product-collection/edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export const INNER_BLOCKS_TEMPLATE: InnerBlockTemplate[] = [
},
},
],
[ 'core/query-no-results' ],
[ 'woocommerce/product-collection-no-results' ],
];

const Edit = ( props: BlockEditProps< ProductCollectionAttributes > ) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "woocommerce/product-collection-no-results",
"title": "No results",
"version": "1.0.0",
"category": "woocommerce",
"description": "The contents of this block will display when there are no products found.",
"textdomain": "woo-gutenberg-products-block",
"keywords": [ "Product Collection" ],
"usesContext": [ "queryId", "query" ],
"ancestor": [ "woocommerce/product-collection" ],
"supports": {
"align": true,
"reusable": false,
"html": false,
"color": {
"gradients": true,
"link": true
},
"typography": {
"fontSize": true,
"lineHeight": true,
"__experimentalFontFamily": true,
"__experimentalFontWeight": true,
"__experimentalFontStyle": true,
"__experimentalTextTransform": true,
"__experimentalTextDecoration": true,
"__experimentalLetterSpacing": true,
"__experimentalDefaultControls": {
"fontSize": true
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* External dependencies
*/
import { useBlockProps, InnerBlocks } from '@wordpress/block-editor';
import { Template } from '@wordpress/blocks';
import { __ } from '@wordpress/i18n';

const TEMPLATE: Template[] = [
[
'core/group',
{
layout: {
type: 'flex',
orientation: 'vertical',
justifyContent: 'center',
flexWrap: 'wrap',
},
},
[
[
'core/paragraph',
{
textAlign: 'center',
fontSize: 'medium',
content: `<strong>${ __(
'No results found',
'woo-gutenberg-products-block'
) }</strong>`,
},
],
[
'core/paragraph',
{
content: `${ __(
'You can try',
'woo-gutenberg-products-block'
) } <a href="#" class="wc-link-clear-any-filters">${ __(
'clearing any filters',
'woo-gutenberg-products-block'
) }</a> ${ __(
'or head to our',
'woo-gutenberg-products-block'
) } <a href="#" class="wc-link-stores-home">${ __(
"store's home",
'woo-gutenberg-products-block'
) }</a>`,
},
],
],
],
];

const Edit = () => {
const blockProps = useBlockProps( {
className: 'wc-block-product-collection-no-results',
} );

return (
<div { ...blockProps }>
<InnerBlocks template={ TEMPLATE } />
</div>
);
};

export default Edit;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* External dependencies
*/
import { registerBlockType } from '@wordpress/blocks';
import { loop as loopIcon } from '@wordpress/icons';

/**
* Internal dependencies
*/
import metadata from './block.json';
import edit from './edit';
import save from './save';

registerBlockType( metadata, {
icon: loopIcon,
supports: {
...metadata.supports,
},
edit,
save,
} );
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* External dependencies
*/
import { InnerBlocks } from '@wordpress/block-editor';

export default function NoResultsSave() {
// @ts-expect-error: `InnerBlocks.Content` is a component that is typed in WordPress core
return <InnerBlocks.Content />;
}
3 changes: 3 additions & 0 deletions bin/webpack-entries.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ const blocks = {
'product-category': {},
'product-categories': {},
'product-collection': {},
'product-collection-no-results': {
customDir: 'product-collection/inner-blocks/no-results',
},
'product-gallery': {
isExperimental: true,
},
Expand Down
141 changes: 141 additions & 0 deletions src/BlockTypes/ProductCollectionNoResults.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
<?php

namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\ProductCollectionUtils;

/**
* ProductCollectionNoResults class.
*/
class ProductCollectionNoResults extends AbstractBlock {

/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-collection-no-results';

/**
* Render the block.
*
* @param array $attributes Block attributes.
* @param string $content Block content.
* @param WP_Block $block Block instance.
*
* @return string | void Rendered block output.
*/
protected function render( $attributes, $content, $block ) {
$content = trim( $content );
if ( empty( $content ) ) {
return '';
}

$query = ProductCollectionUtils::prepare_and_execute_query( $block );

// If the query has products, don't render the block.
if ( $query->post_count > 0 ) {
return '';
}

// Update the anchor tag URLs.
$updated_html_content = $this->modify_anchor_tag_urls( trim( $content ) );

$wrapper_attributes = get_block_wrapper_attributes();
return sprintf(
'<div %1$s>%2$s</div>',
$wrapper_attributes,
$updated_html_content
);
}

/**
* Get the frontend script handle for this block type.
*
* @param string $key Data to get, or default to everything.
*/
protected function get_block_type_script( $key = null ) {
return null;
}

/**
* Set the URL attributes for "clearing any filters" and "Store's home" links.
*
* @param string $content Block content.
*/
protected function modify_anchor_tag_urls( $content ) {
$processor = new \WP_HTML_Tag_Processor( trim( $content ) );

// Set the URL attribute for the "clear any filters" link.
if ( $processor->next_tag(
array(
'tag_name' => 'a',
'class_name' => 'wc-link-clear-any-filters',
)
) ) {
$processor->set_attribute( 'href', $this->get_current_url_without_filters() );
}

// Set the URL attribute for the "Store's home" link.
if ( $processor->next_tag(
array(
'tag_name' => 'a',
'class_name' => 'wc-link-stores-home',
)
) ) {
$processor->set_attribute( 'href', home_url() );
}

return $processor->get_updated_html();
}

/**
* Get current URL without filter query parameters which will be used
* for the "clear any filters" link.
*/
protected function get_current_url_without_filters() {
$protocol = is_ssl() ? 'https' : 'http';

// Check the existence and sanitize HTTP_HOST and REQUEST_URI in the $_SERVER superglobal.
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$http_host = isset( $_SERVER['HTTP_HOST'] ) ? wp_unslash( $_SERVER['HTTP_HOST'] ) : '';
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$request_uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '';

// Sanitize the host and URI.
$http_host = sanitize_text_field( $http_host );
$request_uri = esc_url_raw( $request_uri );

// Construct the full URL.
$current_url = $protocol . '://' . $http_host . $request_uri;

// Parse the URL to extract the query string.
$parsed_url = wp_parse_url( $current_url );
$query_string = isset( $parsed_url['query'] ) ? $parsed_url['query'] : '';

// Convert the query string into an associative array.
parse_str( $query_string, $query_params );

// Remove the filter query parameters.
$params_to_remove = array( 'min_price', 'max_price', 'rating_filter', 'filter_', 'query_type_' );
foreach ( $query_params as $key => $value ) {
foreach ( $params_to_remove as $param ) {
if ( strpos( $key, $param ) === 0 ) {
unset( $query_params[ $key ] );
break;
}
}
}

// Rebuild the query string without the removed parameters.
$new_query_string = http_build_query( $query_params );

// Reconstruct the URL.
$new_url = $parsed_url['scheme'] . '://' . $parsed_url['host'];
$new_url .= isset( $parsed_url['path'] ) ? $parsed_url['path'] : '';
$new_url .= $new_query_string ? '?' . $new_query_string : '';

return $new_url;
}

}
16 changes: 2 additions & 14 deletions src/BlockTypes/ProductTemplate.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

namespace Automattic\WooCommerce\Blocks\BlockTypes;

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

/**
* ProductTemplate class.
Expand Down Expand Up @@ -36,19 +36,7 @@ protected function get_block_type_script( $key = null ) {
* @return string | void Rendered block output.
*/
protected function render( $attributes, $content, $block ) {
$page_key = isset( $block->context['queryId'] ) ? 'query-' . $block->context['queryId'] . '-page' : 'query-page';
// phpcs:ignore WordPress.Security.NonceVerification
$page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ];

// Use global query if needed.
$use_global_query = ( isset( $block->context['query']['inherit'] ) && $block->context['query']['inherit'] );
if ( $use_global_query ) {
global $wp_query;
$query = clone $wp_query;
} else {
$query_args = build_query_vars_from_query_block( $block, $page );
$query = new WP_Query( $query_args );
}
$query = ProductCollectionUtils::prepare_and_execute_query( $block );

if ( ! $query->have_posts() ) {
return '';
Expand Down
1 change: 1 addition & 0 deletions src/BlockTypesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ protected function get_block_types() {
'ProductCategories',
'ProductCategory',
'ProductCollection',
'ProductCollectionNoResults',
'ProductImage',
'ProductImageGallery',
'ProductNew',
Expand Down
34 changes: 34 additions & 0 deletions src/Utils/ProductCollectionUtils.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php
namespace Automattic\WooCommerce\Blocks\Utils;

use WP_Query;

/**
* Utility methods used for the Product Collection block.
* {@internal This class and its methods are not intended for public use.}
*/
class ProductCollectionUtils {
/**
* Prepare and execute a query for the Product Collection block.
* This method is used by the Product Collection block and the No Results block.
*
* @param WP_Block $block Block instance.
*/
public static function prepare_and_execute_query( $block ) {
$page_key = isset( $block->context['queryId'] ) ? 'query-' . $block->context['queryId'] . '-page' : 'query-page';
// phpcs:ignore WordPress.Security.NonceVerification
$page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ];

// Use global query if needed.
$use_global_query = ( isset( $block->context['query']['inherit'] ) && $block->context['query']['inherit'] );
if ( $use_global_query ) {
global $wp_query;
$query = clone $wp_query;
} else {
$query_args = build_query_vars_from_query_block( $block, $page );
$query = new WP_Query( $query_args );
}

return $query;
}
}
Loading