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 2 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 @@ -208,6 +208,7 @@ protected function get_block_types() {
'RatingFilter',
'ReviewsByCategory',
'ReviewsByProduct',
'RelatedProducts',
'ProductDetails',
'StockFilter',
];
Expand Down