WordpressのURLをhackする

hackする、ってほどのことではないんですが、ようやく構造がわかってきたので。メモをかねて。

このブログでは、Wordpressを http://penguinlab.jp/blog にインストールし、記事単一のページのURLを http://penguinlab.jp/blog/post/(一意の記事番号、記事スラッグを利用) にするため、管理ページの「設定」-「パーマリンク設定」で、パーマリンクを post/%postname% とした。

さらに同じ設定ページでカテゴリベースとタグベースを設定した(/blog/category/(カテゴリ名) および /blog/tag/(タグ名) とした)のだが、日付別表示に関してはベースURLの設定がなく、/blog/post/2009/4 のようになってしまう。/blog/postは単一記事専用としたいので、ここは /blog/date/2009/4 のようなかたちにしたいところだ。

いろいろ調べてもいい方法がなさそうだったので、結局プラグインを作ることになった。

WordpressがURLを解決する仕組み

まずはWordpressがインストールされているディレクトリにある.htaccessを見てみよう。

# BEGIN WordPressRewriteEngine
OnRewriteBase /blog/RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /blog/index.php [L]
# END WordPress

細かいところは違うだろうが、だいたいこのかたちになっているはずだ。5,6行目の「RewriteCond %{REQUEST_FILENAME} !-f 」「RewriteCond %{REQUEST_FILENAME} !-d 」は、リクエストされたファイル/ディレクトリがなければ、7行目の書き換えルールを適用するという意味だ。7行目は全部index.phpにアクセスさせるということを意味する。つまり、リクエストされたとこにファイルかディレクトリがればそれを返し、なければWordpressのindex.phpに丸投げしている。

で、index.phpでは、リクエストされたURLを解析してindex.phpにパラメータを付けたかたちに書き換える。たとえば /blog/2009/4は/blog/index.php?year=2009&month=4 のように書き換えられ、投稿日が2009年4月の記事が表示される。試してみるとわかるが、この整形後のURLをブラウザのアドレスバー/ロケーションバーに直接入力してもアクセスできる。

URL書き換えルール

で、じゃあそのURL書き換えルール(Rewrite Rules)をどうやって設定してるかというと、(wordpressのインストールディレクトリ)/wp-include/rewrite.phpでそのルールを作っているらしい。Wordpressの管理者ページの「設定」-「パーマリンク設定」で「変更を保存」した際などにこのルールを作り直してるものと思われる。

で、いま、その整形のルールがどんな風になっているかは Internal Rewrite Viewer というプラグインを導入することでみることができる(インストール→有効化→「設定」-「パーマリンク設定」-「変更を保存」→(ブログURL)/rewritesでアクセスできる)。以下は今回の変更を加える前のURL書き換えルールの抜粋である。

Array(
    [post/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/?$] => index.php?year=$1&monthnum=$2&day=$3
    [post/([0-9]{4})/([0-9]{1,2})/?$] => index.php?year=$1&monthnum=$2
    [post/([0-9]{4})/?$] => index.php?year=$1
)

URL書き換えルールは連想配列で、キーが表向きのURL、値がindex.phpにパラメータが付いたかたちで定義されているのがわかる。さてこれをどのように弄ればいいだろうか。

フィルターでURL書き換えルールを書き換える

Wordpressはプラグインを使うために、フィルターという仕組みを用意している。

Wordpressのソースをみると、プラグインで処理を挟みたいような部分で、apply_filtersという関数が呼ばれている。これは、プラグインがadd_filterという関数で登録した関数を呼び出してくれるものだ。

で、URL書き換えルールを作る部分にもフィルターが設定できる。今回は日付別一覧を弄りたいので、date_rewrite_rulesにフィルターを設定する(「プラグイン API/フィルターフック一覧 - WordPress Codex 日本語版」にURL書き換えのフィルターフック一覧がある)。

フィルター追加のひな形はこんな感じである。

function my_filter($arg){
    $result = "";
    return $result;
}
add_filter('date_rewrite_rules', 'my_filter');

呼び出される関数にどんな引数が渡されるかは、フィルターフックによって変わる。どんなものが渡されるかについてはあんまり情報がないので、場合によってはWordpressのソースからapply_filter呼び出してるとこを探すことになる。

date_rewrite_rulesの場合、上でInternal Rewrite Rulesプラグインで見たような、連想配列のかたちで渡される。

あとはフィルター内で好きなように書き換えて返してやればよい。今回は最初のpost/部分をdate/に書き換える。

function add_date_base($rewrite_rules){
    $r = array();
    while(list ($key, $val) = each($rewrite_rules)) {
        $r[str_replace('post/', 'date/', $key)] = $val;
    }
    return $r;
}
add_filter('date_rewrite_rules', 'add_date_base');

テンプレートで書き出されるリンク先の変更

これで、思い通りのURLでアクセスする方法はわかったが、まだ1つ問題がある。テンプレートで書き出されるリンクが変更前のままなのである。これもまたフィルターを設定することで解決できる。

プラグイン API/フィルターフック一覧 - WordPress Codex 日本語版にリンク関連のフィルターフック一覧がある。これらのフィルターフックは引数として、URL文字列を渡してくる(http://penguinlab.jp/blog/date/2009 のような、プロトコル名から始まる完全なかたちのURLである)。単なる文字列なので、文字列関数で適当に書き換えて返してやればOKだ。なお、日付別表示のリンクは年別、年月別、年月日別の3種あるので、すべて書き換える。

function add_date_base_link($string){
    $string = str_replace('post/', 'date/', $string);
    return $string;
}
add_filter('year_link', 'add_date_base_link');
add_filter('month_link', 'add_date_base_link');
add_filter('day_link', 'add_date_base_link');

あとはこれをプラグインにするだけだ。

プラグインの作成、有効化、URL書き換えルールの再作成

Wordpressプラグインの作成方法は他所にいくらでも書かれていると思うので、簡単な説明にとどめておく。

まず、さきほど書いたコードにタイトルなどのメタデータを書き加え、適当な名前で(ローカルに)保存しておく。

<?php
/*Plugin Name: Add Date BaseDescription: Add date baseVersion: 1.0Author: labocho*/function add_date_base($rewrite_rules){
    $r = array();
    while(list ($key, $val) = each($rewrite_rules)) {
        $r[str_replace('post/', 'date/', $key)] = $val;
    }
    return $r;
}
function add_date_base_link($string){
    $string = str_replace('post/', 'date/', $string);
    return $string;
}
add_filter('year_link', 'add_date_base_link');
add_filter('month_link', 'add_date_base_link');
add_filter('day_link', 'add_date_base_link');
add_filter('date_rewrite_rules', 'add_date_base');
?>

さらに適当な名前のディレクトリを作成し、この中にさきほど保存したファイルを移動しておく。

次に、これを (Wordpressのインストールディレクトリ)/wp-content/plugins の下にディレクトリごとコピーする(あるいはディレクトリごとzipにして、管理ページからアップロードしてもよい)。

あとはWordpressの管理ページを開き「プラグイン」-「インストール済み」にさきほど作成したプラグインが表示されているので、有効化する。

無事、有効化できたら、URL書き換えルールを再作成するために、管理者ページから、「設定」-「パーマリンク設定」-「変更を保存」をクリックする。うまくいっていればこれで作業は完了である。思い通りのURLでアクセス出来るか、確認する。うまくいかない場合は、いや、うまくいった場合でもInternal Rewrite RulesでどのようにURL書き換えルールが変化したか確認しよう。

今回の変更では、上に挙げた部分が以下のように変わっていることを確認した。

Array(
    [date/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/?$] => index.php?year=$1&monthnum=$2&day=$3
    [date/([0-9]{4})/([0-9]{1,2})/?$] => index.php?year=$1&monthnum=$2
    [date/([0-9]{4})/?$] => index.php?year=$1
)

あとがき

このような方法で、WordpressのURLをかなり柔軟にいじれる。本来ならば、変更前のURLへのアクセスを301リダイレクトするとかも考えるべきだろうが、まあ、許してください。ほんとはこれをもっと汎用的なプラグインにしてwordpress.orgから配信できるといいんだけど、いまのところやる気が足りません。要望があれば考えます。