diff --git a/CHANGELOG.md b/CHANGELOG.md index ff61d584..25a7f439 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to `WP Curate` will be documented in this file. -## 0.1.0 - 202X-XX-XX +## 1.2.0 - 2023-10-26 + +- Adds support for AND/OR operators in the Query Parameters, giving more control over what posts to show. + +## 1.1.0 - 2023-09-21 + +- Bug fix: prevents error if post type does not support meta. + +## 1.0.0 - 2023-09-19 - Initial release diff --git a/blocks/query/block.json b/blocks/query/block.json index e63c62fc..5a0f498b 100644 --- a/blocks/query/block.json +++ b/blocks/query/block.json @@ -74,6 +74,26 @@ "type": "array" }, "type": "object" + }, + "termRelations": { + "default": {}, + "items": { + "default": "AND", + "enum": [ + "AND", + "OR" + ], + "type": "string" + }, + "type": "object" + }, + "taxRelation": { + "default": "AND", + "enum": [ + "AND", + "OR" + ], + "type": "string" } } } diff --git a/blocks/query/edit.tsx b/blocks/query/edit.tsx index 2b887286..76cc7974 100644 --- a/blocks/query/edit.tsx +++ b/blocks/query/edit.tsx @@ -9,6 +9,7 @@ import { PanelRow, RadioControl, RangeControl, + SelectControl, TextControl, } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; @@ -30,6 +31,8 @@ import { mainDedupe, } from '../../services/deduplicate'; +import buildTermQueryArgs from '../../services/buildTermQueryArgs'; + import './index.scss'; interface Window { @@ -59,6 +62,8 @@ export default function Edit({ postTypes = ['post'], searchTerm = '', terms = {}, + termRelations = {}, + taxRelation = 'AND', }, setAttributes, }: EditProps) { @@ -69,6 +74,17 @@ export default function Edit({ } = {}, } = (window as any as Window); + const andOrOptions = [ + { + label: __('AND', 'wp-curate'), + value: 'AND', + }, + { + label: __('OR', 'wp-curate'), + value: 'OR', + }, + ]; + // @ts-ignore const [isPostDeduplicating, postTypeObject] = useSelect( (select) => { @@ -93,17 +109,15 @@ export default function Edit({ const [availableTaxonomies, setAvailableTaxonomies] = useState({}); const [availableTypes, setAvailableTypes] = useState({}); - let termQueryArgs = ''; - if (Object.keys(availableTaxonomies).length > 0) { - allowedTaxonomies.forEach((taxonomy) => { - if (terms[taxonomy]?.length > 0) { - const restBase = availableTaxonomies[taxonomy].rest_base; - if (restBase) { - termQueryArgs += `&${restBase}=${terms[taxonomy].map((term) => term.id).join(',')}`; - } - } - }); - } + const taxCount = allowedTaxonomies.filter((taxonomy: string) => terms[taxonomy]?.length > 0).length; // eslint-disable-line max-len + + const termQueryArgs = buildTermQueryArgs( + allowedTaxonomies, + terms, + availableTaxonomies, + termRelations, + taxRelation, + ); const manualPostIds = manualPosts.map((post) => (post ?? null)).join(','); const postTypeString = postTypes.join(','); @@ -143,7 +157,7 @@ export default function Edit({ per_page: 20, }, ); - path += termQueryArgs; + path += `&${termQueryArgs}`; apiFetch({ path, @@ -200,6 +214,14 @@ export default function Edit({ setAttributes({ terms: newTermAttrs }); }); + const setTermRelation = ((type: string, relation: string) => { + const newTermRelationAttrs = { + ...termRelations, + [type]: relation, + }; + setAttributes({ termRelations: newTermRelationAttrs }); + }); + const setNumberOfPosts = (newValue?: number) => { setAttributes({ numberOfPosts: newValue, @@ -319,9 +341,31 @@ export default function Edit({ onSelect={(newCategories: Term[]) => setTerms(taxonomy, newCategories)} multiple /> + {terms[taxonomy]?.length > 1 ? ( + setTermRelation(taxonomy, newValue)} + value={termRelations[taxonomy] ?? 'OR'} + /> + ) : null} +
)) ) : null} + {taxCount > 1 ? ( + setAttributes({ taxRelation: newValue })} + value={taxRelation} + /> + ) : null } setAttributes({ searchTerm: next })} diff --git a/blocks/query/types.ts b/blocks/query/types.ts index df1ff4cc..d1d1a49c 100644 --- a/blocks/query/types.ts +++ b/blocks/query/types.ts @@ -15,6 +15,10 @@ interface EditProps { terms?: { [key: string]: any[]; }; + termRelations?: { + [key: string]: string; + }; + taxRelation?: string; }; setAttributes: (attributes: any) => void; } diff --git a/services/buildTermQueryArgs/index.ts b/services/buildTermQueryArgs/index.ts new file mode 100644 index 00000000..f38151d4 --- /dev/null +++ b/services/buildTermQueryArgs/index.ts @@ -0,0 +1,46 @@ +interface Types { + [key: string]: { + name: string; + slug: string; + rest_base: string; + }; +} + +/** + * Builds the term query args for the WP REST API. + * + * @param string[] allowedTaxonomies The list of allowed taxonomies. + * @param { [key: string]: any[] } terms The selected terms. + * @param { [key: string]: any[] } availableTaxonomies The available taxonomies. + * @param { [key: string]: string } termRelations The AND/OR relation used for each taxonomy. + * @param string taxRelation The AND/OR relation used for all the terms. + * @returns string The term query args. + */ +export default function buildTermQueryArgs( + allowedTaxonomies: string[], + terms: { [key: string]: any[] }, + availableTaxonomies: Types, + termRelations: { [key: string]: string }, + taxRelation: string, +): string { + const taxCount = allowedTaxonomies.filter((taxonomy: string) => terms[taxonomy]?.length > 0).length; // eslint-disable-line max-len + + const termQueryArgs: string[] = []; + if (Object.keys(availableTaxonomies).length > 0) { + allowedTaxonomies.forEach((taxonomy) => { + if (terms[taxonomy]?.length > 0) { + const restBase = availableTaxonomies[taxonomy].rest_base; + if (restBase) { + termQueryArgs.push(`${restBase}[terms]=${terms[taxonomy].map((term) => term.id).join(',')}`); + if (termRelations[taxonomy] !== '' && typeof termRelations[taxonomy] !== 'undefined') { + termQueryArgs.push(`${restBase}[operator]=${termRelations[taxonomy]}`); + } + } + } + }); + if (taxCount > 1) { + termQueryArgs.push(`tax_relation=${taxRelation}`); + } + } + return termQueryArgs.join('&'); +} diff --git a/services/deduplicate/index.ts b/services/deduplicate/index.ts index 71442672..fbb6a09d 100644 --- a/services/deduplicate/index.ts +++ b/services/deduplicate/index.ts @@ -121,13 +121,13 @@ export function mainDedupe() { queryBlocks.forEach((queryBlock) => { const { attributes } = queryBlock; const { - backfillPosts = [], + backfillPosts = null, deduplication = 'inherit', posts = [], numberOfPosts = 5, postTypes = ['post'], } = attributes; - if (!backfillPosts.length) { + if (!backfillPosts) { return; } const postTypeString = postTypes.join(','); diff --git a/src/class-plugin-curated-posts.php b/src/class-plugin-curated-posts.php index 1a43bd95..3b6a9d78 100644 --- a/src/class-plugin-curated-posts.php +++ b/src/class-plugin-curated-posts.php @@ -51,7 +51,7 @@ public function with_query_context( array $context, array $attributes, WP_Block_ if ( isset( $attributes['terms'] ) && is_array( $attributes['terms'] ) && count( $attributes['terms'] ) > 0 ) { $args['tax_query'] = [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query - 'relation' => 'AND', + 'relation' => $attributes['taxRelation'] ?? 'AND', ]; foreach ( $attributes['terms'] as $taxonomy => $terms ) { @@ -59,6 +59,7 @@ public function with_query_context( array $context, array $attributes, WP_Block_ $args['tax_query'][] = [ 'taxonomy' => $taxonomy, 'terms' => array_column( $terms, 'id' ), + 'operator' => is_array( $attributes['termRelations'] ) ? $attributes['termRelations'][ $taxonomy ] ?? 'AND' : 'AND', ]; } } diff --git a/wp-curate.php b/wp-curate.php index a4abd0e5..986e4ee6 100644 --- a/wp-curate.php +++ b/wp-curate.php @@ -3,7 +3,7 @@ * Plugin Name: WP Curate * Plugin URI: https://github.com/alleyinteractive/wp-curate * Description: Plugin to curate homepages and other landing pages - * Version: 1.1.0 + * Version: 1.2.0 * Author: Alley Interactive * Author URI: https://github.com/alleyinteractive/wp-curate * Requires at least: 6.3