Product Collection: Add stock status filter (https://github.com/woocommerce/woocommerce-blocks/pull/9580)
* Add columns control to product collection block editor settings - `InspectorControls` from './inspector-controls' is now imported in `edit.tsx` and used in the returned JSX of `Edit` function. - A new file `columns-control.tsx` is added under 'product-collection' block's 'inspector-controls' directory which exports a `ColumnsControl` component. This component uses `RangeControl` from '@wordpress/components' to control the number of columns in the product collection display layout when the layout type is 'flex'. - The types file (`types.ts`) for 'product-collection' block is updated. The `Attributes` interface is renamed to `ProductCollectionAttributes` and the `ProductCollectionContext` interface is removed. The `ProductCollectionAttributes` now includes 'queryContext', 'templateSlug', and 'displayLayout' properties. * Refactor: Simplify Fallback Return in ColumnsControl Component This commit simplifies the fallback return value of the ColumnsControl component. Instead of returning an empty fragment (<> </>), it now returns null when the condition isn't met. This change improves readability and aligns with best practices for conditional rendering in React. * Feature: Add 'Order By' Control to Product Collection Inspector This commit adds a new 'Order By' control to the product collection inspector. The control allows users to specify the order of products in a collection by various attributes such as title and date. To support this, a new component 'OrderByControl' has been created and included in the product collection inspector. Additionally, the types for 'order' and 'orderBy' attributes have been updated and exported for reuse. * Add more options to OrderBy type * Add orderby handling on frontend & editor The main changes include: 1. Added a new property 'isProductCollectionBlock' in the block.json to denote if a block is a product collection block. 2. In the ProductCollection PHP class, a new initialization function has been defined to hook into the WordPress lifecycle, register the block, and update the query based on this block. 3. Added methods to manage query parameters for both frontend rendering and the Editor. 4. Expanded allowed 'collection_params' for the REST API to include custom 'orderby' values. 5. Defined a function to build the query based on block attributes, filters, and global WP_Query. 6. Created utility functions to handle complex query operations such as merging queries, handling custom sort values, and merging arrays recursively. These improvements allow for more flexible and robust handling of product collections in both the front-end display and the WordPress editor. It also extends support for custom 'orderby' values in the REST API, which allows for more advanced sorting options in product collections. * Add 'on sale' filter and enhance settings management in product collection block This commit introduces several changes to the product collection block. - First, it adds a new 'on sale' filter that can be used to display only the products that are currently on sale. - It also refactors the settings management in the product collection block to use the experimental ToolsPanel component from WordPress, which provides a more flexible and intuitive way to manage block settings. - It moves the 'Columns' control into the ToolsPanel, along with the 'Order by' control. - A new utility function `setQueryAttribute` is introduced to simplify setting nested query parameters. - The structure of the `ProductCollectionAttributes` and `ProductCollectionQuery` types have been adjusted to accommodate the changes. - Finally, it makes corresponding changes in the PHP part to handle the new 'on sale' query parameter. This should enhance the flexibility and user-friendliness of the product collection block. * Add stock status filter to WooCommerce product collection block This commit introduces a stock status filter to the WooCommerce product collection block. The changes include: 1. Added the ability to filter products based on their stock status within the 'product-collection' block. A new stock status control is created within the inspector-controls of the block. 2. A new 'get_stock_status_query' function is introduced in 'ProductCollection.php' which returns a query for products depending on their stock status. Please note that the stock status filter will only appear in the experimental build for now. * Refactor Stock Status control of Product Collection block This commit refactors the Stock Status control. The changes aim to improve the code organization and make the behavior of the component more explicit. The key modifications are: 1. Moved stock status related constants and functions from `inspector-controls/utils.tsx` to `inspector-controls/constants.ts`. This is done to ensure that all constants and similar utility functions are organized in one place. 2. Updated `product-collection/index.tsx` to import `getDefaultStockStatuses` from `inspector-controls/constants` instead of `inspector-controls/utils`. 3. Updated `stock-status-control.tsx` to determine whether the stock status has value or not by comparing with the default stock statuses using `fastDeepEqual`. If the stock status control is deselected, it resets the stock status to the default statuses. These changes do not introduce any new functionalities, but improve the readability and maintainability of the code. * Fix: Default values of attributes not saving as serialized block comment This was happening because of this issue: https://github.com/WordPress/gutenberg/issues/7342 Therefore, I had to use `useEffect` to set the default values of the attributes. Here is the list of changes I made: 1. Removed default values from `block.json` for `query`, `tagName`, and `displayLayout`. 2. Moved the default values to `constants.ts` and created a new object `DEFAULT_ATTRIBUTES` to store them. 3. Relocated `constants.ts` from `inspector-controls` to the parent directory. 4. Refactored `edit.tsx` to use `DEFAULT_ATTRIBUTES` from `constants.ts` to set default attributes using `useEffect`. 5. Removed the attributes assignment from `registerBlockType` in `index.tsx`. 6. Updated `columns-control.tsx`, `index.tsx`, `order-by-control.tsx`, and `stock-status-control.tsx` to import from the relocated `constants.ts`. 7. Updated `ProductCollectionAttributes` and `ProductCollectionQuery` in `types.ts` to include `tagName` and `isProductCollectionBlock` respectively. 8. Modified `ProductCollection.php` to match the updated `orderBy` key in the query parameter. This refactor enhances the readability of the code and reduces duplication by keeping all constants and default values in one place. * Replace usage of 'statii' with 'statuses' in stock status handling This commit replaces all instances of 'statii' with the correct term 'statuses' in the context of handling stock status. This change affects three files: 1. `assets/js/blocks/product-collection/inspector-controls/stock-status-control.tsx` - The term is corrected in a comment block. 2. `assets/js/blocks/product-collection/types.ts` - Updated the name of a variable in the `ProductCollectionQuery` interface. 3. `src/BlockTypes/ProductCollection.php` - Here, the term is replaced in several locations including variable names, comments and the method `get_stock_status_query`. This commit helps improve code readability and consistency across the repository.
This commit is contained in:
parent
e4f1dfdc22
commit
c3060cff0e
|
@ -13,35 +13,13 @@
|
|||
"type": "number"
|
||||
},
|
||||
"query": {
|
||||
"type": "object",
|
||||
"default": {
|
||||
"perPage": 9,
|
||||
"pages": 0,
|
||||
"offset": 0,
|
||||
"postType": "product",
|
||||
"order": "asc",
|
||||
"orderBy": "title",
|
||||
"author": "",
|
||||
"search": "",
|
||||
"exclude": [],
|
||||
"sticky": "",
|
||||
"inherit": false,
|
||||
"taxQuery": null,
|
||||
"parents": [],
|
||||
"isProductCollectionBlock": true,
|
||||
"woocommerceOnSale": false
|
||||
}
|
||||
"type": "object"
|
||||
},
|
||||
"tagName": {
|
||||
"type": "string",
|
||||
"default": "div"
|
||||
"type": "string"
|
||||
},
|
||||
"displayLayout": {
|
||||
"type": "object",
|
||||
"default": {
|
||||
"type": "flex",
|
||||
"columns": 3
|
||||
}
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"providesContext": {
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
import { objectOmit } from '@woocommerce/utils';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
ProductCollectionAttributes,
|
||||
TProductCollectionOrder,
|
||||
TProductCollectionOrderBy,
|
||||
ProductCollectionQuery,
|
||||
ProductCollectionDisplayLayout,
|
||||
} from './types';
|
||||
|
||||
export const STOCK_STATUS_OPTIONS = getSetting< Record< string, string > >(
|
||||
'stockStatusOptions',
|
||||
[]
|
||||
);
|
||||
|
||||
const GLOBAL_HIDE_OUT_OF_STOCK = getSetting< boolean >(
|
||||
'hideOutOfStockItems',
|
||||
false
|
||||
);
|
||||
|
||||
export const getDefaultStockStatuses = () => {
|
||||
return GLOBAL_HIDE_OUT_OF_STOCK
|
||||
? Object.keys( objectOmit( STOCK_STATUS_OPTIONS, 'outofstock' ) )
|
||||
: Object.keys( STOCK_STATUS_OPTIONS );
|
||||
};
|
||||
|
||||
export const DEFAULT_ATTRIBUTES: Partial< ProductCollectionAttributes > = {
|
||||
query: {
|
||||
perPage: 9,
|
||||
pages: 0,
|
||||
offset: 0,
|
||||
postType: 'product',
|
||||
order: 'asc',
|
||||
orderBy: 'title',
|
||||
author: '',
|
||||
search: '',
|
||||
exclude: [],
|
||||
sticky: '',
|
||||
inherit: false,
|
||||
taxQuery: '',
|
||||
parents: [],
|
||||
isProductCollectionBlock: true,
|
||||
woocommerceOnSale: false,
|
||||
woocommerceStockStatus: getDefaultStockStatuses(),
|
||||
},
|
||||
tagName: 'div',
|
||||
displayLayout: {
|
||||
type: 'flex',
|
||||
columns: 3,
|
||||
},
|
||||
};
|
||||
|
||||
export const getDefaultSettings = (
|
||||
currentAttributes: ProductCollectionAttributes
|
||||
): Partial< ProductCollectionAttributes > => ( {
|
||||
displayLayout:
|
||||
DEFAULT_ATTRIBUTES.displayLayout as ProductCollectionDisplayLayout,
|
||||
query: {
|
||||
...currentAttributes.query,
|
||||
orderBy: ( DEFAULT_ATTRIBUTES.query as ProductCollectionQuery )
|
||||
.orderBy as TProductCollectionOrderBy,
|
||||
order: ( DEFAULT_ATTRIBUTES.query as ProductCollectionQuery )
|
||||
.order as TProductCollectionOrder,
|
||||
},
|
||||
} );
|
||||
|
||||
export const DEFAULT_FILTERS = {
|
||||
woocommerceOnSale: ( DEFAULT_ATTRIBUTES.query as ProductCollectionQuery )
|
||||
.woocommerceOnSale,
|
||||
woocommerceStockStatus: getDefaultStockStatuses(),
|
||||
};
|
|
@ -13,6 +13,7 @@ import { ImageSizing } from '../../atomic/blocks/product-elements/image/types';
|
|||
import { ProductCollectionAttributes } from './types';
|
||||
import { VARIATION_NAME as PRODUCT_TITLE_ID } from './variations/elements/product-title';
|
||||
import InspectorControls from './inspector-controls';
|
||||
import { DEFAULT_ATTRIBUTES } from './constants';
|
||||
|
||||
export const INNER_BLOCKS_TEMPLATE: InnerBlockTemplate[] = [
|
||||
[
|
||||
|
@ -82,11 +83,19 @@ const Edit = ( props: BlockEditProps< ProductCollectionAttributes > ) => {
|
|||
|
||||
const instanceId = useInstanceId( Edit );
|
||||
|
||||
/**
|
||||
* Because of issue https://github.com/WordPress/gutenberg/issues/7342,
|
||||
* We are using this workaround to set default attributes.
|
||||
*/
|
||||
useEffect( () => {
|
||||
setAttributes( { ...DEFAULT_ATTRIBUTES, ...attributes } );
|
||||
}, [ setAttributes ] );
|
||||
|
||||
// We need this for multi-query block pagination.
|
||||
// Query parameters for each block are scoped to their ID.
|
||||
useEffect( () => {
|
||||
if ( ! Number.isFinite( queryId ) ) {
|
||||
setAttributes( { queryId: instanceId } );
|
||||
setAttributes( { queryId: Number( instanceId ) } );
|
||||
}
|
||||
}, [ queryId, instanceId, setAttributes ] );
|
||||
|
||||
|
|
|
@ -16,9 +16,6 @@ import './variations';
|
|||
if ( isExperimentalBuild() ) {
|
||||
registerBlockType( metadata, {
|
||||
icon,
|
||||
attributes: {
|
||||
...metadata.attributes,
|
||||
},
|
||||
edit,
|
||||
save,
|
||||
} );
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
ProductCollectionAttributes,
|
||||
ProductCollectionDisplayLayout,
|
||||
} from '../types';
|
||||
import { getDefaultSettings } from './constants';
|
||||
import { getDefaultSettings } from '../constants';
|
||||
|
||||
const ColumnsControl = (
|
||||
props: BlockEditProps< ProductCollectionAttributes >
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import blockJson from '../block.json';
|
||||
import {
|
||||
ProductCollectionAttributes,
|
||||
TProductCollectionOrder,
|
||||
TProductCollectionOrderBy,
|
||||
} from '../types';
|
||||
|
||||
const defaultQuery = blockJson.attributes.query.default;
|
||||
|
||||
export const DEFAULT_FILTERS = {
|
||||
woocommerceOnSale: defaultQuery.woocommerceOnSale,
|
||||
};
|
||||
|
||||
export const getDefaultSettings = (
|
||||
currentAttributes: ProductCollectionAttributes
|
||||
): Partial< ProductCollectionAttributes > => ( {
|
||||
displayLayout: blockJson.attributes.displayLayout.default,
|
||||
query: {
|
||||
...currentAttributes.query,
|
||||
orderBy: blockJson.attributes.query.default
|
||||
.orderBy as TProductCollectionOrderBy,
|
||||
order: blockJson.attributes.query.default
|
||||
.order as TProductCollectionOrder,
|
||||
},
|
||||
} );
|
|
@ -18,7 +18,8 @@ import ColumnsControl from './columns-control';
|
|||
import OrderByControl from './order-by-control';
|
||||
import OnSaleControl from './on-sale-control';
|
||||
import { setQueryAttribute } from './utils';
|
||||
import { DEFAULT_FILTERS, getDefaultSettings } from './constants';
|
||||
import { DEFAULT_FILTERS, getDefaultSettings } from '../constants';
|
||||
import StockStatusControl from './stock-status-control';
|
||||
|
||||
const ProductCollectionInspectorControls = (
|
||||
props: BlockEditProps< ProductCollectionAttributes >
|
||||
|
@ -45,6 +46,7 @@ const ProductCollectionInspectorControls = (
|
|||
} }
|
||||
>
|
||||
<OnSaleControl { ...props } />
|
||||
<StockStatusControl { ...props } />
|
||||
</ToolsPanel>
|
||||
</InspectorControls>
|
||||
);
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { ProductCollectionAttributes } from '../types';
|
||||
import { setQueryAttribute } from './utils';
|
||||
|
||||
const OnSaleControl = (
|
||||
props: BlockEditProps< ProductCollectionAttributes >
|
||||
|
@ -23,14 +24,11 @@ const OnSaleControl = (
|
|||
return (
|
||||
<ToolsPanelItem
|
||||
label={ __( 'On Sale', 'woo-gutenberg-products-block' ) }
|
||||
hasValue={ () => query.woocommerceOnSale }
|
||||
hasValue={ () => query.woocommerceOnSale === true }
|
||||
isShownByDefault
|
||||
onDeselect={ () => {
|
||||
props.setAttributes( {
|
||||
query: {
|
||||
...query,
|
||||
woocommerceOnSale: false,
|
||||
},
|
||||
setQueryAttribute( props, {
|
||||
woocommerceOnSale: false,
|
||||
} );
|
||||
} }
|
||||
>
|
||||
|
@ -41,11 +39,8 @@ const OnSaleControl = (
|
|||
) }
|
||||
checked={ query.woocommerceOnSale || false }
|
||||
onChange={ ( woocommerceOnSale ) => {
|
||||
props.setAttributes( {
|
||||
query: {
|
||||
...query,
|
||||
woocommerceOnSale,
|
||||
},
|
||||
setQueryAttribute( props, {
|
||||
woocommerceOnSale,
|
||||
} );
|
||||
} }
|
||||
/>
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
TProductCollectionOrder,
|
||||
TProductCollectionOrderBy,
|
||||
} from '../types';
|
||||
import { getDefaultSettings } from './constants';
|
||||
import { getDefaultSettings } from '../constants';
|
||||
|
||||
const orderOptions = [
|
||||
{
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { BlockEditProps } from '@wordpress/blocks';
|
||||
import fastDeepEqual from 'fast-deep-equal/es6';
|
||||
import {
|
||||
FormTokenField,
|
||||
// @ts-expect-error Using experimental features
|
||||
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
|
||||
__experimentalToolsPanelItem as ToolsPanelItem,
|
||||
} from '@wordpress/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ProductCollectionAttributes } from '../types';
|
||||
import { setQueryAttribute } from './utils';
|
||||
import { STOCK_STATUS_OPTIONS, getDefaultStockStatuses } from '../constants';
|
||||
|
||||
/**
|
||||
* Gets the id of a specific stock status from its text label
|
||||
*
|
||||
* In theory, we could use a `saveTransform` function on the
|
||||
* `FormFieldToken` component to do the conversion. However, plugins
|
||||
* can add custom stock statuses which don't conform to our naming
|
||||
* conventions.
|
||||
*/
|
||||
function getStockStatusIdByLabel( statusLabel: FormTokenField.Value ) {
|
||||
const label =
|
||||
typeof statusLabel === 'string' ? statusLabel : statusLabel.value;
|
||||
|
||||
return Object.entries( STOCK_STATUS_OPTIONS ).find(
|
||||
( [ , value ] ) => value === label
|
||||
)?.[ 0 ];
|
||||
}
|
||||
|
||||
const StockStatusControl = (
|
||||
props: BlockEditProps< ProductCollectionAttributes >
|
||||
) => {
|
||||
const { query } = props.attributes;
|
||||
return (
|
||||
<ToolsPanelItem
|
||||
label={ __( 'Stock status', 'woo-gutenberg-products-block' ) }
|
||||
hasValue={ () =>
|
||||
! fastDeepEqual(
|
||||
query.woocommerceStockStatus,
|
||||
getDefaultStockStatuses()
|
||||
)
|
||||
}
|
||||
onDeselect={ () => {
|
||||
setQueryAttribute( props, {
|
||||
woocommerceStockStatus: getDefaultStockStatuses(),
|
||||
} );
|
||||
} }
|
||||
isShownByDefault
|
||||
>
|
||||
<FormTokenField
|
||||
label={ __( 'Stock status', 'woo-gutenberg-products-block' ) }
|
||||
onChange={ ( statusLabels ) => {
|
||||
const woocommerceStockStatus = statusLabels
|
||||
.map( getStockStatusIdByLabel )
|
||||
.filter( Boolean ) as string[];
|
||||
|
||||
setQueryAttribute( props, {
|
||||
woocommerceStockStatus,
|
||||
} );
|
||||
} }
|
||||
suggestions={ Object.values( STOCK_STATUS_OPTIONS ) }
|
||||
validateInput={ ( value: string ) =>
|
||||
Object.values( STOCK_STATUS_OPTIONS ).includes( value )
|
||||
}
|
||||
value={
|
||||
query?.woocommerceStockStatus?.map(
|
||||
( key ) => STOCK_STATUS_OPTIONS[ key ]
|
||||
) || []
|
||||
}
|
||||
__experimentalExpandOnFocus={ true }
|
||||
/>
|
||||
</ToolsPanelItem>
|
||||
);
|
||||
};
|
||||
|
||||
export default StockStatusControl;
|
|
@ -8,6 +8,7 @@ export interface ProductCollectionAttributes {
|
|||
];
|
||||
templateSlug: string;
|
||||
displayLayout: ProductCollectionDisplayLayout;
|
||||
tagName: string;
|
||||
}
|
||||
|
||||
export interface ProductCollectionDisplayLayout {
|
||||
|
@ -30,6 +31,21 @@ export interface ProductCollectionQuery {
|
|||
sticky: string;
|
||||
taxQuery: string;
|
||||
woocommerceOnSale: boolean;
|
||||
/**
|
||||
* Filter products by their stock status.
|
||||
*
|
||||
* Will generate the following `meta_query`:
|
||||
*
|
||||
* ```
|
||||
* array(
|
||||
* 'key' => '_stock_status',
|
||||
* 'value' => (array) $stock_statuses,
|
||||
* 'compare' => 'IN',
|
||||
* ),
|
||||
* ```
|
||||
*/
|
||||
woocommerceStockStatus?: string[];
|
||||
isProductCollectionBlock?: boolean;
|
||||
}
|
||||
|
||||
export type TProductCollectionOrder = 'asc' | 'desc';
|
||||
|
|
|
@ -53,7 +53,7 @@ class ProductCollection extends AbstractBlock {
|
|||
'query_loop_block_query_vars',
|
||||
array( $this, 'build_query' ),
|
||||
10,
|
||||
2
|
||||
3
|
||||
);
|
||||
|
||||
// Update the query for Editor.
|
||||
|
@ -76,13 +76,76 @@ class ProductCollection extends AbstractBlock {
|
|||
return $args;
|
||||
}
|
||||
|
||||
$orderby = $request->get_param( 'orderby' );
|
||||
$on_sale = $request->get_param( 'woocommerceOnSale' ) === 'true';
|
||||
$orderby = $request->get_param( 'orderBy' );
|
||||
$on_sale = $request->get_param( 'woocommerceOnSale' ) === 'true';
|
||||
$stock_status = $request->get_param( 'woocommerceStockStatus' );
|
||||
|
||||
$orderby_query = $orderby ? $this->get_custom_orderby_query( $orderby ) : [];
|
||||
$on_sale_query = $this->get_on_sale_products_query( $on_sale );
|
||||
return $this->get_final_query_args(
|
||||
$args,
|
||||
array(
|
||||
'orderby' => $orderby,
|
||||
'on_sale' => $on_sale,
|
||||
'stock_status' => $stock_status,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return array_merge( $args, $orderby_query, $on_sale_query );
|
||||
/**
|
||||
* Get final query args based on provided values
|
||||
*
|
||||
* @param array $common_query_values Common query values.
|
||||
* @param array $query Query from block context.
|
||||
*/
|
||||
private function get_final_query_args( $common_query_values, $query ) {
|
||||
$orderby_query = $query['orderby'] ? $this->get_custom_orderby_query( $query['orderby'] ) : [];
|
||||
$on_sale_query = $this->get_on_sale_products_query( $query['on_sale'] );
|
||||
$stock_query = $this->get_stock_status_query( $query['stock_status'] );
|
||||
$visibility_query = is_array( $query['stock_status'] ) ? $this->get_product_visibility_query( $stock_query ) : [];
|
||||
$tax_query = $this->merge_tax_queries( $visibility_query );
|
||||
|
||||
return $this->merge_queries( $common_query_values, $orderby_query, $on_sale_query, $stock_query, $tax_query );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a custom query based on attributes, filters and global WP_Query.
|
||||
*
|
||||
* @param WP_Query $query The WordPress Query.
|
||||
* @param WP_Block $block The block being rendered.
|
||||
* @param int $page The page number.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function build_query( $query, $block, $page ) {
|
||||
// If not in context of product collection block, return the query as is.
|
||||
$is_product_collection_block = $block->context['query']['isProductCollectionBlock'] ?? false;
|
||||
if ( ! $is_product_collection_block ) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
$block_context_query = $block->context['query'];
|
||||
|
||||
$common_query_values = array(
|
||||
'meta_query' => array(),
|
||||
'posts_per_page' => $block_context_query['perPage'],
|
||||
'order' => $block_context_query['order'],
|
||||
'offset' => $block_context_query['offset'],
|
||||
'post__in' => array(),
|
||||
'post_status' => 'publish',
|
||||
'post_type' => 'product',
|
||||
'tax_query' => array(),
|
||||
'paged' => $page,
|
||||
);
|
||||
|
||||
$is_on_sale = $block_context_query['woocommerceOnSale'] ?? false;
|
||||
|
||||
return $this->get_final_query_args(
|
||||
$common_query_values,
|
||||
array(
|
||||
'on_sale' => $is_on_sale,
|
||||
'stock_status' => $block_context_query['woocommerceStockStatus'],
|
||||
'orderby' => $block_context_query['orderBy'],
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -101,44 +164,6 @@ class ProductCollection extends AbstractBlock {
|
|||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a custom query based on attributes, filters and global WP_Query.
|
||||
*
|
||||
* @param WP_Query $query The WordPress Query.
|
||||
* @param WP_Block $block The block being rendered.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function build_query( $query, $block ) {
|
||||
// If not in context of product collection block, return the query as is.
|
||||
$is_product_collection_block = $block->context['query']['isProductCollectionBlock'] ?? false;
|
||||
if ( ! $is_product_collection_block ) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
$common_query_values = array(
|
||||
'meta_query' => array(),
|
||||
'posts_per_page' => $query['posts_per_page'],
|
||||
'orderby' => $query['orderby'],
|
||||
'order' => $query['order'],
|
||||
'offset' => $query['offset'],
|
||||
'post__in' => array(),
|
||||
'post_status' => 'publish',
|
||||
'post_type' => 'product',
|
||||
'tax_query' => array(),
|
||||
);
|
||||
|
||||
$is_on_sale = $block->context['query']['woocommerceOnSale'] ?? false;
|
||||
|
||||
$merged_query = $this->merge_queries(
|
||||
$common_query_values,
|
||||
$this->get_custom_orderby_query( $query['orderby'] ),
|
||||
$this->get_on_sale_products_query( $is_on_sale )
|
||||
);
|
||||
|
||||
return $merged_query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge in the first parameter the keys "post_in", "meta_query" and "tax_query" of the second parameter.
|
||||
*
|
||||
|
@ -326,4 +351,94 @@ class ProductCollection extends AbstractBlock {
|
|||
|
||||
return $base;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a query for products depending on their stock status.
|
||||
*
|
||||
* @param array $stock_statuses An array of acceptable stock statuses.
|
||||
* @return array
|
||||
*/
|
||||
private function get_stock_status_query( $stock_statuses ) {
|
||||
if ( ! is_array( $stock_statuses ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$stock_status_options = array_keys( wc_get_product_stock_status_options() );
|
||||
|
||||
/**
|
||||
* If all available stock status are selected, we don't need to add the
|
||||
* meta query for stock status.
|
||||
*/
|
||||
if (
|
||||
count( $stock_statuses ) === count( $stock_status_options ) &&
|
||||
array_diff( $stock_statuses, $stock_status_options ) === array_diff( $stock_status_options, $stock_statuses )
|
||||
) {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* If all stock statuses are selected except 'outofstock', we use the
|
||||
* product visibility query to filter out out of stock products.
|
||||
*
|
||||
* @see get_product_visibility_query()
|
||||
*/
|
||||
$diff = array_diff( $stock_status_options, $stock_statuses );
|
||||
if ( count( $diff ) === 1 && in_array( 'outofstock', $diff, true ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return array(
|
||||
'meta_query' => array(
|
||||
array(
|
||||
'key' => '_stock_status',
|
||||
'value' => (array) $stock_statuses,
|
||||
'compare' => 'IN',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a query for product visibility depending on their stock status.
|
||||
*
|
||||
* @param array $stock_query Stock status query.
|
||||
*
|
||||
* @return array Tax query for product visibility.
|
||||
*/
|
||||
private function get_product_visibility_query( $stock_query ) {
|
||||
$product_visibility_terms = wc_get_product_visibility_term_ids();
|
||||
$product_visibility_not_in = array( is_search() ? $product_visibility_terms['exclude-from-search'] : $product_visibility_terms['exclude-from-catalog'] );
|
||||
|
||||
// Hide out of stock products.
|
||||
if ( empty( $stock_query ) && 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) {
|
||||
$product_visibility_not_in[] = $product_visibility_terms['outofstock'];
|
||||
}
|
||||
|
||||
return array(
|
||||
'tax_query' => array(
|
||||
array(
|
||||
'taxonomy' => 'product_visibility',
|
||||
'field' => 'term_taxonomy_id',
|
||||
'terms' => $product_visibility_not_in,
|
||||
'operator' => 'NOT IN',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge tax_queries from various queries.
|
||||
*
|
||||
* @param array ...$queries Query arrays to be merged.
|
||||
* @return array
|
||||
*/
|
||||
private function merge_tax_queries( ...$queries ) {
|
||||
$tax_query = [];
|
||||
foreach ( $queries as $query ) {
|
||||
if ( ! empty( $query['tax_query'] ) ) {
|
||||
$tax_query = array_merge( $tax_query, $query['tax_query'] );
|
||||
}
|
||||
}
|
||||
return [ 'tax_query' => $tax_query ];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,9 +45,8 @@ class ProductTemplate extends AbstractBlock {
|
|||
global $wp_query;
|
||||
$query = clone $wp_query;
|
||||
} else {
|
||||
$query_args = build_query_vars_from_query_block( $block, $page );
|
||||
$query_args['paged'] = $page;
|
||||
$query = new WP_Query( $query_args );
|
||||
$query_args = build_query_vars_from_query_block( $block, $page );
|
||||
$query = new WP_Query( $query_args );
|
||||
}
|
||||
|
||||
if ( ! $query->have_posts() ) {
|
||||
|
|
Loading…
Reference in New Issue