WAIT: Custom Dynamic Tag {author_badge} - Inconsistent in Query Loops (Users) & Disappears After Filtering

Bricks Version: 1.12.4 / 2.0a

Hello Team,

I’m reporting an issue I’m facing with a custom dynamic tag I’ve created.

Issue Description:

I have implemented several custom dynamic tags in my project, and they generally work smoothly. However, one specific tag, {author_badge}, test demo is behaving inconsistently when used within a Bricks Query Loop and fails completely when filtering is applied to the loop.

The purpose of this tag is to display a badge (an icon) next to an author’s name within a Query Loop item, based on the count of published posts of a specific custom post type (‘property’) by that author in my exemple I set 10 .

The code registers the dynamic tag and uses the bricks/dynamic_data/render_content filter to replace the tag with HTML. The rendering logic attempts to determine the correct author ID based on whether the loop is a User Query Loop or a Post Query Loop.

I have spent considerable time (2h) trying to debug this, including reviewing the documentation and also using AI assistance tools , but I haven’t been able to pinpoint the exact cause. It’s puzzling because the tag works perfectly outside of any Query Loop context.

Expected Behavior:

The {author_badge} tag should consistently evaluate and display the badge for each author in the Query Loop whose ‘property’ count meets the defined threshold (currently 10). This should happen reliably on initial page load and persist correctly even after the Query Loop results are filtered (e.g., via Ajax).

Current Behavior (The Bug):

  1. On Initial Page Load/Refresh inside : When the page containing the Query Loop loads, the badge display for items within the loop is inconsistent. It sometimes appears for the wrong authors or fails to appear for authors who meet the criteria. Refreshing the page can sometimes alter which authors are incorrectly shown with or without the badge.
  2. After Filtering: When I use a filter element connected to the Query Loop, and the loop reloads with filtered results, the {author_badge} tag completely disappears from all loop items. It seems the bricks/dynamic_data/render_content filter is either not triggered or fails to correctly identify the loop item context during the filtered render.

Steps to Reproduce:

  1. Ensure you have a custom post type (e.g., ‘property’).
  2. Install the provided custom code (ideally in a snippet manager) to register the {author_badge} dynamic tag and handle its rendering.
  3. Create a Bricks template or page.
  4. Add a Query Loop element. Configure it to query > Users
  5. Inside the Query Loop item add a Basic Text element. Insert the {author_badge} dynamic tag, perhaps placed after the dynamic field for the Author Name.
  6. Add a filter element on the same page and connect it to filter the Query Loop.
  7. View the page on the frontend. Observe the inconsistent badge display before filtering.
  8. Use the filter element to filter the loop results. Observe that the {author_badge} tag is no longer present in any of the filtered loop items.

Code Used:

<?php
/**
 * Custom Bricks Builder Dynamic Data Tag: Author Badge based on Property Count.
 *
 * This code defines a dynamic tag {author_badge} for Bricks Builder.
 * It displays an icon badge next to an author's name if they have published
 * a certain threshold of 'property' custom post types (default: 10).
 *
 * Place this code in your child theme's functions.php or a custom plugin.
 */

if (!defined('ABSPATH')) {
    exit;
}

/**
 * Get the count of 'property' posts published by a specific author, with caching.
 */
if (!function_exists('get_property_count_by_author_id')) {
    function get_property_count_by_author_id($author_id) {
        if (!$author_id || !is_numeric($author_id)) {
            return 0;
        }

        $cache_key = "author_{$author_id}_property_count_property";
        $count = wp_cache_get($cache_key, 'user_posts_count');

        if ($count === false) {
            $count = count_user_posts($author_id, 'property', true);
            wp_cache_set($cache_key, $count, 'user_posts_count', HOUR_IN_SECONDS);
        }

        return (int) $count;
    }
}

/**
 * Generate HTML for the author badge based on property count threshold.
 */
if (!function_exists('get_author_badge_html')) {
    function get_author_badge_html($author_id, $threshold = 10) {
        if (!$author_id || !is_numeric($author_id)) {
            return '';
        }

        $property_count = get_property_count_by_author_id($author_id);

        if ($property_count >= $threshold) {
            $badge_icon_html = '<i class="ion-md-checkmark-circle-outline brxe-icon"></i>';
            $title_text = sprintf(esc_attr__('Author has %d+ properties', 'your-textdomain'), $threshold);
            $badge_html = '<span class="author-badge" title="' . esc_attr($title_text) . '">' . $badge_icon_html . '</span>';

            return $badge_html;
        }

        return '';
    }
}

/**
 * Register the custom author badge dynamic data tag with Bricks Builder.
 */
add_filter('bricks/dynamic_tags_list', function($tags) {
    $tags[] = [
        'name'  => '{author_badge}',
        'label' => esc_html__('Author Badge (10+ Properties)', 'your-textdomain'),
        'group' => esc_html__('Author Data', 'your-textdomain'),
    ];

    return $tags;
});

/**
 * Handle rendering the author badge tag when it appears within element content.
 */
add_filter('bricks/dynamic_data/render_content', function($content, $post, $context) {
    if (stripos($content, '{author_badge}') === false) {
        return $content;
    }

    $author_id = 0;

    $loop_object_id  = apply_filters('bricks/query/loop_object_id', 0);
    $loop_object_type = apply_filters('bricks/query/loop_object_type', '');

    if ($loop_object_id && ($loop_object_type === 'user' || $loop_object_type === 'post')) {
        if ($loop_object_type === 'user') {
            $author_id = $loop_object_id;
        } elseif ($loop_object_type === 'post') {
            $current_loop_post = get_post($loop_object_id);
            if ($current_loop_post instanceof WP_Post) {
                $author_id = $current_loop_post->post_author;
            }
        }
    }

    if ($author_id === 0) {
        if ($post instanceof WP_Post) {
             $author_id = $post->post_author;
        } else {
             $queried_object = get_queried_object();
             if ($queried_object && $queried_object instanceof WP_User) {
                 $author_id = $queried_object->ID;
             } elseif (get_the_author_meta('ID')) {
                 $author_id = get_the_author_meta('ID');
             }
        }
    }

    if ($author_id <= 0) {
        return str_ireplace('{author_badge}', '', $content);
    }

    $badge_html = get_author_badge_html($author_id);
    $content = str_ireplace('{author_badge}', $badge_html, $content);

    return $content;

}, 10, 3);

/**
 * Add basic CSS for the author badge container and the icon size.
 */
add_action('wp_head', function() {
    ?>
    <style>
        .author-badge {
            display: inline-block;
            vertical-align: middle;
            margin-left: 5px;
        }
        .author-badge i.brxe-icon {
            font-size: 16px;
        }
    </style>
    <?php
});

// This code should be placed in functions.php of a child theme or in a custom plugin.
// Avoid placing it in the Bricks editor or code snippet plugins for reliable execution.
?>

It seems like the issue might be related to how Bricks handles the dynamic data rendering context within Query Loops, especially during Ajax-based filtering. Any guidance or potential fixes would be greatly appreciated.

Thank you for your time and support!


//overview of post counts and target authors

single agent page

//preview in archive authors and or any region of the site with this tag accompanied by its author

agents page
//preview how the tag reacts in a users loop

Hi @neosan ,

I just moved your thread to Developers category because the issue is only happening when using your custom dynamic tag instead of those provided by Bricks.

I haven’t test your code, but I don’t really understand this.

$loop_object_id  = apply_filters('bricks/query/loop_object_id', 0);
$loop_object_type = apply_filters('bricks/query/loop_object_type', '');

Instead of firing the filters, please try to do it like this

$is_any_looping = \Bricks\Query::is_any_looping();

$loop_object_id  = \Bricks\Query::get_loop_object_id( $is_any_looping );
$loop_object_type = \Bricks\Query::get_loop_object_type( $is_any_looping );

Then try to error_log out the $loop_object_id, $loop_object_type and final $author_id. See if you can get the correct data you want from the filter AJAX endpoints + PHP page load or not.

Regards,
Jenn

Hello @itchycode
Thank you for your response. I initially didn’t reply because I needed more information to fully understand the issue and provide an explanation. Now that I’ve identified where the problem was, I wanted to share my experience as a reference for what not to do when working with dynamic tags.

Since this was my first attempt at creating a custom dynamic tag, I may have gone a little overboard. As a result, Bricks had trouble interpreting the context correctly, leading to errors and inconsistent behavior in different scenarios.

here the initial full code


/**************************************
 * 1. CONSTANT DEFINITIONS
 **************************************/
define('SCF_FIELD_AGENT_SPECIALTIES', 'agent_specialties');
define('SCF_FIELD_AGENT_SERVICE_AREAS', 'agent_service_areas');

/**************************************
 * 2. REGISTER DYNAMIC TAGS
 **************************************/
add_filter('bricks/dynamic_tags_list', function ($tags) {
  // Author Stats
  $tags[] = [
    'name'  => '{author_property_count}',
    'label' => esc_html__('Property Count', 'textdomain'),
    'group' => esc_html__('Author Stats', 'textdomain'),
  ];

  $tags[] = [
    'name'  => '{author_availability_dot}',
    'label' => esc_html__('Availability Dot', 'textdomain'),
    'group' => esc_html__('Author Stats', 'textdomain'),
  ];

  $tags[] = [
    'name'  => '{author_badge}',
    'label' => esc_html__('Author Badge', 'textdomain'),
    'group' => esc_html__('Author Stats', 'textdomain'),
  ];

  // Author Data
  $tags[] = [
    'name'  => '{author_taxonomy}',
    'label' => esc_html__('Taxonomy List', 'textdomain'),
    'group' => esc_html__('Author Data', 'textdomain'),
  ];

  return $tags;
});

/**************************************
 * 3. CONTEXT-AWARE AUTHOR ID FUNCTION
 **************************************/
function get_current_author_id() {
  // 1. Check query loop context
  $author_id = (int) bricks_render_dynamic_data('{wp_user_id}');
  if ($author_id) return $author_id;

  // 2. Check author archive
  if (is_author()) return get_queried_object_id();

  // 3. Check current post author
  global $post;
  if (!empty($post->post_author)) return (int) $post->post_author;

  // 4. Global fallback
  global $authordata;
  return $authordata->ID ?? 0;
}

/**************************************
 * 4. RENDER FUNCTIONS
 **************************************/

// AUTHOR PROPERTY COUNT
add_filter('bricks/dynamic_data/render_tag', 'render_author_property_count', 10, 3);
function render_author_property_count($tag, $post, $context) {
  if ($tag !== '{author_property_count}') return $tag;
  return number_format_i18n(count_user_posts(get_current_author_id(), 'property', true));
}

// AUTHOR AVAILABILITY DOT
add_filter('bricks/dynamic_data/render_tag', 'render_availability_dot', 10, 3);
function render_availability_dot($tag, $post, $context) {
  if ($tag !== '{author_availability_dot}') return $tag;

  $author_id = get_current_author_id();
  $latest = get_posts([
    'post_type'      => 'property',
    'author'         => $author_id,
    'posts_per_page' => 1,
    'fields'         => 'ids',
    'orderby'        => 'date',
    'order'          => 'DESC'
  ]);

  $days = $latest ? (time() - get_post_time('U', false, $latest[0])) / DAY_IN_SECONDS : 0;
  
  $status = 'grey';
  if ($days > 0) {
    if ($days <= 7) $status = 'green';
    elseif ($days <= 30) $status = 'orange';
  }

  return sprintf('<span class="availability-dot availability-%s"></span>', esc_attr($status));
}

// AUTHOR BADGE
add_filter('bricks/dynamic_data/render_tag', 'render_author_badge', 10, 3);
function render_author_badge($tag, $post, $context) {
  if (strpos($tag, '{author_badge') === false) return $tag;

  $threshold = 3;
  if (preg_match('/{author_badge:(\d+)}/', $tag, $matches)) {
    $threshold = max(1, (int) ($matches[1] ?? 3));
  }

  $count = count_user_posts(get_current_author_id(), 'property', true);
  return $count > $threshold 
    ? '<i class="ion-ios-checkmark-circle bricks-author-badge"></i>'
    : '';
}

// AUTHOR TAXONOMY LIST
add_filter('bricks/dynamic_data/render_tag', 'render_author_taxonomy_list', 10, 3);
function render_author_taxonomy_list($tag, $post, $context) {
  if (strpos($tag, '{author_taxonomy') === false) return $tag;

  $modifiers = explode(':', trim($tag, '{}'));
  array_shift($modifiers);
  
  $field = $modifiers[0] ?? '';
  $format = $modifiers[1] ?? 'plain';

  if (!in_array($field, [SCF_FIELD_AGENT_SPECIALTIES, SCF_FIELD_AGENT_SERVICE_AREAS])) {
    return '';
  }

  $terms = get_field($field, 'user_' . get_current_author_id());
  if (empty($terms) || !is_array($terms)) return '';

  $output = [];
  foreach ($terms as $term) {
    $term = is_numeric($term) ? get_term($term) : $term;
    if (!$term || is_wp_error($term)) continue;

    if ($format === 'link') {
      $link = get_term_link($term);
      $output[] = !is_wp_error($link) 
        ? sprintf('<a href="%s">%s</a>', esc_url($link), esc_html($term->name))
        : esc_html($term->name);
    } else {
      $output[] = esc_html($term->name);
    }
  }

  return implode(', ', $output);
}

/**************************************
 * 5. SIMPLE CONTENT RENDERING
 **************************************/
add_filter('bricks/dynamic_data/render_content', 'render_author_content', 10, 3);
add_filter('bricks/frontend/render_data', 'render_author_content', 10, 2);
function render_author_content($content, $post, $context = 'text') {
  // Property Count
  if (strpos($content, '{author_property_count}') !== false) {
    $value = render_author_property_count('{author_property_count}', $post, $context);
    $content = str_replace('{author_property_count}', $value, $content);
  }

  // Availability Dot
  if (strpos($content, '{author_availability_dot}') !== false) {
    $value = render_availability_dot('{author_availability_dot}', $post, $context);
    $content = str_replace('{author_availability_dot}', $value, $content);
  }

  // Author Badge
  if (strpos($content, '{author_badge') !== false) {
    preg_match_all('/{author_badge(:\d+)?}/', $content, $matches);
    foreach ($matches[0] as $tag) {
      $value = render_author_badge($tag, $post, $context);
      $content = str_replace($tag, $value, $content);
    }
  }

  // Taxonomy List
  if (strpos($content, '{author_taxonomy') !== false) {
    preg_match_all('/{author_taxonomy(:[a-z_]+(:link|:plain)?)?}/', $content, $matches);
    foreach ($matches[0] as $tag) {
      $value = render_author_taxonomy_list($tag, $post, $context);
      $content = str_replace($tag, $value, $content);
    }
  }

  return $content;
}

/**************************************
 * 6. CSS STYLES
 **************************************/
add_action('wp_head', function() {
  echo '<style>
  .availability-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 100%;
    margin: 0 3px;
    vertical-align: middle;
  }
  .availability-green { background: #4CAF50; }
  .availability-orange { background: #FF9800; }
  .availability-grey { background: #9E9E9E; }

  .bricks-author-badge {
    font-size: 16px;
    color: black;
    vertical-align: middle;
    margin-left: 3px;
  }
  </style>';
});

The Initial Approach

Originally, my idea was to use dynamic tags to display specific information visually. Here’s what I attempted:

  • {author_property_count} – Used to list the number of posts an author has under the cpt property.
  • {author_badge} – Displays an icon if the author has a certain number of posts (e.g., 100 posts).
  • {author_availability} – Shows whether the author is considered “online” or “offline” based on whether they have posted within a specified time frame.

Additionally, I used the Taxonomy List to display taxonomies attached to user profiles via SCF (ACF). However, if you’re like me and attempt to attach taxonomies to users, you’ll quickly notice that only the taxonomy IDs (e.g., 20, 12, 15, 33) are displayed instead of the actual titles (e.g., Title1, Title2, Title3). This happens regardless of whether the return value is set as a term object or an ID.

dynamic tags


The Issue

Although my setup initially seemed to work, I quickly noticed inconsistencies in the display. After investigating, I realized that the issue stemmed from this part

/**************************************
 * 3. CONTEXT-AWARE AUTHOR ID FUNCTION
 **************************************/
function get_current_author_id() {
  // 1. Check query loop context
  $author_id = (int) bricks_render_dynamic_data('{wp_user_id}');
  if ($author_id) return $author_id;

  // 2. Check author archive
  if (is_author()) return get_queried_object_id();

  // 3. Check current post author
  global $post;
  if (!empty($post->post_author)) return (int) $post->post_author;

  // 4. Global fallback
  global $authordata;
  return $authordata->ID ?? 0;
}

The Solution ?

I decided to shift my approach and replaced dynamic tags with author_meta values instead:

  • {author_meta:property_count} (alternative to {author_property_count})
/**
 * Adds custom author meta for CPT 'property' post count using WordPress core functions.
 * Works with Bricks Builder's {author_meta:property_count} dynamic tag.
 * 
 * - Automatically updates when posts are published/unpublished/deleted
 * - Counts only published posts
 * - Requires zero maintenance
 * - Uses optimized database queries
 */
add_filter('get_the_author_property_count', 'custom_author_property_count', 10, 3);

function custom_author_property_count($value, $user_id, $original_user_id) {
    // Use original user ID if available, otherwise fall back to current author
    $target_user_id = $original_user_id ?: $user_id;
    
    // Verify valid user ID
    if (!absint($target_user_id)) return 0;

    // Count published posts for 'property' CPT using core WordPress function
    return count_user_posts(
        absint($target_user_id),  // User ID
        'property',               // Custom Post Type
        true                      // Count only published posts
    );
}

for

  • {author_meta:agent_specialties}
  • {author_meta:agent_service_area}

this code

/**
 * BRICKS-INTEGRATED TAXONOMY TERMS FOR AUTHOR META
 */
add_filter('get_the_author_agent_specialties', 'integrated_bricks_author_terms', 10, 3);
add_filter('get_the_author_agent_service_areas', 'integrated_bricks_author_terms', 10, 3);

function integrated_bricks_author_terms($value, $user_id, $original_meta_key = null) {
    static $taxonomy_map = [
        'agent_specialties' => 'specialties',
        'agent_service_areas' => 'service_areas',
    ];

    $meta_key = str_replace('get_the_author_', '', current_filter());
    
    if (!isset($taxonomy_map[$meta_key])) {
        return $value;
    }

    $taxonomy = $taxonomy_map[$meta_key];
    $has_acf = function_exists('get_field');
    
    $terms = $has_acf 
        ? get_field($meta_key, "user_{$user_id}") 
        : get_user_meta($user_id, $meta_key, true);

    if (empty($terms)) {
        return '';
    }

    // Original plain detection method
    $is_plain = isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], ':plain') !== false;

    $output = [];
    foreach ((array)$terms as $term_data) {
        // Original term processing logic
        if (is_numeric($term_data)) {
            $term = get_term($term_data, $taxonomy);
        } elseif (is_object($term_data) && isset($term_data->term_id)) {
            $term = $term_data;
        } elseif (is_array($term_data) && isset($term_data['term_id'])) {
            $term = get_term($term_data['term_id'], $taxonomy);
        } else {
            continue;
        }

        if (!$term || is_wp_error($term)) {
            continue;
        }

        if ($is_plain) {
            $output[] = esc_html($term->name);
        } else {
            $term_link = get_term_link($term);
            $output[] = !is_wp_error($term_link)
                ? sprintf('<a href="%s" class="term-link term-%s">%s</a>',
                    esc_url($term_link),
                    esc_attr($term->slug),
                    esc_html($term->name)
                  )
                : esc_html($term->name);
        }
    }

    return implode(', ', $output);
}

/**
 * FALLBACK PROCESSING
 */
add_filter('bricks/dynamic_data/render', 'process_author_terms_fallback', 20, 3);

function process_author_terms_fallback($content, $tag, $post_id) {
    // Original tag detection logic
    if (strpos($tag, 'author_meta:agent_specialties') !== 0 && 
        strpos($tag, 'author_meta:agent_service_areas') !== 0) {
        return $content;
    }

    if (!empty($content)) {
        return $content;
    }

    // Original fallback regex pattern
    if (preg_match('/@fallback:\'([^\']+)\'/', $tag, $matches)) {
        return !empty($matches[1]) ? $matches[1] : '';
    }

    return $content;
}

I also removed the previous dynamic tag implementation. With this adjustment, all data is now correctly displayed across the cards, whether the user is logged in or logged out.

Final Thoughts

It seems that Bricks does not support taxonomies via author_meta natively. If this could be addressed, either by implementing a fix or including official support within Bricks, it would allow me to eliminate the extra code needed to work around this issue.

Hopefully, this helps others avoid similar challenges!
If you have any other good news or a better idea, don’t hesitate, I’m interested.