I have images in my Wordpress posts with the following field setup:

Caption: This field contains the actual image caption to be displayed below the image.

Description: This field contains photo credits with links to attributed sources.

I need both these values to be displayed right below the image like so:

enter image description here

Taking cue from an excellent answer from @toscho to this question, I tried this function:

add_shortcode('wp_caption', 'img_caption_add_description');
add_shortcode('caption', 'img_caption_add_description');
function img_caption_add_description($attr, $content = null)
    $post_id = str_replace('attachment_', '', $attr['id']);
    $img = get_post((int)$post_id);

    if (is_a($img, 'WP_Post')) {
        $attr['caption'] = $img->post_content;

    return img_caption_shortcode($attr, $content);

As you can see in the screenshot, the function does its job well except that it strips off all hyperlinks. I need them! Is there any way to modify the above function in a way that it honors the links? In the above example, for instance, the texts "Amit Schandillia" and "CC BY-SA 3.0", both are meant to be hyperlinks. I have the links alright in my Description field as below:

Photo credit: <a href="" target="_blank" rel="nofollow">Amit Schandillia</a> licensed <a href="" target="_blank" rel="nofollow"><i class="fa fa-creative-commons"></i> BY-SA 3.0</a>

How do I make sure the <a> tags stay when being rendered on the post page?

1 Answer 1


When you look at the last line img_caption_shortcode(), then you can see wp_kses() stripping non allowed HTML tags there. The wp_kses_hook() function does only wrap a filter:

return apply_filters( 'pre_kses', $string, $allowed_html, $allowed_protocols );

which allows to hook in and alter the output before wp_kses_split() finally removes the HTML tag by using preg_replace_callback with _wp_kses_split_callback() as function to remove the tags.

As inside _wp_kses_split_callback(), the wp_kses_split2() function is called, you got a (bad) alternative as well: Altering a global.

You got the following two globals in the callback:

global $pass_allowed_html, $pass_allowed_protocols;

Meaning that you can do the following as well (not recommended):

// Fetch the global context
global $pass_allowed_html;
// Save the global context for restoring it to not nuke other code
$temp = $pass_allowed_html;
// Alter the context
$GLOBALS['pass_allowed_html'] = [ /* your allowed HTML tags here */ ];
// Fetch the result
$result = img_caption_shortcode();
// Reset the global context
$GLOBALS['pass_allowed_html'] = $temp;
// Return your altered result
return $result;

Keep in mind that wp_kses_allowed_html() will then trigger it's explicit case.

As img_caption_shortcode() only passes the string of value post to wp_kses(), you can as well then take profit from the following lines inside wp_kses_split2():

if ( ! is_array( $allowed_html ) )
    $allowed_html = wp_kses_allowed_html( $allowed_html );

Looking at [wp_kses_allowed_html()] again:

return apply_filters( 'wp_kses_allowed_html', $allowedposttags, $context );

you can simply use this filter. Make sure that you remove the filter again after it has run to not conflict with other content and therefore allowing the tag elsewhere by accident.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.