check-a-tag-has-an-image-or-not1

WORDPRESS投稿内のリンクに独自クラスを付与し、cssでのテキスト装飾やライトボックス系のJSプラグインを扱いやすくする

cssでテキストを装飾したり、ライトボックス系のJSプラグインの設定する方法はいくつかあります。ブログの管理者がある程度ウェブ馴れしているなら、image_send_to_editorフックを用いて記事作成時にクラスを与える(※外部リンク)方法がシンプルでよいと思います。しかし、CMSなどの納品したブログでは、どこからかコピペしてきた謎のHTMLを挿入されてまんまとレイアウト崩れを起こす…なんてこともしばしば。だったらもういっそ、すべての投稿に含まれる画像やリンクにまとめてクラスをつけてしまおうと、そういうわけです。具体的には、the_contentで投稿にフックし、正規表現でその中からリンクを拾い、画像リンクかテキストリンクのどちらなのか、さらにリンク先が画像かどうかを明示するクラスを挿入しています。これならwordpressのエディタを使って書いていない記事にも対応できます。

出力した感じはざっくりとこんなですね。

<p>テキストテキストテキスト。</p>
<p><a class="text-link" href="http://example.com">おすすめのウェブサイトです。</a></p>
<p><a class="image-link" href="http://example.com"><img src="/screenshot.jpg"></a></p>
<p>テキストテキストテキスト。</p>
<p><a class="text-link lightbox" href="/image.jpg">画像を拡大表示します。</a></p>
<p><a class="image-link lightbox" href="/image2.jpg"><img src="/image2s.jpg"></a></p>
<p>テキストテキストテキスト。</p>

 

CODE: Link item CSS class filtering

<?php
//Check the content has an <a> tag with an image or not, and add class to it. 
function linkclass_filter( $content ) {
    global $post;
    preg_match_all('/<a.*?</a>/i', $content, $matches, PREG_SET_ORDER);
    foreach ($matches as $def_link) {
        $link = $def_link[0];
        if ( preg_match('/<a.*?>.*?<img.*?>.*?</a>/i', $link) ) { //画像を含むとき
            $link = preg_replace('/<img(.*?)src=['"]([^'"]+).(bmp|gif|jpeg|jpg|png)['"](.*?)>/i', '<img$1src="$2#p#$3"$4>', $link); //画像srcのエスケープ
            $link = preg_replace('/<img(.*?)class=(.*?)>/i', '<img$1cl#a#ss=$2>', $link); //画像classのエスケープ
            $linkclass = 'image-link'; //画像リンクにつけるクラス
        } else {
            $linkclass = 'text-link'; //テキストリンクにつけるクラス
        }
        if ( preg_match('/<a.*?href=['"]([^'"]+).(bmp|gif|jpeg|jpg|png)['"].*?>/i', $link) ) { //リンク先が画像のとき
            $linkclass .= ' lightbox'; //ライトボックスなどのjs用のクラス
            $linkrel = ' data-group="lightbox-'.$post->ID.'"'; //任意のデータやrelを持たせる処理
        } else {
            $linkrel = '';
        }
        if ( preg_match('/<a.*?class=.*?href=.*?>.*?</a>/i', $link) || preg_match('/<a.*?href=.*?class=.*?>.*?</a>/i', $link) ) {//aタグにclassがあるとき
            $link = preg_replace('/<a(.*?)class="(.*?)"(.*?)>/i', '<a$1class="'.$linkclass.' $2"$3'.$linkrel.'>', $link);  //aタグにclassを追加
        } else {
            $link = preg_replace('/<a(.*?)>/i', '<a class="'.$linkclass.'"$1'.$linkrel.'>', $link); //aタグにclassを付与
        }
        $link = str_replace( array('#p#','#a#'), array('.','a'), $link); //エスケープ解除
        $content = str_replace( $def_link[0], $link, $content );
    }
    return $content;
}
add_filter('the_content','linkclass_filter');
?>

 

設置方法

上記をfunctions.phpに記載します。もう少しスマートにできそうな気もしますが…まあ機能してるしいいんです…。

ajaxfy_comment

プラグインに頼らずテーマ内で完結するAJAXコメントフォーム。

前回に引き続き、コメントの周りのカスタマイズ。

一昔前にQuick Commentsというのがあって、いまはAjax CommentsやWP Ajaxify Commentsというのもあって、でも環境依存の問題か上手く動作しなかったり、もっさりしていたりで、いくつもある他のプラグインの動作確認をやるのもしんどいしで、どうにも…あ゛ーって感じになったので、自作してみたら意外とすんなりまとまった、と。

※2015/2/25 …. undefinedを区別するようにjsを修正しました。このコードはAJAXを使用しているので、htmlタグが誤っていたり、複雑な構造になっている場合、あるいは他のjs系プラグインを使用している場合、干渉しあって期待通りの動作をしないこともあります。十分に注意してお使いください。

 

CODE: Ajaxify WordPress comments

<?php
// コメントフォームのAJAX化
function ajaxify_comments_script(){
if ( is_singular() && comments_open() )://固定や投稿等シングルページで動作、場所によっては要変更
?>
<script id="comment-script" type="text/javascript">
jQuery(function( $ ){

    var commentbox_name = '#comments', //コメントテンプレート名、カスタマイズ時は要変更
        commentform_name = '#commentform', //コメントフォーム名、カスタマイズ時は要変更
        commentbox = $( commentbox_name ),
        commentform = $( commentform_name, commentbox );
    ajaxCommentsTrigger();

    function ajaxComments( this_form ){
        var statusdiv = $('<div class="comment-ajax"></div>'); // AJAXの結果を吹き出す窓、デザインはCSSで指定してね
        this_form.append( statusdiv ).submit( function (e) {
            var formdata = this_form.serialize(), // フォームをシリアライズ
                formurl = this_form.attr('action'); // フォームのactionに設定されたurl
            e.preventDefault();
            statusdiv.html('<div class="ajax-processing">コメントを送信中…</div>'); // 送信されると.comment-ajax内に表示
            $.ajax({ 
                type: 'post',
                url: formurl,
                data: formdata
            }).done(function(data,status){
                if( status == "success" ){
                    var matches = data.match(/<body+s+id="error-page">([sS]*?)</body>/);
                    if ( matches ) {
                        var output = $('<div class="ajax-error">' + matches[1] + '</div>');
                        output.find('*:empty').remove();
                        output.find('*:first-child').siblings().remove();
                        statusdiv.html( output );
                        return false;
                    }
                    var newlist = $( data ).find( commentbox_name );
                    if ( newlist.length ){ 
                        statusdiv.html('<div class="ajax-success">コメントありがとうございました。</div>');
                        reply = this_form.parents( 'li.depth-1' ).attr( 'id' );
                        if ( typeof reply !== "undefined" ) {
                            window.location.hash = reply;
                            statusdiv.find( '.ajax-success' ).stop().delay( 500 ).slideUp( 200,function(){
                                commentbox.replaceWith( newlist );
                                commentbox = $( commentbox_name );
                                ajaxCommentsTrigger();
                                $('html, body').animate({
                                    scrollTop: $( '#' + reply ).offset().top
                                }, 500);
                            });
                        }
                    } else {
                        statusdiv.html('<div class="ajax-error">エラーが発生しました。ウェブマスターにご連絡ください。</div>');
                        return false;
                    }
                } else {
                    statusdiv.html('<div class="ajax-error">エラーが発生しました。時間をおいて再度送信して下さい。</div>');
                    return false;
                }
            }).fail(function(data){
                statusdiv.html('<div class="ajax-error">送信が中断されました。<strong>記入もれや間違いがないか</strong>再度ご確認ください。</div>');
            });
        });
    }

    function ajaxCommentsTrigger(){
        var replybox = $( '<div id="reply-box"></div>' ),
            commentform = $( commentform_name, commentbox );
        commentform.clone( true ).appendTo( replybox );
        replybox.find( '[id]' ).each(function(){
            $(this).prop('id', 'reply-' + $(this).attr( 'id' ) );
        });
        ajaxComments( commentform );
        ajaxComments( replybox.children( 'form' ) );
        $( 'a.comment-reply-link', commentbox ).click(function(e){ 
            e.preventDefault();
            var replylink = $(this), list = replylink.parents( 'li.depth-1' );
            if ( list.hasClass( 'replying' ) ){
                list.removeClass( 'replying' );
                replylink.html( '返信する' );
                replybox.slideUp( 500 );
            } else {
                list.addClass( 'replying' );
                list.siblings('.replying').removeClass( 'replying' ).find( 'a.comment-reply-link').html( '返信する' );
                replylink.html( '返信をキャンセル' );
                var parentid = list.attr('id').slice(8); // ex:#comment-999
                replybox.find( '#reply-comment_parent' ).val( parentid );
                replybox.hide().appendTo( list ).slideDown( 500 );
            }
        });
    }
});
</script>
<?php
endif;
}
add_action('wp_footer', 'ajaxify_comments_script');
?>

このサンプルコードではfunctions.phpに記載することを前提にwp_footerにフックしてますが、jQueryだけで完結してるんでjs部分をテンプレートに直書きしてもOKです。動作はここのフォームでコメントなしで送信ボタンを押せば、エラーが返ってきますのでどうぞ。ちなみに前記事「Really Simple CAPTCHAをコメント欄に応用する。」も併用できます。

 

どうやってヴァリデートするか、が肝なのかも。

ajaxでpostするところまではどのプラグインでも動作は大体同じ。コメントのヴァリデーション方法については作者毎に個性があるが、comment_postにフックしてajax時のみ処理を分岐させて云々…というのが常道のようだった。それで良いのだけども、結局、コメント投稿に失敗したときに返ってくるエラーページを読み込んでPOSTの可否を判断することにした。htmlソースで判断することの是非はあるかもしれないが、フック処理が不要になるしwordpressのエラーメッセージを流用できるので、関数をシンプルに保てて、保安上もメリットになると思えたからだ。

もうひとつの大きな設計判断としては、コメント後にユーザーにだけ見えるモデレート待ち のコメントを表示する方法をどうするかだけども、これはコメントが通過後に返ってくる新しいポストから#commentsタグを抽出して丸ごと差し替えること にした。キャプチャを使用している場合にはその更新が必要だったり、コメントテンプレートのデザインやソートがテーマ毎にまちまちだったりするので、コメントテンンプレートをまるごと差し替えるのが現実的だろうというわけです。

おまけ:ここで適用してるcss

ajaxは思いの外時間がかかることもあるので、css 3のアニメーションなどを活用してユーザーを迷わせないよう設計することが大事ですね。

.comment-ajax { margin:5px 0; }
.comment-ajax > div { padding:5px 10px; font-size:16px; }
.ajax-error { background:#356; color:#fff; }
.ajax-success { background:#2bb; color:#fff; } 
.ajax-processing { position:relative; background:#eee; } 
.ajax-processing:before { width:100%; display:block; content:' '; margin:0; padding:0; background:#a9e3e4; position:absolute; height:3px; left:0; bottom:0; -webkit-animation:fullexpand 1.5s ease-out; z-index:1; animation:fullexpand 1.5s ease-out; }
@-webkit-keyframes fullexpand { 0%  { width:0;} 100%{ width:100%;} }
@keyframes fullexpand { 0% { width:0;} 100%{ width:100%;} }

Really Simple CAPTCHAをコメント欄に応用する

WordPressのスパムコメント、スパムメールは結構困り者で、キャプチャ(画像認証)を導入するケースも多いと思われる。ところで、Contact Form 7には公式モジュールとしてReally Simple CAPTCHAがあり、名前のとおり軽量でデザイン性も高くありながら、大抵のブログにとって必要充分な機能をもたらしてくれるという優れもの。一方でコメントスパム防止に導入できるキャプチャプラグインも幾つかあるわけだが、Contact Form 7を使っているなら、デザインの整合性からいってこれが流用できるとプラグイン構成の簡素化もできてうれしいのでやってみた、という記事です。

 

CODE:using Really Simple CAPTCHA for wordpress comments

以下、コメントフォームへの挿入とヴァリデーションを有効化するコードです。functions.phpに記入、my_comment_captcha以下のフォームの記述は、テーマに合わせて変更します。Contact Form 7での設置の仕方は、公式でどうぞ。

<?php
// コメント欄にアンチスパム・キャプチャを追加
// contact-form7用プラグイン Really Simple CAPTCHA をインストール済の場合だけ有効
if ( !is_user_logged_in() && class_exists('ReallySimpleCaptcha') ) :

function my_comment_captcha() {
    $captcha = new ReallySimpleCaptcha();
    // 以下キャプチャのサイズや色をカスタマイズ
    // $captcha->img_size = array( '72', '24' ); //画像サイズ
    // $captcha->fg = array( '0', '0', '0' ); // 文字色
    // $captcha->bg = array( '255', '255', '255' ); // 画像背景色
    $captcha_word = $captcha->generate_random_word();
    $captcha_prefix = mt_rand();
    $captcha_src = $captcha->generate_image($captcha_prefix, $captcha_word); // Generate CAPTCHA image
?>
<p class="comment-form-captcha">
    <label for="captcha_code">キャプチャ *</label>
    <img src="<?php echo site_url(). '/wp-content/plugins/really-simple-captcha/tmp/' . $captcha_src; ?>" alt="captcha" width="<?php echo $captcha->img_size[0]; ?>" height="<?php echo $captcha->img_size[1]; ?>" />
    <input id="comment_captcha_code" name="comment_captcha_code" size="<?php echo $captcha->char_length; ?>" type="text" />
    <input id="comment_captcha_prefix" name="comment_captcha_prefix" type="hidden" value="<?php echo $captcha_prefix; ?>" />
</p>
<?php
}
add_action( 'comment_form_after_fields' , 'my_comment_captcha' );//#url.inputと#comment.textareaの間に挿入

function my_check_comment_captcha( $approved, $comment_data ) {
    $captcha = new ReallySimpleCaptcha();
    $captcha_prefix = $_POST['comment_captcha_prefix']; // ユーザー入力によるキャプチャレスポンス
    $captcha_code = $_POST['comment_captcha_code']; // ヴァリデーション
    $captcha_correct = $captcha->check( $captcha_prefix, $captcha_code ); // ヴァリデーションチェックが通ると[true]を返す
    if ( !$captcha_correct ) wp_die('キャプチャコードが間違っています。再入力してください。', 200); //エラーコード200を設定すると、他のコメントヴァリデートと同じ挙動になる
    $captcha->remove( $captcha_prefix );
    $captcha->cleanup();
    return $approved;
}
add_filter( 'pre_comment_approved', 'my_check_comment_captcha', 99, 2 );

endif; // if ( class_exists('ReallySimpleCaptcha') ):
?>

 

関連リンクとダウンロード

sc 2015-03-28 14.45.32

実はこれ結構古いネタで、アンチョコは2010年の「Using Really Simple CAPTCHA Plugin for Comments」というやつです。いつの間にかこの記事の筆者はプラグイン化したみたいで、それは試してません。ちょっと弄れば短くなるからfunctions.phpでもええんちゃうかなー、どうせコメント周りの関数いくつかあるしなー、ってことで今にいたります。ヴァリデーション前後の動作も改変してるのでほとんど別ものですが。コンタクトフォームは数あれど、やっぱContact Form 7が好きだなー、という私のような人にどうぞ。

本家サイト:Really Simple CAPTCHA / contact form 7

wordpressで複数タクソノミによるPOSTの絞り込みの方法はそちこちで紹介されています。(個人的にはここが参照しやすいと思う。)こいつを、従来のカテゴリページのようにページングができる複数タクソノミのアーカイブページとして設置したい、というわけです。
multiple_tax

 

パーマリンクがないからパラメータでURLをカスタマイズする

例えばポストに次のような2種類のカスタムタクソノミを割り当てているとします。

taxonomy: color   terms: red / blue / green

taxonomy: form   terms: square / circle / triangle

普通にタクソノミーのテンプレートでは
//example.com/color/red
…とか
//example.com/form/square/page/2
…とかは表示できても、colorはredかつformはsquareみたいにできない。そもそもそんなパーマリンクが存在しないのだから、ページングできるわけがない。この問題を回避するためにURLクエリパラメータを与えることにします。
最終的なURLは
//example.com/items?color=red&form=square
…とか
//example.com/items/page/2?color=blue&form=circle
…のような感じになります。googleさんの検索結果ページと一緒ですね。

 


 

設置の順序

① まずはカスタムタクソノミを作ります。

カスタムタクソノミを$postかカスタム投稿タイプに割り当てます。(まずそこからという方はこちらとかを参照して下さい。)

② アーカイブページの代わりにするための固定ページを作成

自動的に作られるアーカイブページの代わりに適当な名前の固定ページを作成し、適当なページ用のテンプレートphpを用意します。(今ひとつ意味が分からない方はこちらとかが参考になるかもしれません。)WP_Queryを使ってカスタム投稿タイプのアーカイブを表示します。(※以下ではカスタム投稿タイプは「item」、固定ページのslugは「items」と設定したものとして表記していきます。)

<?php
	$args = array(
		'post_type' => 'item',
	);
	$wp_query = new WP_Query();
	$wp_query->query( $args );
	$li = '';
	while ( $wp_query->have_posts() ) : $wp_query->the_post(); 
?>
<div class="post">
	<a href="<?php echo get_permalink( $post->ID );?>" title="<?php echo get_the_title( $post->ID );?">
		<h2><?php the_title( ); ?></h2>
		<div class="excerpt"><?php the_excerpt( ); ?></div>
	</a>
</div>
<?php endwhile; ?>
<div class="page-navigation">
    <div class="older"><?php previous_posts_link( '&laquo; 前へ' ); ?></div>
    <div class="newer"><?php next_posts_link( '次へ &raquo;' ); ?></div>
</div>
<?php $wp_reset_postdata(); ?>

 

③ カテゴリのリンクを作成する

ここから本題です。②の前にカテゴリのリンクを作成します。

<?php 
	$this_url = get_permalink( $post->ID );
	$tax1 = 'color'; //TAX1に自由なタクソノミ名を設定
	$tax2 = 'form'; //TAX2に自由なタクソノミ名を設定
	if ( isset( $_GET[$tax1] ) ) $tax1_get = $_GET[$tax1]; else $tax1_get = 'all'; //URLパラメータを取得
	if ( isset( $_GET[$tax2] ) ) $tax2_get = $_GET[$tax2]; else $tax2_get = 'all'; //URLパラメータを取得
	$tax1_terms = get_terms( $tax1, '&hide_empty=true' );
	if ( $tax1_terms ){ 
		$current = '';
		if ( $tax1_get == 'all' ) $current = 'current';
		$tax1_term_items = "t" .'<li><a href="'.$this_url.'?'.$tax1.'=all&'.$tax2.'='.$tax2_get.'" title="View all '.$tax1.'" class="'.$current.'">All '.$tax1.'</a></li>'. "n";
		foreach( $tax1_terms as $tax1_term ){
			$current = '';
			if ( $tax1_get == $tax1_term->slug ) $current = 'current';
			$tax1_term_items .= "t" .'<li><a href="'.$this_url.'?'.$tax1.'='.$tax1_term->slug.'&'.$tax2.'='.$tax2_get.'" title="'.$tax1_term->name.'" class="'.$current.'">' .esc_html( $tax1_term->name ). '</a></li>'. "n";
		}
	}
	$tax1_term_items = '<ul id="tax1">' ."n". $tax1_term_items. '</ul>' ."n";
	echo $tax1_term_items;

	$tax2_terms = get_terms( $tax2, '&hide_empty=true' );
	if ( $tax2_terms ){ 
		$current = '';
		if ( $tax2_get == 'all' ) $current = 'current';
		$tax2_term_items = "t" .'<li><a href="'.$this_url.'?'.$tax1.'='.$tax1_get.'&'.$tax2.'=all" title="View all '.$tax2.'" class="'.$current.'">All '.$tax2.'</a></li>'. "n";
		foreach( $tax2_terms as $tax2_term ){
			$current = '';
			if ( $tax2_get == $tax2_term->slug ) $current = 'current';
			$tax2_term_items .= "t" .'<li><a href="'.$this_url.'?'.$tax1.'='.$tax1_get.'&'.$tax2.'='.$tax2_term->slug.'" title="'.$tax2_term->name.'" class="'.$current.'">' .esc_html( $tax2_term->name ). '</a></li>'. "n";
		}
	}
	$tax2_term_items = '<ul id="tax2">' ."n". $tax2_term_items. '</ul>' ."n";
	echo $tax2_term_items;
?>

※特定のタクソノミに絞られている環境で、好みのデザインを適用するイメージで書いてますので、ループさせずに似たような処理を繰り返していますが、不特定多数のタクソノミで絞り込んでいくようなカタログサイトだったらadd_query_argなどの関数を使ってループさせた方が吉です。

出力したカテゴリはたぶんこんな感じ↓になるはず。

<ul id="tax1">
	<li><a href="http://example.com/items?color=all&form=all" title="View all color" class="current">color</a></li>
	<li><a href="http://example.com/items?color=red&form=all" title="レッド" class="">red</a></li>
	<li><a href="http://example.com/items?color=blue&form=all" title="ブルー" class="">blue</a></li>
	<li><a href="http://example.com/items?color=green&form=all" title="グリーン" class="">green</a></li>
</ul>
<ul id="tax2">
	<li><a href="http://example.com/items?color=all&form=all" title="View all form" class="current">form</a></li>
	<li><a href="http://example.com/items?color=all&form=square" title="square" class="">square</a></li>
	<li><a href="http://example.com/items?color=all&form=circle" title="circle" class="">circle</a></li>
	<li><a href="http://example.com/items?color=all&form=triangle" title="triangle" class="">triangle</a></li>
</ul>

$_GET[ $tax ]でパラメータを取得し、表示されているカテゴリのaタグのclassにcurrentを付与してます。上はパラメータが何も付いていないか、allが選択されている場合です。もし現在選択されているcurrentがblueとtriangleだとすると、以下のように代入されます。
#tax1↓
href=”~/items?color=all&form=triangle” class=””
href=”~/items?color=red&form=triangle” class=””
href=”~/items?color=blue&form=triangle” class=”current
href=”~/items?color=green&form=triangle” class=””

#tax2↓
href=”~/items?color=blue&form=all” class=””
href=”~/items?color=blue&form=square” class=””
href=”~/items?color=blue&form=circle” class=””
href=”~/items?color=blue&form=triangle” class=”current

④ tax_Queryにパラメータを代入してやる

最後にWP_Queryのtax_queryにカスタムタクソノミのパラメータを放り込んでやる必要があります。②の$argsを下記のように補完してやります。

<?php
	$paged = get_query_var( 'paged' ) ? get_query_var( 'paged' ) : 1;
	$args = array(
		'post_type' => 'item', //自由なポストタイプを設定
		'paged' => $paged,
	);
	if ( $tax1_get !== 'all' ) { 
		$tax1_arg = array(
			'taxonomy' => $tax1,
			'field' => 'slug',
			'terms' => $tax1_get,
		);
	} else {
		$tax1_arg = '';
	}
	if ( $tax2_get !== 'all' ) { 
		$tax2_arg = array(
			'taxonomy' => $tax2,
			'field' => 'slug',
			'terms' => $tax2_get,
		);
	} else {
		$tax2_arg = '';
	}
	if ( !empty($tax1_arg) || !empty($tax2_arg) ){
		$args['tax_query'] = array(
			$tax1_arg, $tax2_arg
		);
	}
?>

 


 

PHPの記述まとめ

phpの記述をまとめると次のようになります。適当にカスタマイズして下さい。post_typeをpostにタクソノミ名をcategoryとpost_tagにすれば、従来の投稿でも使えます。

<?php 
	//カテゴリのリンクを作成する
	$this_url = get_permalink( $post->ID );
	$tax1 = 'color'; //TAX1に自由なタクソノミ名を設定
	$tax2 = 'form'; //TAX2に自由なタクソノミ名を設定
	if ( isset( $_GET[$tax1] ) ) $tax1_get = $_GET[$tax1]; else $tax1_get = 'all'; //URLパラメータを取得
	if ( isset( $_GET[$tax2] ) ) $tax2_get = $_GET[$tax2]; else $tax2_get = 'all'; //URLパラメータを取得
	$tax1_terms = get_terms( $tax1, '&hide_empty=true' );
	if ( $tax1_terms ){ 
		$current = '';
		if ( $tax1_get == 'all' ) $current = 'current';
		$tax1_term_items = "t" .'<li><a href="'.$this_url.'?'.$tax1.'=all&'.$tax2.'='.$tax2_get.'" title="View all '.$tax1.'" class="'.$current.'">All '.$tax1.'</a></li>'. "n";
		foreach( $tax1_terms as $tax1_term ){
			$current = '';
			if ( $tax1_get == $tax1_term->slug ) $current = 'current';
			$tax1_term_items .= "t" .'<li><a href="'.$this_url.'?'.$tax1.'='.$tax1_term->slug.'&'.$tax2.'='.$tax2_get.'" title="'.$tax1_term->name.'" class="'.$current.'">' .esc_html( $tax1_term->name ). '</a></li>'. "n";
		}
	}
	$tax1_term_items = '<ul id="tax1">' ."n". $tax1_term_items. '</ul>' ."n";
	echo $tax1_term_items;
	$tax2_terms = get_terms( $tax2, '&hide_empty=true' );
	if ( $tax2_terms ){ 
		$current = '';
		if ( $tax2_get == 'all' ) $current = 'current';
		$tax2_term_items = "t" .'<li><a href="'.$this_url.'?'.$tax1.'='.$tax1_get.'&'.$tax2.'=all" title="View all '.$tax2.'" class="'.$current.'">All '.$tax2.'</a></li>'. "n";
		foreach( $tax2_terms as $tax2_term ){
			$current = '';
			if ( $tax2_get == $tax2_term->slug ) $current = 'current';
			$tax2_term_items .= "t" .'<li><a href="'.$this_url.'?'.$tax1.'='.$tax1_get.'&'.$tax2.'='.$tax2_term->slug.'" title="'.$tax2_term->name.'" class="'.$current.'">' .esc_html( $tax2_term->name ). '</a></li>'. "n";
		}
	}
	$tax2_term_items = '<ul id="tax2">' ."n". $tax2_term_items. '</ul>' ."n";
	echo $tax2_term_items;
	//tax_Queryにパラメータを代入
	$paged = get_query_var( 'paged' ) ? get_query_var( 'paged' ) : 1;
	$args = array(
		'post_type' => 'item', //自由なポストタイプを設定
		'paged' => $paged,
	);
	if ( $tax1_get !== 'all' ) { 
		$tax1_arg = array(
			'taxonomy' => $tax1,
			'field' => 'slug',
			'terms' => $tax1_get,
		);
	} else {
		$tax1_arg = '';
	}
	if ( $tax2_get !== 'all' ) { 
		$tax2_arg = array(
			'taxonomy' => $tax2,
			'field' => 'slug',
			'terms' => $tax2_get,
		);
	} else {
		$tax2_arg = '';
	}
	if ( !empty($tax1_arg) || !empty($tax2_arg) ){
		$args['tax_query'] = array(
			$tax1_arg, $tax2_arg
		);
	}
	//クエリを出力
	$wp_query = new WP_Query();
	$wp_query->query( $args );
	while ( $wp_query->have_posts() ) : $wp_query->the_post();
?>
<div class="post">
	<a href="<?php echo get_permalink( $post->ID );?>" title="<?php echo get_the_title( $post->ID );?">
		<h2><?php the_title( ); ?></h2>
		<div class="excerpt"><?php the_excerpt( ); ?></div>
	</a>
</div>
<?php endwhile; ?>
<div class="page-navigation">
    <div class="older"><?php previous_posts_link( '&laquo; 前へ' ); ?></div>
    <div class="newer"><?php next_posts_link( '次へ &raquo;' ); ?></div>
</div>
<?php wp_reset_postdata(); ?>

abstract13

関連度に応じた関連記事を表示する

お題の通り、wordpress用の自作の関連記事表示ファンクションです。精度の高さを求めるなら記事内容に踏み込んだプラグイン(YARPPとか)を使うのが基本です。しかしそういったプラグインを回避したいこともある。その場合、タグで記事を表示したり、カテゴリ毎の新着記事を出力したりで充分なケースもあるが、やはり関連性という点でもの足らず、どうしても古い記事が埋もれてしまいがちに。で、ここで紹介する方法は、既存のプラグイン未満だけどもタグとタクソノミの重複度で関連度合いの評価を加えたものです。このページの下で使っています。

 

主な特徴

関数で処理するメリットとしてはやっぱりアレンジが容易いことです。主な特徴としては以下の通り。

カスタム投稿タイプやカスタムタクソノミに対応

タグ・カテゴリ・カスタムタクソノミの重複回数に応じて関連度を計測

カスタムフィールドに関連度の高い記事のIDを登録

ちなみに、デフォルトではタグが他のタクソノミよりも優先的に点数付けされる設定にしていますが、カスタムタクソノミなどケースバイケースで設定してください。

 

CODE : Related post IDs in a custom field

以下の関数に任意にアレンジを加えてfunctions.phpに記述します。

 

/*
* plugin name: Related post IDs in a custom field
* author: Mizuho Ogino
* author URI: //web.contempo.jp/
* license: //www.gnu.org/licenses/gpl.html GPL v2 or later
*/
/////////////////////// タグとカテゴリによる関連記事の表示 /////////////////////// 

function get_related_posts( $show_post = 5, $target_posttype = '', $post_id = ''){
    if ( empty( $post_id ) ) $post_id = get_post()->ID;
    if ( empty( $post_id ) ) return;
    if ( empty( $target_posttype ) ) $target_posttype = get_post( $post_id )->post_type;
    $ex_show_post = get_post_meta( $post_id, '_my_relatedposts_shownum', true ); // 関連記事の表示数
    $rel_date = get_post_meta( $post_id, '_my_relatedposts_update', true ); // 関連記事の登録日
    $modified_date = mysql2date( 'Ymd', get_lastpostmodified(), false ); // ブログの最終更新日
    $posts_per_page = -1; // 検索するpost数  [-1] で全て
    //以下、 関連記事が登録されていない場合 || 関連記事登録日よりもあとに記事が更新されている場合 || 表示関連記事数の設定が変わった場合、のみ新規で関連記事を設定する
    if( empty($rel_date) || $modified_date > $rel_date || $show_post != $ex_show_post ){

        $rel_ids = array();
        $taxes = get_object_taxonomies( get_post( $post_id )->post_type, 'names' ); // 使用タクソノミを取得(カテゴリ+カスタムタクソノミ)
        unset( $taxes['post_format'] ); // post_formatを除外
        $tax_count = 0;
        $tax_count = count($taxes);
        $tax_array = array_fill( 0, $tax_count, '' );
        if ($taxes) { 
            foreach ( $taxes as $taxname ) {
                 $terms = get_the_terms( $post_id, $taxname );
                if ( $terms ){
                    foreach ( $terms as $term ) {
                        $rel_ids[ $taxname ][] = $term->term_id; // タームIDを配列に入れる
                    }
                    $tax_array[] = array(
                        'taxonomy' => $taxname, // categoryとかpost_tagとか
                        'field' => 'id', // 'id' または 'slug'
                        'terms' => $rel_ids[ $taxname ], // int または string または array
                        'include_children' => false, // 子カテゴリを含まない
                        'operator' => 'IN',
                    );
                } 
            }
        }
        $args = array(
            'post__not_in' => array( $post_id ), // このpostを除外
            'posts_per_page' => $posts_per_page, // 検索するpost数
            'post_status' => 'publish', // 公開postに限定
            'post_type' => $target_posttype, // ターゲットポストタイプ
            'tax_query' => array_merge( array('relation' => 'OR'), $tax_array )
        );
        $rel_query = get_posts( $args );
        if ( $rel_query ) : foreach ($rel_query as $rel ):
            $rel_point = 0;
            $set_id = $rel->ID;
            foreach ( $taxes as $taxname ) {
                 $terms = get_the_terms( $set_id, $taxname );
                if ( is_array($terms)) : foreach ( $terms as $term ):
                    if ( isset( $rel_ids[ $taxname ]) && in_array( $term->term_id , (array)$rel_ids[ $taxname ] ) ){ // 関連IDを含むかチェック
                        $rel_point++; 
                        if( $taxname == 'post_tag' ){ $rel_point++; } // ポストタグを他タクソノミより優先するようにポイントを補正 必要に応じてコメントアウトや変更をして下さい。
                    }
                endforeach; endif; // end $terms foreach
            } // end $taxes foreach
             $rel_with[ $set_id ] = intval($rel_point); // 関連度数
        endforeach; endif; 
        if ( !empty( $rel_with ) ) {
            arsort( $rel_with ); // 関連度数でソート
            $i = 0;
            foreach ( $rel_with as $key => $val ) { 
                if( $i >= $show_post ) { 
                    break;
                } else {
                    $rel_posts[] = $key;
                    echo '<!-- POST ID:'.$key. ' / RELATION POINT:' .$val. ' -->' ."n";
                    $i++;
                }
            }
            update_post_meta( $post_id, '_my_relatedposts', $rel_posts );
            echo '<!-- RELATED POSTS FOR POST-'.$post_id. ' ARE UPLOADED!! -->' ."n";
        } else {
            $no_related = false;
            update_post_meta( $post_id, '_my_relatedposts', $no_related );
        }
        $timestamp = date( 'Ymd' );
        update_post_meta( $post_id, '_my_relatedposts_update', $timestamp );
        update_post_meta( $post_id, '_my_relatedposts_shownum', $show_post );
    }
    $return_array = get_post_meta( $post_id, '_my_relatedposts', true );
    return $return_array;
}

関連記事IDをカスタムフィールドに登録しているので、通常表示には処理時間はかかりません。記事の新規作成があったときに関連記事が再設定されるため、そのページを訪れた最初のユーザーにだけ処理が発生します。関数の冒頭にある[ $modified_date ]は[ 20140430 ]のような日付型になっていますので、ブログの更新頻度が高い場合、更新後一ヶ月以上経っていたら関連記事も更新する[ $modified_date = mysql2date( ‘Ymd’, get_lastpostmodified(), false ) – 30; ]とすればかなりの程度負担が調整できます。また[ $posts_per_page = -1; ]を[ $posts_per_page = 200; ]としたりして新しい記事限定にすれば、検索にかかる時間が短縮できます。(wp_cronの使用に支障のある環境だったので上記のような手法で設計していますが、問題なく使える環境であればcronですべての投稿を処理してしまっても良いと思います。)

 

テーマ内で呼び出す方法

get_related_posts()という関数で呼び出すと、IDが配列で得られます。このサイトでは以下のように呼び出しています。

<?php
$related_posts = get_related_posts(5);
   $related_links = "t". '<ul>' ."n"; 
if ( $related_posts ):
  foreach( $related_posts as $rid ):
    $related_links .= "tt". '<li id="related-post-'.$rid.'"><a href="'.get_permalink( $rid ).'">' .get_the_title( $rid ). '</a></li>'. "n";
  endforeach; 
    $related_links .= "t". '</ul>' ."n";
?>
<!--関連記事リスト START-->
<div id="related-posts">
  <strong>Related posts</strong>
<?php echo $related_links; ?>
</div><!--関連記事リスト END-->
<?php else: ?>	
関連記事がない場合の処理
<?php endif; ?>