diff --git a/lib/compat.php b/lib/compat.php index b5ad85956d068..ee8676f11962b 100644 --- a/lib/compat.php +++ b/lib/compat.php @@ -418,6 +418,27 @@ function gutenberg_render_block_with_assigned_block_context( $pre_render, $parse } } + if ( isset( $wp_query->tax_query->queried_terms['post_tag'] ) ) { + if ( isset( $context['query'] ) ) { + $context['query']['tagIds'] = array(); + } else { + $context['query'] = array( 'tagIds' => array() ); + } + + foreach ( $wp_query->tax_query->queried_terms['post_tag']['terms'] as $tag_slug_or_id ) { + $tag_ID = $tag_slug_or_id; + + if ( 'slug' === $wp_query->tax_query->queried_terms['post_tag']['field'] ) { + $tag = get_term_by( 'slug', $tag_slug_or_id, 'post_tag' ); + + if ( $tag ) { + $tag_ID = $tag->term_id; + } + } + $context['query']['tagIds'][] = $tag_ID; + } + } + /** * Filters the default context provided to a rendered block. * diff --git a/packages/block-library/src/query-loop/edit.js b/packages/block-library/src/query-loop/edit.js index 4f993327f191f..06a7949a87e4d 100644 --- a/packages/block-library/src/query-loop/edit.js +++ b/packages/block-library/src/query-loop/edit.js @@ -19,7 +19,14 @@ const TEMPLATE = [ [ 'core/post-title' ], [ 'core/post-content' ] ]; export default function QueryLoopEdit( { clientId, context: { - query: { perPage, offset, categoryIds, order, orderBy } = {}, + query: { + perPage, + offset, + categoryIds, + tagIds = [], + order, + orderBy, + } = {}, queryContext, }, } ) { @@ -31,6 +38,7 @@ export default function QueryLoopEdit( { const query = { offset: perPage ? perPage * ( page - 1 ) + offset : 0, categories: categoryIds, + tags: tagIds, order, orderby: orderBy, }; @@ -46,7 +54,7 @@ export default function QueryLoopEdit( { blocks: select( 'core/block-editor' ).getBlocks( clientId ), }; }, - [ perPage, page, offset, categoryIds, order, orderBy, clientId ] + [ perPage, page, offset, categoryIds, tagIds, order, orderBy, clientId ] ); const blockContexts = useMemo( diff --git a/packages/block-library/src/query-loop/index.php b/packages/block-library/src/query-loop/index.php index 43ba6ce252ded..45c1622627e29 100644 --- a/packages/block-library/src/query-loop/index.php +++ b/packages/block-library/src/query-loop/index.php @@ -32,6 +32,9 @@ function render_block_core_query_loop( $attributes, $content, $block ) { if ( isset( $block->context['query']['categoryIds'] ) ) { $query['category__in'] = $block->context['query']['categoryIds']; } + if ( isset( $block->context['query']['tagIds'] ) ) { + $query['tag__in'] = $block->context['query']['tagIds']; + } if ( isset( $block->context['query']['order'] ) ) { $query['order'] = strtoupper( $block->context['query']['order'] ); } @@ -42,6 +45,7 @@ function render_block_core_query_loop( $attributes, $content, $block ) { $query['posts_per_page'] = $block->context['query']['perPage']; } } + $posts = get_posts( $query ); $content = ''; diff --git a/packages/block-library/src/query/block.json b/packages/block-library/src/query/block.json index 2561ccbe1f844..a4d39f0c7b3dd 100644 --- a/packages/block-library/src/query/block.json +++ b/packages/block-library/src/query/block.json @@ -12,6 +12,7 @@ "pages": 1, "offset": 0, "categoryIds": [], + "tagIds": [], "order": "desc", "orderBy": "date" } diff --git a/packages/block-library/src/query/constants.js b/packages/block-library/src/query/constants.js new file mode 100644 index 0000000000000..311232afe3adc --- /dev/null +++ b/packages/block-library/src/query/constants.js @@ -0,0 +1,5 @@ +export const MAX_FETCHED_TERMS = 100; + +export default { + MAX_FETCHED_TERMS, +}; diff --git a/packages/block-library/src/query/edit/query-toolbar.js b/packages/block-library/src/query/edit/query-toolbar.js index b80e9a4f8a29c..d62a4a10b2110 100644 --- a/packages/block-library/src/query/edit/query-toolbar.js +++ b/packages/block-library/src/query/edit/query-toolbar.js @@ -12,32 +12,40 @@ import { import { __ } from '@wordpress/i18n'; import { postList } from '@wordpress/icons'; +/** + * Internal dependencies + */ +import { getTermsInfo } from '../utils'; +import { MAX_FETCHED_TERMS } from '../constants'; + export default function QueryToolbar( { query, setQuery } ) { - const { categories, categoriesMapById, categoriesMapByName } = useSelect( - ( select ) => { - const _categories = select( 'core' ).getEntityRecords( - 'taxonomy', - 'category' - ); - return { - categories: _categories, - ..._categories?.reduce( - ( acc, category ) => ( { - categoriesMapById: { - ...acc.categoriesMapById, - [ category.id ]: category, - }, - categoriesMapByName: { - ...acc.categoriesMapByName, - [ category.name ]: category, - }, - } ), - { categoriesMapById: {}, categoriesMapByName: {} } - ), - }; - }, - [] - ); + const { categories, tags } = useSelect( ( select ) => { + const { getEntityRecords } = select( 'core' ); + const termsQuery = { per_page: MAX_FETCHED_TERMS }; + const _categories = getEntityRecords( + 'taxonomy', + 'category', + termsQuery + ); + const _tags = getEntityRecords( 'taxonomy', 'post_tag', termsQuery ); + return { + categories: getTermsInfo( _categories ), + tags: getTermsInfo( _tags ), + }; + }, [] ); + + // Handles categories and tags changes. + const onTermsChange = ( terms, queryProperty ) => ( newTermValues ) => { + const termIds = newTermValues.reduce( ( accumulator, termValue ) => { + const termId = termValue?.id || terms.mapByName[ termValue ]?.id; + if ( termId ) accumulator.push( termId ); + return accumulator; + }, [] ); + setQuery( { [ queryProperty ]: termIds } ); + }; + const onCategoriesChange = onTermsChange( categories, 'categoryIds' ); + const onTagsChange = onTermsChange( tags, 'tagIds' ); + return ( - { categories && ( + { categories?.terms && ( ( { id: categoryId, value: - categoriesMapById[ categoryId ] + categories.mapById[ categoryId ] .name, } ) ) } - suggestions={ categories.map( - ( category ) => category.name + suggestions={ categories.names } + onChange={ onCategoriesChange } + /> + ) } + { tags?.terms && ( + ( { + id: tagId, + value: tags.mapById[ tagId ].name, + } ) ) } - onChange={ ( newCategoryNames ) => { - const categoryIds = newCategoryNames.map( - ( categoryName ) => - categoriesMapByName[ categoryName ] - ?.id - ); - if ( categoryIds.includes( undefined ) ) - return; - setQuery( { categoryIds } ); - } } + suggestions={ tags.names } + onChange={ onTagsChange } /> ) } diff --git a/packages/block-library/src/query/test/fixtures/index.js b/packages/block-library/src/query/test/fixtures/index.js new file mode 100644 index 0000000000000..c285343bf3db6 --- /dev/null +++ b/packages/block-library/src/query/test/fixtures/index.js @@ -0,0 +1,21 @@ +export const terms = [ + { + count: 2, + id: 4, + meta: [], + name: 'nba', + parent: 0, + slug: 'nba', + taxonomy: 'category', + }, + { + count: 0, + id: 11, + link: 'http://localhost:8888/?tag=featured', + name: 'featured', + slug: 'featured', + taxonomy: 'post_tag', + }, +]; + +export default { terms }; diff --git a/packages/block-library/src/query/test/utils.js b/packages/block-library/src/query/test/utils.js new file mode 100644 index 0000000000000..c7acb4b6110ba --- /dev/null +++ b/packages/block-library/src/query/test/utils.js @@ -0,0 +1,30 @@ +/** + * Internal dependencies + */ +import { terms } from './fixtures'; +import { getTermsInfo } from '../utils'; + +describe( 'Query block utils', () => { + describe( 'getTermsInfo', () => { + it( 'should return an empty object when no terms provided', () => { + expect( getTermsInfo() ).toEqual( { terms: undefined } ); + } ); + it( 'should return proper terms info object', () => { + expect( getTermsInfo( terms ) ).toEqual( + expect.objectContaining( { + mapById: expect.objectContaining( { + '4': expect.objectContaining( { name: 'nba' } ), + '11': expect.objectContaining( { + name: 'featured', + } ), + } ), + mapByName: expect.objectContaining( { + nba: expect.objectContaining( { id: 4 } ), + featured: expect.objectContaining( { id: 11 } ), + } ), + names: expect.arrayContaining( [ 'nba', 'featured' ] ), + } ) + ); + } ); + } ); +} ); diff --git a/packages/block-library/src/query/utils.js b/packages/block-library/src/query/utils.js new file mode 100644 index 0000000000000..2e55a6b9365ce --- /dev/null +++ b/packages/block-library/src/query/utils.js @@ -0,0 +1,47 @@ +/** + * WordPress term object from REST API. + * Categories ref: https://developer.wordpress.org/rest-api/reference/categories/ + * Tags ref: https://developer.wordpress.org/rest-api/reference/tags/ + * + * @typedef {Object} WPTerm + * @property {number} id Unique identifier for the term. + * @property {number} count Number of published posts for the term. + * @property {string} description HTML description of the term. + * @property {string} link URL of the term. + * @property {string} name HTML title for the term. + * @property {string} slug An alphanumeric identifier for the term unique to its type. + * @property {string} taxonomy Type attribution for the term. + * @property {Object} meta Meta fields + * @property {number} [parent] The parent term ID. + */ + +/** + * The object used in Query block that contains info and helper mappings + * from an array of WPTerm. + * + * @typedef {Object} QueryTermsInfo + * @property {WPTerm[]} terms The array of terms. + * @property {Object} mapById Object mapping with the term id as key and the term as value. + * @property {Object} mapByName Object mapping with the term name as key and the term as value. + * @property {string[]} names Array with the terms' names. + */ + +/** + * Returns a helper object with mapping from WPTerms. + * + * @param {WPTerm[]} terms The terms to extract of helper object. + * @return {QueryTermsInfo} The object with the terms information. + */ +export const getTermsInfo = ( terms ) => ( { + terms, + ...terms?.reduce( + ( accumulator, term ) => { + const { mapById, mapByName, names } = accumulator; + mapById[ term.id ] = term; + mapByName[ term.name ] = term; + names.push( term.name ); + return accumulator; + }, + { mapById: {}, mapByName: {}, names: [] } + ), +} ); diff --git a/packages/e2e-tests/fixtures/blocks/core__query.json b/packages/e2e-tests/fixtures/blocks/core__query.json index 9e2d6307436c1..e06e002a5a314 100644 --- a/packages/e2e-tests/fixtures/blocks/core__query.json +++ b/packages/e2e-tests/fixtures/blocks/core__query.json @@ -9,6 +9,7 @@ "pages": 1, "offset": 0, "categoryIds": [], + "tagIds": [], "order": "desc", "orderBy": "date" } diff --git a/packages/edit-site/src/components/editor/index.js b/packages/edit-site/src/components/editor/index.js index 5758a7f2b8e9f..4e4f40e6eea86 100644 --- a/packages/edit-site/src/components/editor/index.js +++ b/packages/edit-site/src/components/editor/index.js @@ -136,7 +136,7 @@ function Editor() { const blockContext = useMemo( () => ( { ...page?.context, - query: page?.context.query || { categoryIds: [] }, + query: page?.context.query || { categoryIds: [], tagIds: [] }, queryContext: [ page?.context.queryContext || { page: 1 }, ( newQueryContext ) =>