diff --git a/.phpcs.xml b/.phpcs.xml index 11586e9..7f045b7 100644 --- a/.phpcs.xml +++ b/.phpcs.xml @@ -29,6 +29,10 @@ + + + + diff --git a/inc/namespace.php b/inc/namespace.php index 63829fe..92f6386 100644 --- a/inc/namespace.php +++ b/inc/namespace.php @@ -73,7 +73,7 @@ function filter_query_loop_block_query_vars( array $query, \WP_Block $block, int /** * Fires after the query variable object is created, but before the actual query is run. * - * @param WP_Query $query The WP_Query instance (passed by reference). + * @param WP_Query $query The WP_Query instance (passed by reference). */ function pre_get_posts_transpose_query_vars( WP_Query $query ) : void { $query_id = $query->get( 'query_id', null ); @@ -105,12 +105,29 @@ function pre_get_posts_transpose_query_vars( WP_Query $query ) : void { // Handle taxonomies specifically. if ( get_taxonomy( $key ) ) { + // If multiple taxonomy filters are selected, ALL of them must match. $tax_query['relation'] = 'AND'; - $tax_query[] = [ - 'taxonomy' => $key, - 'terms' => [ $value ], - 'field' => 'slug', - ]; + + // Handle multiple values separated by commas (for checkbox mode) + $values = wp_parse_list( $value ); + + if ( count( $values ) > 1 ) { + // If multiple terms in a taxonomy are selected, posts with + // ANY of the selected terms should be returned. + $tax_query[] = [ + 'taxonomy' => $key, + 'terms' => $values, + 'field' => 'slug', + 'operator' => 'IN', + ]; + } else { + // Single value: normal behavior + $tax_query[] = [ + 'taxonomy' => $key, + 'terms' => $values, + 'field' => 'slug', + ]; + } } else { // Other options should map directly to query vars. $key = sanitize_key( $key ); @@ -119,6 +136,12 @@ function pre_get_posts_transpose_query_vars( WP_Query $query ) : void { continue; } + // post_type accepts multiple comma-separated values in checkbox mode. + // Parse as list so WP_Query returns results from any selected post_type. + if ( $key === 'post_type' ) { + $value = wp_parse_list( $value ); + } + $query->set( $key, $value @@ -176,7 +199,7 @@ function render_block_search( string $block_content, array $block, \WP_Block $in ? sprintf( 'query-%d-s', $instance->context['queryId'] ?? 0 ) : 'query-s'; - $action = str_replace( '/page/'. get_query_var( 'paged', 1 ), '', add_query_arg( [ $query_var => '' ] ) ); + $action = str_replace( '/page/' . get_query_var( 'paged', 1 ), '', add_query_arg( [ $query_var => '' ] ) ); // Note sanitize_text_field trims whitespace from start/end of string causing unexpected behaviour. // phpcs:ignore HM.Security.ValidatedSanitizedInput.InputNotSanitized diff --git a/package.json b/package.json index cf51ff8..575332c 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "format": "wp-scripts format", "lint:css": "wp-scripts lint-style --fix", "lint:js": "wp-scripts lint-js", + "lint:php": "composer phpcs", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "Human Made Limited", diff --git a/src/post-type/block.json b/src/post-type/block.json index 0d1af2e..e3f2b37 100644 --- a/src/post-type/block.json +++ b/src/post-type/block.json @@ -1,6 +1,6 @@ { "$schema": "https://schemas.wp.org/trunk/block.json", - "apiVersion": 2, + "apiVersion": 3, "name": "query-filter/post-type", "version": "0.1.0", "title": "Post Type Filter", @@ -51,6 +51,14 @@ "showLabel": { "type": "boolean", "default": true + }, + "displayType": { + "type": "string", + "default": "select" + }, + "layoutDirection": { + "type": "string", + "default": "vertical" } }, "textdomain": "query-filter", diff --git a/src/post-type/edit.js b/src/post-type/edit.js index 5bbd52c..4a68bc9 100644 --- a/src/post-type/edit.js +++ b/src/post-type/edit.js @@ -1,10 +1,18 @@ import { __ } from '@wordpress/i18n'; import { useBlockProps, InspectorControls } from '@wordpress/block-editor'; -import { PanelBody, TextControl, ToggleControl } from '@wordpress/components'; +import { + PanelBody, + TextControl, + ToggleControl, + SelectControl, + __experimentalToggleGroupControl as ToggleGroupControl, + __experimentalToggleGroupControlOption as ToggleGroupControlOption, +} from '@wordpress/components'; import { useSelect } from '@wordpress/data'; export default function Edit( { attributes, setAttributes, context } ) { - const { emptyLabel, label, showLabel } = attributes; + const { emptyLabel, label, showLabel, displayType, layoutDirection } = + attributes; const allPostTypes = useSelect( ( select ) => { return ( @@ -38,6 +46,67 @@ export default function Edit( { attributes, setAttributes, context } ) { <> + { + if ( nextDisplayType === 'select' ) { + setAttributes( { + displayType: nextDisplayType, + layoutDirection: undefined, + } ); + } else { + setAttributes( { + displayType: nextDisplayType, + } ); + } + } } + /> + { ( displayType === 'radio' || + displayType === 'checkbox' ) && ( + + setAttributes( { layoutDirection } ) + } + isBlock + __nextHasNoMarginBottom + __next40pxDefaultSize + > + + + + ) } ) } - + { displayType === 'select' && ( + + ) } + { displayType === 'radio' && ( +
+ + { postTypes.map( ( type ) => ( + + ) ) } +
+ ) } + { displayType === 'checkbox' && ( +
+ { postTypes.map( ( type ) => ( + + ) ) } +
+ ) } ); diff --git a/src/post-type/render.php b/src/post-type/render.php index 774e771..1493d09 100644 --- a/src/post-type/render.php +++ b/src/post-type/render.php @@ -1,7 +1,14 @@ context['query']['inherit'] ) { $query_var = 'query-post_type'; @@ -45,10 +52,52 @@ - - + + +
+ + + + +
+ +
+ + + name, $selected_types, true ); + $new_types = $is_checked + ? array_diff( $selected_types, [ $post_type->name ] ) + : array_merge( $selected_types, [ $post_type->name ] ); + $new_types = array_filter( $new_types ); + $checkbox_url = empty( $new_types ) + ? $base_url + : add_query_arg( [ $query_var => implode( ',', $new_types ), $page_var => false ], $base_url ); + ?> + + +
+ diff --git a/src/taxonomy/block.json b/src/taxonomy/block.json index 1f1a331..f0992b4 100644 --- a/src/taxonomy/block.json +++ b/src/taxonomy/block.json @@ -54,6 +54,14 @@ "showLabel": { "type": "boolean", "default": true + }, + "displayType": { + "type": "string", + "default": "select" + }, + "layoutDirection": { + "type": "string", + "default": "vertical" } }, "textdomain": "query-filter", diff --git a/src/taxonomy/edit.js b/src/taxonomy/edit.js index 5562901..4855e3d 100644 --- a/src/taxonomy/edit.js +++ b/src/taxonomy/edit.js @@ -5,11 +5,20 @@ import { SelectControl, TextControl, ToggleControl, + __experimentalToggleGroupControl as ToggleGroupControl, + __experimentalToggleGroupControlOption as ToggleGroupControlOption, } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; export default function Edit( { attributes, setAttributes } ) { - const { taxonomy, emptyLabel, label, showLabel } = attributes; + const { + taxonomy, + emptyLabel, + label, + showLabel, + displayType, + layoutDirection, + } = attributes; const taxonomies = useSelect( ( select ) => { @@ -60,6 +69,67 @@ export default function Edit( { attributes, setAttributes } ) { } ) } /> + { + if ( nextDisplayType === 'select' ) { + setAttributes( { + displayType: nextDisplayType, + layoutDirection: undefined, + } ); + } else { + setAttributes( { + displayType: nextDisplayType, + } ); + } + } } + /> + { ( displayType === 'radio' || + displayType === 'checkbox' ) && ( + + setAttributes( { layoutDirection } ) + } + isBlock + __nextHasNoMarginBottom + __next40pxDefaultSize + > + + + + ) } ) } - + { displayType === 'select' && ( + + ) } + { displayType === 'radio' && ( +
+ + { terms.map( ( term ) => ( + + ) ) } +
+ ) } + { displayType === 'checkbox' && ( +
+ { terms.map( ( term ) => ( + + ) ) } +
+ ) } ); diff --git a/src/taxonomy/index.js b/src/taxonomy/index.js index 30d8967..0157b8f 100644 --- a/src/taxonomy/index.js +++ b/src/taxonomy/index.js @@ -1,7 +1,6 @@ import { registerBlockType } from '@wordpress/blocks'; import Edit from './edit'; import metadata from './block.json'; -import './style-index.css'; registerBlockType( metadata.name, { /** diff --git a/src/taxonomy/render.php b/src/taxonomy/render.php index 362966a..81753b1 100644 --- a/src/taxonomy/render.php +++ b/src/taxonomy/render.php @@ -1,9 +1,16 @@ slug). +// phpcs:ignore HM.Security.ValidatedSanitizedInput.InputNotSanitized -- Sniff can't perceive the sanitize_text_field() outside the urldecode(). +$current_value = sanitize_text_field( urldecode( wp_unslash( $_GET[ $query_var ] ?? '' ) ) ); ?>
'wp-block-query-filter' ] ); ?> data-wp-interactive="query-filter" data-wp-context="{}"> -
diff --git a/src/taxonomy/style-index.css b/src/taxonomy/style-index.css index 621fee1..29f189a 100644 --- a/src/taxonomy/style-index.css +++ b/src/taxonomy/style-index.css @@ -7,3 +7,33 @@ flex-direction: column; justify-content: stretch; } + +/* Radio group styles */ +.wp-block-query-filter__radio-group, +.wp-block-query-filter__checkbox-group { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.wp-block-query-filter__radio-group label, +.wp-block-query-filter__checkbox-group label { + display: flex; + align-items: center; + gap: 0.5rem; + cursor: pointer; + padding: 0.25rem 0; +} + +.wp-block-query-filter__radio-group input, +.wp-block-query-filter__checkbox-group input { + margin: 0; +} + +/* Horizontal layout option */ +.wp-block-query-filter__radio-group.horizontal, +.wp-block-query-filter__checkbox-group.horizontal { + flex-direction: row; + flex-wrap: wrap; + gap: 1rem; +}