From 9fc54717952b05e78b305d8f2851a3139fc68f5d Mon Sep 17 00:00:00 2001 From: Manish Menaria Date: Tue, 31 Oct 2023 17:28:26 +0530 Subject: [PATCH 1/4] Add support for filtering products by featured status - Added `featured` attribute to `ProductCollectionQuery` type to enable filtering by featured status. - Implemented `FeaturedProductsControl` to provide a toggle option in the inspector controls. - Integrated `FeaturedProductsControl` into `ProductCollectionInspectorControls`. - Added `get_featured_query` function in `ProductCollection` class to generate query for fetching featured products. - Updated existing functions and queries in `ProductCollection` class to support featured products filtering. --- .../js/blocks/product-collection/constants.ts | 2 + .../featured-products-control.tsx | 52 +++++++++++++++++++ .../inspector-controls/index.tsx | 2 + assets/js/blocks/product-collection/types.ts | 4 ++ composer.lock | 16 +++--- src/BlockTypes/ProductCollection.php | 39 +++++++++++++- 6 files changed, 105 insertions(+), 10 deletions(-) create mode 100644 assets/js/blocks/product-collection/inspector-controls/featured-products-control.tsx diff --git a/assets/js/blocks/product-collection/constants.ts b/assets/js/blocks/product-collection/constants.ts index 6a5759c3597..d5cc3b02bba 100644 --- a/assets/js/blocks/product-collection/constants.ts +++ b/assets/js/blocks/product-collection/constants.ts @@ -45,6 +45,7 @@ export const DEFAULT_QUERY: ProductCollectionQuery = { inherit: null, taxQuery: {}, isProductCollectionBlock: true, + featured: false, woocommerceOnSale: false, woocommerceStockStatus: getDefaultStockStatuses(), woocommerceAttributes: [], @@ -86,4 +87,5 @@ export const DEFAULT_FILTERS: Partial< ProductCollectionQuery > = { woocommerceAttributes: [], taxQuery: DEFAULT_QUERY.taxQuery, woocommerceHandPickedProducts: [], + featured: DEFAULT_QUERY.featured, }; diff --git a/assets/js/blocks/product-collection/inspector-controls/featured-products-control.tsx b/assets/js/blocks/product-collection/inspector-controls/featured-products-control.tsx new file mode 100644 index 00000000000..dec43da0da5 --- /dev/null +++ b/assets/js/blocks/product-collection/inspector-controls/featured-products-control.tsx @@ -0,0 +1,52 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { + BaseControl, + ToggleControl, + // @ts-expect-error Using experimental features + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis + __experimentalToolsPanelItem as ToolsPanelItem, +} from '@wordpress/components'; + +/** + * Internal dependencies + */ +import { QueryControlProps } from '../types'; + +const FeaturedProductsControl = ( props: QueryControlProps ) => { + const { query, setQueryAttribute } = props; + + return ( + query.featured === true } + onDeselect={ () => { + setQueryAttribute( { + featured: false, + } ); + } } + > + + { + setQueryAttribute( { + featured, + } ); + } } + /> + + + ); +}; + +export default FeaturedProductsControl; diff --git a/assets/js/blocks/product-collection/inspector-controls/index.tsx b/assets/js/blocks/product-collection/inspector-controls/index.tsx index 8d8ffed8f85..4a5d180e260 100644 --- a/assets/js/blocks/product-collection/inspector-controls/index.tsx +++ b/assets/js/blocks/product-collection/inspector-controls/index.tsx @@ -39,6 +39,7 @@ import AttributesControl from './attributes-control'; import TaxonomyControls from './taxonomy-controls'; import HandPickedProductsControl from './hand-picked-products-control'; import LayoutOptionsControl from './layout-options-control'; +import FeaturedProductsControl from './featured-products-control'; const ProductCollectionInspectorControls = ( props: BlockEditProps< ProductCollectionAttributes > @@ -98,6 +99,7 @@ const ProductCollectionInspectorControls = ( + ) : null } diff --git a/assets/js/blocks/product-collection/types.ts b/assets/js/blocks/product-collection/types.ts index ad2f0d4a26a..0a739d7824e 100644 --- a/assets/js/blocks/product-collection/types.ts +++ b/assets/js/blocks/product-collection/types.ts @@ -39,6 +39,10 @@ export interface ProductCollectionQuery { postType: string; search: string; taxQuery: Record< string, number[] >; + /** + * If true, show only featured products. + */ + featured: boolean; woocommerceOnSale: boolean; /** * Filter products by their stock status. diff --git a/composer.lock b/composer.lock index 57aa9fb3766..c2ea677181c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6866bccfee9df864f774a3ad47d1fdf6", + "content-hash": "b2547b778103abc9da48f4991993a563", "packages": [ { "name": "automattic/jetpack-a8c-mc-stats", @@ -55,20 +55,20 @@ }, { "name": "automattic/jetpack-admin-ui", - "version": "v0.2.23", + "version": "v0.2.24", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-admin-ui.git", - "reference": "2684f3ee3b458074d95e727e70ae994802501688" + "reference": "334858057237be51aa916352c7573fc4c06bbd6c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-admin-ui/zipball/2684f3ee3b458074d95e727e70ae994802501688", - "reference": "2684f3ee3b458074d95e727e70ae994802501688", + "url": "https://api.github.com/repos/Automattic/jetpack-admin-ui/zipball/334858057237be51aa916352c7573fc4c06bbd6c", + "reference": "334858057237be51aa916352c7573fc4c06bbd6c", "shasum": "" }, "require-dev": { - "automattic/jetpack-changelogger": "^3.3.9", + "automattic/jetpack-changelogger": "^3.3.11", "automattic/jetpack-logo": "^1.6.3", "automattic/wordbless": "dev-master", "yoast/phpunit-polyfills": "1.1.0" @@ -102,9 +102,9 @@ ], "description": "Generic Jetpack wp-admin UI elements", "support": { - "source": "https://github.com/Automattic/jetpack-admin-ui/tree/v0.2.23" + "source": "https://github.com/Automattic/jetpack-admin-ui/tree/v0.2.24" }, - "time": "2023-09-19T18:19:10+00:00" + "time": "2023-10-30T08:37:02+00:00" }, { "name": "automattic/jetpack-autoloader", diff --git a/src/BlockTypes/ProductCollection.php b/src/BlockTypes/ProductCollection.php index 675608d28d2..c43719b27d4 100644 --- a/src/BlockTypes/ProductCollection.php +++ b/src/BlockTypes/ProductCollection.php @@ -207,6 +207,8 @@ public function update_rest_query_in_editor( $args, $request ): array { $stock_status = $request->get_param( 'woocommerceStockStatus' ); $product_attributes = $request->get_param( 'woocommerceAttributes' ); $handpicked_products = $request->get_param( 'woocommerceHandPickedProducts' ); + $featured = $request->get_param( 'featured' ) === 'true'; + // This argument is required for the tests to PHP Unit Tests to run correctly. // Most likely this argument is being accessed in the test environment image. $args['author'] = ''; @@ -219,6 +221,7 @@ public function update_rest_query_in_editor( $args, $request ): array { 'stock_status' => $stock_status, 'product_attributes' => $product_attributes, 'handpicked_products' => $handpicked_products, + 'featured' => $featured, ) ); } @@ -305,6 +308,7 @@ private function get_final_frontend_query( $query, $page = 1, $is_exclude_applie $product_attributes = $query['woocommerceAttributes'] ?? []; $taxonomies_query = $this->get_filter_by_taxonomies_query( $query['tax_query'] ?? [] ); $handpicked_products = $query['woocommerceHandPickedProducts'] ?? []; + $featured = $query['featured'] ?? false; $final_query = $this->get_final_query_args( $common_query_values, @@ -315,6 +319,7 @@ private function get_final_frontend_query( $query, $page = 1, $is_exclude_applie 'product_attributes' => $product_attributes, 'taxonomies_query' => $taxonomies_query, 'handpicked_products' => $handpicked_products, + 'featured' => $featured, ), $is_exclude_applied_filters ); @@ -335,10 +340,11 @@ private function get_final_query_args( $common_query_values, $query, $is_exclude $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 ) : []; + $featured = $query['featured'] ?? false; + $featured_query = $this->get_featured_query( $featured ); $attributes_query = $this->get_product_attributes_query( $query['product_attributes'] ); $taxonomies_query = $query['taxonomies_query'] ?? []; - $tax_query = $this->merge_tax_queries( $visibility_query, $attributes_query, $taxonomies_query ); - + $tax_query = $this->merge_tax_queries( $visibility_query, $attributes_query, $taxonomies_query, $featured_query ); // We exclude applied filters to generate product ids for the filter blocks. $applied_filters_query = $is_exclude_applied_filters ? [] : $this->get_queries_by_applied_filters(); @@ -628,6 +634,35 @@ private function get_product_visibility_query( $stock_query ) { ); } + /** + * Generates a tax query to filter products based on their "featured" status. + * If the `$featured` parameter is true, the function will return a tax query + * that filters products to only those marked as featured. + * If `$featured` is false, an empty array is returned, meaning no filtering will be applied. + * + * @param bool $featured A flag indicating whether to filter products based on featured status. + * + * @return array A tax query for fetching featured products if `$featured` is true; otherwise, an empty array. + */ + private function get_featured_query( $featured ) { + if ( ! $featured ) { + return array(); + } + + return array( + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query + 'tax_query' => array( + array( + 'taxonomy' => 'product_visibility', + 'field' => 'name', + 'terms' => 'featured', + 'operator' => 'IN', + ), + ), + ); + } + + /** * Merge tax_queries from various queries. * From b575ad862aa1d247f6101ea70e714f5a933f5c64 Mon Sep 17 00:00:00 2001 From: Manish Menaria Date: Tue, 31 Oct 2023 17:40:34 +0530 Subject: [PATCH 2/4] Revert changes to composer.lock --- composer.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.lock b/composer.lock index c2ea677181c..57aa9fb3766 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b2547b778103abc9da48f4991993a563", + "content-hash": "6866bccfee9df864f774a3ad47d1fdf6", "packages": [ { "name": "automattic/jetpack-a8c-mc-stats", @@ -55,20 +55,20 @@ }, { "name": "automattic/jetpack-admin-ui", - "version": "v0.2.24", + "version": "v0.2.23", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-admin-ui.git", - "reference": "334858057237be51aa916352c7573fc4c06bbd6c" + "reference": "2684f3ee3b458074d95e727e70ae994802501688" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-admin-ui/zipball/334858057237be51aa916352c7573fc4c06bbd6c", - "reference": "334858057237be51aa916352c7573fc4c06bbd6c", + "url": "https://api.github.com/repos/Automattic/jetpack-admin-ui/zipball/2684f3ee3b458074d95e727e70ae994802501688", + "reference": "2684f3ee3b458074d95e727e70ae994802501688", "shasum": "" }, "require-dev": { - "automattic/jetpack-changelogger": "^3.3.11", + "automattic/jetpack-changelogger": "^3.3.9", "automattic/jetpack-logo": "^1.6.3", "automattic/wordbless": "dev-master", "yoast/phpunit-polyfills": "1.1.0" @@ -102,9 +102,9 @@ ], "description": "Generic Jetpack wp-admin UI elements", "support": { - "source": "https://github.com/Automattic/jetpack-admin-ui/tree/v0.2.24" + "source": "https://github.com/Automattic/jetpack-admin-ui/tree/v0.2.23" }, - "time": "2023-10-30T08:37:02+00:00" + "time": "2023-09-19T18:19:10+00:00" }, { "name": "automattic/jetpack-autoloader", From 3685504495f44099bdaa92480e46ca8fd44e9470 Mon Sep 17 00:00:00 2001 From: Manish Menaria Date: Wed, 1 Nov 2023 11:20:45 +0530 Subject: [PATCH 3/4] Refactor handling of 'featured' parameter This commit makes the handling of the 'featured' parameter consistent in the ProductCollection class. Previously, the 'featured' parameter was being type-casted to boolean, which was not necessary and could lead to incorrect results. Now, the 'featured' parameter is used directly without type-casting, and the check for 'featured' products in the get_featured_query method has been updated accordingly. This ensures that the 'featured' parameter is handled consistently and correctly throughout the class. --- src/BlockTypes/ProductCollection.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/BlockTypes/ProductCollection.php b/src/BlockTypes/ProductCollection.php index c43719b27d4..a3f55556359 100644 --- a/src/BlockTypes/ProductCollection.php +++ b/src/BlockTypes/ProductCollection.php @@ -207,7 +207,7 @@ public function update_rest_query_in_editor( $args, $request ): array { $stock_status = $request->get_param( 'woocommerceStockStatus' ); $product_attributes = $request->get_param( 'woocommerceAttributes' ); $handpicked_products = $request->get_param( 'woocommerceHandPickedProducts' ); - $featured = $request->get_param( 'featured' ) === 'true'; + $featured = $request->get_param( 'featured' ); // This argument is required for the tests to PHP Unit Tests to run correctly. // Most likely this argument is being accessed in the test environment image. @@ -308,7 +308,6 @@ private function get_final_frontend_query( $query, $page = 1, $is_exclude_applie $product_attributes = $query['woocommerceAttributes'] ?? []; $taxonomies_query = $this->get_filter_by_taxonomies_query( $query['tax_query'] ?? [] ); $handpicked_products = $query['woocommerceHandPickedProducts'] ?? []; - $featured = $query['featured'] ?? false; $final_query = $this->get_final_query_args( $common_query_values, @@ -319,7 +318,7 @@ private function get_final_frontend_query( $query, $page = 1, $is_exclude_applie 'product_attributes' => $product_attributes, 'taxonomies_query' => $taxonomies_query, 'handpicked_products' => $handpicked_products, - 'featured' => $featured, + 'featured' => $query['featured'], ), $is_exclude_applied_filters ); @@ -340,8 +339,7 @@ private function get_final_query_args( $common_query_values, $query, $is_exclude $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 ) : []; - $featured = $query['featured'] ?? false; - $featured_query = $this->get_featured_query( $featured ); + $featured_query = $this->get_featured_query( $query['featured'] ); $attributes_query = $this->get_product_attributes_query( $query['product_attributes'] ); $taxonomies_query = $query['taxonomies_query'] ?? []; $tax_query = $this->merge_tax_queries( $visibility_query, $attributes_query, $taxonomies_query, $featured_query ); @@ -645,7 +643,7 @@ private function get_product_visibility_query( $stock_query ) { * @return array A tax query for fetching featured products if `$featured` is true; otherwise, an empty array. */ private function get_featured_query( $featured ) { - if ( ! $featured ) { + if ( true !== $featured && 'true' !== $featured ) { return array(); } From a91545733b867a65a21b49f8a7aed87ebf2f5ef1 Mon Sep 17 00:00:00 2001 From: Manish Menaria Date: Wed, 1 Nov 2023 12:45:31 +0530 Subject: [PATCH 4/4] Handle undefined 'featured' index This commit adds null coalescing operator to handle the case when 'featured' index is not set in the $query array. This prevents potential PHP notices or errors that may arise when trying to access an undefined index. --- src/BlockTypes/ProductCollection.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BlockTypes/ProductCollection.php b/src/BlockTypes/ProductCollection.php index a3f55556359..3e7585d789b 100644 --- a/src/BlockTypes/ProductCollection.php +++ b/src/BlockTypes/ProductCollection.php @@ -318,7 +318,7 @@ private function get_final_frontend_query( $query, $page = 1, $is_exclude_applie 'product_attributes' => $product_attributes, 'taxonomies_query' => $taxonomies_query, 'handpicked_products' => $handpicked_products, - 'featured' => $query['featured'], + 'featured' => $query['featured'] ?? false, ), $is_exclude_applied_filters ); @@ -339,7 +339,7 @@ private function get_final_query_args( $common_query_values, $query, $is_exclude $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 ) : []; - $featured_query = $this->get_featured_query( $query['featured'] ); + $featured_query = $this->get_featured_query( $query['featured'] ?? false ); $attributes_query = $this->get_product_attributes_query( $query['product_attributes'] ); $taxonomies_query = $query['taxonomies_query'] ?? []; $tax_query = $this->merge_tax_queries( $visibility_query, $attributes_query, $taxonomies_query, $featured_query );