関連記事を手動で設定できる Microkid’s related posts WordPressプラグイン

投稿記事に関連する記事を手動で選択しリンク表示できるプラグインはいくつかあるのですが、そのひとつ「Microkid’s related posts」をインストールしてみました。

タイトルが「Related Posts for WordPress」となっていて、同一名で別のプラグインがあったりとインストールには注意が必要です。すでに開発が終了しているのか、2年以上プラグインの更新が止まっていますが、WordPress3.9.2で問題なくインストールできました。以前から存在したプラグインですので、私が無知なだけで、今回紹介するまでもなかったのかもしれませんが(汗)。

Microkid’s related posts」の機能を簡単に説明しますと、関連記事の登録は手動のみ。関連記事数も手動なだけに制限なし。記事元に関連記事を登録するとリンク先記事にもリンク記事元が自動で登録され、相互リンクとなります。
例えばA記事、B記事があるとします。A記事からB記事へ関連記事をリンク登録したとします。するとB記事には自動でA記事への関連記事が登録されます。
サイト外(他人)へのリンクであれば拒否られてしまい一方通行ということもありますが、サイト内の記事であれば普通は相互リンクでよいはずですよね。

唯一の問題点は、関連記事の選択順(ソート順)が維持されないということ。相互リンクが逆に悪影響を及ぼしているように見えますが、ここは設計(テーブル構造)から見直さないと難しそうなので目をつぶることにします。作成者は順番を意識したテーブル構成にはなっているようですが、実装がまずいのか選択した順序通り表示されない場合があります。


Microkid’s related postsのインストールと設定手順

  1. プラグインのインストール
    WordPressのプラグインインストール検索でキーワードを「Related Posts for WordPress」で検索しますと、検索結果が同一名で2件ヒットします。このうちひとつはVersion.1.7.2、もうひとつが4.0.3で全く別物です。4.0.3のほうが「Microkid’s related posts」になります。作成者(Author)が「Microkid」になっているほうですね。一方本来の「Microkid’s related posts」で検索すれば、正しく1件ヒットします。
  2. 設定メニュー
  3. 設定画面
  4. 関連記事登録画面

「Microkid’s related posts」をしばらく使用していると不具合が発覚。関連記事を検索するキーワードが日本語(マルチバイト)の場合、検索にヒットせず結果が表示されないという不具合。キーワードにヒットする記事もあるのですが、取りこぼす記事もあるなど検索機能がいまいちです。とにかく検索で記事をヒットさせない限り関連記事を登録するすべがないのでこれは致命的です。

記事検索でヒットしないとなると考えられるのは検索対象のデータベース。WordPressはMySQLというデータベースを使用していますので、おそらく記事テーブルを検索するクエリー(SQL)があやしいはず。すでに開発が2年以上止まってますしサポートは期待できそうもないので、SQLに問題がないか調べてみることにしました。

早速プラグインのソースを斜め読みしますと、あっさり検索のためにSQLを発行する関数を特定。microkids-related-posts.phpのMRP_ajax_search_results()という関数ですね。対象ファイル数が少ないので簡単でした。
このMRP_ajax_search_results()関数を詳しくみますと、ビンゴです、検索条件を指定するWHERE句に正規表現(REGEXP)という文字列検索演算子が使用されていました。MySQLのREGEXP演算子は日本語などのマルチバイトをサポートしていないため、おそらくここが原因かと。

REGEXP演算子の代替手段はLIKE演算子ですね。マルチバイト圏の開発ではMySQLでの文字列検索なら普通はLIKE演算子を使用するか、どうしても正規表現を使用したいなら拡張しますよね。蛇足ですが、PostgreSQLであれば、10年以上前(Ver7.2.3)にすでにサポート済なのでREGEXP演算子(~)でもよいのですが。
このプラグインを作成した作者はおそらくシングルバイト圏(アルファベット文字のみ)の方なのでしょう、マルチバイトを考慮していなかった模様。個人レベルならそこまでの試験はしないのでここは仕方ないですね。
ただREGEXPを使用するに至った経緯は、おそらく検索の”速さ”なのでしょうが、信憑性のない検索結果なんてユーザからすれば不要なんですよね!ちょっとぐらい遅くても正確な検索がなんですよね!

すでに開発が2年以上止まってしますし、今後プラグインの更新はないだろうと踏み、プラグインに手を入れることにしました。

まず、どのようなSQLが発行されているか「チャンギ」という検索キーワードで調べてみました。

  • ■「チャンギ」検索キーワードでの実行SQL
    SELECT
        ID,
        post_title,
        post_type,
        post_status
    FROM
        hogehogeposts
    WHERE
        post_content REGEXP '[[:<:]]チャンギ' AND
        post_type = 'post' AND
        ID != 2725 AND
        post_status NOT IN ('inherit', 'auto-draft')
    ORDER BY
        post_date DESC LIMIT 50
    

    やはり、「REGEXP ‘[[:<:]]チャンギ’」になっていました。これでは検索にヒットしないのもわかります。

ソースコードは以下です。

  • ■microkids-related-posts.php の MRP_show_related_posts() 関数
    /**
    * MRP_ajax_search_results - Display AJAX search results
    *
    */ 
    function MRP_ajax_search_results() {
    	global $wpdb;
    	$s = $wpdb->escape( rawurldecode( $_GET['mrp_s'] ) );
    	$scope = (int) $_GET['mrp_scope'];
    	$post_type = $wpdb->escape( $_GET['mrp_post_type'] );
    	$regexp = "[[:<:]]" . $s;
    	$where = "";
    	switch( $scope ) {
    		case 1 :
    			$where = "post_title REGEXP '$regexp'";
    			break;
    		case 2 :
    			$where = "post_content REGEXP '$regexp'";
    			break;
    		default :
    			$where = "( post_title REGEXP '$regexp' OR post_content REGEXP '$regexp' )";
    			break;
    	}
    	$query = "SELECT ID, post_title, post_type, post_status FROM $wpdb->posts WHERE $where AND post_type = '$post_type' ";
    
    
  • ■変更箇所
    上記関数の536,539,542行目のREGEXP演算子を以下のようにLIKE演算子に書き換えます。

    REGEXP '$regexp'
       ↓
    LIKE '%$s%'
    
  • ■変更後のmicrokids-related-posts.php の MRP_show_related_posts() 関数
    /**
    * MRP_ajax_search_results - Display AJAX search results
    *
    */ 
    function MRP_ajax_search_results() {
    	global $wpdb;
    	$s = $wpdb->escape( rawurldecode( $_GET['mrp_s'] ) );
    	$scope = (int) $_GET['mrp_scope'];
    	$post_type = $wpdb->escape( $_GET['mrp_post_type'] );
    	$regexp = "[[:<:]]" . $s;
    	$where = "";
    	switch( $scope ) {
    		case 1 :
    			$where = "post_title LIKE '%$s%'";
    			break;
    		case 2 :
    			$where = "post_content LIKE '%$s%'";
    			break;
    		default :
    			$where = "( post_title LIKE '%$s%' OR post_content LIKE '%$s%' )";
    			break;
    	}
    	$query = "SELECT ID, post_title, post_type, post_status FROM $wpdb->posts WHERE $where AND post_type = '$post_type' ";
    
  • ■diff確認
    変更前後のdiffは以下の通りです。

    % diff -u microkids-related-posts.php.org microkids-related-posts.php
    --- microkids-related-posts.php.org     2012-04-02 16:31:56.000000000 +0900
    +++ microkids-related-posts.php 2014-11-09 14:21:25.000000000 +0900
    @@ -533,13 +533,13 @@
            $where = "";
            switch( $scope ) {
                    case 1 :
    -                       $where = "post_title REGEXP '$regexp'";
    +                       $where = "post_title LIKE '%$s%'";
                            break;
                    case 2 :
    -                       $where = "post_content REGEXP '$regexp'";
    +                       $where = "post_content LIKE '%$s%'";
                            break;
                    default :
    -                       $where = "( post_title REGEXP '$regexp' OR post_content REGEXP '$regexp' )";
    +                       $where = "( post_title LIKE '%$s%' OR post_content LIKE '%$s%' )";
                            break;
            }
            $query = "SELECT ID, post_title, post_type, post_status FROM $wpdb->posts WHERE $where AND post_type = '$post_type' ";
    
  • この修正で日本語(マルチバイト)文字でも正しく検索可能になりました。
    蛇足ですが、記事IDも検索対象としたい場合は、542行目を以下のように変更すれば「both」を選んだ場合は記事IDも検索対象となります。

    			$where = "( ID LIKE '%$s%' OR post_title LIKE '%$s%' OR post_content LIKE '%$s%' )";
    

まさかこんなトラップが仕掛けられているとは夢にも思ってなかったので(汗)、かなりのの長文になってしました。(汗)この業界、得てしてこのような意図しないトラップが仕掛けられている場合が多いんですよね(汗)