Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format
on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). Each changelog entry gets prefixed with the category of the
item (Added, Changed, Depreciated, Removed, Fixed, Security).

## [2026.04]

- Updated: Related Posts now supports current post types, taxonomy-based matching, latest-item fallbacks, and mixed post type manual selection.

## [2026.03]

- Updated: Refactor Horizontal Tabs block into a dynamic block. Add reordering functionality.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Tribe\Plugin\Components\Abstracts\Abstract_Block_Controller;
use Tribe\Plugin\Components\Traits\Post_Data;
use Tribe\Plugin\Taxonomies\Category\Category;

class Post_Card_Controller extends Abstract_Block_Controller {

Expand All @@ -15,6 +16,7 @@ class Post_Card_Controller extends Abstract_Block_Controller {
public function __construct( array $args = [] ) {
parent::__construct( $args );
$this->set_post( $args['post_id'] ?? 0 );
$this->set_display_term_taxonomy( $args['taxonomy_slug'] ?? Category::NAME );

$this->layout = $this->attributes['layout'] ?? 'vertical';
$this->heading_level = $this->attributes['heading_level'] ?? 'h3';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

class Related_Posts_Controller extends Abstract_Block_Controller {

private const string LATEST_ITEMS_VALUE = '__latest__';

/**
* @var array <mixed>
*/
Expand All @@ -21,6 +23,9 @@ class Related_Posts_Controller extends Abstract_Block_Controller {
protected bool $has_automatic_selection;
protected int $posts_to_show;
protected string $block_layout;
protected string $taxonomy_slug;
protected string $current_post_type;
protected string $card_taxonomy_slug = '';
protected \WP_Query $query;

public function __construct( array $args = [] ) {
Expand All @@ -32,6 +37,8 @@ public function __construct( array $args = [] ) {
$this->chosen_posts = $this->attributes['chosenPosts'] ?? [];
$this->posts_to_show = absint( $this->attributes['postsToShow'] ?? 3 );
$this->block_layout = $this->attributes['layout'] ?? 'grid';
$this->current_post_type = get_post_type( $this->post_id ) ?: Post::NAME;
$this->taxonomy_slug = $this->get_effective_taxonomy_slug( $this->attributes['taxonomySlug'] ?? Category::NAME );

$this->block_classes .= " b-related-posts--layout-{$this->block_layout}";

Expand All @@ -47,16 +54,21 @@ public function get_query(): \WP_Query {
return $this->query;
}

public function get_card_taxonomy_slug(): string {
return $this->card_taxonomy_slug;
}

private function set_query_args(): void {
$this->query_args = [
'post_type' => Post::NAME,
'post_type' => $this->current_post_type,
'post_status' => 'publish',
];

if ( ! $this->has_automatic_selection ) {
$this->query_args = array_merge( $this->query_args, [
'post__in' => array_map( 'absint', wp_list_pluck( $this->chosen_posts, 'id' ) ),
'orderby' => 'post__in',
'post_type' => 'any',
'post__in' => array_map( 'absint', wp_list_pluck( $this->chosen_posts, 'id' ) ),
'orderby' => 'post__in',
Comment on lines +69 to +71
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

Manual selection queries don't set posts_per_page, so WordPress will apply the default posts-per-page limit (often 10) and can truncate the selected post__in list. Consider setting posts_per_page to count( $this->chosen_posts ) (or at least a large enough value) when post__in is used so all manually chosen posts render.

Suggested change
'post_type' => 'any',
'post__in' => array_map( 'absint', wp_list_pluck( $this->chosen_posts, 'id' ) ),
'orderby' => 'post__in',
'post_type' => 'any',
'post__in' => array_map( 'absint', wp_list_pluck( $this->chosen_posts, 'id' ) ),
'orderby' => 'post__in',
'posts_per_page' => count( $this->chosen_posts ),

Copilot uses AI. Check for mistakes.
] );

return;
Expand All @@ -67,7 +79,11 @@ private function set_query_args(): void {
'post__not_in' => [ $this->post_id ],
] );

$post_terms = get_the_terms( $this->post_id, Category::NAME );
if ( self::LATEST_ITEMS_VALUE === $this->taxonomy_slug || ! is_object_in_taxonomy( $this->current_post_type, $this->taxonomy_slug ) ) {
return;
}

$post_terms = get_the_terms( $this->post_id, $this->taxonomy_slug );

if ( empty( $post_terms ) || is_wp_error( $post_terms ) ) {
return;
Expand All @@ -76,14 +92,49 @@ private function set_query_args(): void {
$term_ids = wp_list_pluck( $post_terms, 'term_id' );

$this->query_args['tax_query'][] = [
'taxonomy' => Category::NAME,
'taxonomy' => $this->taxonomy_slug,
'field' => 'term_id',
'terms' => $term_ids,
];
}

private function set_query(): void {
$this->query = new \WP_Query( $this->query_args );

if ( $this->has_automatic_selection && ! $this->query->have_posts() && ! empty( $this->query_args['tax_query'] ) ) {
unset( $this->query_args['tax_query'] );
$this->query = new \WP_Query( $this->query_args );
}

$this->card_taxonomy_slug = ! empty( $this->query_args['tax_query'] ) ? $this->taxonomy_slug : '';
}

/**
* @return array<string, \WP_Taxonomy>
*/
private function get_available_taxonomies(): array {
return array_filter(
get_object_taxonomies( $this->current_post_type, 'objects' ),
static fn( \WP_Taxonomy $taxonomy ): bool => (bool) $taxonomy->show_in_rest
);
}

private function get_effective_taxonomy_slug( string $taxonomy_slug ): string {
if ( self::LATEST_ITEMS_VALUE === $taxonomy_slug ) {
return $taxonomy_slug;
}

$available_taxonomies = $this->get_available_taxonomies();

if ( is_object_in_taxonomy( $this->current_post_type, $taxonomy_slug ) ) {
return $taxonomy_slug;
}

if ( array_key_exists( Category::NAME, $available_taxonomies ) ) {
return Category::NAME;
}

return array_key_first( $available_taxonomies ) ?: '';
}

}
33 changes: 33 additions & 0 deletions wp-content/plugins/core/src/Components/Traits/Post_Data.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ trait Post_Data {
protected \WP_Post_Type|null $post_type_object = null;
protected int|false $image_id = false;
protected \WP_Term|null $primary_category = null;
protected \WP_Term|null $display_term = null;
protected string $post_title = '';
protected string $author_id = '0';
protected string $author = '';
Expand All @@ -31,6 +32,7 @@ public function set_post( mixed $post_id ): void {
$this->post_type_object = get_post_type_object( $this->post_type );
$this->image_id = get_post_thumbnail_id( $this->post_id );
$this->primary_category = $this->get_primary_term( $this->post_id, Category::NAME );
$this->display_term = $this->primary_category;
$this->post_title = get_the_title( $this->post_id );
$this->author_id = get_post_field( 'post_author', $this->post_id );
$this->date = get_the_date( 'M j, Y', $this->post_id );
Expand Down Expand Up @@ -64,6 +66,37 @@ public function get_primary_category_name(): string {
return $this->has_primary_category() ? $this->primary_category->name : '';
}

public function set_display_term_taxonomy( string $taxonomy ): void {
if ( null === $this->post_id || 0 === $this->post_id || '' === $taxonomy ) {
$this->display_term = null;

return;
}

if ( Category::NAME === $taxonomy ) {
$this->display_term = $this->primary_category;

return;
}

$terms = get_the_terms( $this->post_id, $taxonomy );
if ( $terms && ! is_wp_error( $terms ) ) {
$this->display_term = reset( $terms ) ?: null;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you please add some context here? How we can have reset( $terms ) empty?
get_the_terms returns WP_Error, false, array of WP_Terms. If we pass if statement we will have $terms that has at least 1 WP_Term element.


return;
}

$this->display_term = null;
Comment on lines +82 to +89
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

For non-category taxonomies, set_display_term_taxonomy() uses reset( $terms ) rather than the existing get_primary_term() helper (which already supports arbitrary taxonomies and respects Yoast/RankMath primary term settings). Consider using get_primary_term( $this->post_id, $taxonomy ) here to keep displayed terms consistent with the project's primary-term logic.

Suggested change
$terms = get_the_terms( $this->post_id, $taxonomy );
if ( $terms && ! is_wp_error( $terms ) ) {
$this->display_term = reset( $terms ) ?: null;
return;
}
$this->display_term = null;
$this->display_term = $this->get_primary_term( $this->post_id, $taxonomy );

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

+1 to this. get_primary_term already falls back to exactly what you have here, it's possible you don't need this entire function?

}

public function has_display_term(): bool {
return null !== $this->display_term;
}

public function get_display_term_name(): string {
return $this->has_display_term() ? $this->display_term->name : '';
}

public function get_post_title(): string {
return $this->post_title;
}
Expand Down
4 changes: 4 additions & 0 deletions wp-content/themes/core/blocks/tribe/related-posts/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
"type": "number",
"default": 3
},
"taxonomySlug": {
"type": "string",
"default": "category"
},
"layout": {
"type": "string",
"enum": [ "grid", "list" ],
Expand Down
Loading
Loading