Match dynamic tag to category in query loop

I’ve been trying dozens of different things, but can’t quite figure this out. I have four post categories: art, cinema, literature, music. Posts typically only have one category, but sometimes they have multiple categories. Using ACF, I created a Repeater field, “list_post_name,” a text subfield with the field name, “postname,” and a “Select” subfield with the field name, “category.” Each post has at least one subject name (“postname”), but sometimes multiple names that each belong to different categories. (e.g. postname: Charles Mingus; category: music). I created dynamic tags that output a postname and its associated category: {post_name} and {post_name_category}. The {post_name_category} has the same values as the post categories: art, cinema, literature, music.

My issue is that I can’t figure out how to only output the post name and post name category if it matches the category called by the query loop. Right now, when my query loop calls for the “music” category and a post also has a post name in a different category, it’ll show the name that isn’t being called in the query loop. I don’t know if my approach is flawed or if there’s a much more simple way of doing this. I’ve tried so many different methods and refining in different ways and I feel like I can’t see the forest for the trees anymore. I feel like there must be some incredibly simple solution to this, but I’m exhausted at this point.

This is my dynamic tag code:

/** Code to get post name from ACF */

// Register the custom dynamic tags
add_filter('bricks/dynamic_tags_list', 'add_custom_dynamic_tags');
function add_custom_dynamic_tags($tags) {
    $custom_tags = [
        [
            'name' => '{post_name}',
            'label' => 'Post Name',
            'group' => 'Custom',
        ],
        [
            'name' => '{post_name_category}',
            'label' => 'Post Name Category',
            'group' => 'Custom',
        ],
    ];
    return array_merge($tags, $custom_tags);
}

// Render the custom dynamic tags
add_filter('bricks/dynamic_data/render_tag', 'render_custom_dynamic_tags', 10, 3);
function render_custom_dynamic_tags($tag, $post, $context) {
    switch ($tag) {
        case 'post_name':
            return render_post_name_tag($post);
        case 'post_name_category':
            return render_post_name_category_tag();
        default:
            return $tag;
    }
}

function render_post_name_tag($post) {
    $post_id = $post->ID;
    $post_names = get_field('list_post_name', $post_id);
    
    if (!$post_names || !is_array($post_names) || empty($post_names)) {
        return 'No names available';
    }

    global $entry;
    $entry = $post_names[array_rand($post_names)];
    
    return $entry['postname'] ?? 'Name not found';
}

function render_post_name_category_tag() {
    global $entry;
    return $entry['category'] ?? 'Category not found';
}

// Handle the custom dynamic tags in content
add_filter('bricks/dynamic_data/render_content', 'render_custom_content', 10, 3);
add_filter('bricks/frontend/render_data', 'render_custom_content', 10, 2);
function render_custom_content($content, $post, $context = 'text') {
    $tags = [
        '{post_name}' => function() use ($post) {
            $name = render_post_name_tag($post);
            $category = render_post_name_category_tag();
            return "<span class=\"post-name {$category}\">{$name}</span>";
        },
        '{post_name_category}' => function() {
            $category = render_post_name_category_tag();
            return "<span class=\"post-name-category {$category}\">{$category}</span>";
        },
    ];

    foreach ($tags as $tag => $replacement) {
        if (strpos($content, $tag) !== false) {
            $content = str_replace($tag, $replacement(), $content);
        }
    }

    return $content;
}

Can I be a pain in the arse, and ask you to forget ‘solutions’ or problems… and instead tell us what you wish to achieve.
It sounds like you could use a relationship field - but the actual outcome you want, what these two things (the posts and the things in the repeater field) are - and how they are related - isn’t that clear.

1 Like

Thank you so much for your help.

Here’s an example. On my front page I have four sliders. Each slider is a query loop for a specific category. Art slider, Cinema slider, Literature Slider, Music slider. My query loop setup is a Taxonomy Query: Taxonomy: Categories (Posts); field: slug; term: music; compare: IN.

For some posts, I assigned multiple categories. Every post has at least one subject name, but sometimes more, and each subject also has its own category that matches the post categories. The setup in ACF is: repeater “list post name”, subfield “text”, “postname”; subfield “select”, “category.”

For instance, a post that’s assigned the art and music category has two names: postname: Andy Warhol, category: art; postname: David Bowie, category: music.

I want to use the “postname” in a heading, so I created a dynamic tag, {post_name}. In my Music slider, I only want to see a “postname” with a category that matches the category from the query loop, music. However, because the post has two categories and two postnames each with their own category, when I use the {post_name} dynamic tag in the Music slider, it’s displaying a postname with a category that isn’t in the query loop. So, using this example, for the Music slider query loop, I’m also seeing “Andy Warhol” appear when I only want to see “David Bowie.”

Here’s an example from the front end. You can see that the wrong categories (using the custom dynamic tag, {post_name_category} which matches the {post_name}) and names are appearing there, because those names appear in posts that also have postnames with the “music” category and it isn’t filtering it properly.

I’ve written a variety of javascript, added more php, different functions, different wp queries, tried to figure out a “Condition,” and I can’t get it figured out. Right now, the only code I have that works is the dynamic data tag code I posted above. I put that code in my functions.php for the Bricks child theme. One thing I consistently can’t get is the category name called by the query loop to be used in the php in my functions.php.

This is my first time building a Wordpress website and my first time coding in more than a decade, so I’m sort of learning as I go. But this is my third day trying to figure this out and I think I’ve just lost the plot. I keep trying increasingly convoluted ways to figure this out, adding more and more, when I think the answer might be something very simple.

I considered the Relationship ACF field, but I assumed the Repeater field would be better for this use.

Forgive me, but you haven’t done as I asked. You’ve presented me with method and issue. But not desired outcome. And it still isn’t clear what you want.
You want andy warhol and bowie related. Does this mean you want to be able to relate many things? e.g. Warhol and Bowie, Warhol and Lou Reed, Warhol and (some authors, some films)?
Equally relating writers to artists and musicians… and all that jazz?
If so - how do you want to display this on the Front page. The screenshot suggests a simple grid of four, showing Art, Cinema, Lit, Music . Or is it a grid of 4 ART - but click Music and it fetches 4 Music items etc.?
Once we understand this - why do you need to fetch related items - and where should they display.

because…

If you have categories for posts, you could use a simply Bricks query loop to fetch posts - add a FILTER which gives the Art, Music, Lit, Film menu (to show only one type) - all with no code or anything.

If you use the ACF relationship field, Bricks allows you to directly query that and show items.

If you want to show type (e.g. Art) you can just use the dynamic tag {post_terms}. Show the title with {post_title}, any ACF filed with the {acf_field_name}

I’m pretty sure what you want to do is easy, but (apologies) you simply don’t understand how Wordpress, posts, terms ACF etc work? That’s why I want to strip away everything except a plain language description of desired outcome. Then we can see how to get there.

Also, it’s late here… so my reply may be tomorrow…

Thanks again for helping. I’m still doing my best to figure this out and I tremendously appreciate you taking your time to help with this. I really hope this is as easy as you say.

Here’s a screenshot of the ACF repeater field for one of the posts:

The desired outcome is to filter the postname and category in the ACF repeater to match the query loop. So in the example above, I have three postnames each with their own category and I selected three categories for the post. You’re right, in the screenshot, I have tabs that the user can select from and it returns the grid with a query loop for that specific category. So when the user clicks, “Music,” they should only see posts from the music category and also only see a “postname” that, in ACF, has the “music” category. However, it is showing postnames from different categories.

So when the user clicks, “Art,” or anytime I use the “Art” category in a query loop, I should only see a postname that has the “art” category selected in ACF in the repeater field.

This is a screenshot of how it should ultimately look, with each “postname” matching the query loop category.

You’re right, I’m certain I’m missing something incredibly simple in terms of implementing this. Thanks again! I can’t tell you how much I appreciate you taking a look at this.

The final piece of the puzzle for me is: why are you making ‘postname’ and the ACF category? Are you just wanting to list these, in which case why… or should they lead to an actual post (e.g. ‘David Bowie’-> with an article about him)?
If they should lead to a post, you are making it very complicated. You should just create posts, and then have an ACF field where you can select those posts.

But better - you can create a ‘related posts’ element to show further posts of X category. (lots on related posts here: Query Loop – Bricks Academy).
None of what you are doing so far requires code. You can just build the query to get posts of X type (e.g. Events) and related posts… the POSTS loop element even has a filter built in… or use a slider with a query and the Filter element.

To clarify. If you’re building the ACF repeater to enable filtering, it’s redundant, because categories themselves enable filtering. Just fetch posts (not categories) and use a filter to allow only posts of X category.
To show the title, use {post_title} in the query loop. To show the category use {post_terms_category} or any of these dozens of options: Dynamic Data – Bricks Academy

From what I can see, you need to understand the wealth of fields and terms you can access because it’s WP and Bricks - you don’t need to build all this custom stuff. Unless I’m totally misunderstanding the result you want.

I created the dynamic tag and subfield, “post name,” because I want to dynamically show one of the subject names in a post and have it match the query loop. The “post name” subfield is just a list of the names that come up in the post which is why I used the repeater field and created the dynamic tag {post_name}. It has nothing to do with the post title or any other default fields. In this example, it’s important for the dynamic tag, {post_name}, to match with the post category, but the subject names come up throughout the site in various ways.

I would absolutely need to create a custom dynamic tag. It seems you’re unfamiliar with how to implement this: Create Your Own Dynamic Data Tag – Bricks Academy

I know how query loops work which is why I explained exactly how I am using them. That’s what the screenshots above show. That’s the issue: when a query loop calls for “music,” the subject name, using the dynamic tag, {post_name}, has to match the category query loop.

I know how dynamic data works which is why I created a custom dynamic data tag. What you’re saying doesn’t apply to posts with multiple categories. If I use {post_term_category} it returns a list of all categories in the post, not just the one being called by the query loop. None of those “dozens of options” do what I specified repeatedly that I’d like to do.

I absolutely do need to write code to create a dynamic data tag. You have not suggested even one method of implementing what I’ve described repeatedly.

Regardless, I was finally able to be able to filter the {post_name} based on the category called in the query loop by changing the dynamic data tag function code. I’m leaving the full code for anyone that needs similar implementation in the future. The functionality of the code is pretty versatile.

// Register the custom dynamic tags
add_filter('bricks/dynamic_tags_list', 'add_custom_dynamic_tags');
function add_custom_dynamic_tags($tags) {
    $categories = ['art', 'cinema', 'literature', 'music'];
    $custom_tags = [];

    foreach ($categories as $category) {
        $custom_tags[] = [
            'name' => "{post_name:$category}",
            'label' => "Post Name ($category)",
            'group' => 'Custom',
        ];
    }

    $custom_tags[] = [
        'name' => '{post_name}',
        'label' => 'Random Post Name',
        'group' => 'Custom',
    ];

    $custom_tags[] = [
        'name' => '{post_name_categories}',
        'label' => 'Post Name Categories',
        'group' => 'Custom',
    ];

    return array_merge($tags, $custom_tags);
}

// Render the custom dynamic tags
add_filter('bricks/dynamic_data/render_tag', 'render_custom_dynamic_tags', 10, 3);
function render_custom_dynamic_tags($tag, $post, $context) {
    if (strpos($tag, 'post_name:') === 0) {
        $category = substr($tag, 10);
        return render_post_name_tag($post, $category);
    } elseif ($tag === 'post_name') {
        return render_post_name_tag($post);
    } elseif ($tag === 'post_name_categories') {
        return render_post_name_categories_tag($post);
    }

    return $tag;
}

function get_random_entry($post_id, $post_names, $category = null) {
    if (!$post_names || !is_array($post_names) || empty($post_names)) {
        return null;
    }

    if ($category) {
        $filtered_names = array_filter($post_names, function($entry) use ($category) {
            $entry_categories = explode(',', $entry['category']);
            return in_array(strtolower($category), array_map('strtolower', $entry_categories));
        });
        
        if (empty($filtered_names)) {
            return null;
        }
        
        $entry = $filtered_names[array_rand($filtered_names)];
    } else {
        $entry = $post_names[array_rand($post_names)];
    }

    return $entry;
}

function render_post_name_tag($post, $category = null) {
    $post_id = $post->ID;
    $post_names = get_field('list_post_name', $post_id);

    $entry = get_random_entry($post_id, $post_names, $category);

    if (!$entry) {
        $message = $category ? "No matching names found for category: $category" : "No names available";
        return $message;
    }
    
    // Store the selected entry for {post_name_categories}
    $GLOBALS['last_selected_entry_' . $post_id] = $entry;
    
    return $entry['postname'] ?? 'Name not found';
}

function render_post_name_categories_tag($post) {
    $post_id = $post->ID;
    
    // Use the last selected entry if available
    if (isset($GLOBALS['last_selected_entry_' . $post_id])) {
        $entry = $GLOBALS['last_selected_entry_' . $post_id];
    } else {
        // Fallback to random selection if no entry was selected before
        $post_names = get_field('list_post_name', $post_id);
        $entry = get_random_entry($post_id, $post_names);
    }

    if (!$entry) {
        return 'No categories available';
    }

    $categories = $entry['category'] ?? 'Categories not found';
    return ucwords(str_replace(',', ', ', $categories));
}

// Handle the custom dynamic tags in content
add_filter('bricks/dynamic_data/render_content', 'render_custom_content', 10, 3);
add_filter('bricks/frontend/render_data', 'render_custom_content', 10, 2);
function render_custom_content($content, $post, $context = 'text') {
    // First, process all {post_name:category} tags
    $pattern = '/{post_name(?::(\w+))?}/';
    $content = preg_replace_callback($pattern, function($matches) use ($post) {
        return render_post_name_tag($post, $matches[1] ?? null);
    }, $content);

    // Then, process {post_name_categories} tags
    if (strpos($content, '{post_name_categories}') !== false) {
        $categories = render_post_name_categories_tag($post);
        $content = str_replace('{post_name_categories}', $categories, $content);
    }

    return $content;
}

// Clear the global variables after rendering the entire post
add_action('bricks/frontend/after_render_content', 'clear_post_name_globals');
function clear_post_name_globals() {
    foreach ($GLOBALS as $key => $value) {
        if (strpos($key, 'last_selected_entry_') === 0) {
            unset($GLOBALS[$key]);
        }
    }
}
1 Like

Well, I’m glad you got there. Apologies… this: “The “post name” subfield is just a list of the names that come up in the post which is why I used the repeater field and created the dynamic tag {post_name}.”
is the first time I’ve properly understood what you actually want! Probably my tired brain.
I wouldn’t have used dynamic tags, probably because I’m too lazy and dense… but just looped the repeater and if repeater-field={term_name} the thing… we all do things our own way.
Congrats on cracking it.