Apply class to every paragraph that holds image?

When adding an image to a page or post, WordPress is automatically adding a paragraph tag <p> as parent holding the image. Like so:

<p><img src="my-image.jpg" alt=""></p>

Since there is no parent selector in CSS I’d love to find a solution to apply a specific classname to those paragraphs holding an actual image. The example above would thus result in:

Read More
<p class="my-class"><img src="my-image.jpg" alt=""></p>

Any idea if I can use add_filter() to apply a classname to each p that holds an image?

Related posts

Leave a Reply

4 comments

  1. You could use jQuery if you don’t mind to rely on JavaScript for adding the class.

    $(document).ready(function() {
        $('p:has(img)').addClass('image');
    });
    

    Update: the .has() method is probably faster, see this jsperf.com test.

    $(document).ready(function() {
        $('p').has('img').addClass('image');
    });
    
  2. If you need this to be a PHP solution. You could do something like this using ‘the_content’ filter. This will add the class name “content-img-wrap” to any paragraph that wraps only an image tag when WordPress prints your post content. So this will not wrap a paragraph with an image and a span. See update if that is what your looking for.

    add_filter( 'the_content', 'img_p_class_content_filter' ,20);
    function img_p_class_content_filter($content) {
        // assuming you have created a page/post entitled 'debug'
        $content = preg_replace("/(<p)(>[^<]*<img[^>]+>[^<]*)(</p>)/i", "$1 class='content-img-wrap'$2$3", $content);
    
        return $content;
    }
    

    ** UPDATE BELOW: In response to the need for a more flexible wrap. I created and quickly tested this regex against 3 automatically wrapped paragraphs (meaning I didn’t add the p tags, WP did) with images at the end of a post. They each also contained some random tags and carriage returns for testing likely cases and flexibility. The idea is that you don’t have to match anything after the <img> because the presence of any <img> proves our case. Then we just add our custom class/es in (improvements more then welcome):

    function img_p_class_content_filter($content) {
        // assuming you have created a page/post entitled 'debug'
        $content = preg_replace("/(<p[^>]*)(>.*)(<img.*)(</p>)/im", "$1 class='content-img-wrap'$2$3$4", $content);
    
        return $content;
    }
    
  3. While I understand wanting the front end to be as lean as possible and therefore stating you want a PHP solution…

    Personally, I spent quite a bit of time on this just for fun, and I kept coming up with ways to break the regex matching (hence, why not to use PHP without a library add-on specifically designed to traverse HTML, as mentioned in the link above)…

    For example:

    This screenshot’s rule will match <p> or even <P > (the simplest match, not trying yet for P tags that already have a class or have other attributes but not a class), but see that it has to be matched WITHIN the entire block of closing tags (where PHP regex for HTML falls apart at the seams)… the arrow points to where a closing P tag and opening P tag are on the same line and therefore if you were to count this as a FIND and then replace the <p with <p class="my-class", you’d be adding my-class to the wrong paragraph.

    If you didn’t want such an “every scenario” use case, it’s doable via regex. If you narrow your scope a bit.

    If you decide to limit your scope, you might be interested in this information:

        From wp-includes/default-filters.php
        these run on the_content with default priority (10):
            wptexturize
            convert_smilies
            convert_chars
            wpautop
            shortcode_unautop
            prepend_attachment
        shortcode renders may add new paragraphs and/or new images
            do_shortcode runs on the_content priority 11 so we hook in afterwards so $content already has shortcodes rendered
    */
    add_filter('the_content', 'se_16033', 15); //a priority higher than 11
    

    Here’s some starting code if you do force yourself down this complicated route (instead of via jQuery):

        function se_16033($content) {
        if( !is_singular() || !is_main_query() //https://pippinsplugins.com/playing-nice-with-the-content-filter/
        ) {
            return $content;
        }
    
        $content = force_balance_tags($content); //We do not want to be REGEXing on unbalanced HTML tags so we must make sure they are balanced before we get started -- garbage in, worse garbage out -- http://codex.wordpress.org/Data_Validation#HTML
    
        $patterns = array();
        $patterns[0] = '/quick/'; //first match p tags with existing classes because we just want to add an additional class
        $patterns[1] = '/brown/'; //second, match p tags with space (e.g. style) but without class=""
        $patterns[2] = '/fox/'; //third, match <p> tags without attributes
        ksort($patterns);
    
        $replacements = array();
        $replacements[0] = 'slow';
        $replacements[1] = 'black';
        $replacements[2] = 'bear';
        ksort($replacements);
    
        preg_replace_all($patterns, $replacements, $content);
    
        return $content;
    
    }
    

    Note the use of http://codex.wordpress.org/Function_Reference/force_balance_tags
    By default, it gets used on the Excerpt and Comments
    If you look at its source, it actually uses REGEX: https://core.trac.wordpress.org/browser/tags/4.1/src/wp-includes/formatting.php#L1453

    SIDENOTE
    I assume the reason they fallback (not ideal) to using REGEX is because of WP’s need to run on almost any host’s PHP configuration imaginable (i.e. not wanting to load a library in case that PHP extension is disabled). You could choose to load a PHP library designed to do this in your own solution.
    /SIDENOTE

    It’s a whole lot of code to be ran every time is_singular(). As such, I’m not sure it’d be worth it to use as inspiration for some complex PHP solution when your site is likely already using JS/jQuery.

    • Is this P class somehow needing to work even on non-JS-enabled browsers?
    • Are you an über-performance guru? If yes, do you have any jQuery in use on your site currently (e.g. slider, motion, etc)? If yes, the minimalistic jQuery needed to do this is not going to add bloat. Just go with it.
    • If you do NOT have jQuery loaded on front-end, we could come up with a pure JavaScript way to accomplish this (i.e. not via jQuery)

    Please share your thoughts.

  4. WordPress uses the autop function to add <p></p> to line breaks in your content.

    The autop function can be found on line 373 in formatting.php. An option is to disable the default autop (see below), create a new version of the function adding your class, and then apply it to your content (see the link below for that too). I guess you’d be adding a regex around line 442 to do that.

    If you just want to remove the effect of the paragraph you can disable autop like this:

    remove_filter( 'the_content', 'wpautop' );
    remove_filter( 'the_excerpt', 'wpautop' );
    

    Which can be found on the autop page: