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

Commit

Permalink
Add product query support for Stock indicator block (#7734)
Browse files Browse the repository at this point in the history
* 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:
#7734 (comment)
#7734 (comment)
  • Loading branch information
imanish003 authored Dec 1, 2022
1 parent e3c491b commit a136ff3
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -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;
27 changes: 21 additions & 6 deletions assets/js/atomic/blocks/product-elements/stock-indicator/edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 (
<div { ...blockProps }>
<EditProductLink />
<Block { ...attributes } />
<Block { ...blockAttrs } />
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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', {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export interface BlockAttributes {
productId: number;
isDescendentOfQueryLoop: boolean;
}
87 changes: 85 additions & 2 deletions src/BlockTypes/ProductStockIndicator.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;

/**
* ProductStockIndicator class.
*/
Expand Down Expand Up @@ -48,7 +50,88 @@ protected function get_block_type_supports() {
* This registers the scripts; it does not enqueue them.
*/
protected function register_block_type_assets() {
parent::register_block_type_assets();
$this->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 .= '<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;

}
}

0 comments on commit a136ff3

Please sign in to comment.