Add custom "dynamic data" tag with value not of type "string", so for example array

Hi,
I’m working on a client project that requires a CPT “tour”.
The “tour” CPT supports the native “Featured Image”.
The “tour” CPT as also an ACF Gallery to add other images.

Now in templates I want to render a “Bricks Carousel” that contains all tour image, so featured + acf gallery images.

- 1 - My first attempt was to create a new “Query Loop” Object Type , but the Carousel did not recognize it (other elements successfully recognized it) , so i moved on.
Further details of this attempt will be included on a later comment of this post becuase the main scope is not about this.

- 2 - My second attempt was to create a “Dynamic Data” tag and use that in Carousel source > Media > Dynamic Data.
Following the official docs i made this script (script #1 ) that doesn’t work and triggers an error.
Then , after a little debug session i modified the code like so (script #2).
Now it works, but becuase is not official I would like to have a feedback on this from core devs, to ensure that I will ship a production-ready solution.

Details about the error are in the code comments, but mainly:

  • the carousel requires an array of integers (array of attachment id).
  • the docs sugget to uses bricks/dynamic_data/render_tag with callback priority 10 (as in script#1) , but in this case your callback cannot returns an array of things, only a string, due to the next filters callback that expects a string as input.
  • If you instead use priority 11 (as in script#2 )you can return other types, but you must add “curly braces” to the “check if this is my dynamic tag” part of the callback

Which is the production ready path i should follow ???

Thanks.


script #1


// Add a new "Dynamic Data" selectable in the dropdown
add_filter('bricks/dynamic_tags_list', function ($tags) {
  // Ensure your tag is unique (best to prefix it)
  $tags[] = [
    'name'  => '{bc__tour__all_images}',
    'label' => 'BC - Tour - All Images (Featured + ACF Gallery)',
    'group' => 'BC',
  ];

  return $tags;
});

// Replace Dynamic Data Tag with real value
// TODO: How this hook is different to "bricks/dynamic_data/render_content" ???
// TODO: How this hook is different to "bricks/frontend/render_data" ???
add_filter(
  'bricks/dynamic_data/render_tag',
  function ($tag, $post, $context = 'text') {
    // $tag is the tag name without the curly braces
    //
    // NOTE: 👈
    // THIS CALLBACK IS INVOKED WITH PRIORITY 10, AS IN THE BRICKS DOCS,
    // BUT IT WILL NOT WORK BECAUSE BRICKS COMPLAINS THAT THE RETURNED VALUE
    // MUST BE STRING, AND HERE WE RETURNS AN ARRAY OF INTEGERS
    if ($tag !== 'bc__tour__all_images') {
      return $tag;
    }

    // Do your custom logic here, you should define run_my_dd_tag_logic() function
    $my_value = get_value("UNRESOLVED", $post, $context);
    if ($my_value === "UNRESOLVED") return $tag;

    return $my_value;
  },
  10, // 👈
  3
);

// Replace Dynamic Data Tag with real value
// TODO: How this hook is different to "bricks/dynamic_data/render_tag" ???
add_filter('bricks/dynamic_data/render_content', 'render_my_tag', 10, 3);
add_filter('bricks/frontend/render_data', 'render_my_tag', 10, 2);
function render_my_tag($content, $post, $context = 'text') {

  // $content might consists of HTML and other dynamic tags
  // Only look for dynamic tag {my_dd_tag}
  if (strpos($content, '{bc__tour__all_images}') === false) {
    return $content;
  }

  return $content;

  // Do your custom logic here, you should define run_my_dd_tag_logic() function
  $my_value = get_value("UNRESOLVED", $post, $context);
  if ($my_value === "UNRESOLVED") return $content;

  // Replace the tag with the value you want to display
  $content = str_replace('{bc__tour__all_images}', $my_value, $content);

  return $content;
}

/**
 * Get all images of a "tour" CPT.
 * Returns an array of "attachment ids" ( [ 24, 56, 4634, ... ] ).
 * The array contains the "featured_image" followed by the ACF Gallery items 
 * of the ACF Field named "image_gallery".
 *
 * @param string $fallback
 * @param \WP_Post $post
 * @param "text"|"image"|"media" $context
 * @return int[]
 */
function get_value($fallback, $post, $context) {

  // we handle only "image" context
  if ($context !== 'image') return $fallback;

  // initialize an array that will contains "attachment_id" of all images
  $output_images_ids = [];

  // get featured image from "tour"
  // and also "acf gallery images" of the "tour"
  $featured_image_id = get_post_thumbnail_id($post);
  $acf_gallery = \get_field("image_gallery", $post->ID);

  // populate the output array 
  if ($featured_image_id) $output_images_ids[] = $featured_image_id;
  foreach ($acf_gallery as $gallery_item_index => $gallery_item) {
    if (is_int($gallery_item)) {
      $output_images_ids[] = $gallery_item;
      continue;
    }
    if (is_array($gallery_item)) {
      $output_images_ids[] = $gallery_item['id'];
      continue;
    }
  }

  return $output_images_ids;
}


script #2

// Add a new "Dynamic Data" selectable in the dropdown
add_filter('bricks/dynamic_tags_list', function ($tags) {
  // Ensure your tag is unique (best to prefix it)
  $tags[] = [
    'name'  => '{bc__tour__all_images}',
    'label' => 'BC - Tour - All Images (Featured + ACF Gallery)',
    'group' => 'BC',
  ];

  return $tags;
});

// Replace Dynamic Data Tag with real value
// TODO: How this hook is different to "bricks/dynamic_data/render_content" ???
// TODO: How this hook is different to "bricks/frontend/render_data" ???
add_filter(
  'bricks/dynamic_data/render_tag',
  function ($tag, $post, $context = 'text') {
    // $tag is the tag name without the curly braces
    //
    // NOTE: 👈
    // BECAUSE THIS CALLBACK IS INVOKED WITH PRIORITY 11
    // WE CHECK FOR "{bc__tour__all_images}" , NOT "bc__tour__all_images
    //
    // IF THIS CALLBACK IS INVOKED WITH PRIORITY 10,
    // BRICKS COMPLAINS BECAUSE THE RETURN VALUE OF THIS FUNCTION MUST BE STRING
    // BUT HERE WE RETURN AN ARRAY OF INT
    if ($tag !== '{bc__tour__all_images}') {
      return $tag;
    }

    // Do your custom logic here...
    $my_value = get_value("UNRESOLVED", $post, $context);
    if ($my_value === "UNRESOLVED") return $tag;

    return $my_value;
  },
  11, // 👈
  3
);

// Replace Dynamic Data Tag with real value
// TODO: How this hook is different to "bricks/dynamic_data/render_tag" ???
add_filter('bricks/dynamic_data/render_content', 'render_my_tag', 10, 3);
add_filter('bricks/frontend/render_data', 'render_my_tag', 10, 2);
function render_my_tag($content, $post, $context = 'text') {

  // $content might consists of HTML and other dynamic tags
  // Only look for dynamic tag {bc__tour__all_images}
  if (strpos($content, '{bc__tour__all_images}') === false) {
    return $content;
  }

  return $content;

  // Do your custom logic here, you should define run_my_dd_tag_logic() function
  $my_value = get_value("UNRESOLVED", $post, $context);
  if ($my_value === "UNRESOLVED") return $content;

  // Replace the tag with the value you want to display
  $content = str_replace('{bc__tour__all_images}', $my_value, $content);

  return $content;
}

In case the explanation is not clear I can provide more details

I can’t go deep in your description at this moment but have you seen my post? Support ACF Photo Gallery - #11 by Matiasko

Thanks @Matiasko for the reply.

The post you mentioned is a good solution to the problem i am facing, that is to show “featured image + acf gallery images” inside a single carousel.
I think i will adopt your solution , that is to use “echo” dynamic data tag with a custom PHP function.
This solution requires less code, it is simpler in every aspect.

But…
Even if your solution is great, the main “topic” of this post I opened is to understand how to properly extend Bricks builder in a way that seems “native”.
The carousel problem can be too simple to justify the creation of a native options in the builder but it will be beneficial to the Bricks users to know how to properly extends the builder.

I bought this builder one week ago because it is the only builder that is made for developers, I resonate to the “vision” that want to be a highly customizable builder that the developer can extend with code.

I know that taking care of the documentation is a time consuming task, but I think that only by adding a few “example” in the docs with a production-ready snippets can cover almost every developer question, and now the docs has no complete example…