Place Ads in between Text-Only Paragraphs

I am using the following code to place some ad code inside my content .

<?php
$content = apply_filters('the_content', $post->post_content);
$content = explode (' ', $content);
$halfway_mark = ceil(count($content) / 2);
$first_half_content = implode(' ', array_slice($content, 0, $halfway_mark));
$second_half_content = implode(' ', array_slice($content, $halfway_mark));
echo $first_half_content.'...';
echo ' YOUR ADS CODE';
echo $second_half_content;
?>

How can I modify this so that I can place 2 ads out at the same time in between text only paragraphs (both the <p>...</p> enclosing the ad should not have images or embedded videos).

Read More

I want to avoid jQuery.

Example of my Usecase…
Example of my usecase

  • I want to insert 2 advert blocks in this article.
  • I want the 1st ad block to be after the 1st paragraph. But any image in the 1st paragraph should be removed.
  • For the 2nd ad it should be placed possible in the second half of the article in between text-only paragraph such that an ad code sandwitched between a good amount of text and is never very near to an embedded image or video etc.
  • There should be at least 2 paragraphs between 2 ads

Related posts

Leave a Reply

8 comments

  1. Here is my approach to the question. Sorry for posting a bit late ( and missing the bounty 🙁 ), but it has been a hectic week, so I did everything in bits and pieces.

    QUICK RUNDOWN

    • I have not removed the attachments in the first paragraph. I don’t really see why content must be vandalized for the sake of an ad

    • I have done quite a lot of checks in my code to try and make sure that ads are only inserted in between text only paragraphs. Text only paragraphs are taken to be paragraphs that does not contain img, li and ul tags

    • I have documented each block of code properly, so you can easily work through each section. I have also added todo doc blocks which you need to attend to, if necessary

    • I have used wptexturize to apply p tags to the_content. Each double line break constitutes a paragraph

    • Modify and use this code as you see fit. I have tested it, so it is bug free on my side.

    Here is the code. Hope this works for you as expected. *PS! All of this goes into your functions.php. No need for any other code or mods to your template files

    <?php
    add_filter( 'the_content', 'so_25888630_ad_between_paragraphs' );
    
    function so_25888630_ad_between_paragraphs($content){
        /**-----------------------------------------------------------------------------
         *
         *  @author       Pieter Goosen <http://stackoverflow.com/users/1908141/pieter-goosen>
         *  @return       Ads in between $content
         *  @link         http://stackoverflow.com/q/25888630/1908141
         * 
         *  Special thanks to the following answers on my questions that helped me to
         *  to achieve this
         *     - http://stackoverflow.com/a/26032282/1908141
         *     - http://stackoverflow.com/a/25988355/1908141
         *     - http://stackoverflow.com/a/26010955/1908141
         *     - http://wordpress.stackexchange.com/a/162787/31545
         *
        *------------------------------------------------------------------------------*/ 
        if( in_the_loop() ){ //Simply make sure that these changes effect the main query only
    
            /**-----------------------------------------------------------------------------
             *
             *  wptexturize is applied to the $content. This inserts p tags that will help to  
             *  split the text into paragraphs. The text is split into paragraphs after each
             *  closing p tag. Remember, each double break constitutes a paragraph.
             *  
             *  @todo If you really need to delete the attachments in paragraph one, you want
             *        to do it here before you start your foreach loop
             *
            *------------------------------------------------------------------------------*/ 
            $closing_p = '</p>';
            $paragraphs = explode( $closing_p, wptexturize($content) );
    
            /**-----------------------------------------------------------------------------
             *
             *  The amount of paragraphs is counted to determine add frequency. If there are
             *  less than four paragraphs, only one ad will be placed. If the paragraph count
             *  is more than 4, the text is split into two sections, $first and $second according
             *  to the midpoint of the text. $totals will either contain the full text (if 
             *  paragraph count is less than 4) or an array of the two separate sections of
             *  text
             *
             *  @todo Set paragraph count to suite your needs
             *
            *------------------------------------------------------------------------------*/ 
            $count = count( $paragraphs );
            if( 4 >= $count ) {
                $totals = array( $paragraphs ); 
            }else{
                $midpoint = floor($count / 2);
                $first = array_slice($paragraphs, 0, $midpoint );
                if( $count%2 == 1 ) {
                    $second = array_slice( $paragraphs, $midpoint, $midpoint, true );
                }else{
                    $second = array_slice( $paragraphs, $midpoint, $midpoint-1, true );
                }
                $totals = array( $first, $second );
            }
    
            $new_paras = array();   
            foreach ( $totals as $key_total=>$total ) {
                /**-----------------------------------------------------------------------------
                 *
                 *  This is where all the important stuff happens
                 *  The first thing that is done is a work count on every paragraph
                 *  Each paragraph is is also checked if the following tags, a, li and ul exists
                 *  If any of the above tags are found or the text count is less than 10, 0 is 
                 *  returned for this paragraph. ($p will hold these values for later checking)
                 *  If none of the above conditions are true, 1 will be returned. 1 will represent
                 *  paragraphs that qualify for add insertion, and these will determine where an ad 
                 *  will go
                 *  returned for this paragraph. ($p will hold these values for later checking)
                 *
                 *  @todo You can delete or add rules here to your liking
                 *
                *------------------------------------------------------------------------------*/ 
                $p = array();
                foreach ( $total as $key_paras=>$paragraph ) {
                    $word_count = count(explode(' ', $paragraph));
                    if( preg_match( '~<(?:img|ul|li)[ >]~', $paragraph ) || $word_count < 10 ) {  
                        $p[$key_paras] = 0; 
                    }else{
                        $p[$key_paras] = 1; 
                    }   
                }
    
                /**-----------------------------------------------------------------------------
                 *
                 *  Return a position where an add will be inserted
                 *  This code checks if there are two adjacent 1's, and then return the second key
                 *  The ad will be inserted between these keys
                 *  If there are no two adjacent 1's, "no_ad" is returned into array $m
                 *  This means that no ad will be inserted in that section
                 *
                *------------------------------------------------------------------------------*/ 
                $m = array();
                foreach ( $p as $key=>$value ) {
                    if( 1 === $value && array_key_exists( $key-1, $p ) && $p[$key] === $p[$key-1] && !$m){
                        $m[] = $key;
                    }elseif( !array_key_exists( $key+1, $p ) && !$m ) {
                        $m[] = 'no-ad';
                    }
                } 
    
                /**-----------------------------------------------------------------------------
                 *
                 *  Use two different ads, one for each section
                 *  Only ad1 is displayed if there is less than 4 paragraphs
                 *
                 *  @todo Replace "PLACE YOUR ADD NO 1 HERE" with your add or code. Leave p tags
                 *  @todo I will try to insert widgets here to make it dynamic
                 *
                *------------------------------------------------------------------------------*/ 
                if( $key_total == 0 ){
                    $ad = array( 'ad1' => '<p>PLACE YOUR ADD NO 1 HERE</p>' );
                }else{
                    $ad = array( 'ad2' => '<p>PLACE YOUR ADD NO 2 HERE</p>' );
                }
    
                /**-----------------------------------------------------------------------------
                 *
                 *  This code loops through all the paragraphs and checks each key against $mail
                 *  and $key_para
                 *  Each paragraph is returned to an array called $new_paras. $new_paras will
                 *  hold the new content that will be passed to $content.
                 *  If a key matches the value of $m (which holds the array key of the position
                 *  where an ad should be inserted) an add is inserted. If $m holds a value of
                 *  'no_ad', no ad will be inserted
                 *
                *------------------------------------------------------------------------------*/ 
                foreach ( $total as $key_para=>$para ) {
                    if( !in_array( 'no_ad', $m ) && $key_para === $m[0] ){
                        $new_paras[key($ad)] = $ad[key($ad)];
                        $new_paras[$key_para] = $para;
                    }else{
                        $new_paras[$key_para] = $para;
                    }
                }
            }
    
            /**-----------------------------------------------------------------------------
             *
             *  $content should be a string, not an array. $new_paras is an array, which will
             *  not work. $new_paras are converted to a string with implode, and then passed
             *  to $content which will be our new content
             *
            *------------------------------------------------------------------------------*/ 
            $content =  implode( ' ', $new_paras );
        }
        return $content;
    }
    

    EDIT

    From your comments:

    • You should use the following code to debug <pre><?php var_dump($NAME_OF_VARIABLE); ?></pre>. For your first issue, I will use ?><pre><?php var_dump($paragraphs); ?></pre><?php just after this line $paragraphs = explode( $closing_p, wpautop($content) ); to exactly see how the content is split up. This will give you an idea if your content is being split up correctly.

    • Also use ?><pre><?php var_dump($p); ?></pre><?php just after this line $p = array(); to inspect what value is given to a specific paragraph. Remember, paragraphs with img, li and ul tags should have a 0, also paragraphs with less than 10 words. The rest should have a 1

    • EDIT -> Issue with stripped paragraphs is resolved. Thanks for pointing that flaw out to me. Never realized this. I have updated the code, so just simply copy and paste it

    EDIT 2

    Please note, you cannot test your code with the online tool you are using. This is not a true reflection of of what is output by the_content(). The output that you see using your online tool is filtered and marked up before being send to the screen as the_content(). If you inspect your output with a browser like google chrome, you will see that p tags are correctly applied.

    I have also changed the a tag with image tag in the code

  2. with the sample code you have given me this is the best i can do, there were far too many images in there, you would need a genius to figure out the logic required for your requirements, but try it out it might not be too far off. You’ll need php 5.5 for this.

    a couple of points to note:
    1. it identifies paragraphs as being wrapped in p elements, not as in visual paragraphs.

    2. if p elements exist inside other elements, it will also recognise them as paragraphs. The first ad is a example of this. Avoid using p inside blockquotes, lists, etc, its not necessary, use spans, divs for text instead.
    3. I have commented a line calling a function in __construct, uncomment this to insert the 2nd image. This actually works well but your content has a lot of p elements with sentances split over a number of p’s, this is unlikely to be a factor in actual content.
    4. It searches for images in para 1 + para 2 and removes them.

    $content = apply_filters('the_content', get_the_content() ); 
    
        class adinsert {
    
    
        var $content;
        var $paragraphs;    
        var $ad_pos1=2;
        var $ad_pos2;
        var $ad_pos3;
        var $ad= '<h1>ad position here</h1>';
    
    public function __construct($content) {
    
        if(!$content)
            return $content;
    
        $this->set_content($content);
    
        $this->paragrapherize(2);
        $this->paragraph_numbers();
        $this->get_first_pos();
        $this->paragrapherize();
        $this->paragraph_numbers();
        $this->find_images();
        $this->find_ad_pos(); 
        $this->insert_ads();
    
    }
    
    
    public function echo_content(){
        echo $this->content;
    }
    
    private function insert_ads() {
    
        if($this->ad_pos2 && $this->ad_pos2 != 'end'):
            $posb= $this->ad_pos2;
            $this->content=substr_replace($this->content,$this->ad,$posb ,0);
        else:
            $this->content.= $this->ad;    
        endif;
    
        //comment the below line to remove last image insertion 
        $this->content.= $this->ad;
    }
    
    private function get_first_pos() {
    
        $i=0;
    
        foreach($this->paragraphs as $key=>$data):
            if($i==0):
    
                $length= $data['end']-$data['start'];
                $string= substr($this->content, $data['start'],$length);    
                $newkey= $key+1;
                $lengthb= $this->paragraphs[$newkey]['end']-$this->paragraphs[$newkey]['start'];
                $stringb= substr($this->content, $this->paragraphs[$newkey]['start'],$lengthb);
    
                $wcount= count(explode(' ', $string));
    
                if( preg_match('/(<img[^>]+>)/i', $string, $image) ):
                        $newstring=preg_replace('/(<img[^>]+>)/i', '', $string);
    
                            if($wcount>10):
                                $newstring.=$this->ad;
                                $this->ad_pos1=1;       
                                $this->content=str_replace($string,$newstring,$this->content);
                            endif;
                else:
                            if($wcount>10) :
                                $newstring=$string.$this->ad;
                                echo $newstring;
                                $this->ad_pos1=1;
                                //$this->content=str_replace($string,$newstring,$this->content);
                                $this->content= preg_replace('~'.$string.'~', $newstring, $this->content, 1);
                            endif;
                endif;
    
                if( preg_match('/(<img[^>]+>)/i', $stringb, $imageb) ):
                            $newstringb=preg_replace('/(<img[^>]+>)/i', '', $stringb);  
                            if($wcount<10) :
                            $newstringb.=$this->ad;
                            $this->ad_pos1=2;
                            $this->content=str_replace($stringb,$newstringb,$this->content);
                            endif;
                else:
                            if($wcount<10) :
                                $newstring=$stringb.$this->ad;
                                $this->ad_pos1=2;
                                $this->content=str_replace($stringb,$newstringb,$this->content);
                            endif;
                endif;
    
            else:
                break;
            endif;
            $i++;       
        endforeach;
    }
    
    
    private function find_ad_pos() {
    
        $remainder_images= $this->paragraph_count;
        if($remainder_images < $this->ad_pos1 + 3):
            $this->ad_pos2='end';
        else:   
    
            foreach($this->paragraphs as $key=>$data):
                $p[]=$key;
            endforeach;
    
            unset($p[0]);
            unset($p[1]);
    
            $startpos= $this->ad_pos1 + 2;
            $possible_ad_positions= $remainder_images - $startpos;
        //figure out half way
            if($remainder_images < 3): //use end pos
                $pos1= $startpos;
                $pos1=$this->getclosestkey($pos1, $p);
            else: // dont use end pos
                $pos1=  ($remainder_images/2)-1;
                $pos1= $this->getclosestkey($pos1, $p);
            endif;
            $this->ad_pos2= $this->paragraphs[$pos1]['end'];
        endif;
    }
    
    
    private function getclosestkey($key, $keys) {
        $close= 0;
        foreach($keys as $item): //4>4
            if($close == null || $key - $close > $item - $key ) :
              $close = $item;
            endif;
        endforeach;
        return $close;
    }
    
    
    
    private function find_images() {
    
        foreach($this->paragraphs as $item=>$key):
            $length= $key['end']-$key['start'];
            $string= substr($this->content, $key['start'],$length);
            if(strpos($string,'src')!==false && $item !=0 && $item !=1):
                //unset the number, find start in paragraphs array + 1 after
                unset($this->paragraphs[$item]);
                $nextitem= $item+1;
                $previtem= $item-1;
                unset($this->paragraphs[$nextitem]);
                unset($this->paragraphs[$previtem]);
            endif;          
        endforeach;
    
    }
    
    
    
    
    
    private function paragraph_numbers() {
    
        $i=1;
        foreach($this->paragraphs as $item):
            $i++;
        endforeach; 
        $this->paragraph_count=$i;
    }
    
    private function paragrapherize($limit=0) {
    
        $current_pos=0;
        $i=0;
    
        while( strpos($this->content, '<p', $current_pos) !== false ):
    
        if($limit && $i==$limit)
            break;
    
        if($i==105) {
            break;
        }
            if($i!=0) {
                $current_pos++; 
            }
    
    
            $paragraph[$i]['start']=strpos($this->content, '<p', $current_pos);//1
    
        //looking for the next time a /p follows a /p so is less than the next position of p
    
        $nextp= strpos($this->content, '<p', $paragraph[$i]['start']+1); //14 failing on next???
        $nextendp= strpos($this->content, '</p>', $current_pos);//22
    
        if($nextp>$nextendp)://NO
            $paragraph[$i]['end']=$nextendp;
            if( ($nextendp - $paragraph[$i]['start']) < 80 ):
                unset($paragraph[$i]);
            endif;
    
            $current_pos= $nextendp;
            $i++;   
        else:   
    
        $startipos = $nextendp;
    
            $b=0;                                           
            do {
                if($b==100){
                   break;
                }
    
                $nextp= strpos($this->content, '<p', $startipos); //230
                $nextendp= strpos($this->content, '</p>', $startipos+1);//224
    
    
                if($nextp>$nextendp) {
    
                    $paragraph[$i]['end']=$nextendp;
                    $current_pos= $nextendp;
    
                    $i++;
                } else {
                    $startipos = $nextendp+1;
                }
                $b++;
    
            } while ($nextp < $nextendp );
        endif;
            endwhile;
            $this->paragraphs= $paragraph;
        }
    
        public function set_content($content) {
            $this->content= $content;
        }
    
    }
    
    $newcontent= new adinsert($content);
    

    then where you want to output your content

     <?php echo $newcontent->echo_content(); ?>
    
  3. This is one solution. It’s not fully programmatic, but I’ve done it before and it will work. Basically, use a “shortcode”. The problem with doing it fully programatically is that there’s no nice way of finding if the flow of an article with inline images, videos, etc. would cause the ad placements to look really bad. Instead, set up your CMS with a shortcode so that editors can place ads in an optimal place in the article.

    E.g.

    <p>Bacon ipsum dolor sit amet short ribs tenderloin venison pastrami meatloaf kevin, shoulder meatball landjaeger pork corned beef turkey salami frankfurter jerky. Pork loin bresaola porchetta strip steak meatball t-bone andouille chuck chicken shankle shank tongue. Hamburger flank kevin short ribs. Pork loin landjaeger frankfurter corned beef, fatback salami short loin ground round biltong.</p>
    
    [ad_block]
    
    <p>Pastrami jerky drumstick swine ribeye strip steak pork belly kevin tail rump pancetta capicola. Meatloaf doner porchetta, rump tenderloin t-bone biltong pork belly. Porchetta boudin ham ribeye frankfurter flank short loin, drumstick pork loin filet mignon chuck fatback. Strip steak jowl capicola ham hock turducken biltong ground round filet mignon venison prosciutto chuck pork. Venison ribeye fatback kielbasa, ball tip turducken capicola drumstick sausage pancetta boudin turkey ham strip steak corned beef.</p>
    

    Then, using PHP’s str_replace you can simply swap out the ad_block shortcode with your HTML for your ads.

    E.g.

    echo str_replace('[ad_block]', $ad_block_html, $content);
    
  4. I did some rework and this is what I finally got.
    Feel free to test and give me some feedback.

    class Advertiser
    {
        /**
         * All advertises to place
         *
         * @access  private
         * @var     array
         */
        private $ads        = array();
    
        /**
         * The position behind the </p> element nearest to the center
         *
         * @access  private
         * @var     int
         */
        private $center     = null;
    
        /**
         * The content to parse
         *
         * @access  private
         * @var     string
         */
        private $content    = null;
    
        /**
         * Constructor method
         *
         * @access  public
         * @param   string  $content    the content to parse (optional)
         * @param   array   $ads        all advertises to place (optional)
         * @return  object              itself as object
         */
        public function __construct ($content = null, $ads = array())
        {
            if (count($ads)) $this->ads = $ads;
            if ($content) $this->setContent($content);
    
            return $this;
        }
    
        /**
         * Calculates and sets the position behind the </p> element nearest to the center
         *
         * @access  public
         * @return  object              the position behind the </p> element nearest to the center
         */
        public function calcCenter ()
        {
            $content = $this->content;
    
            if (!$content) return $this;
    
            $center = ceil(strlen($content)/2);
    
            $rlpos  = strripos(substr($content, 0, $center), '</p>');
            $rrpos  = stripos($content, '</p>', $center);
    
            $this->center = 4 + ($center-$rlpos <= $rrpos-$center ? $rlpos : $rrpos);
    
            return $this;
        }
    
        /**
         * Places the first ad
         *
         * @access  public
         * @param   string  $ad optional; if not specified, take the internally setted ad
         * @return  object      itself as object
         */
        public function placeFirstAd ($ad = null)
        {
            $ad = $ad ? $ad : $this->ads[0];
            $content = $this->content;
    
            if (!$content || !$ad) return $this;
    
            // the position before and after the first paragraph
            $pos1 = strpos($content, '<p');
            $pos2 = strpos($content, '</p>') + 4;
    
            // place ad
            $content = substr($content, 0, $pos2) . $ad . substr($content, $pos2);
    
            // strip images
            $content = substr($content, 0, $pos1) . preg_replace('#<img(?:s.*?)?/?>#i', '', substr($content, $pos1, $pos2)) . substr($content, $pos2);
    
            $this->content = $content;
    
            return $this;
        }
    
        /**
         * Places the second ad
         *
         * @access  public
         * @param   string  $ad optional; if not specified, take the internally set ad
         * @return  object      itself as object
         */
        public function placeSecondAd ($ad = null)
        {
            $ad = $ad ? $ad : $this->ads[1];
            $content = $this->content;
    
            if (!$content || !$ad) return $this;
    
            $center = $this->center;
    
            // place ad
            $content = substr($content, 0, $center) . $ad . substr($content, $center);
    
            $this->content = $content;
    
            return $this;
        }
    
        /* Getters */
    
        /**
         * Gets the content in it's current state
         *
         * @access  public
         * @return  string  the content in it's current state
         */
        public function getContent ()
        {
            return $this->content;
        }
    
        /* Setters */
    
        /**
         * Sets the content
         *
         * @access  public
         * @param   string  $content    the content to parse
         * @return  object              itself as object
         */
        public function setContent ($content)
        {
            $this->content = $content;
    
            $this->calcCenter();
    
            return $this;
        }
    
        /**
         * Sets the first ad
         *
         * @access  public
         * @param   string  $ad the ad
         * @return  object      itself as object
         */
        public function setFirstAd ($ad)
        {
            if ($ad) $this->ad[0] = $ad;
    
            return $this;
        }
    
        /**
         * Sets the second ad
         *
         * @access  public
         * @param   string  $ad the ad
         * @return  object      itself as object
         */
        public function setSecondAd ($ad)
        {
            if ($ad) $this->ad[1] = $ad;
    
            return $this;
        }
    }
    

    Usage example:

    $first_ad   = 'bacon';
    $second_ad  = 'ham';
    
    $content    = apply_filters('the_content', $post->post_content);
    
    $advertiser = new Advertiser($content);
    
    $advertiser->placeFirstAd($first_ad);
    //$advertiser-> placeSecondAd($second_ad);
    
    $advertised_content = $advertiser->getContent();
    

    You can comment the placeSecondAd() out or replace it with your working function.

  5. Here is a smart way:

    $content = "<p>A</p><p>B</p><p>C</p><p>D</p>";
    $pos = 2;
    $content = preg_replace('/<p>/', '<helper>', $content, $pos + 1); //<helper>A</p><helper>B</p><helper>C</p><p>D</p>
    $content = preg_replace('/<helper>/', '<p>', $content, $pos);     //<p>A</p><p>B</p><helper>C</p><p>D</p>
    $content = str_replace("<helper>", "<p>ad</p><p>", $content);     //<p>A</p><p>B</p><p>ad</p><p>C</p><p>D</p>
    

    Here is a complete function:

    function insertAd($content, $ad, $pos = 0){
      // $pos = 0 means randomly position in the content
      $count = substr_count($content, "<p>");
      if($count == 0  or $count <= $pos){
        return $content;
      }
      else{
        if($pos == 0){
          $pos = rand (1, $count - 1);
        }
        $content = preg_replace('/<p>/', '<helper>', $content, $pos + 1);
        $content = preg_replace('/<helper>/', '<p>', $content, $pos);
        $content = str_replace('<helper>', $ad . "n<p>", $content);
        return $content;
      }
    }
    
  6. Ok this is an idea to move you in (possibly) a direction closer to the fully programmatic way you’re asking…

    Use a HTML DOM parser like http://simplehtmldom.sourceforge.net/ to parse each article. Using this, you should theoretically be able to pick out all the paragraph tags, and then insert the ad blocks in between the right ones based on your math.

    $html = str_get_html($content);
    $paragraphs_arr = $html->find('p'); //Returns all paragraphs
    $halfway_mark = ceil(count($paragraphs_arr) / 2);
    
    $halfway_paragraph = $paragraphs_arr[$halfway_mark];
    
    // Somewhere here now you just have to figure out how to inject the ad right after the halfway paragraph.
    
    E.g.
    $p_content = $halfway_paragraph->innertext();
    $p_content = $p_content."</p><p><!-- Ad --></p>"; // Force close the tag as we're in the innertext.
    $halfway_paragraph->innertext($p_content);
    

    Does that get a bit closer to what you’re trying to do?

  7. $content = apply_filters('the_content', $post->post_content);
    // $middle will mark the center of the $content based on number of characters and is aware of words and will therefor mark the middle to the nearest space. +1 is added to then remove the whitespace at the end of the string.
    $middle = strrpos(substr($content, 0, floor(strlen($content) / 2)), ' ') + 1;
    // $first_half will substring the text to the first half based on where $middle says to split it
    $first_half= substr($text, 0, $middle);
    // $second_half substrings the text to the seconf half based on where $first_half is split.
    $second_half= substr($text, $middle);
    echo $first_half.'...';
    echo ' YOUR ADS CODE';
    echo $second_half;