From 0d851fdb29d1dd3cee39939d04d7ab39d2f1c61f Mon Sep 17 00:00:00 2001 From: Manish Menaria Date: Thu, 1 Dec 2022 17:14:05 +0530 Subject: [PATCH] Add product query support for Stock indicator block (https://github.com/woocommerce/woocommerce-blocks/pull/7734) * Add product query support for Stock indicator block On the client side, when the Stock indicator block is used within the product query block, the markup will be rendered on the server side - No javascript related to Stock indicator block will be rendered. * Escape all values in output string Whenever we are rendering data, we should escape it. Escaping output prevents XSS (Cross-site scripting) attacks. * Change $is_on_backorder type & escape just before printing For more info: https://github.com/woocommerce/woocommerce-blocks/pull/7734#discussion_r1035971939 https://github.com/woocommerce/woocommerce-blocks/pull/7734#discussion_r1035975712 --- .../stock-indicator/attributes.ts | 22 +++-- .../product-elements/stock-indicator/edit.tsx | 27 ++++-- .../product-elements/stock-indicator/index.ts | 8 +- .../product-elements/stock-indicator/types.ts | 1 + .../src/BlockTypes/ProductStockIndicator.php | 87 ++++++++++++++++++- 5 files changed, 128 insertions(+), 17 deletions(-) diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/attributes.ts b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/attributes.ts index b2cae177672..a92f80b861d 100644 --- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/attributes.ts +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/attributes.ts @@ -1,15 +1,23 @@ -interface BlockAttributes { - productId: { - type: string; - default: number; - }; -} +/** + * Internal dependencies + */ +import { BlockAttributes } from './types'; -export const blockAttributes: BlockAttributes = { +export const blockAttributes: Record< + keyof BlockAttributes, + { + type: string; + default: unknown; + } +> = { productId: { type: 'number', default: 0, }, + isDescendentOfQueryLoop: { + type: 'boolean', + default: false, + }, }; export default blockAttributes; diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/edit.tsx b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/edit.tsx index a27e2d88b2e..22f2ca99582 100644 --- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/edit.tsx +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/edit.tsx @@ -3,6 +3,9 @@ */ import EditProductLink from '@woocommerce/editor-components/edit-product-link'; import { useBlockProps } from '@wordpress/block-editor'; +import type { BlockEditProps } from '@wordpress/blocks'; +import { ProductQueryContext as Context } from '@woocommerce/blocks/product-query/types'; +import { useEffect } from 'react'; /** * Internal dependencies @@ -16,16 +19,28 @@ import { } from './constants'; import type { BlockAttributes } from './types'; -interface Props { - attributes: BlockAttributes; -} - -const Edit = ( { attributes }: Props ): JSX.Element => { +const Edit = ( { + attributes, + setAttributes, + context, +}: BlockEditProps< BlockAttributes > & { context: Context } ): JSX.Element => { const blockProps = useBlockProps(); + + const blockAttrs = { + ...attributes, + ...context, + }; + const isDescendentOfQueryLoop = Number.isFinite( context.queryId ); + + useEffect( + () => setAttributes( { isDescendentOfQueryLoop } ), + [ setAttributes, isDescendentOfQueryLoop ] + ); + return (
- +
); }; diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/index.ts b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/index.ts index 213ea732545..8acb8ebba6e 100644 --- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/index.ts +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/index.ts @@ -10,7 +10,6 @@ import type { BlockConfiguration } from '@wordpress/blocks'; import sharedConfig from '../shared/config'; import attributes from './attributes'; import edit from './edit'; -import { Save } from './save'; import { supports } from './supports'; import { @@ -28,7 +27,12 @@ const blockConfig: BlockConfiguration = { attributes, supports, edit, - save: Save, + usesContext: [ 'query', 'queryId', 'postId' ], + ancestor: [ + '@woocommerce/all-products', + '@woocommerce/single-product', + 'core/post-template', + ], }; registerExperimentalBlockType( 'woocommerce/product-stock-indicator', { diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/types.ts b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/types.ts index e64df6f3d4f..b695a3b5f5d 100644 --- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/types.ts +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/types.ts @@ -1,3 +1,4 @@ export interface BlockAttributes { productId: number; + isDescendentOfQueryLoop: boolean; } diff --git a/plugins/woocommerce-blocks/src/BlockTypes/ProductStockIndicator.php b/plugins/woocommerce-blocks/src/BlockTypes/ProductStockIndicator.php index 33a894f6865..51ae70dc3fd 100644 --- a/plugins/woocommerce-blocks/src/BlockTypes/ProductStockIndicator.php +++ b/plugins/woocommerce-blocks/src/BlockTypes/ProductStockIndicator.php @@ -1,6 +1,8 @@ register_chunk_translations( [ $this->block_name ] ); + return null; + } + + /** + * Register the context. + */ + protected function get_block_type_uses_context() { + return [ 'query', 'queryId', 'postId' ]; + } + + /** + * Get stock text based on stock. For example: + * - In stock + * - Out of stock + * - Available on backorder + * - 2 left in stock + * + * @param [bool] $is_in_stock Whether the product is in stock. + * @param [bool] $is_low_stock Whether the product is low in stock. + * @param [int|null] $low_stock_amount The amount of stock that is considered low. + * @param [bool] $is_on_backorder Whether the product is on backorder. + * @return string Stock text. + */ + protected static function getTextBasedOnStock( $is_in_stock, $is_low_stock, $low_stock_amount, $is_on_backorder ) { + if ( $is_low_stock ) { + return sprintf( + /* translators: %d is number of items in stock for product */ + __( '%d left in stock', 'woo-gutenberg-products-block' ), + $low_stock_amount + ); + } elseif ( $is_on_backorder ) { + return __( 'Available on backorder', 'woo-gutenberg-products-block' ); + } elseif ( $is_in_stock ) { + return __( 'In stock', 'woo-gutenberg-products-block' ); + } else { + return __( 'Out of stock', 'woo-gutenberg-products-block' ); + } + } + + + + /** + * Include and render the block. + * + * @param array $attributes Block attributes. Default empty array. + * @param string $content Block content. Default empty string. + * @param WP_Block $block Block instance. + * @return string Rendered block type output. + */ + protected function render( $attributes, $content, $block ) { + if ( ! empty( $content ) ) { + parent::register_block_type_assets(); + $this->register_chunk_translations( [ $this->block_name ] ); + return $content; + } + + $post_id = $block->context['postId']; + $product = wc_get_product( $post_id ); + $is_in_stock = $product->is_in_stock(); + $is_on_backorder = $product->is_on_backorder(); + + $low_stock_amount = $product->get_low_stock_amount(); + $total_stock = $product->get_stock_quantity(); + $is_low_stock = $low_stock_amount && $total_stock <= $low_stock_amount; + + $classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes ); + + $classnames = isset( $classes_and_styles['classes'] ) ? ' ' . $classes_and_styles['classes'] . ' ' : ''; + $classnames .= isset( $attributes['className'] ) ? ' ' . $attributes['className'] . ' ' : ''; + $classnames .= ! $is_in_stock ? ' wc-block-components-product-stock-indicator--out-of-stock ' : ''; + $classnames .= $is_in_stock ? ' wc-block-components-product-stock-indicator--in-stock ' : ''; + $classnames .= $is_low_stock ? ' wc-block-components-product-stock-indicator--low-stock ' : ''; + $classnames .= $is_on_backorder ? ' wc-block-components-product-stock-indicator--available-on-backorder ' : ''; + + $output = ''; + $output .= '