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
This commit is contained in:
parent
9f3d61e1ea
commit
0d851fdb29
|
@ -1,15 +1,23 @@
|
||||||
interface BlockAttributes {
|
/**
|
||||||
productId: {
|
* Internal dependencies
|
||||||
type: string;
|
*/
|
||||||
default: number;
|
import { BlockAttributes } from './types';
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const blockAttributes: BlockAttributes = {
|
export const blockAttributes: Record<
|
||||||
|
keyof BlockAttributes,
|
||||||
|
{
|
||||||
|
type: string;
|
||||||
|
default: unknown;
|
||||||
|
}
|
||||||
|
> = {
|
||||||
productId: {
|
productId: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
|
isDescendentOfQueryLoop: {
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default blockAttributes;
|
export default blockAttributes;
|
||||||
|
|
|
@ -3,6 +3,9 @@
|
||||||
*/
|
*/
|
||||||
import EditProductLink from '@woocommerce/editor-components/edit-product-link';
|
import EditProductLink from '@woocommerce/editor-components/edit-product-link';
|
||||||
import { useBlockProps } from '@wordpress/block-editor';
|
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
|
* Internal dependencies
|
||||||
|
@ -16,16 +19,28 @@ import {
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import type { BlockAttributes } from './types';
|
import type { BlockAttributes } from './types';
|
||||||
|
|
||||||
interface Props {
|
const Edit = ( {
|
||||||
attributes: BlockAttributes;
|
attributes,
|
||||||
}
|
setAttributes,
|
||||||
|
context,
|
||||||
const Edit = ( { attributes }: Props ): JSX.Element => {
|
}: BlockEditProps< BlockAttributes > & { context: Context } ): JSX.Element => {
|
||||||
const blockProps = useBlockProps();
|
const blockProps = useBlockProps();
|
||||||
|
|
||||||
|
const blockAttrs = {
|
||||||
|
...attributes,
|
||||||
|
...context,
|
||||||
|
};
|
||||||
|
const isDescendentOfQueryLoop = Number.isFinite( context.queryId );
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => setAttributes( { isDescendentOfQueryLoop } ),
|
||||||
|
[ setAttributes, isDescendentOfQueryLoop ]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div { ...blockProps }>
|
<div { ...blockProps }>
|
||||||
<EditProductLink />
|
<EditProductLink />
|
||||||
<Block { ...attributes } />
|
<Block { ...blockAttrs } />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,7 +10,6 @@ import type { BlockConfiguration } from '@wordpress/blocks';
|
||||||
import sharedConfig from '../shared/config';
|
import sharedConfig from '../shared/config';
|
||||||
import attributes from './attributes';
|
import attributes from './attributes';
|
||||||
import edit from './edit';
|
import edit from './edit';
|
||||||
import { Save } from './save';
|
|
||||||
import { supports } from './supports';
|
import { supports } from './supports';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -28,7 +27,12 @@ const blockConfig: BlockConfiguration = {
|
||||||
attributes,
|
attributes,
|
||||||
supports,
|
supports,
|
||||||
edit,
|
edit,
|
||||||
save: Save,
|
usesContext: [ 'query', 'queryId', 'postId' ],
|
||||||
|
ancestor: [
|
||||||
|
'@woocommerce/all-products',
|
||||||
|
'@woocommerce/single-product',
|
||||||
|
'core/post-template',
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
registerExperimentalBlockType( 'woocommerce/product-stock-indicator', {
|
registerExperimentalBlockType( 'woocommerce/product-stock-indicator', {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
export interface BlockAttributes {
|
export interface BlockAttributes {
|
||||||
productId: number;
|
productId: number;
|
||||||
|
isDescendentOfQueryLoop: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||||
|
|
||||||
|
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ProductStockIndicator class.
|
* ProductStockIndicator class.
|
||||||
*/
|
*/
|
||||||
|
@ -48,7 +50,88 @@ class ProductStockIndicator extends AbstractBlock {
|
||||||
* This registers the scripts; it does not enqueue them.
|
* This registers the scripts; it does not enqueue them.
|
||||||
*/
|
*/
|
||||||
protected function register_block_type_assets() {
|
protected function register_block_type_assets() {
|
||||||
parent::register_block_type_assets();
|
return null;
|
||||||
$this->register_chunk_translations( [ $this->block_name ] );
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 .= '<div class="wc-block-components-product-stock-indicator ' . esc_attr( $classnames ) . '"';
|
||||||
|
$output .= isset( $classes_and_styles['styles'] ) ? ' style="' . esc_attr( $classes_and_styles['styles'] ) . '"' : '';
|
||||||
|
$output .= '>';
|
||||||
|
$output .= wp_kses_post( self::getTextBasedOnStock( $is_in_stock, $is_low_stock, $low_stock_amount, $is_on_backorder ) );
|
||||||
|
$output .= '</div>';
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue