WordPress inserting opening <p> tag inside of my shortcode

I have written a small function to display the latest video on my startpage. The function itself works just fine. The only problem is, that wordpress keeps inserting an opening <p> tag at one place of the code.
Here is my function:

function video_start() {
// the query
$the_query = new WP_Query(array('post_type'=>'page','post_parent'=>'17','order'=>'ASC','orderby' => 'date','posts_per_page'=>1));
// The Loop 
if ( $the_query->have_posts() ) 
{
    while ( $the_query->have_posts() ) 
    {
        $the_query->the_post();
        if ( has_post_thumbnail() ) 
        {
            $picid = get_post_thumbnail_id($post_id);
            $alt_text = get_post_meta($picid , '_wp_attachment_image_alt', true);
            $string .= '<div class="featured-start">';
            $string .= '<a href="' . get_the_permalink() .'" class="b-link" rel="bookmark">';
            $string .= '<h2 class="mar-bot">Latest Video</h2>';
            $string .= '<img src="'.wp_get_attachment_url(get_post_thumbnail_id($post_id)).'" class="img-responsive" alt="'.$alt_text.'" />';
            $string .= '<p>'.get_the_title().'</p>';
            $string .= '<div class="orange-button">Watch Video</div></a></div>';
        }
    }
}
else 
{
// no posts found
}
$string .= '<div class="clear"></div>';
return $string;
/* Restore original Post Data */
wp_reset_postdata();
}
// Add a shortcode
add_shortcode('video_startpage', 'video_start');
// Enable shortcodes in text widgets
add_filter('widget_text', 'do_shortcode');

The output I am having a problem with is this:

Read More
<div class="featured-start">
<a href="http://www.kundenwebseite.inweco.de/videos/pet-project-09-part-1/" class="b-link" rel="bookmark"><br />
<h2 class="mar-bot">Latest Video</h2>
<p><img src="http://www.kundenwebseite.inweco.de/wp-content/uploads/2015/11/pet-project-2009-part-1.jpg" class="img-responsive" alt="" />
<p>Pet Project 09 – Part 1</p>
<div class="orange-button">Watch Video</div>
<p></a></div>
<div class="clear"></div>

I have tried removing the wp-autop filter several ways but it simply wont work. And even after using google for over two hours now i cant find a solution.

Related posts

5 comments

  1. This is a known issue since years. Please have a look at WordPress Ticket.

    As others maybe mentioned before, there is a Plugin to fix this issue. It is called “Shortcode Empty Paragraph Fix“. Its not the best solution but not so bad.

    Im not a fan of putting a lot of bloat to my wp installation – so i wrapped the shortcode in a div without a class and fixed the problem.

    <div> [woocommerce_cart] </div>
    

    I hope the issue will adressed in the next few years 😉 so i can remove my divs – in my opinion a saver way, because sometimes extra functions in the funcitons.php can cause some problems down the road.

  2. Alright. After a long search ( with a lot of help from Steve ) i have finally found a solution.
    I took the wpautop function ( beginning in line 456 and ending in line 604 ) from

    https://core.trac.wordpress.org/browser/tags/4.3.1/src/wp-includes/formatting.php#L0

    and replaced the existing wpautop function in the formatting.php file found in the /wp-includes/ folder with the new qpautop function from the provided link.
    Then i commented the line 499 out.

    All of that got it to work for me.

  3. WordPress will automatically apply wpautop filter on your content, either directly added on post editor or generated by shortcode,

    to avoid wordpress from adding undesired tags on your content,

    you can wrap all the inline element with block element, without adding new line to that inline element, .e.g

    <div><a href="#">Hello</a></div>

    will output <div><a href="#">Hello</a></div>

    <div>
        <a href="#">Hello</a>
    </div> 
    

    will output <div><p><a href="#">Hello</a><p></div>

    your options are;

    1. trim all new line to the content generated by your shortcode, though this wouldn’t be a good idea as having no newline on your HTML can affect CSS property

    return trim(preg_replace('/s+/', ' ', $string));

    1. Override wordpress default auto formatter with custom formatter,

    this code below is builtin function on my theme

    function my_custom_content_formatter($content) {
        $new_content = '';
        $pattern_full = '{([raw].*?[/raw])}is';
        $pattern_contents = '{[raw](.*?)[/raw]}is';
        $pieces = preg_split($pattern_full, $content, -1, PREG_SPLIT_DELIM_CAPTURE);
        foreach ($pieces as $piece) {
            if (preg_match($pattern_contents, $piece, $matches)) {
                $new_content .= $matches[1];
            } else {
                $new_content .= wptexturize(wpautop($piece));
            }
        }
    
        return $new_content;
    }
    
    // Remove the 2 main auto-formatters
    remove_filter('the_content', 'wpautop');
    remove_filter('the_content', 'wptexturize');
    
    // apply new auto-formatter
    add_filter('the_content', 'my_custom_content_formatter', 99);
    add_filter('widget_text', 'my_custom_content_formatter', 99);
    

    You can then add your shortcode like this [raw][video_startpage][/raw]

  4. The wpautop() function is in wp-includes/formatting.php which allows you to specify whether you want a <p> or not but it seems that passing the arguments to switch it off is not working in your case – these methods might be of some use.

    This one uses str_replace() which is not so bad, and offers several other options:

    https://wordpress.org/support/topic/shortcode-is-being-surrounded-by-p-tags

    also an offering on that same page which removes the newlines which are being converted automatically into <p> tags str_replace(array("nr", "n", "r"), '', $content );

    This is a plugin offered on that page but I don’t know if it is OK – you seem to be able to take the function offered and add it to your functions.php – if you have a child theme https://codex.wordpress.org/Child_Themes you can put it in the functions.php inside that so it doesn’t get lost when your parent theme is updated. (Regarded as best practice).

    https://wordpress.org/plugins/shortcode-empty-paragraph-fix/

  5. I had the same problem and since nothing above really worked, I have decided to solve it. So here is my solution (though only works for shortcodes that have the closing tags).

    Just so we’re all on the same page,having a basic shortcode for row and col that outputs divs with their speciffic classes, if we have this in wp editor:

    lorem test
    [row]
    [col]inside col[/col]
    [/row]
    dolor
    

    the result output will be :

    <p>lorem test<br />
    <div class="row "><div class="columns ">inside col</div></div><br />
    dolor</p>
    

    which is wrong, the correct way should be:

    <p>lorem test</p>
    <div class="row "><div class="columns ">inside col</div></div>
    <p>dolor</p>
    

    To achieve this we first replace the wpautop filter with our own

    add_action('init','cod_custom_wpautop',90);
    function cod_custom_wpautop() {
       remove_filter('the_content', 'wpautop');
       remove_filter('acf_the_content', 'wpautop');
       add_filter('the_content', 'cod_wpautop');
       add_filter('acf_the_content', 'cod_wpautop');
    }
    

    and then the actual function is mostly the same as the one on wp-includes/formating.php , but with our own changes:

    function cod_wpautop( $pee, $br = true ) {
    $pre_tags = array();
    
    if ( trim($pee) === '' )
        return '';
    
    // Just to make things a little easier, pad the end.
    $pee = $pee . "n";
    
    /*
     * Pre tags shouldn't be touched by autop.
     * Replace pre tags with placeholders and bring them back after autop.
     */
    if ( strpos($pee, '<pre') !== false ) {
        $pee_parts = explode( '</pre>', $pee );
        $last_pee = array_pop($pee_parts);
        $pee = '';
        $i = 0;
    
        foreach ( $pee_parts as $pee_part ) {
            $start = strpos($pee_part, '<pre');
    
            // Malformed html?
            if ( $start === false ) {
                $pee .= $pee_part;
                continue;
            }
    
            $name = "<pre wp-pre-tag-$i></pre>";
            $pre_tags[$name] = substr( $pee_part, $start ) . '</pre>';
    
            $pee .= substr( $pee_part, 0, $start ) . $name;
            $i++;
        }
    
        $pee .= $last_pee;
    }
    // Change multiple <br>s into two line breaks, which will turn into paragraphs.
    $pee = preg_replace('|<brs*/?>s*<brs*/?>|', "nn", $pee);
    
    $allblocks = '(?:table|thead|tfoot|caption|col|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|form|map|area|blockquote|address|math|style|p|h[1-6]|hr|fieldset|legend|section|article|aside|hgroup|header|footer|nav|figure|figcaption|details|menu|summary)';
    
    // Add a double line break above block-level opening tags.
    $pee = preg_replace('!(<' . $allblocks . '[s/>])!', "nn$1", $pee);
    
    // Add a double line break below block-level closing tags.
    $pee = preg_replace('!(</' . $allblocks . '>)!', "$1nn", $pee);
    
    $shortcode_blocks = '(?:row|col)';
    $pee = preg_replace('!([' . $shortcode_blocks . '[s/]])!', "nn$1", $pee);
    $pee = preg_replace('!([/' . $shortcode_blocks . '])!', "$1nn", $pee);
    
    // Standardize newline characters to "n".
    $pee = str_replace(array("rn", "r"), "n", $pee);
    
    // Find newlines in all elements and add placeholders.
    $pee = wp_replace_in_html_tags( $pee, array( "n" => " <!-- wpnl --> " ) );
    
    // Collapse line breaks before and after <option> elements so they don't get autop'd.
    if ( strpos( $pee, '<option' ) !== false ) {
        $pee = preg_replace( '|s*<option|', '<option', $pee );
        $pee = preg_replace( '|</option>s*|', '</option>', $pee );
    }
    
    /*
     * Collapse line breaks inside <object> elements, before <param> and <embed> elements
     * so they don't get autop'd.
     */
    if ( strpos( $pee, '</object>' ) !== false ) {
        $pee = preg_replace( '|(<object[^>]*>)s*|', '$1', $pee );
        $pee = preg_replace( '|s*</object>|', '</object>', $pee );
        $pee = preg_replace( '%s*(</?(?:param|embed)[^>]*>)s*%', '$1', $pee );
    }
    
    /*
     * Collapse line breaks inside <audio> and <video> elements,
     * before and after <source> and <track> elements.
     */
    if ( strpos( $pee, '<source' ) !== false || strpos( $pee, '<track' ) !== false ) {
        $pee = preg_replace( '%([<[](?:audio|video)[^>]]*[>]])s*%', '$1', $pee );
        $pee = preg_replace( '%s*([<[]/(?:audio|video)[>]])%', '$1', $pee );
        $pee = preg_replace( '%s*(<(?:source|track)[^>]*>)s*%', '$1', $pee );
    }
    
    // Remove more than two contiguous line breaks.
    $pee = preg_replace("/nn+/", "nn", $pee);
    
    // Split up the contents into an array of strings, separated by double line breaks.
    $pees = preg_split('/ns*n/', $pee, -1, PREG_SPLIT_NO_EMPTY);
    
    // Reset $pee prior to rebuilding.
    $pee = '';
    
    // Rebuild the content as a string, wrapping every bit with a <p>.
    foreach ( $pees as $tinkle ) {
        $pee .= '<p>' . trim($tinkle, "n") . "</p>n";
    }
    
    // Under certain strange conditions it could create a P of entirely whitespace.
    $pee = preg_replace('|<p>s*</p>|', '', $pee);
    
    // Add a closing <p> inside <div>, <address>, or <form> tag if missing.
    $pee = preg_replace('!<p>([^<]+)</(div|address|form)>!', "<p>$1</p></$2>", $pee);
    
    // If an opening or closing block element tag is wrapped in a <p>, unwrap it.
    $pee = preg_replace('!<p>s*(</?' . $allblocks . '[^>]*>)s*</p>!', "$1", $pee);
    $pee = preg_replace('!<p>s*([/?' . $shortcode_blocks . '[^]]*])s*</p>!', "$1", $pee);
    
    // In some cases <li> may get wrapped in <p>, fix them.
    $pee = preg_replace("|<p>(<li.+?)</p>|", "$1", $pee);
    
    // If a <blockquote> is wrapped with a <p>, move it inside the <blockquote>.
    $pee = preg_replace('|<p><blockquote([^>]*)>|i', "<blockquote$1><p>", $pee);
    $pee = str_replace('</blockquote></p>', '</p></blockquote>', $pee);
    
    // If an opening or closing block element tag is preceded by an opening <p> tag, remove it.
    $pee = preg_replace('!<p>s*(</?' . $allblocks . '[^>]*>)!', "$1", $pee);
    $pee = preg_replace('!<p>s*([/?' . $allblocks . '[^]]*])!', "$1", $pee);
    
    // If an opening or closing block element tag is followed by a closing <p> tag, remove it.
    $pee = preg_replace('!(</?' . $allblocks . '[^>]*>)s*</p>!', "$1", $pee);
    $pee = preg_replace('!([/?' . $allblocks . '[^]]*])s*</p>!', "$1", $pee);
    
    // Optionally insert line breaks.
    if ( $br ) {
        // Replace newlines that shouldn't be touched with a placeholder.
        $pee = preg_replace_callback('/<(script|style).*?</\1>/s', '_autop_newline_preservation_helper', $pee);
    
        // Normalize <br>
        $pee = str_replace( array( '<br>', '<br/>' ), '<br />', $pee );
    
        // Replace any new line characters that aren't preceded by a <br /> with a <br />.
        $pee = preg_replace('|(?<!<br />)s*n|', "<br />n", $pee);
    
        // Replace newline placeholders with newlines.
        $pee = str_replace('<WPPreserveNewline />', "n", $pee);
    }
    
    // If a <br /> tag is after an opening or closing block tag, remove it.
    $pee = preg_replace('!(</?' . $allblocks . '[^>]*>)s*<br />!', "$1", $pee);
    $pee = preg_replace('!([/?' . $shortcode_blocks . '[^]]*])s*<br />!', "$1", $pee);
    
    // If a <br /> tag is before a subset of opening or closing block tags, remove it.
    $pee = preg_replace('!<br />(s*</?(?:p|li|div|dl|dd|dt|th|pre|td|ul|ol)[^>]*>)!', '$1', $pee);
    $pee = preg_replace( "|n</p>$|", '</p>', $pee );
    
    // Replace placeholder <pre> tags with their original content.
    if ( !empty($pre_tags) )
        $pee = str_replace(array_keys($pre_tags), array_values($pre_tags), $pee);
    
    // Restore newlines in all elements.
    if ( false !== strpos( $pee, '<!-- wpnl -->' ) ) {
        $pee = str_replace( array( ' <!-- wpnl --> ', '<!-- wpnl -->' ), "n", $pee );
    }
    
    return $pee;
    }
    

    To explain it a bit, we’ve added a $shortcode_blocks that does the same thing as $allblocks does , essentially making them recognize as a block and not just a simple text. This way first those tags will be wrapped with a paragraph tag that will be later removed.

    Works for me, so hope it will for you to 😉

Comments are closed.