Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 65 additions & 7 deletions packages/block-library/src/term-template/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
*/
import clsx from 'clsx';

/**
* Internal dependencies
*/
import { useCurrentTerm } from './use-current-term';

/**
* WordPress dependencies
*/
Expand Down Expand Up @@ -82,13 +87,17 @@ export default function TermTemplateEdit( {
showNested = false,
perPage,
include,
inherit = false,
} = {},
},
__unstableLayoutClassNames,
} ) {
const { type: layoutType, columnCount = 3 } = layout || {};
const [ activeBlockContextId, setActiveBlockContextId ] = useState();

// Extract current term from template slug when inheriting.
const currentTerm = useCurrentTerm( inherit );

const queryArgs = {
hide_empty: hideEmpty,
order,
Expand All @@ -99,9 +108,24 @@ export default function TermTemplateEdit( {
per_page: perPage || -1,
};

// Nested terms are returned by default from REST API as long as parent is not set.
// If we want to show nested terms, we must not set parent at all.
if ( ! showNested && ! include?.length ) {
// Handle inherit logic.
if ( inherit && currentTerm ) {
// When inheriting, use the current term's taxonomy.
queryArgs.taxonomy = currentTerm.taxonomy;

// For hierarchical taxonomies, show children of the current term.
// If showNested is true, use child_of to include nested terms.
// Otherwise, use parent to show only direct children.
if ( showNested ) {
// For nested terms, we need to fetch all terms and filter client-side
// since the REST API doesn't support child_of like WP_Term_Query does.
// Don't set parent - we'll filter the results after fetching.
} else {
queryArgs.parent = currentTerm.id;
}
} else if ( ! showNested && ! include?.length ) {
// Nested terms are returned by default from REST API as long as parent is not set.
// Set parent to 0 to show only top-level terms.
queryArgs.parent = 0;
}

Expand All @@ -112,12 +136,46 @@ export default function TermTemplateEdit( {
queryArgs.order = 'asc';
}

const { records: terms } = useEntityRecords(
// Use the taxonomy from queryArgs if it was set (for inherit), otherwise use the context taxonomy.
const currentTaxonomy = queryArgs.taxonomy || taxonomy;

const { records: allTerms } = useEntityRecords(
'taxonomy',
taxonomy,
currentTaxonomy,
queryArgs
);

// Filter terms for nested functionality.
const terms = useMemo( () => {
if ( ! allTerms || ! inherit || ! currentTerm ) {
return allTerms;
}

if ( showNested ) {
// For nested terms, filter to show all descendants of the current term.
const isDescendant = ( term ) => {
// Check if this term is a descendant of the current term.
const findParent = ( termId ) => {
const foundTerm = allTerms.find( ( t ) => t.id === termId );
return foundTerm ? foundTerm.parent : 0;
};

let currentParent = term.parent;
while ( currentParent !== 0 ) {
if ( currentParent === currentTerm.id ) {
return true;
}
currentParent = findParent( currentParent );
}
return false;
};

return allTerms.filter( isDescendant );
}

return allTerms;
}, [ allTerms, inherit, currentTerm, showNested ] );

const blocks = useSelect(
( select ) => select( blockEditorStore ).getBlocks( clientId ),
[ clientId ]
Expand All @@ -128,12 +186,12 @@ export default function TermTemplateEdit( {
const blockContexts = useMemo(
() =>
terms?.map( ( term ) => ( {
taxonomy,
taxonomy: currentTaxonomy,
termId: term.id,
classList: `term-${ term.id }`,
termData: term,
} ) ),
[ terms, taxonomy ]
[ terms, currentTaxonomy ]
);

if ( ! terms ) {
Expand Down
10 changes: 8 additions & 2 deletions packages/block-library/src/term-template/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,16 @@ function render_block_core_term_template( $attributes, $content, $block ) {
// Get the current term and taxonomy from the queried object.
$queried_object = get_queried_object();

// For hierarchical taxonomies, show direct children of the current term.
// For hierarchical taxonomies, show children of the current term.
// For non-hierarchical taxonomies, show all terms (don't set parent).
if ( is_taxonomy_hierarchical( $queried_object->taxonomy ) ) {
$query_args['parent'] = $queried_object->term_id;
// If showNested is true, use child_of to include nested terms.
// Otherwise, use parent to show only direct children.
if ( ! empty( $query['showNested'] ) ) {
$query_args['child_of'] = $queried_object->term_id;
} else {
$query_args['parent'] = $queried_object->term_id;
}
}
$query_args['taxonomy'] = $queried_object->taxonomy;
} else {
Expand Down
88 changes: 88 additions & 0 deletions packages/block-library/src/term-template/use-current-term.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
Copy link
Member Author

@mikachan mikachan Oct 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This useCurrentTerm is very similar to the useArchiveLabel, useTermDescription, and useTermName hooks, so I think we should consolidate this functionality as much as possible in a follow-up.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This functionality had some heavy discussion in #72300 which ultimately didn't get merged. There are issues if taxonomy slugs contain hyphens, because the template slug does not include additional separators. Historically WordPress applies a template if it matches certain criteria but doing the reverse is tricky.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd echo @cr0ybot and I've actually created an issue to improve the existing functionality.

Let's don't worry about the editor preview for now and fix the main issue (which I'd consider a bug that we should also back port and not an enhancement).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks both, sounds like something that would be best in its own PR. I've removed the changes to the editor logic in 546598a.

* WordPress dependencies
*/
import { useSelect } from '@wordpress/data';
import { store as coreStore } from '@wordpress/core-data';

/**
* Hook to get the current term from template context.
* This is used when blocks need to inherit from the current taxonomy template.
*
* @param {boolean} inherit Whether to inherit from current context.
* @return {Object|null} The current term object or null.
*/
export function useCurrentTerm( inherit ) {
return useSelect(
( select ) => {
if ( ! inherit ) {
return null;
}

// Access core/editor by string to avoid @wordpress/editor dependency.
// eslint-disable-next-line @wordpress/data-no-store-string-literals
const {
getCurrentPostId,
getCurrentPostType,
getCurrentTemplateId,
} = select( 'core/editor' );
const currentPostType = getCurrentPostType();
const templateId =
getCurrentTemplateId() ||
( currentPostType === 'wp_template'
? getCurrentPostId()
: null );
const templateSlug = templateId
? select( coreStore ).getEditedEntityRecord(
'postType',
'wp_template',
templateId
)?.slug
: null;

if ( ! templateSlug ) {
return null;
}

const taxonomyMatches = templateSlug.match(
/^(category|tag|taxonomy-([^-]+))$|^(((category|tag)|taxonomy-([^-]+))-(.+))$/
);

if ( ! taxonomyMatches ) {
return null;
}

let currentTaxonomy;
let termSlug;

// If it's for all taxonomies of a type (e.g., category, tag).
if ( taxonomyMatches[ 1 ] ) {
currentTaxonomy = taxonomyMatches[ 2 ]
? taxonomyMatches[ 2 ]
: taxonomyMatches[ 1 ];
}
// If it's for a specific term (e.g., category-news, tag-featured).
else if ( taxonomyMatches[ 3 ] ) {
currentTaxonomy = taxonomyMatches[ 6 ]
? taxonomyMatches[ 6 ]
: taxonomyMatches[ 4 ];
termSlug = taxonomyMatches[ 7 ];
}

if ( ! currentTaxonomy || ! termSlug ) {
return null;
}

currentTaxonomy =
currentTaxonomy === 'tag' ? 'post_tag' : currentTaxonomy;

const { getEntityRecords } = select( coreStore );
const termRecords = getEntityRecords( 'taxonomy', currentTaxonomy, {
slug: termSlug,
per_page: 1,
} );

return termRecords && termRecords[ 0 ] ? termRecords[ 0 ] : null;
},
[ inherit ]
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ export default function TermsQueryInspectorControls( {
templateSlug?.startsWith( 'taxonomy-' ) ||
templateSlug?.startsWith( 'category-' ) ||
templateSlug?.startsWith( 'tag-' );
// Only display the showNested control if the taxonomy is hierarchical and not inheriting.
const displayShowNestedControl = isTaxonomyHierarchical && ! inheritQuery;
// Display the showNested control if the taxonomy is hierarchical.
const displayShowNestedControl = isTaxonomyHierarchical;
const hasIncludeFilter = !! include?.length;

// Labels shared between ToolsPanelItem and its child control.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,12 @@ export default function InheritControl( { value, onChange, label } ) {
onChange={ ( newValue ) => {
onChange( {
inherit: newValue === 'default',
// When enabling inherit, hierarchical is not supported.
...( newValue === 'default' ? { showNested: false } : {} ),
} );
} }
help={
value
? __(
'Display terms based on the current taxonomy archive. For hierarchical taxonomies, shows direct children of the current term. For non-hierarchical taxonomies, shows all terms.'
'Display terms based on the current taxonomy archive. For hierarchical taxonomies, shows children of the current term. For non-hierarchical taxonomies, shows all terms.'
)
: __( 'Display terms based on specific criteria.' )
}
Expand Down
Loading