WordPress Core ソースを読んでみよう (投稿更新編)

この記事は WordPress Advent Calendar 2014 の 10日めの記事として書いています。

WordPress に限らず Advent Calendar への参加は初めてです。 11月の終わりになってそう言えばそんな時期だなと思い、今年はどんなカレンダーがあるのだろうと Web サーフィン (死語) をしていたところ、WordPress Advent Calendar が埋まっていないのを見つけました。 そして、枠を埋めるつもりで気軽に応募し、この記事を書いています。

ちなみに私自身は WordPress プラグインの作者 (プラグインその1その2) ですが、本業は WordPress に関係ありません。 Advent Calendar 用として何を書くかについてはいろいろ悩みました。 利用者向けに書いてみたい気もしましたが、説教臭くなってしまう恐れがあるので(「何故、自前の WordPress なんでしょうか?ブログサービスじゃダメなんでしょうか?」とかw)、結局開発に興味ある方向けに、解説されることの少ない投稿の更新処理についてコードを追いかけてみることにしました。

というわけで今回は投稿編集画面で「公開」/「更新」ボタンを押したときの処理を見ていきます。 是非実際にコードを参照しながらお読みください。

なお、コードは WordPress 4.01 のものを参照しています。 紛らわしいのですが、HTTP メソッドの POST は大文字で、WordPress の投稿を指す際は小文字の post で表すことにします。

フォームの submit

まず、投稿編集画面ですが、wp-admin/post-new.php (新規) あるいは wp-admin/post.php (更新) によって表示されます。 編集画面を表示する実体としてはどちらも wp-admin/edit-form-advanced.php によって処理されるのですが、ここには深入りしません。

post-new.php の処理で注目したいのが、71行目で get_default_post_to_edit() を呼んでいる点です。 この時点でデータベース内に投稿データが作成され、新しい post ID が付与されることになります。 つまり、新規投稿であろうが更新であろうが、公開/更新ボタンを押したときの処理はデータベース内の post データを更新する処理になり、実のところ submit されたフォームデータの処理から先は共通になっています。

編集画面は post.php に POST するフォーム (form 要素は id=post) になっています。 hidden 項目の一つとして (name=’action’, value=’editpost’) という項目が POST されます。 これが wp-admin/post.php 17行目の wp_reset_vars() で $action = ‘editpost’ となり、104行目からの switch ($action) で edit_post() が実行されます。

以下関数名を使って追いかけていきます。

edit_post() in wp-admin/include/post.php

edit_post() では、引数が null で実行されることになるので POST されたデータが $post_data に設定されます。 $post_ID も POST された値が用いられます。

そしてもろもろの前処理 (詳細省略) を行った後 320行目で wp_update_post() を呼びます。

wp_update_post() in wp-includes/post.php

引数の $postarr には編集画面のフォームより POST された投稿データが含まれています。 3541行目の get_post() で $post にデータベース内の投稿データがセットされます。 attachment の更新の場合は wp_insert_attachment() をコールしたりとかありますが、今回は post なので wp_insert_post() が呼ばれます。 まあ、大した処理をしていないのでさっさと次に行きましょう。

次の wp_insert_post() の処理が最も重要です。

wp_insert_post () in wp-includes/post.php

引数 $postarr には編集画面のフォームより入力された投稿データが含まれています。 wp_update_post() に続きここでも get_post() でデータベースの投稿データを引っ張りだしています (3095行目)。 非効率な気もしますが、get_post() から呼ばれる get_instance() で読んだデータをキャッシュする仕組みがあるようなので大丈夫でしょう。

3090行目からの if 文の処理ですが、編集画面フォームから submit したときの処理は新規の場合でも $postarr[‘ID’] が存在して $update = true になります。 では $update = false となるのはどんな時かと言うと、編集画面を表示する際に get_default_post_to_edit() 経由で呼ばれたときですね。

そして、この wp_insert_post() はかなり長いのですが、やっていることの大半は post データの設定です。 どこでデータベースが更新されるかわかりづらいのですが、$update = true の場合は 3341行目の $wpdb->update() で更新されます。 この周辺にはアクションフック/フィルターフックも多数存在するので、この wp_update_post() だけでも一度真剣に読んでみると良いと思います。

さて、ここまでで投稿されたデータは無事データベースに反映されましたが、リビジョンの処理についてもう少し見てみます。

wp-includes/default-filters.php 内で既定のフック関数が登録されているのですが、この中に post_updated のフック関数として wp_save_post_revision() が登録されています (251行目)。 これが wp_insert_post() から呼び出されます (3478行目)。

wp_save_post_revision() in wp-includes/revision.php

それではリビジョン関連処理を見ていきます。 下の図は投稿更新時のリビジョン関連処理の概要を表したものです。 同じ色は同じコンテンツを表していますが、このように現在の post の内容と同一のものが最新リビジョンとして post_type=’revision’ でデータベース内に存在します。 詳しくはこちらの記事をどうぞ。

Update flow 3.6

では wp_save_post_revision() の処理を見ていきます。

まず wp_get_post_revisions() で更新された投稿についての全リビジョンを取得します (104行目)。 そして、最新のリビジョン、すなわち更新前の post と同じデータを持つリビジョンの ID を $last_revision にセットします (106行目からの foreach ループ)。 続く、127行目の if 文では内容に変更があったかを確認し、変更が無いようであればそのまま return してしまいます。

その後に _wp_put_post_revision() を呼んで新しい post と同じ内容を持つ post_type=’revision’ のデータをデータベース内に作成します (142行目)。 この処理において _wp_put_post_revision() から wp_insert_post() が呼ばれるわけです。 「$postarr のデータが post_type=’post’ でなく、post_type=’revision’ だったら処理はどう変わるのだろう?」という新たな視点から wp_insert_post() を読み直すことができるわけですねw

wp_put_post_revision() を呼んだ後 (144行目以降) は不要となったリビジョンの削除処理です。 保持するリビジョン数は wp_revisions_to_keep() が返す値を使っていますが、この関数の中をみると、WP_POST_REVISIONS を設定する他にも wp_revisions_to_keep フックをつくれば何世代リビジョンを保持するかを指定できることがわかります。

まとめ

ここまで説明してきた関数をコールツリーにまとめておきます。

call-tree

いかがでしょう? 思ったより簡単に読めたのではないでしょうか?

この記事がきっかけで Core のコードに興味を持ち、実際にプラグインを書いたりする人が増えるととても嬉しいです。 他にも WordPress 関連記事はありますので、気が向いたらどうぞ。 なお、投稿更新「編」としましたが、関連記事がこれっきりになる可能性がそれなりに高いことをあらかじめご承知おきくださいw

明日は岡本秀高さん濱田文さんです。 何と 2つの記事が読めちゃいます!

スパム 0、アタック対象 ID とパスワード

何のことかと言うと、WordPress のセキュリティ関連のお話です。

スパムをシャットアウト

画像で示した通り、9月終わりに SI CAPTCHA プラグインに手を入れてからコメントスパムをシャットアウトしています。 やったね!

一応、公式フォーラムに「文字数と画像サイズを可変にして欲しい!」って書いておきましたが、何の音沙汰もないので対応は望み薄ですね。 対策したい人は先の記事を参考にしてみてください。

wp-login.php へのアタック

それと、同じ頃に WordPress ログイン画面に入力された ID とパスワードを記録するようにしました。 テーブルに情報が溜まるようにしたのですが、約 1ヶ月でトータル 11万件ほどになってますw 今日はアタックに使われている ID、パスワードの Top 20 を大公開しちゃいます。

> select login, count(*)
> from login_table
> group by login
> order by count(*) desc
> limit 20;
+--------------------+----------+
| login              | count(*) |
+--------------------+----------+
| admin              |    72358 |
| administrator      |     9277 |
| user               |     4704 |
| user2              |     4680 |
| adm                |     4675 |
| tester             |     4672 |
| test               |     3872 |
| support            |     3513 |
| en                 |     2757 |
| en.hetarena.com    |     2752 |
| author             |     2186 |
| blogger323         |     1473 |
|                    |      373 |
| hetarena           |      222 |
| root               |      195 |
| en@en.hetarena.com |       60 |
| demo               |       50 |
| developer          |       46 |
| webmaster          |       45 |
| admin1             |       44 |
+--------------------+----------+
20 rows in set (0.09 sec)

これを見ると admin が危ないのはもちろんですが、著者名 (blogger323) もアタックに使われてますね。 正しくログイン名を守る方法はこちらの記事を参照してください。 ちなみに en.hetarena.com というのは英語版のホスト名です。

続いてパスワード Top 20。

> select password, count(*)
> from login_table
> group by password
> order by count(*) desc
> limit 20;
+---------------+----------+
| password      | count(*) |
+---------------+----------+
| admin         |      573 |
| 123456        |      174 |
| password      |      159 |
| admin123      |      157 |
| 123123        |      146 |
| qwerty        |      145 |
| pass          |      144 |
| 111111        |      137 |
| 12345678      |      127 |
| abc123        |      125 |
| 123456789     |      123 |
| 12345         |      122 |
| 1234567       |      121 |
| 1234          |      112 |
| 123321        |      111 |
| administrator |      109 |
| password1     |      108 |
| root          |      108 |
| admin1        |      100 |
| INTERNET      |      100 |
+---------------+----------+
20 rows in set (0.15 sec)

簡単なパスワードはダメですよと。 パスワードの方は下の 20 も見てみましょうかね。 (件数 1件のものはもっとありますが、SQL 文が返した 20件ということで)

+--------------+----------+
| password     | count(*) |
+--------------+----------+
| destiny1     |        1 |
| ##           |        1 |
| gabber       |        1 |
| milad        |        1 |
| 1?2?3?4?     |        1 |
| ra           |        1 |
| archibold    |        1 |
| cirstoforo   |        1 |
| blondy       |        1 |
| deadgirl     |        1 |
| lookup       |        1 |
| react        |        1 |
| prima        |        1 |
| padraig      |        1 |
| sebastiano   |        1 |
| webmasterddd |        1 |
| Tuulia       |        1 |
| abigale      |        1 |
| yasicheng    |        1 |
| qwe1234      |        1 |
+--------------+----------+

何だかよくわかりませんね。 ただ、一つ言えるのは最後の例のようにキーボード配列を使ったパターンはシフトキーの組み合わせ含め様々なものが試されていたので、避けるのが賢明です。

WordPress のユーザー名と表示名

プラグインの機能拡張の時に WordPress のユーザー名関連について調べたのでまとめておきます。

ユーザー名に関する以下の項目が WP_USERS テーブルに入っています。

user_login
ログイン時に使う名前
display_name
表示用の名前
user_nicename
著者別の URL 用 (例: https://hetarena.com/archives/author/blogger323) に使われる名前

重要なのがログイン画面で入力する user_login です。 サイトへのアタックに使われると嫌なのでこれは知られたくないですね。 そこで表示用に display_name (管理画面上は「ブログ用の表示名」) というものがあって、これを設定しておくと投稿の表示などにはこちらが使われます。

ただ、ちょっと曲者なのが、user_nicename です。 これは user_login を元に自動的に設定され、管理画面から変えることができません。 そして、リダイレクトのテクニックを使うと訪問者にわかってしまいます。

どういうことかというとサイトの設定によっては以下の形式の URL にアクセスすると user_nicename を使った著者別 URL にリダイレクトされるのです。

https://hetarena.com/?author=1

このサイトでは author=1 だと URL には admin が表示されます。 admin と表示されるのは嫌だと思っても管理用 GUI からこれを変えることができません。 この動作があるので「ブログ用の表示名」を変えただけでは不十分であり、皆さんログイン名情報を隠すのに苦労しているようです。

そんな悩みをお持ちの方にちょっとしたテクニックをお伝えしておきます。 私の場合は逆にユーザー作成後に user_login を SQL 文で変えてしまっています。

UPDATE wp_users
SET user_login = 'himitsu'
WHERE id = 1;

ユーザー作成時のアカウント名はそのまま表示用として display_name と user_nicename に残しておけば良いわけです。 SQL 文でやらねばならないところがネックかも知れませんが…。

ちなみに user_nicename のデフォルト値ですが、WordPress Core の user.php 内 wp_insert_user() で以下のようにセットされてます。

$user_nicename = sanitize_title( $user_login );

それと「ニックネーム」は WP_USERS テーブルではなくて WP_USERMETA の方に保管されます。

なお、そもそもの話としては複雑なパスワードを使うというのが一番重要であることを最後に強調しておきたいと思います。

WordPress でのコメントスパムとの戦い

このブログのように弱小サイトでもコメントスパムが来ます。 最盛期は 1か月に 6万7千件という記録があります。 最近は減ってきたものの、それでも管理画面にログインして「200件のコメントがスパムとして捕えられています」というようなメッセージを見るのは気分がいいものではありません。 今回、原因調査と対策をしたことでスパムキューに入るコメントの数そのものを減らすことができたので、経緯をまとめておきたいと思います。

スパムキューに入るコメントの数を減らしたい

使っている人は誰でも知っていると思いますが、WordPress には標準で Akismet というスパム対策用のプラグインがついてきます。 個人用には無償で利用でき、これはこれで有難いプラグインなのですが、日本語についてはたまに誤検知が発生するようです。 少なくとも私は正規のコメントをスパムキューに入れられた経験が複数回あります。

そういうことがあると、どうしてもスパムキューのチェックをしたくなってしまいます。 スパムと正規コメントがそれこそ 10,000 : 1 ぐらいの比で入っているようなキューをチェックするのは至難の業です。 やはり、根本的な対策を行ってスパムキューに入るコメントの数を減らしたい。

そこで私は CAPTCHA プラグインを導入してみました。

SI CAPCHA を導入したのにスパムが減らない?

私が導入したのは SI CAPTCHA プラグインです。 レビュー評価も高く、最近もメンテナンスされている様子なのでこれを選びました。

しかし、何故か導入後も思ったようにスパムキューに入るコメントの数が減らなかったのです。 そこで、この原因を調べてみました。

人間じゃないな

まずは httpd のログを確認します。 スパムコメントが投稿される際のログはたった 4行でした。

192.168.138.148 - - [27/Sep/2014:10:28:03 +0900] "GET /archives/503 HTTP/1.0" 200 60116
192.168.138.148 - - [27/Sep/2014:10:28:05 +0900] "GET /wp-content/plugins/si-captcha-for-wordpress/captcha/securimage_show.php?si_form_id=com&prefix=TdkHIF8aBTtW9BWL HTTP/1.0" 200 6099
192.168.138.148 - - [27/Sep/2014:10:28:05 +0900] "POST /wp-comments-post.php HTTP/1.0" 302 -
192.168.138.148 - - [27/Sep/2014:10:28:09 +0900] "GET /archives/503 HTTP/1.0" 200 60260

通常の Web ブラウザを使った閲覧ならば、1行目のページ全体の html がダウンロードされた後に関連する画像や JavaScript 等がダウンロードされるのですが、それは行われていません。 CAPTCHA の画像 (2行目) をダウンロード後、コメントを POST (3行目) し、再度そのページを取得 (4行目) しているだけです。

注目すべきは 2行目と 3行目の間の経過時間、すなわち CAPTCHA 画像をダウンロードしてからコメントを投稿するまでの時間です。 これをみると 1秒も経たずに行われています。

以前、この世界には安価な人力を使って CAPTCHA を突破するような仕組みが存在するという話をどこかで見かけたのですが、このログを見る限り人間が処理しているわけではないようです。 私はこの時点は 2つの可能性を考えていました。 一つはプラグインが何か脆弱性を持っていてそこを突かれている可能性、もう一つは CAPCHA 画像を機械的に認識処理している可能性です。

作者の方には申し訳ないのですが、ぼんやりと脆弱性の可能性の方が高いように考えていました。

SI CAPTCHA の仕組み

SI CAPTCHA の仕組みを簡単に説明しておきます。

ブログのページが表示されたとき、4文字の CAPTCHA 文字列 (captcha_code) がランダムに生成され、その内容は cache ディレクトリ内の一時ファイルに保存されます。 保存されたファイル名にはランダムの文字列 (si_code_com) が付与されます。 先のログの「TdkHIF8aBTtW9BWL」の部分がこの si_code_com に相当します。

CAPTCHA

コメント投稿時に POST された captcha_code と si_code_com から一時ファイルの内容と合っているかを確認し、合っていなければエラーとする仕組みです。 上の図はこれらの処理をイメージ化したものです。 この例では si_code_com = TdkHIF8aBTtW9BWL、captcha_code = 9YgA です。

画像認識処理されてるよ!

SI CAPTCHA のソースコードを読んでこれらを把握したのですが、コードを読むだけでは特に問題となりそうな箇所は見つけることができませんでした。 そこで、投稿されている内容をログに記録することにしました。 具体的には si-captcha.php 内にある関数 si_captcha_comment_post() で $_POST の値を確認するようにし、プログラムが生成時に使用している captcha_code や si_code_com と比べてみました。

すると驚いたことにスパマーは正しい captcha_code と si_code_com を送ってきていたのです。 一応付け加えておくと出鱈目な captcha_code を付けて POST してくるケースもあったのですが、それらはきちんと弾かれていました。

captcha_code と si_code_com が正しいとなるとどうやら画像認識処理をしているようです。 作者さん、疑ってごめんなさい!

キューに入る前にシャットアウト!

さて、画像認識処理が機械的に行われているとなるとどうすればよいでしょうか。 高度な画像認識処理が行われていて、認識率が結構高いとなるとやっかいです。 プラグインを変えなければならないかも知れません。

それは面倒だなあと思いつつ、まずはできるところを試してみようと思いました。 何を行ったかというと SI CAPTCHA のコードをいじって文字数と画像サイズを変えたのです。

すると、スパマーが投稿する際の captcha_code に変化が見られました。

["captcha_code"]=> string(14) "!UNKNOWN_TYPE!"

やったね!!

これだけで Unknown type とか言っちゃう程度の処理エンジンであればこの対策だけで十分な気がしてきました。 恐らく画像認識処理は SI CAPTCHA の文字数と画像サイズに最適化されていたのでしょう。

これでキューに入る前に弾かれるようになりました。

変更箇所

修正の仕方が広まってスパマー側に対策されるのが嫌なので、私がどのように変えたかはあえて記しません。 興味があれば、実際のコメント欄をご覧ください。(丸わかりですね)

ここでは変更箇所だけお伝えしておきます。 それだけじゃコードを修正できないという方は、是非 SI CAPTCHA プラグインのサポートページで文字数や画像サイズが可変となるよう要望を出してみてください。

  • si-captcha.php の関数 si_captcha_captcha_html() 内の以下部分の width を変更

      // url for no session captcha image
      $securimage_show_url = $si_captcha_url .'/securimage_show.php?';
      $securimage_size = 'width="175" height="60"';
      if($si_captcha_opt['si_captcha_captcha_small'] == 'true' || $label == 'si_image_side_login' ) {
        $securimage_show_url .= 'si_sm_captcha=1&';
        $securimage_size = 'width="132" height="45"';
      }
    

    これらは管理画面の CSS で変えられるかも知れません。

  • secureimage_show.php の $char_length と $img->image_width の値

宣伝!

話が全然違いますが、私もプラグイン作者なのです。 以下のプラグインも是非使ってみてください!

PhpStrom 8 で WordPress プラグイン開発が快適に!

9月中旬に PhpStorm 8 がリリースされたので、ちょっと間が空きましたがバージョンアップして触ってみました。 このバージョンから WordPress に関するサポート機能が追加されています。

WordPress 開発環境のサポート

PhpStorm 8 の WordPress 関連機能の詳細はこちらのページで確認できますし、こちらのブログで丁寧に紹介されているので、ここではできることを簡単に箇条書きでまとめるにとどめておきます。

WordPress 用 Code Style
WordPress 用 Code Style が用意され、WordPress のコーディング基準に従ってフォーマットされます。 既存のコードに適用するには「Reformat Code」ですね。
フック (filter/action) 名称のの入力補完
WordPress Core で宣言されているフック名を入力補完してくれます。
フックの呼び出し箇所へのジャンプ
例えば、

add_action( 'admin_init', 'my_hook' );

というコードを書いているときに行頭カラム内のアイコンをクリックすると、

do_action( 'admin_init' );

というようなフックの呼び出し場所へ一発で飛ぶことができます。

フック関数へのジャンプ
上の例で add_action の例で ‘my_hook’ の部分を Ctrl + マウスクリック をすることで ‘my_hook’ の宣言箇所へジャンプすることができます。 クラスメンバ関数をフック関数として登録している場合もちゃんと飛んでくれました!
WordPress.org 内の検索
関数名などの文字列を選択した後のコンテキストメニューより「Search on WordPress.org」を実行できるようになりました。 なお、検索結果は新しい開発者ポータルである developer.wordpress.org のページが表示されます。 (developer.wordpress.org に関する情報はこちら)
WP-CLI の統合
WP-CLI を Command Line Tool Console から使うことができます。 各種オプションの補完も効きます。 WP-CLI そのものについては公式サイト「サイトの拡張性を飛躍的に高める WordPressプラグイン開発のバイブル」をどうぞ。

ついでに紹介 – JetBrains Open Source License

PhpStorm の開発元 JetBrains からオープンソース開発者向けに Open Source License という無償ライセンス提供プログラムが提供されています。 開発プロジェクトの情報 (公式ホームページ、開発レポジトリやフォーラムの URL 等) を送り審査を受けて基準が満たされていればライセンスが発行されます。 英語でのやりとりとなりますが、WordPress.org に自作品を登録しているプラグイン開発者であれば英語にも慣れているでしょうし、入力項目としても WordPress.org のレポジトリやサポートフォーラムを伝えればよいのでそれほどハードルは高くないと思います。 興味のある方はどうぞ。

WordPress リビジョンメモ機能強化!

Thin Out Revisions 1.7 をリリースしました。このバージョンではリビジョンメモ機能が強化され、投稿にリビジョンメモを表示できるようになりました。 余裕がなくて日本語リソースができていないのですが、その分この記事で説明しておきます。

以下のようなパラメータが TOR の設定画面に追加されています。 「On」を選ぶとこの機能が有効になります。 また、デフォルトの状態を「Show」(表示) とするか「Hide」(隠す) とするかを選択できます。

TOR 設定画面
TOR 設定画面

個別の投稿設定で「Show」、「Hide」を選ぶこともできます。 こちらの設定が優先されます。

投稿の編集画面
投稿の編集画面

メモを表示する場合もその投稿の一部のメモを隠すことができます。 先頭に「#」(半角) をつけてください。

投稿の編集画面
投稿の編集画面

上のメモがあるときに投稿上のメモ表示は下の通りになります。

投稿に表示されたメモ
投稿に表示されたメモ

メモ自体は DL としてマークアップされます。 DL の前の見出しは設定画面で入力できますが、いくつかのタグ (<script> 等) は使えません。 入れても削除されます。

リビジョンメモ機能もリビジョン削除機能に劣らず便利なので、是非使ってみてください。 まだ更新できていない日本語公式ページはこちら (苦笑)。

「WordPress プラグイン開発のバイブル」買いました

WordPress プラグイン作者としては買っておかねばと思い、先月発売された「サイトの拡張性を飛躍的に高める WordPressプラグイン開発のバイブル」を買いました。 一応プラグイン作者なので知っていることも多いためざっと読んだだけですが、感じたことをまとめておきます。

  • プラグインをつくるために必要な知識が一通りまとまっています。 面白いと感じたのは、技術的な面だけでなくライセンスや収益モデルなどビジネス的な側面に関わることもまとめられていることです。
  • 私はプラグインの作り方を洋書で学びましたが、これからプラグイン作成を行おうとする方には間違いなく本書の方がお勧めです。
  • 強いて挙げるならば、Ajax 関連Cron 関連もあると良かったかも知れません。
  • ただし、WordPress のコアの部分の解説は本書の範囲ではないので、そこは期待してはいけません。

どれぐらいの数の日本人がプラグイン開発に興味を持っているのかが想像できないのですが、興味があれば是非本書を手に取っていただきたいところです。

以下は私の個人的な意見や補足です。

  • 公式レポジトリへプラグインを登録し、ある程度メンテナンスもしていこうと考えているのであれば、情報としては日本語サイトではなく本家を参照するようにしましょう。
  • 怖がらずに英語でメッセージやドキュメントを書き、サポートも WordPress.org 内に用意されるそのプラグイン用サポートページを第一の場にするといろいろなものが得られるはずです。
  • 私は紙の本を買いましたが、Kindle で慣れているのであればそちらの方が検索したりするのには便利かも。 もう少し Kindle 版が安くなればなあ…。
  • gettext については msgmerge を知っていると良いかも。私の書いた記事はこちら。
  • 公式リポジトリへの登録には subversion が必要ですが、普段は GitHub を使っていて WordPress.org への登録のみ svn という人も多いようです。 そういう人は大抵最新ファイルをコピーして svn commit するスクリプトを使っているようです (検索すればサンプルが見つかるはず)。 ちなみに私は git-svn 派ですが、ちと面倒なので少数派です。もし、git-svn で行くのであればこちらの記事を参考にしてください。
  • 本書のコラムにもありますが、Settings API について学習すると良いと思います。 ちなみに私のプラグインでは、管理画面で入力された値のチェックは admin_init のコールバック関数内で行うのではなく、Setting API に含まれる register_setting() の第 3引数で指定する関数内で行っています。 このチェックでエラーがあった時は add_settings_error() を呼ぶようにしています。

何にせよ、本書の影響で日本人が作ったプラグインが公式リポジトリにもっと登録されるようになれば良いと思います。

SWE の JavaScript プログラミング

Standard Widget Extentions (SWE) という WordPress プラグインを作って公開しています。 先日は SWE のスクロール時固定機能 (Sticky Sidebar について「そこらへんに落ちているサンプルコードとは動きが違うんだよ」という話を書きました。 今回は JavaSciprt 的にいろいろ苦労したところを書いておきます。

なお、ここで紹介している関数は特に断りがなければ jQuery の API です。

幅やマージンの %指定

レスポンシブ Web デザインでは以下のように width や margin がパーセント表示で比率指定されていたりします。 テーマ製作者はピクセルの比率から値を計算しているのでこのように結構エグい小数になっていたりします。

.content-sidebar {
  margin-left: -29.04761904%;
  width: 29.04761904%;
}

ここで一つ問題となったのが、width が %指定のままで position = fixed にして位置を固定すると比率の基準となる要素が変わるため幅が変化してしまうことです。 これを避けるには一度取得した値を再度設定しピクセル値で固定することです。

var w = $('#sidebvar').width();
$('#sidebar').width(w);

では、ブラウザがリサイズされたときはどのようにすれば良いでしょう? リサイズ前の幅のままではレイアウトが意図通りになりません。しかし、新しい幅はどうやって計算すればよいのでしょうか? 比率を取得するために CSS ファイルを自力で読まねばならないのでしょうか…。

わかってしまえば簡単なのですが、一旦 width をクリアすることで解決します。

$('#sidebar').css('width', '');

jQuery API リファレンスをよく読むと書いてあるのですが、空文字列に指定するとこれまでの API 経由で設定したスタイル指定をクリアし CSS ファイルの記述通りに振る舞うようになります。まとめて書くと以下の様に一旦クリアし再度取得したピクセル値で設定することになります。

$('#sidebar').css('width', '');
w = $('#sidebvar').width();
$('#sidebar').width(w);

一瞬なんだかわからないコードですよね。

サイズ関連

.width() で取得した値と .css(‘width’) で取得した値は異なることがあります。 一方が 250 なのに他方は ‘310px’ などと返ってきたりします。 型の差は置くとして、異なる数値になっているので困ります。 これは box-sizing プロパティのせいだったりするのですが、この値を見て動作を変えるようなコードを書くのはいろいろ面倒です。

それで結局どうしたかというと以下のようにしました。

  • 位置計算等に使用する幅の取得は .outerWidth(true) を使って、いつでも border や margin 込みの値を用いる。 jQuery の .width(), .innerWidth(), .outerWidth() は box-sizing の値に依って動作が変わることはない。
  • 先述の width の操作についてのみ、取得・設定とも統一して .css() で行う。

このように jQuery の関連関数は box-sizing プロパティに依らず使えるので便利です。

座標関連

座標取得には document を基準として位置を取得できる .offset() が便利でした。 ただし、以下のように .css(‘top’) を使って操作するときとで対象としている座標の位置が異なるので注意が必要です。

jquery-offset

ちなみに position = absolute のときの基準要素を見つけるには .offsetParent() が便利です。jQuery にはいろいろな関数が揃っていますね。

position と z-index

position = static だと z-index が指定されているテーマで不具合を起こすため、position = static を使うのでなく position = relative で (top, left) = (0, 0) としています。

小数点の扱い

サイズ関連でもう一つ困ったのは小数の扱いがブラウザによって異なることです。 整数 (ピクセル値) に直す時に四捨五入なのか切り捨てかみたいな話です。 深入りしたくなかったこともあり細かい挙動は忘れてしまったのですが、気になる方は検索してみるとこの話題を扱っているサイトが見つかると思います。 SWE では先の .css(‘width’, ”) を使うテクニックにより、これらの問題を避けています。

というわけで、Standard Widget Extensions のサイドバースクロール時固定機能はその辺のサンプルコードと違うのです! 興味がわいた方は使ってみてください。

誰かがサイドバー固定のコードは簡単と言ってたけど

WordPress 用プラグイン Standard Widget Extensions (SWE) をリリースしていますが、このプラグインはスクロール時にサイドバー (Widget Area) を固定する機能 (Sticky Sidebar 機能) を持っています。 世の中にその動作を実現するサンプルコードは溢れていますが、よく見るとそれらは動作条件が限られていることがほとんどです。 プラグインとしてリリースするにはより汎用化して幅広い条件で安定して動くことが求められます。 今回は SWE の Sticky Sidebar 動作のこだわりを説明します。 この記事を読むと、きっとあなたもこのプラグインを試してみたくなるはず!

止まる位置を自然に

SWE ではウインドウサイズと比べて長いサイドバーと短いサイドバーで表示位置の基準を変えています。 長いサイドバーであればサイドバー下端が画面下端となって止まります。 短いサイドバーはサイドバー上端が画面上端で止まります。

上端で合わせる動作のみのプラグイン/コードが多いと思いますが、SWE はより自然な動作となっています。

フッターが来たら再び動き始める動作

サイドバーが固定されてから更にスクロールし続けると、フッターとかぶさらないように再度サイドバーが動き始めます。 SWE では、この動作を開始する位置はコンテンツ要素とサイドバー要素の位置と高さから計算します。

スクロール再開位置の決定方法は他にも以下のようなやり方があると思いますが、それぞれ欠点があり、特に 2本のサイドバーを使っているテーマでは困ることになります。

フッター要素の位置とスクロール量から再開位置を計算する
レスポンシブ Web デザインでは、画面サイズに応じてサイドバーがフッター化する等でコンテンツ要素の直後にくる要素が変わってしまったりします。 それに合わせて計算に用いるフッター要素を変えるような仕組みがないとうまくいきませんが、この実装はなかなか難しいと思います。
予めページ最下端からのマージンを決め数値をセットしておく
やはりレスポンシブ Web デザインでコンテンツの後の要素が増えていくと固定されたマージンでは困ることになります。

というわけで、これらの方法よりも SWE の方法の方が優れています。

レスポンシブ Web デザイン対応

このように SWE ではレスポンシブ Web デザイン対応テーマでの動作にこだわっています。 要素の幅やマージンが ‘%’ 指定されていたりすると固定時やブラウザリサイズ時の動作が怪しくなるコードが多いですが、SWE ならばこれも大丈夫です。 このあたりの動作は Version 1.5.2 でかなり改善しましたので、昔使って今一つと思った方も最新バージョンでは納得いただけるのではないかと思います。

様々なプラグイン/コードでブラウザをリサイズ時動作を比較してみると違いがわかりますよ!

2本のサイドバーの動き

SWE では 2本までサイドバーを指定することができます。 コンテンツより長いサイドバーがある場合、コンテンツではなくその長い方のサイドバーに合わせて他方のスクロールバーのスクロール停止位置を決めます。

応用として、この動作を裏技的に使うと長い方に合わせてスクロール停止するような 2カラムレイアウトを実現することもできると思います。

折り返しの動作

あんまり話題にならないのですが、Quick Switch Back モードというモードがあり、これを有効にするとスクロール方向を変えた時にすぐにサイドバーが動くようになります。 作者的には密かに目玉だと思っています。

動的コンテンツへの対応

SWE ではページロード後にコンテンツサイズが変化するようなテーマに備えて、タイマー + カウンターを使って固定位置等の再計算を実行する仕組みがあります。 ただし、Quick Switch Back モード使用時は再計算時の位置調整が目立つので再計算の実行は必要最小限にするのがお勧めです。

丁寧な計算処理

padding や border、margin が 0 でなかったり、box-sizing が指定されていたり、z-index を使っていたりするテーマでは注意してコードを書かないとすぐに動かなかったり、ぎこちなかったりしてしまいます。 その点、SWE ならば大丈夫です。

この記事を読んで興味を持った方は是非 Standard Widget Extensions を試してみてください。もちろんこのサイトでも使用しているので動作を確認いただけます。 日本語公式ページはこちらです。

Thin Out Revisions が WPML 互換リストに載りました

Thin Out Revisions のサポート対応の流れから WPML の開発者向けプログラムに申し込んだのが 4月の終わり。 そして、先日無事にチェックをパスして互換リストに載せてもらうことができました。

ああ、もう一か月経っていたのか…

WPML certified

補足説明しておきます。 WPML は多言語化のための WordPress 用プラグインです。 一つの記事を他の言語に翻訳して使うようなサイトを作ろうとしたときにこれがあればページ/投稿の管理がとても楽になります。

実際にどんな感じかというと、投稿一覧で以下のように複数言語版を管理できるようになります。 これは英語+中国語、仏語、日本語の例です。 「+」サインはまだ翻訳版が無いことを表していて、ここをクリックすると翻訳版の編集画面に移ります。

WPML

投稿を表示した際の言語切り替え方法はいくつか用意されていますが、例えば以下の様にサイドバーに表示された言語リストで切り替えることがきます。 これは Twenty Fourteen のサイドバーです。

WPML

もちろんアーカイブ表示などでは選択言語のみ表示されます。素晴らしい!

ちなみに多言語化ということでは qTranslate を紹介しているサイトもありますが、残念ながら 2014年 6月現在きちんとメンテナンスができていないようです。 ユーザーレビューがひどいことになってますが、サポートやアップデートを求める人は居ても Donation してくれる人は少ないでしょうし、作者に同情してしまうところがあります。 「サポート要るなら金をくれ!」っていうと叩かれちゃうのかなあ (ぼやき)。

…ということで、お手軽に WordPress サイトを多言語化しようとすると今は WPML しか選択肢がないのではないでしょうか。

そして Thin Out Revisions ですが、これは私が作成したリビジョン管理のためのプラグインです。 WPML を使ったマルチリンガルなサイトならばリビジョン管理機能を使用することが多いと思いますが、Thin Out Revisions ならば柔軟な管理ができます。 詳しくは日本語公式ページで!

WPML の力で少しは認知度があがらないかな… (他力本願)