WAIT: Image Element with Dynamic Tag does not output value

Browser: Chrome 110
OS: macOS
URL: Template Test with Image Element using dynamic data tag

The content for dynamic tag used for the source in Image Element does not output the correct values.

Expected: //example.com
Actual: {

Tried:

  • https://example.com → outputs {
  • //example.com → outputs {
  • ['//example.com'] Fatal Error: str_pos() expects a string, array given

The dynamic tag does appear to work since I have tried it with in the link section and the value shows up in the href attribute. The image processing code has a few areas where the issue could be but I can’t step through on the server and I don’t have an environment setup locally for this project to determine where it is that { is showing up. I suspect that it might be where it explodes for tag arguments but that doesn’t actually appear to be the case.

Even having {ns_my_thumbnail:url} does not appear to do change anything.

Created the dynamic tag:

add_filter( 'bricks/dynamic_tags_list', 'add_bricks_dynamic_tags' );
add_filter( 'bricks/dynamic_data/render_tag', 'tag_value', 10, 3 );

add_filter( 'bricks/dynamic_data/render_content', 'render_thumbnail_tag', 10, 3 );
add_filter( 'bricks/frontend/render_data', 'render_thumbnail_tag', 10, 2 );

function tag_value( $tag, $post, $context = 'text' )
{
    // $tag is the tag name without the curly braces
    if ( $tag !== 'ns_my_thumbnail' ) {
        return $tag;
    }

    tag_setup();

    return 'Thumbnail';
}

function add_bricks_dynamic_tags( $tags )
{
    $tags[] = [
        'name'  => '{ns_my_thumbnail}',
        'label' => 'Thumbnail',
        'group' => 'Product',
    ];

    return $tags;
}

function render_thumbnail_tag( $content, $post, $context = 'text' )
{
    // $content might consists of HTML and other dynamic tags
    // Only look for dynamic tag {my_dd_tag}
    if ( ! str_contains($content, '{ns_my_thumbnail}') ) {
        return $content;
    }

    tag_setup();

    $content = str_replace( '{ns_my_thumbnail}', '//example.com', $content );

    return $content;
}

function tag_setup() { }

I discovered this on Bricks 1.7.2 and upgraded to 1.8.6 and the problem still persists. It could be a skill issue but I tried returning various values with no change. It is weird as it seems that the filter expects an array from tag_value() or from render_thumbnail_tag() but providing an array errors.

			$images = $this->render_dynamic_data_tag( $image['useDynamicData'], 'image', [ 'size' => $image['size'] ] );

			if ( ! empty( $images[0] ) ) {
				if ( is_numeric( $images[0] ) ) {
					$image['id'] = $images[0];
				} else {
					$image['url'] = $images[0];
				}
			}

This code from image.php lines 631-639 appear to be suspect. It shouldn’t be a PHP version issue as I believe PHP 8.1 still supports accessing a string position using array brackets. / or h should not be empty. I was thinking tag_value() would require the link to be returned but that doesn’t appear to be the case either. Even if it did, it still does not explain why other dynamic tag values work, including the same dynamic tag elsewhere. I haven’t been able to even confirm that this is the area without stepping through.

I forgot that

$image['url'] = $images[0];

Would get the first character from a string. Which may explain why the image source is just { but not really since the value shouldn’t have { any longer. It is partly why I thought that it was expecting an array.

Hey Jacob,

thanks for your report.

I am not sure I understand what your dynamic tag should do / return.

In the tag_value function you return the string Thumbnail. While in the render_thumbnail_tag function your value is //example.com. And what does your tag_setup() function do if it does not return anything?

I first need to understand what you’re trying to do to help here I guess. :slight_smile:

Best,

André

Neither value is shown anywhere. Neither Thumbnail nor //example.com is found in the src attribute. You can check in the reference link and by viewing the source. The source is an example to reproduce the issue, it is not a request for support. I will post in another topic for that. Thank you for the opportunity but it feels like it would be better to stay on the issue of the dynamic tag value from the filter not being applied correctly.

To answer your question. tag_setup() does nothing and everything right now is just magic values to prove out the functionality. The magic values will eventually be replaced with actual values from the data source.

EDIT: I guess you can’t actually see the template because it isn’t public to guests. :frowning:

The purpose is to create a dynamic tag that gets the URL for the image source.

It was discovered that the dynamic tag works every where used except for the Image element src meaning the image in the preview and in the live view shows a broken image. It works when used as dynamic tag for the link (same Image element but might be using another element for parsing the dynamic tag content).

This demonstrates that the issue is not with the custom dynamic tag code but with the Bricks Image Element dynamic tag handling.

Okay, after some debugging, I am still confused. It appears image element (and possibly elsewhere) is tag_value() to return some thing other than the tag element.

$images = $this->render_dynamic_data_tag( $image['useDynamicData'], 'image', [ 'size' => $image['size'] ] );

Returns {//example.com} which is not what is expected. However, removing the filter so that it is the correct {ns_my_thumbnail} appears to remove the image element completely as the system no longer reads the element as doing anything.

Which is where my confusion is. If I have the function return a value, then it uses the wrong value ({) in the src. If I don’t have the function return anything, then it doesn’t do anything because it looks like to the system that it is a noop.

$images = $this->render_dynamic_data_tag( $image['useDynamicData'], 'image', [ 'size' => $image['size'] ] );

if ( ! empty( $images[0] ) ) {
	if ( is_numeric( $images[0] ) ) {
		$image['id'] = $images[0];
	} else {
		$image['url'] = $images[0];
	}
}

Essentially what $images is on my dynamic tag is literal {//example.com} Which means $images[0] is equal to {, which means $images['url'] is equal to {. The irony is that $image['useDynamicData'] doesn’t have any dynamic tag at all. It is the URL that I want. I get the purpose is to capture any dynamic tags that may still exist. The problem is that I am unsure why the value is still being wrapped in { and }. This appears to be in the filter at the end.

Suggestion: $images[0] is a defect and does not do what the original author thinks it does. If the purpose is to capture an array, then please use is_array() after !empty().

I do not know the rest of the code base and I am trying to stay out of it as it is not my domain. It appears that the actual fix is something larger and would take additional time.

Solution is below:

Changing wp-content/themes/bricks/includes/elements/image.php:631

from

			$images = $this->render_dynamic_data_tag( $image['useDynamicData'], 'image', [ 'size' => $image['size'] ] );

			if ( ! empty( $images[0] ) ) {
				if ( is_numeric( $images[0] ) ) {
					$image['id'] = $images[0];
				} else {
					$image['url'] = $images[0];
				}
			}

To

			$fromDynamicTag = $this->render_dynamic_data_tag( $image['useDynamicData'], 'image', [ 'size' => $image['size'] ] );
            $fromDynamicTag = str_replace([ '{', '}'], '', $fromDynamicTag);

			if ( is_numeric($fromDynamicTag) && preg_match('/^[0-9]+$/', $fromDynamicTag) === 1 ) {
				$image['id'] = $fromDynamicTag;
            } else if (strpos($fromDynamicTag, '/') !== false) {
                $image['url'] = $fromDynamicTag;
            }

Produces the correct result. I am not sure if the following code is up to the plugin/company standards. I couldn’t find whether WordPress has a function for checking whether a string is an URL. I am guessing you could just use parse_url() checking for false (LOL, I was trying to be permissive and I forget if parse_url() is useful in PHP as a validator, I guess you could also use the filter_var($fromDynamicTag, FILTER_VALIDATE_URL) !== false. I guess instead of using the regex for validating integer, you could use filter_var($fromDynamicTag, FILTER_VALIDATE_INT) !== false.

I am unsure how you will use this, if you do. The $fromDynamicTag = str_replace([ '{', '}'], '', $fromDynamicTag); hints that there is a larger issue at play and this may not be the correct solution.

Hi @jacob

I just came across your post here since I am having the same issue.

I’ve create my functionality for a custom dynamic image tag similar to yours and it also outputs src="{" in my DOM. What I’ve noticed is that if manually calling the function using the image filter like: {echo:return_nextgen_image_url:image} is working correctly.

I will read through your posts again, maybe I find a useful hint.

Here’s my code as a reference:

<?php 

add_filter('bricks/dynamic_tags_list', 'add_nextgen_image_tag_to_builder');

function add_nextgen_image_tag_to_builder($tags) {
    $tags[] = [
        'name'  => '{nextgen_image}',
        'label' => 'NextGEN Gallery Image',
        'group' => 'NextGEN',
    ];

    return $tags;
}


add_filter('bricks/dynamic_data/render_content', 'render_nextgen_image_tag', 10, 3);
add_filter('bricks/frontend/render_data', 'render_nextgen_image_tag', 10, 2);

function render_nextgen_image_tag($content, $post, $context = 'image') {
    if (strpos($content, '{nextgen_image}') === false) {
        return $content;
    }
    
    $image_url = return_nextgen_image_url();
    vi($image_url, 'blue', 'NextGen image URL');
    // Replace the tag with the image URL
    $content = str_replace('{nextgen_image}', $image_url, $content);
    
    return $content;
}

add_filter( 'bricks/dynamic_data/render_tag', 'get_nextgen_tag_value', 10, 3 );
function get_nextgen_tag_value( $tag, $post, $context = 'text' ) {
  
	// Only look for dynamic tag starts with nextgen:
    if ($tag !== 'nextgen_image') {
        return $tag;
    }
    $value = return_nextgen_image_url();
    return $value;
}

function return_nextgen_image_url() {
    $image = \Bricks\Query::get_loop_object();
    return isset($image->imageURL) ? $image->imageURL : '';
}
1 Like

If I use my custom tag {nextgen_image} in the external URL field of an image element, it is working fine although it tells me that the image was not found:

If I use my custom tag {nextgen_image} in the external URL field of an image element, it is working fine although it tells me that the image was not found:

Thanks @rikhen , I may switch over to that as an option as I don’t want to keep maintaining the diff to the theme. It would be better to create a child class and apply the changes there. I am hoping that Bricks will apply the diff so that the fix is in the next release.

I am on PHP 8.1 and I am not sure which versions of PHP are supported and can’t test if everything works on their supported PHP versions. $string[$index] acts on the string like Clang, so each character is retrieved by its position. This is why { is in the DOM, it is capturing the first character in the string, which is { for some reason (the filter is wrapping the value in curly brackets in some higher up filter function).

It doesn’t appear returning arrays from the filter is supported so I am unsure why the string is being accessed using array semantics. The value being wrapped with curly brackets prevents the is_numeric check entirely, so that is always false.


As an aside, @rikhen, please be aware that if you have a loop, it is possible that multiple tags could be outputted and sent to be replaced. I think the recommended way of correcting this is to replace the tag with {nextgen_image:$index} so that you may do str_replace('{nextgen_image:0}', $value, $content);. I however, decided to YOLO it and just use strpos() and substr_replace() instead.

I switched to @rikhen solution because using dynamic tag is still broken even with the fix. The problem is that the tag is replaced prior to the $this->render_dynamic_data_tag() call so the method does not see a supported tag and wraps it in {whatever_the_value_is}. The problem I was having is that it replaces it with an empty string. I am guessing in an attempt for security.

There are a few bugs involved.

  1. It shouldn’t be rendering a dynamic tag if there isn’t a dynamic tag.
  2. If it is already rendering the dynamic tag then it shouldn’t attempt to render a second time.
  3. It shouldn’t capture the first character from the string and replace the entire external URL or ID with that first character. Even if everything else was correct, this would still be a defect.

I thought about simply removing the $this->render_dynamic_data_tag() since it is not doing what it should. This solution does not appear to work either.