Take the 2-minute tour ×
WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. It's 100% free, no registration required.

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).

share|improve this question
    
Do you insert <div class="entry-content"> in the post editor? Or is applied by your template? –  toscho May 19 '12 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 '12 at 0:34
    
I've unmarked the answer in case it's making others think it's over. –  its_me Jul 3 '12 at 2:15
    
Does it have to catch cases where the p tag has attributes (style, class etc.)? –  toscho Jul 3 '12 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 '12 at 2:49

3 Answers 3

up vote 6 down vote accepted
+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);
    }
}
share|improve this answer
    
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 '12 at 9:15
    
i see, will now that we know the use case i'll try to think of something else. –  Bainternet Jul 3 '12 at 9:43
    
@AahanKrish i added a simple solution using DOMdocument to search the content. –  Bainternet Jul 3 '12 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 '12 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 '12 at 10:36

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];
}
share|improve this answer
    
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 '12 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 J. Hansen Jul 4 '12 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 '12 at 15:42
    
How do you parse just the first paragraph? –  Bainternet Jul 5 '12 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 J. Hansen Jul 5 '12 at 23:19

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;
}
share|improve this answer
    
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 '12 at 19:28
1  
@AahanKrish See my edit. –  toscho Jul 2 '12 at 19:32

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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