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

Add Related Products block #8522

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions assets/js/atomic/blocks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ import './product-elements/stock-indicator';
import './product-elements/add-to-cart';
import './product-elements/product-image-gallery';
import './product-elements/product-details';
import './product-elements/related-products';
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "woocommerce/related-products",
"version": "1.0.0",
"title": "Related Products",
"icon": "product",
"description": "Display related products.",
"category": "woocommerce",
"supports": {
"align": true,
"reusable": false
},
"keywords": [ "WooCommerce" ],
"usesContext": [ "postId", "postType", "queryId" ],
"textdomain": "woo-gutenberg-products-block",
"apiVersion": 2,
"$schema": "https://schemas.wp.org/trunk/block.json"
}
45 changes: 45 additions & 0 deletions assets/js/atomic/blocks/product-elements/related-products/edit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* External dependencies
*/
import {
BLOCK_ATTRIBUTES,
INNER_BLOCKS_TEMPLATE,
} from '@woocommerce/blocks/product-query/variations';
import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';
import { InnerBlockTemplate } from '@wordpress/blocks';
import { Disabled, Notice } from '@wordpress/components';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import './editor.scss';

const Edit = () => {
const TEMPLATE: InnerBlockTemplate[] = [
[ 'core/query', BLOCK_ATTRIBUTES, INNER_BLOCKS_TEMPLATE ],
];
const blockProps = useBlockProps();

return (
<div { ...blockProps }>
<Disabled>
<Notice
className={ 'wc-block-editor-related-products__notice' }
status={ 'warning' }
isDismissible={ false }
>
<p>
{ __(
'These products will vary depending on the main product in the page',
'woo-gutenberg-products-block'
) }
</p>
</Notice>
</Disabled>
<InnerBlocks template={ TEMPLATE } />
</div>
);
};

export default Edit;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.wc-block-editor-related-products__notice {
margin: 10px auto;
max-width: max-content;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* External dependencies
*/
import { box as icon } from '@wordpress/icons';
import { registerBlockType, unregisterBlockType } from '@wordpress/blocks';
import { registerBlockSingleProductTemplate } from '@woocommerce/atomic-utils';

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

registerBlockSingleProductTemplate( {
registerBlockFn: () => {
// @ts-expect-error: `registerBlockType` is a function that is typed in WordPress core.
registerBlockType( metadata, {
icon,
edit,
save,
} );
},
unregisterBlockFn: () => {
unregisterBlockType( metadata.name );
},
blockName: metadata.name,
} );
17 changes: 17 additions & 0 deletions assets/js/atomic/blocks/product-elements/related-products/save.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* External dependencies
*/
import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';

const Save = () => {
const blockProps = useBlockProps.save();

return (
<div { ...blockProps }>
{ /* @ts-expect-error: `InnerBlocks.Content` is a component that is typed in WordPress core*/ }
<InnerBlocks.Content />
</div>
);
};

export default Save;
1 change: 1 addition & 0 deletions assets/js/blocks/product-query/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { CORE_NAME as PRODUCT_TEMPLATE_ID } from './variations/elements/product-
import './inspector-controls';
import './style.scss';
import './variations/product-query';
import './variations/related-products';

const EXTENDED_CORE_ELEMENTS = [
PRODUCT_SUMMARY_ID,
Expand Down
1 change: 1 addition & 0 deletions assets/js/blocks/product-query/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,5 @@ export interface ProductQueryContext {
export enum QueryVariation {
/** The main, fully customizable, Product Query block */
PRODUCT_QUERY = 'woocommerce/product-query',
RELATED_PRODUCTS = 'woocommerce/related-products',
}
1 change: 1 addition & 0 deletions assets/js/blocks/product-query/variations/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './related-products';
133 changes: 133 additions & 0 deletions assets/js/blocks/product-query/variations/related-products.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/**
* External dependencies
*/
import {
InnerBlockTemplate,
registerBlockVariation,
unregisterBlockVariation,
} from '@wordpress/blocks';
import { Icon } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { stacks } from '@woocommerce/icons';
import { registerBlockSingleProductTemplate } from '@woocommerce/atomic-utils';

/**
* Internal dependencies
*/
import { QUERY_LOOP_ID } from '../constants';

import { VARIATION_NAME as PRODUCT_TEMPLATE_ID } from './elements/product-template';
import { VARIATION_NAME as PRODUCT_TITLE_ID } from './elements/product-title';

const VARIATION_NAME = 'woocommerce/related-products';

export const BLOCK_ATTRIBUTES = {
namespace: VARIATION_NAME,
allowedControls: [],
displayLayout: {
type: 'flex',
columns: 5,
},
query: {
perPage: 5,
pages: 0,
offset: 0,
postType: 'product',
order: 'asc',
orderBy: 'title',
author: '',
search: '',
exclude: [],
sticky: '',
inherit: false,
},
lock: {
remove: true,
move: true,
},
};

export const INNER_BLOCKS_TEMPLATE: InnerBlockTemplate[] = [
[
'core/post-template',
{ __woocommerceNamespace: PRODUCT_TEMPLATE_ID },
[
[
'woocommerce/product-image',
{
productId: 0,
},
],
[
'core/post-title',
{
textAlign: 'center',
level: 3,
fontSize: 'medium',
__woocommerceNamespace: PRODUCT_TITLE_ID,
},
[],
],
[
'woocommerce/product-price',
{
textAlign: 'center',
fontSize: 'small',
style: {
spacing: {
margin: { bottom: '1rem' },
},
},
},
[],
],
[
'woocommerce/product-button',
{
textAlign: 'center',
fontSize: 'small',
style: {
spacing: {
margin: { bottom: '1rem' },
},
},
},
[],
],
],
],
];

registerBlockSingleProductTemplate( {
registerBlockFn: () =>
registerBlockVariation( QUERY_LOOP_ID, {
description: __(
'Display related products.',
'woo-gutenberg-products-block'
),
name: 'Related Products Controls',
title: __(
'Related Products Controls',
'woo-gutenberg-products-block'
),
isActive: ( blockAttributes ) =>
blockAttributes.namespace === VARIATION_NAME,
icon: (
<Icon
icon={ stacks }
className="wc-block-editor-components-block-icon wc-block-editor-components-block-icon--stacks"
/>
),
attributes: BLOCK_ATTRIBUTES,
// Gutenberg doesn't support this type yet, discussion here:
// https://github.com/WordPress/gutenberg/pull/43632
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
allowedControls: [],
innerBlocks: INNER_BLOCKS_TEMPLATE,
scope: [ 'block' ],
} ),
unregisterBlockFn: () =>
unregisterBlockVariation( QUERY_LOOP_ID, 'Related Products' ),
blockName: VARIATION_NAME,
} );
4 changes: 2 additions & 2 deletions src/BlockTypes/ProductQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ protected function initialize() {
* @param array $parsed_block The block being rendered.
* @return boolean
*/
private function is_woocommerce_variation( $parsed_block ) {
public static function is_woocommerce_variation( $parsed_block ) {
thealexandrelara marked this conversation as resolved.
Show resolved Hide resolved
return isset( $parsed_block['attrs']['namespace'] )
&& substr( $parsed_block['attrs']['namespace'], 0, 11 ) === 'woocommerce';
}
Expand All @@ -99,7 +99,7 @@ public function update_query( $pre_render, $parsed_block ) {

$this->parsed_block = $parsed_block;

if ( $this->is_woocommerce_variation( $parsed_block ) ) {
if ( self::is_woocommerce_variation( $parsed_block ) ) {
// Set this so that our product filters can detect if it's a PHP template.
$this->asset_data_registry->add( 'has_filterable_products', true, true );
$this->asset_data_registry->add( 'is_rendering_php_template', true, true );
Expand Down
89 changes: 89 additions & 0 deletions src/BlockTypes/RelatedProducts.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
* RelatedProducts class.
*/
class RelatedProducts extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'related-products';

/**
* The Block with its attributes before it gets rendered
*
* @var array
*/
protected $parsed_block;


/**
* Initialize this block type.
*
* - Hook into WP lifecycle.
* - Register the block with WordPress.
* - Hook into pre_render_block to update the query.
*/
protected function initialize() {
parent::initialize();
add_filter(
'pre_render_block',
array( $this, 'update_query' ),
10,
2
);

}

/**
* Update the query for the product query block.
*
* @param string|null $pre_render The pre-rendered content. Default null.
* @param array $parsed_block The block being rendered.
*/
public function update_query( $pre_render, $parsed_block ) {
if ( 'core/query' !== $parsed_block['blockName'] ) {
return;
}

$this->parsed_block = $parsed_block;

if ( ProductQuery::is_woocommerce_variation( $parsed_block ) && 'woocommerce/related-products' === $parsed_block['attrs']['namespace'] ) {
// Set this so that our product filters can detect if it's a PHP template.
add_filter(
'query_loop_block_query_vars',
array( $this, 'build_query' ),
10,
1
);
}
}



/**
* Return a custom query based on attributes, filters and global WP_Query.
*
* @param WP_Query $query The WordPress Query.
* @return array
*/
public function build_query( $query ) {
$parsed_block = $this->parsed_block;
if ( ! ProductQuery::is_woocommerce_variation( $parsed_block ) && 'woocommerce/related-products' !== $parsed_block['attrs']['namespace'] ) {
return $query;
}
$product = wc_get_product();
$related_products = wc_get_related_products( $product->get_id() );

return array(
'post_type' => 'product',
'post__in' => $related_products,
'post_status' => 'publish',
);
}


}
1 change: 1 addition & 0 deletions src/BlockTypesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ protected function get_block_types() {
'RatingFilter',
'ReviewsByCategory',
'ReviewsByProduct',
'RelatedProducts',
'ProductDetails',
'StockFilter',
];
Expand Down