5

I would like to add some code in functions.php that does this:

  • If the first paragraph contains an image, show my custom code right AFTER it. For example:

    <div class="entry-content">
        <p><img src="http://example.com/example.jpg" alt="Image in 1st para" /></p>
        <div>MY CUSTOM CODE</div>
    </div>
    
  • But if the first paragraph is text (i.e. NOT an image), show my custom code right BEFORE it.

    <div class="entry-content">
        <div>MY CUSTOM CODE</div>
        <p>This 1st paragraph contains text, not an image.</p>
    </div>
    

How do I do it? An example code snippet is most welcome.

PS: The custom code could also be an ad (e.g. Google Adsense).

5
  • Do you insert <div class="entry-content"> in the post editor? Or is applied by your template?
    – fuxia
    May 19, 2012 at 18:20
  • @toscho it's in the template. But everything in between, EXCEPT the conditionally applied custom code, is added in the post editor.
    – its_me
    May 20, 2012 at 0:34
  • I've unmarked the answer in case it's making others think it's over.
    – its_me
    Jul 3, 2012 at 2:15
  • Does it have to catch cases where the p tag has attributes (style, class etc.)?
    – fuxia
    Jul 3, 2012 at 2:43
  • @toscho Oh, that would be great. I don't normally use style, class or id attributes with the p tag, but your question reminds me that I may have to. How thoughtful. :) Every tut on the web, unfortunately, also seems to use regex :S
    – its_me
    Jul 3, 2012 at 2:49

3 Answers 3

6
+100

How about a few simple lines With jQuery?

jQuery(document).ready( function ($) {
    if ($(".entry-content:first-child").has('img').length) //this check for the img tag
        $(".entry-content:first-child").after("<div>MY CUSTOM CODE</div>");
    else
        $(".entry-content:first-child").before("<div>MY CUSTOM CODE</div>");
});

Update:

Here is a simple solution using php's native DOMDocument

add_filter('the_content','add_code_before_afterImage');

function add_code_before_afterImage($content){
    $MYCODE = '<div>this is my custom code</div>';
    $doc = new DOMDocument();
    @$doc->loadHTML('<?xml encoding="'.get_bloginfo('charset').'">'.$content);
    $ps = $doc->getElementsByTagName('p');
    foreach ($ps as $p) {
        if (false !== stripos($p->nodeValue,'img'));
            return str_replace($p->nodValue, $p->nodValue.$MYCODE, $content);
        }
        break;
    }
    //if we got here then there is no img tag in the first paragraph
    //so we return the code before the content.
    return $MYCODE.$content;
}

Update 2:

@Sarathi's comment go me thinking the you don't actually need to parse any part of the content just pull the first paragraph and check if it has an img tag so here is a simpler and by far faster solution using just PHP's native str_replace and stripos

add_filter('the_content','simple_img_tag_search');
function simple_img_tag_search($content){

    $MYCODE = '<div>this is my custom code</div>';

    //split content to first paragraph and the rest
    $paragraphs = explode( '</p>', $content, 2 );

    //extract the first paragraph
    $first_paragraph = $paragraphs[0];

    //then just look for img tag
    if (false === stripos($first_paragraph, "<img")){
        //not found then just return the code before the content
        return $MYCODE.$content;
    }else{
        // img tag found so we return the code after the first paragraph
        return str_replace($first_paragraph.'</p>',$first_paragraph.'</p>'.$MYCODE,$content);
    }
}
13
  • Sorry, unfortunately dynamic positioning is not an option. (Reason: For example, AdSense Ads suck when positioned via JavaScript) — thanks for trying to help, and congratulations on reaching 30K. :)
    – its_me
    Jul 3, 2012 at 9:15
  • i see, will now that we know the use case i'll try to think of something else.
    – Bainternet
    Jul 3, 2012 at 9:43
  • @AahanKrish i added a simple solution using DOMdocument to search the content.
    – Bainternet
    Jul 3, 2012 at 10:09
  • Please don't get me wrong, is your solution better than the existing regex one? (I don't know PHP, and am unable to decide)
    – its_me
    Jul 3, 2012 at 10:12
  • 2
    1) no the problems mentioned there are regex only. 2)not so true but again make no real difference. 3) i did'nt miss that if you look at my code it looks for p tags and inside the first one it looks for img tag.
    – Bainternet
    Jul 3, 2012 at 10:36
4

To catch the first paragraph (<p>) you can use a regex. That's not optimal, so be warned. :)

Then you test the match for an image and insert the extra content depending on the test result. I use two functions here, one for each step: The first finds the first paragraph, the second changes the first match.

// Late priority parameter to let shortcodes and other filters do their work first.
add_filter( 'the_content', 'wpse_52662_add_extra', 1000 );

/**
 * Reads the content and calls a callback to add extra content.
 *
 * @param  string $content
 * @return string
 */
function wpse_52662_add_extra( $content )
{
    // restrict to single posts:
    if ( ! is_single() )
    {
        return $content;
    }

    return preg_replace_callback(
        '~<p(>|\s+[^>]*>)(.*?)</p>~miU' // find <p>
    ,   'wpse_52662_callback'           // pass the result to the callback
    ,   $content
    ,   1                               // stop after first match
    );
}

/**
 * Callback for wpse_52662_add_extra()
 *
 * @param array $m Matches. $m[0] contains the whole match,
 *                          $m[2] the content of the paragraph.
 * @return string
 */
function wpse_52662_callback( $m )
{
    $extra = '<p><b>Hello World!</b></p>';
    return ( FALSE === strpos( $m[2], '<img' ) ) ? $extra.$m[0] : $m[0].$extra;
}
2
  • How should I pass a conditional tag is_single so that the regex function above does its job only if it's a Post i.e. is_single?
    – its_me
    Jul 2, 2012 at 19:28
  • 1
    @AahanKrish See my edit.
    – fuxia
    Jul 2, 2012 at 19:32
1

You can use DOMDocument to parse the HTML content like so:

add_filter( 'the_content', 'add_html_after_first_image' );
function add_html_after_first_image( $content ) {

    $my_custom_html = '<div>MY CUSTOM CODE</div>';

    // Create a dom document from the post content.
    // The content needs to be wrapped in a root element to be valid xml, hence the div tags.
    // I used loadXML because loadHTML adds the unnecessary <DOCTYPE>, <html>, and <body>.
    if( $dom = DOMDocument :: loadXML( '<div>' . $content . '</div>' ) ) {

        // Create a document fragment with your custom html.
        $custom = $dom -> createDocumentFragment();
        $custom -> appendXML( $my_custom_html );

        // Get the root element, and first child.
        $root   = $dom  -> firstChild;
        $first  = $root -> firstChild;

        // Check if the first child is a paragraph, the first grandchild is an image,
        // and if the paragraph has no content other than the image.
        $has_image = 'p'   == $first -> tagName
                &&   ''    == trim( $first -> textContent )
                &&   1     == count( $first -> childNodes )
                &&   'img' == $first -> firstChild -> tagName;

        // If $has_image is true then add the custom div after the first paragraph,
        // otherwise add it before.
        $root -> insertBefore( $custom, $has_image ? $first -> nextSibling : $first );

        // Set the new content to the altered html.
        $content = $dom -> saveHTML();
    }

    return $content;
}


EDIT:

Here is an updated function that has been optimized to run more quickly than the function above.

Tip: The <p> tag must not contain any content other than the <img /> tag. (That includes whitespace)

add_filter( 'the_content', 'add_html_after_first_image' );
function add_html_after_first_image( $content ) {

    $my_custom_html = '<div>MY CUSTOM CODE</div>';

    // Explode the content to extract and parse only the first paragraph.
    $parts = explode( '</p>', $content, 2 );
    $p = $parts[0] . '</p>';

    // Create a dom document from the first paragraph of content.
    // I used loadXML because loadHTML adds the unnecessary <DOCTYPE>, <html>, and <body>.
    // Checking for an opening <p> tag prevents the creation of a DOMDocument,
    // and lowers the execution time on posts that don't start with a paragraph.
    if( substr( $p, 0, 3 ) == '<p>' && $dom = @DOMDocument :: loadXML( $p ) ) {

        // Create a document fragment with your custom html.
        $custom = $dom -> createDocumentFragment();
        $custom -> appendXML( $my_custom_html );

        $first  = $dom -> firstChild;

        // Check if the first child is a paragraph, the first grandchild is an image,
        // and if the paragraph has no content other than the image.
        $has_image = 'p'   == $first -> tagName
                &&   ''    == $first -> textContent
                &&    1    == count( $first -> childNodes )
                &&   'img' == $first -> firstChild -> tagName;

        // If $has_image is true then add the custom div after the first paragraph,
        // otherwise add it before.
        $dom -> insertBefore( $custom, $has_image ? $first -> nextSibling : $first );

        // Set the new content to the altered html.
        $p = $dom -> saveHTML();

    }
    // If the dom document could not be created, then the first element is not a
    // paragraph and the custom code should be prepended to the content.
    else $p = $my_custom_html . $p;

    // Append the rest of the content to the paragraph.
    return $p . $parts[1];
}
7
  • I have a question. In a comment to his answer, Bainternet says that using DOMDocument to parse HTML could be slower (although very negligible, and effectively overcome with caching) as it parses the whole document. Does it apply to your solution as well? It's not a problem at all, just asking.
    – its_me
    Jul 4, 2012 at 9:49
  • @AahanKrish I altered the function so that the DOMDocument would only parse the first paragraph, then tested the execution time against the original. I would agree that the speed difference is negligible (on my local machine, I'm not sure if the difference would be greater on a web server). Here are my results, Original: 0.00034-0.00049 seconds, New Function: 0.00018-0.00026 seconds, If you would like, I can edit my answer to include the new function.
    – Sarathi
    Jul 4, 2012 at 15:40
  • yeah sure, but please add the new function below the old one (don't remove the old function). Also, can you please recheck your code (if your time permits) for possible bugs? Thanks for your time. :)
    – its_me
    Jul 4, 2012 at 15:42
  • How do you parse just the first paragraph?
    – Bainternet
    Jul 5, 2012 at 7:26
  • 3
    @Bainternet To parse just the first paragraph, I explode the content like so $parts = explode( '</p>', $content, 2 ); then I parse only the first item in the $parts array, i.e. the first paragraph.
    – Sarathi
    Jul 5, 2012 at 23:19

Your Answer

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

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