WordPress:DOMDocumentを使ってAdSenseを挿入する

このブログはまだはてなブログですが、WordPress に移行したサイトがあり、そのサイトの目次のない記事中に AdSense を入れる方法です。



目次があれば h2 の前、なければ…

当該サイトにははてなダイアリーの頃からの記事がありますので目次なんてものが入っていない記事がたくさんあります。はてなブログで運用していた時には、Javascript で本文の子要素 Element.children の何個置きかに AdSense を挿入していたんですが、せっかく WordPress にしたんですから、サーバーサイドで挿入できないかということです。


目次がある場合

PHPにもDOM操作を出来る DOMDocument というクラスがありますが、目次があるのであれば各項目記事の最後、見出し <h2></2> の前に入れたほうが読む流れを中断しないということで preg_replace を使って挿入するほうが簡単にすみます。ググってもそうした記事がほとんどです。


例えば、

add_filter('the_content', 'add_adsense');
function add_adsense($content){
    $ads = '<div class="ads-content">
<ins class="adsbygoogle" style="display:block" data-ad-client="ca-pub-1635719600228159" data-ad-slot="4785084320" data-ad-format="auto" data-full-width-responsive="true"></ins>
<script>(adsbygoogle = window.adsbygoogle || []).push({});</script>
</div>';
    if( strpos( $content, '</h2>') !== false ){
        $content = preg_replace( '/(<h2.+?<\/h2>)$/im', $ads . PHP_EOL . '$1', $content );
    }
    return $content;
}

これで <h2></h2> の前に AdSense が挿入されます。もちろん各 AdSense データはご自身のものに変更してください。


目次がない場合

目次がない場合でも上記と同じ方法で、例えば </figure></blockqupote> を検索してその前や後ろに入れることも出来ますが、それですと AdSense が入っているのかどうかもわかりませんし、何個入っているかもコントロールできません。


(略)
if( strpos( $content, '</figure>') !== false ){
    $content = preg_replace( '/(<figure.+?<\/figure>)$/ims', $ads . PHP_EOL . '$1', $content );
}else if( strpos( $content, '</blockquote>') !== false ){
    $content = preg_replace( '/(<\/blockquote>)$/im', '$1' . PHP_EOL . $ads, $content );
}
(略)


<p></p> の何個置きかで挿入

<p></p> の数を数えてその何個置きかに挿入する方法もないわけではありません。


(略)
$paragraph = '/<p.+?<\/p>/i';
preg_match_all( $paragraph, $content, $matches, PREG_OFFSET_CAPTURE );
$count = count($matches[0]);
if( $matches[0] ){
    for( $i=0; $i<$count; $i+=10 ){
        $content = str_replace( $matches[0][$i][0], $ads . PHP_EOL . $matches[0][$i][0], $content );
    }
}
(略)


これで <p></p> 10個置きに AdSense が挿入されます。ただし、問題があります。たとえば、<p> </p> のように空白だけなどの段落がありますと、そのすべてにマッチしてしまいますので、記事中が AdSense で埋まってしまいます。実際当該サイトにはそうした記事が結構あり、これでは使い物になりません。


DOMDocument クラスでDOM操作に挑戦


DOMDocument で Javascript のようにDOMを扱えるかと思いましたら、なかなかそういうわけにいかず、いろいろやった結果、次の方法で本文の子要素を数えてその途中に要素を挿入することが出来ました。他に方法があるのかもしれません。


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

    // ここからは class-wp-widget-text.php のコードをを利用した
    $doc = new DOMDocument();

    // Suppress warnings generated by loadHTML.
    $errors = libxml_use_internal_errors( true );
    // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
    @$doc->loadHTML(
        sprintf(
            '<!DOCTYPE html><html><head><meta charset="%s"></head><body>%s</body></html>',
            esc_attr( get_bloginfo( 'charset' ) ),
            $content
        )
    );
    libxml_use_internal_errors( $errors );
    // ここまでは class-wp-widget-text.php のコードをを利用した

    $xpath = new DOMXPath( $doc );

    // AdSense は個人データ
    $ads = $doc->createElement('div');
    $ads->setAttribute('class', 'ads-content');
    $ins = $doc->createElement('ins');
    $ins->setAttribute('class', 'adsbygoogle');
    $ins->setAttribute('style', 'display:block');
    $ins->setAttribute('data-ad-client', 'ca-pub-1635719600228159');
    $ins->setAttribute('data-ad-slot', '4785084320');
    $ins->setAttribute('data-ad-format', 'auto');
    $ins->setAttribute('data-full-width-responsive', 'true');
    $ads->appendChild($ins);
    $script = $doc->createElement('script', '(adsbygoogle = window.adsbygoogle || []).push({});');
    $ads->appendChild($script);
    // AdSenseここまで

    $elem = $xpath->query( '//body/h2' );
    if($elem->length != 0){
        for ($i = 0; $i < $elem->length; $i++){
            $child = $elem->item($i);
            $child->parentNode->insertBefore($ads->cloneNode(true), $child);
        }
    }else{
        $elem = $xpath->query( '//body/*' );
        // 子要素の3個めの前から20個おきの場合
        for ($i = 2; $i < $elem->length; $i+=20){
            $child = $elem->item($i);
            $child->parentNode->insertBefore($ads->cloneNode(true), $child);
        }
    }

    $html = $doc->saveHTML();
    preg_match('/<body>(.*)<\/body>/s', $html, $matches);
    return $matches[1];

}


多分、目次がある場合は preg_replace を使ったほうが早いような気がしますので、

(略)
if( strpos( $content, '</h2>') !== false ){
        $content = preg_replace( '/(<h2.+?<\/h2>)$/im', $ads . PHP_EOL . '$1', $content );
}else{
(略)
}

で分岐したほうがいいように思います。


今回、初めて DOMDocument を使いましたのでまだよくわからないところも多いのですが、どうやら $doc = new DOMDocument(); だけではDOMにはならないようです。


他サイトをパースしたい案件もありますので、DOMDocument まだだまこれからです。


ライセンス等

ご使用の場合は以下の注意事項をお守りください。

  • ライセンスは IMUZA.com にあります。
  • 紹介は歓迎ですが、バグ対応ができなくなりますので転載はしないでください。
  • 紹介していただく場合は、当記事へのリンクをお願いします。
  • 自己責任でお使いください。
  • お問い合わせ、バグの報告、仕様変更のご要望等は Contact Us までお願いします。