WordPress Popular Posts(ja)

English version is available in WordPress Popular Posts

ブログ投稿や固定ページの閲覧回数や、コメントの数を計算して、サイトのどこがもっとも利用されているかを訪問者に知らせるプラグインがあります。昔はどこのサイトにも「アクセスカウンタ」というものが設置してあって、閲覧回数を数値で表示していました。サイト作成者の虚栄心を満たすにはよいツールでしたが、ブログの投稿でそれをやろうというのがこのタイプのプラグインです。ランキングにする、というのがミソで、ウェブでデータベースを普通に使えるようになったおかげですね。ただし、現在は、Google Analyticsや、Automattic社の運営するWordPress.comとそれが提供するJetpackのようなプラグインがあって、人気のある投稿を表示するのに、ウィジェットを使うことができるようになっているので、必ずしも自前のデータベースで運用しなければならないということはなくなりました。

Google APIを利用するものには、Google Analyticsと連動した、Google Analytics Popular Postsというのもありますが(*1)、今回はWordPress Popular Posts(WPPと略記)の方を使ってみます。WPPは、Google Analytics Popular Postsとはちがって、プラグイン単体で使えるというお手軽さが魅力です。

  1. もうメンテナンスされていないかもしれません。日本語のサポートページはつながらず、2年近く更新がとまっています。

Super Cacheと同時に使えないという報告もちらほらあります。この手のプラグインは、画面推移をトリガーにして、現在のページIDの閲覧数を加算していくというデータの持ち方をするのが基本ですから、そのままの動作を維持しながらキャッシュ系のプラグインと同時に使うのは難しいでしょう。WPPはページIDの取得を、the_contentをトリガーにして、WordPressの$post変数を通して受け取るモードと、$_POST[‘ID’]を使うモードを分けて、キャッシュ系プラグインがある場合は後者を使うことで、両立させようとしているようです。が、My Thumbnails keep dissapearing after upgradeを見ると、W3 Total CacheでCDNを使っているとサムネールが表示されないとか、まだ不具合があるようです。その他、qTranslateにも対応させてあるようです。「一緒には使えません」、「仕様です」って言ってもいいのに、ちゃんと対応しようとしている姿勢は好感が持てます。

さて、SQLite Integrationで使う場合ですが、インストール、有効化は問題なくできますが、そのままでは意図したとおりに動作しません。原因は、エイリアスにシングル・クオートを使っているためです。たとえば、こういうところ。

カラムのエイリアスには全てシングルクオートが使われ、テーブルのエイリアスには使われていません。バッククオートを使っている場所は一か所もありません(*1)

  1. ANSIモードでMySQLを使う場合、Oracleでは、ダブルクオーテーションが使えます。MS SQL Server、MS Accessではスクエアブラケット([])が使えます。

ためしに実験してみました。コマンドラインで、sqlite3を使っています(バージョンは3.7.14)。

sqlite> SELECT p.ID AS id, p.post_title AS title FROM wp_posts AS p;
id          title
----------  ----------
1           Hello world!
2           サンプルページ

sqlite> SELECT p.ID AS 'id', p.post_title AS 'title' FROM wp_posts AS 'p';
id          title
----------  ----------
1           Hello world!
2           サンプルページ

同じ出力が得られます。ためしにダブルクオーテーションにしても同じ出力が得られました。その他、MS SQL Server互換のスクエア・ブラケット([])、MySQL互換のバッククオート(`)も使うことができます。SQL As Understood By SQLite, SQLite Keywordsを読むと、シングルクオートの場合だけ、文字列リテラルと解釈されるようですが、文字列リテラルが許されない場所で使われると、識別子として解釈されるそうです。

一方のMySQLはというと、MySQLに付属のユーティリティmysqlで試してみると、下のようになります。

mysql> SELECT p.ID AS id, p.post_title AS title FROM wp_posts AS p;
+----+-----------------------+
| id | title                 |
+----+-----------------------+
|  1 | Hello world!          |
|  2 | サンプルページ        |
+----+-----------------------+

mysql> SELECT p.ID AS 'id', p.post_title AS 'title' FROM wp_posts AS 'p';
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''p'' at line 1

mysql> SELECT p.ID AS 'id', p.post_title AS 'title' FROM wp_posts AS p;
+----+-----------------------+
| id | title                 |
+----+-----------------------+
|  1 | Hello world!          |
|  2 | サンプルページ        |
+----+-----------------------+

どうやら、テーブルのエイリアスにシングルクオートを使うとエラーになるようです。ダブルクオートを使っても同じです。バッククオートは、テーブルエイリアスに使っても動作しました。

さて、上の実験を見ると、WordPress Popular Postsのコードは全く問題がないように見えます。事実、問題ないのですが、SQLite Integrationの関数を通すと、違った振る舞いをします。まず、クオートなしの場合。

実行結果は下のようになります。予想どおりですね。

Array
(
    [0] => Array
        (
            [id] => 1
            [title] => Hello world!
        )

    [1] => Array
        (
            [id] => 2
            [title] => サンプルページ
        )
)

クオートをつけた場合。

これを実行すると、空の配列が返ります。

Array
(
)

これは、SQLite Integrationのシングルクオートとダブルクオートの扱いによります。SQLite Integrationは、PHPのPDO::bindValueをエミュレートする形で、クオートの中を別の配列に保存し、SQLステートメントのほうは":param_number"というようなプレースホルダに変えてPDO::prepareを実行します。正しい場所で変換が行われれば、PDOStatementオブジェクトが返り、PDOStatement::execute()にその配列を渡して実行すれば正しい結果が返ります。詳しくは、PHP Data Objectsを参照していただければと思いますが、例をあげましょう。

:param_0のところには、もともと’Hello world!’というようなクオートされたデータが入っていたところです。もともとの機能としては、次のような使い方を想定しているようです。

  1. データがユーザの入力の場合、SQLステートメントに最初から入れるのではなくて、危険な入力を事前に処理できる(*1)
  2. 同じステートメントを値を変えただけで実行するときに、ステートメントがキャッシュされるので、2度目以降のオーバーヘッドがない
  1. WordPressが持っている$wpdb->prepare()メソッドはこれを模した仕組みで、addslashes()かmysql_real_escape_string()[PHP5.5.0で非推奨]を使ってデータをエスケープします。

ところが、後の例は、正しい場所に:param_numがないために、PDOStatement::prepareの段階でエラーになります。オブジェクトが作られず、例外が発生しますが、この例外はSQLite Integrationがキャッチして、エラー処理をします。この例外の結果は、$wpdb->suppress_errorsをfalseに、$wpdb->show_errorsの値をtrueにそれぞれセットすると出力されるようになりますが、デフォルトでは出力されません。

さて、これを回避する手段は今のところありません。申し訳ありませんが、WordPress Popular Postsの方を書き換えてご利用ください。というわけで、パッチです。上記の修正のほか、2か所ほどプロパティとメソッドを間違えているところがあったので、直してあります(*1)

  1. 作者のHéctorには報告済み。次のリリースで直すそうです。

readme.txt にあるように、WPP は、WP-PostRatingsと連動することができます。WP-PostRatings の方は特に何もしなくても使えます。必要な場合は、通常の手順でインストールしてください。

One thought on “WordPress Popular Posts(ja)

  1. Pingback: WordPress Popular PostsをSQLite Integrationで動かす方法 | ブログが書けたよ!

Leave a Reply