To properly count paragraphs, and to be consistant, the best will be is to make use of raw post content, and then apply the wpautop
filter to the raw content. This way, WordPress will handle all the variables which at the end will determine how paragraphs are build and displayed
We can also look at a couple of other things to make this as reliable as possible
Because this is only needed for a single post, we will make use of the queried object to get our raw post content from
Make sure we have a minimum amount of paragraphs before inserting our adds
Make use of the in_the_loop()
condition to only target the main query loop
-
I have decided to add all of this in a class
/**
* Class AdsINParagraphs
*
* Class to inject ads into single post's content between paragraphs
* as required
*/
class GoogleAdsINParagraphs
{
/**
* @var string $ad
* @access protected
* @since 1.1.0
*/
protected $ad;
/**
* @var int|string $min
* @access protected
* @since 1.1.0
*/
protected $min;
/**
* @var array $inject
* @access protected
* @since 1.1.0
*/
protected $inject;
/**
* @var $post = NULL
* @access protected
* @since 1.1.0
*/
protected $post = NULL;
/**
* @var $paragraphs = NULL
* @access protected
* @since 1.1.0
*/
protected $paragraphs = NULL;
/**
* @var $paraCount = 0
* @access protected
* @since 1.1.0
*/
protected $paraCount = 0;
/**
* @var $contentReplacement = NULL
* @access protected
* @since 1.1.0
*/
protected $contentReplacement = NULL;
/**
* Public method __construct()
*
* Constructor for the GoogleAdsINParagraphs class
*
* @param string $ad The add that should be injected
* @param string|int $min The minimum amount of paragraphs that must exist
* @param array $inject An array of paragraph numbers where the add should be injected
* If you need an ad before the content, simply pass 0 as first value
* @access public
* @since 1.1.0
*/
public function __construct( $ad = NULL, $min = 4, $inject = [] )
{
$this->ad = $ad;
$this->min = $min;
$this->inject = $inject;
}
/**
* Public method init()
*
* Initialize the 'the_content' filter callback
*
* @access public
* @since 1.1.0
*/
public function init()
{
add_filter( 'the_content', [$this, 'postContent'] );
}
/**
* Protected method initHelperMethods()
*
* Initialize all the helper functions for the 'the_content' filter callback
*
* @access protected
* @since 1.1.0
*/
protected function initHelperMethods()
{
$this->post();
$this->paragraphs();
$this->paraCount();
$this->contentReplacement();
}
/**
* Protected method post()
*
* Gets the current post object and sanitize the post object. Sanitation
* is done as precaution should the query object being altered by some other
* program/function/malicious code
*
* @access protected
* @since 1.1.0
*/
protected function post()
{
$post_raw = get_queried_object();
// Sanitize the post object in case that something externally changed the queried object
$post = sanitize_post( $post_raw );
$this->post = $post;
}
/**
* Protected method paragraphs()
*
* Gets the current post's post content, sanitizes it and then applies the wpautop filter
* function to it to apply p tags to the raw content. After the filter function is applied,
* the content is exploded into an array of paragraphs
*
* @access protected
* @since 1.1.0
*/
protected function paragraphs()
{
// Just in case, as extra precaution, run the raw post comment through wp_kses_post
$noEvilContent = wp_kses_post( $this->post->post_content );
$peedContent = wpautop( $noEvilContent );
$paragraphs = explode( '<p>', $peedContent );
$this->paragraphs = $paragraphs;
}
/**
* Protected method paraCount()
*
* Counts the amount of paragraphs created by the paragraphs() method
*
* @access protected
* @since 1.1.0
*/
protected function paraCount()
{
$paraCount = count( $this->paragraphs );
$this->paraCount = (int) $paraCount;
}
/**
* Protected method contentReplacement()
*
* This is where everything happens. The following checks are run
* before ads are inserted into the content
* - Check if there is at least the set minimum amount of paragraphs.
* Checks are done against $this->min
* - Check if there at least enough paragraphs to fullfil the lowest
* passed number in $this->inject
* - Check to see if $this->inject is a valid array with values
*
* If all checks pass, ads is inserted into the content. If any value
* in $this->inject is more than the amount of paragraphs, the ad is ignored
* and not inserted
*
* @access protected
* @since 1.1.0
*/
protected function contentReplacement()
{
// Lets first run all our checks
$this->contentReplacement = $contentReplacement = NULL;
// Make sure that we have at least more paragraphs than our minimum, bail if not
if ( $this->paraCount < $this->min )
return $this->contentReplacement;
// Make sure that $this->inject is an array and not empty
if ( !is_array( $this->inject )
|| !$this->inject
)
return $this->contentReplacement;
/**
* We will do the following checks and validation on $this->inject
* - validate the array of ID's and remove negative numbers
* - sort $this->inject array numerically ASC according to passed ID's
* - remove possible dublicates
*/
$this->inject = filter_var(
$this->inject,
FILTER_VALIDATE_INT,
[
'flags' => FILTER_REQUIRE_ARRAY,
'options' => [
'min_range' => 0
]
]
);
asort( $this->inject );
$this->inject = array_unique( $this->inject );
// Make sure that we actaully have enough paragraphs to inject an add into
if ( $this->paraCount < $this->inject[0] )
return $this->contentReplacement;
// Now we can start to inject our ads into our content. Set $output as empty string
$output = '';
foreach ( $this->paragraphs as $key=>$paragraph ) {
// Add ad in front of content if $this->inject[0] === 0
if ( 0 === $key
&& 0 === $this->inject[0]
) {
$output .= '<p>' . $this->ad . '</p>' . $paragraph;
} elseif ( !in_array( $key, $this->inject ) ) {
$output .= $paragraph;
} else {
$output .= $paragraph . '<p>' . $this->ad . '</p>';
}
} // endforeach
$this->contentReplacement = $output;
}
/**
* Public method postContent()
*
* This is the callback method for the 'the_content' filter. The content passed by reference
* inside the filter is replaced by any valid value from $this->contentReplacement. This is done
* purely for convenience
*
* @access public
* @since 1.1.0
* @return $content
*/
public function postContent( $content )
{
// Lets first run our checks, make sure this is a single page and the main query
if ( !is_single() )
return $content;
if ( !in_the_loop() )
return $content;
// Initialize our other helper methods. They should only run inside our condition
$this->initHelperMethods();
// Make sure $this->contentReplacement is not NULL, if so, just return $content
if ( NULL === $this->contentReplacement )
return $content;
// We can now replace $content with $this->contentReplacement
$content = $this->contentReplacement;
return $content;
}
} // end of class GoogleAdsINParagraphs
You can now use it as follow
$ad = 'PLACE YOUR AD HERE';
$add_ads = new GoogleAdsINParagraphs(
$ad, // Your ad which you want to add
4, // There should be at least 4 paragraphs
[4, 20] // Array of paragraph numbers after which you want to inject an add
);
$add_ads->init();