この記事は、WordCamp Tokyo 2012でお話した「WordPressでWebアプリケーションを作る方法~Croppy編~」の内容をまとめ、分かりやすいように + 一部のコードについてセッションよりも詳しく加筆したものです。
WordPress に Twitter・Facebook ログインのようなソーシャル性を取り入れたWebアプリを作るためのプレゼンをするにあたり、最初は「ノンプログラマーでもできる」みたいなタイトルにしようかと思ったのですが、さすがに PHP が分かっていないと厳しいかと思いますので入れませんでした。
普段 PHP を全く書かない方には少々難しいかもしれませんが、ある程度 WordPress をカスタマイズできる方ならなんとかなるかな…という程度かと思います。
Croppyとは
Croppy は、ほぼ WordPress でできているWebアプリです。
気になるデザインのページがあれば、その一部をブラウザ上で切り抜いて共有できるWeb制作者のためのサービスです。
Webページの一部をブラウザ上で切り抜けるという部分ですが、これは実際に動いているものを見ていただいたほうが分かりやすいと思います。
今回は、WordCamp Tokyoのロゴがかっこいいと思ったので切り抜いてみます。
ブックマークレットをクリックして、ジャンルを選んで範囲をドラッグで指定するだけです。
そして、切り抜いたパーツを一覧にして見ることができます。
全体だけでなく、自分が切り抜いたものや、自分がフォローした人が切り抜いたもの、自分がお気に入りに入れたものなどの絞り込みも可能です。
フォロー機能は独自に実装しています。
この後作り方をお話しします。
なぜWordPressで作ったか
Croppy を作るにあたり、CakePHP や Ruby on Rails などのフレームワークを使わず、あえて WordPress で作った理由ですが、
- WordPress で本格的なWebアプリが作れるか試してみたかった
- 既に WordPress で作られたWebアプリがあった
- CakePHP や Ruby on Rails などのフレームワークが苦手
などです。
実際に WordPress でWebアプリを作ってみて「これは良い」と感じたことは、最初から高機能な管理画面が付いていることや、SQL文を意識せずにWebアプリが作れることですかね。
PHP + WordPress テーマ作成や functions.php の書き方がわかっていればなんとかなったので、CakePHP などに比べて敷居が低いと感じました。
そして、WordPress には最初から以下の様なたくさんの機能があります。
Croppyが実装したい機能も、まさにこれらの機能でした。
TwitterやFacebookアカウントでWordPressにログインさせる
Croppyに記事を投稿する際は、TwitterやFacebookでログインすると、「そのユーザーが存在しなければWordPressのユーザーを新たに作成」、「既に存在するユーザーであればそのユーザーでログイン」させたいと思いました。
Social Connectは、これ1つで、Twitter、Facebook、Googleなどたくさんのログインに対応できるプラグインです。
本来は、SNSのアカウントでログインし、記事にコメントしたり管理画面にログインするためのものです。
Facebookのログインはこのプラグインでうまく動いたのですが、TwitterでログインしてWordPressのユーザー作成はどうもうまく動かなかったため、Twitterでのログインは別のプラグインを使うことにしました。
Twitterアカウントでのログインには、Twit Connectプラグインを使用しています。
Twit Connectは、WordPressの管理画面でいろいろな設定をすることができ、ボタンのデザインなども選ぶことができます。
そのままだと、トップページなどに大量の JavaScript がインラインで埋め込まれてしまうため、プラグインを書き換えて JavaScript は別ファイルにしました。
WordPressに独自のフォロー機能をつける
続いて、ユーザーのフォロー機能です。
Croppyは、Twitterのフォロワーなどとは関係なく、独自のフォロー機能を備えています。Croppy上でユーザーをフォローすると、そのユーザーが新しく追加した投稿を確認できるようになっています。
Croppy 関連で1番よく質問されるのがフォロー機能についてです。
たぶん、今日来られた方もここが気になっている方が多いのではないかと…
ユーザーのフォロー機能をつける場合、まずテーマの functions.php にこのように書きます。
user_contactmethods にフックして、「フォロー」「フォロワー」という項目を追加しています。
// 自分のプロフィール編集画面にフォームを追加
function custom_user_contactmethods( $user_contactmethods ) {
return array(
'user_following' => 'フォロー',
'user_follower' => 'フォロワー'
);
}
add_filter( 'user_contactmethods', 'custom_user_contactmethods' );
add_filter() は「指定したフィルターフックに、関数をフックする」つまり、WordPress が特定の動作をする瞬間や特定の箇所に、指定した関数をフックするものです。
ここでは、user_contactmethods(WordPress 管理画面>ユーザー>連絡先情報)を、カスタマイズした custom_user_contactmethods() という関数でフックしています。
そして、custom_user_contactmethods() 関数では「フォロー」「フォロワー」という項目を追加しています。
すると、WordPress管理画面のユーザー情報のところに「フォロー」「フォロワー」項目が追加されていることが確認できると思います。
ちなみに、ユーザー連絡先のカスタマイズについては下記の記事で知りました。より詳しいカスタマイズ方法も書いてあります。
WordPressのユーザー管理画面カスタマイズまとめ | Simple Colors
フォローボタンの設置
続いて、記事ページのサイドバーのテンプレートにこのように書きます。
ユーザーがログインしている時のみ「フォローする」ボタンを表示します。
「フォローする」ボタンは画像ではなくCSSを使ってdivの背景画像で指定しており、既にフォローされていた場合は follow_class 関数で「follow_button_done」というclass名が出力され、ボタンの表示は「フォロー中」になります。
フォローボタンのdivの中には投稿者IDが入ります。投稿者IDの数値はCSSで非表示にしています。
先ほどのPHPから実際に出力されたHTMLの例です。
今回はフォロー済みのユーザーを表示したので、「follow_button_done」classが出力されており、ボタンの表示が「フォロー中」になっているのが確認できると思います。
divの中にはフォロー対象のユーザーIDが入っており、CSSで非表示になっています。この数値は何に使うかという説明を今からします。
続いて、フォローボタンをクリックした時の動作を画面遷移なしで実装したいと思います。つまり Ajax です。
まず、WordPressのインストールフォルダ内に「ajax」というフォルダを新規作成し、「follow.php」というPHPファイルを新規作成します。
follow.php では以下のような処理を行います。
- フォローしたいユーザーのIDをPOSTで受け取る
- ユーザーのフォロー情報を取得
- ユーザーのフォロー情報の末尾にフォローするユーザーのIDを追加
- フォローしたユーザーのフォロワー情報の末尾に自分のIDを追加
- 「フォローしました」という文字列を返す
文章で書くとたったのこれだけですが…
実際の follow.php の内容は、こんな感じの90行ほどのPHPです。
user_following;
// フォロー情報があれば
if ( $user_following ) {
// '2,3,4' などフォローしているユーザーIDのカンマ区切り文字列が返ってくるはずなので、それを配列に変換
$following = explode( ',', $user_following );
// 配列の中にターゲットのIDが含まれるか true/false で返す
$bool = in_array( $target, $following );
// ターゲットのIDが含まれれば
if ( $bool === true ) {
echo 'フォロー済みです';
} else {
// ターゲットのIDを追加
$user_following = $user_following . ',' . $target;
// ユーザーのフォロー情報を更新
update_user_meta( $user_ID, "user_following", $user_following );
/* ここからターゲットユーザーのフォロワー情報更新 */
// ユーザーIDを指定
$target_user_data = get_userdata( $target );
// ユーザーのフォロー情報を取得
$target_user_following = $target_user_data->user_following;
// フォロー情報が空でなければ
if ( $target_user_following ) {
// ターゲットのIDを追加
$target_user_following = $target_user_following . ',' . $user_ID;
} else {
// ターゲットのIDを追加
$target_user_following = $user_ID;
}
// ユーザーのフォロー情報を更新
update_user_meta( $target, "user_follower", $target_user_following );
echo 'フォローしました';
}
} else {
// ユーザーのフォロー情報を更新
update_user_meta( $user_ID, "user_following", $target );
echo 'フォローしました';
}
たったあれだけのものがなんでこんなに長くなるかというと、受け取った値が不正なものだったらどうするかとか、既にフォローしてた場合はどうするかなどの処理と SCRF 対策ですね。
不正なアクセスだったら処理を行わないとかそういうやつです。
そういう部分を省いて、エッセンスだけざっくり説明したいと思います。
(ちなみに、今思えばもっと細かく関数に分けておいたほうが良かったな…と思っています。)
フォローボタンを押した時、フォローしたいユーザーのIDをPOSTで送りますので、それを受け取ります。
そして、POSTで受け取ったIDをもとにフォロー情報などを含むユーザーの情報を取得するわけですが、この follow.php は WordPress の関連ファイル、つまりテーマファイルなどではないため、このままでは WordPress の情報にアクセスすることができません。
しかし、WordPress をインストールした wp-load.php というファイルを読み込むことにより、WordPress のテーマファイルやプラグイン同様、WordPressの情報を使ったりデータベースに情報を書き込んだりできるようになります。
その上で、POSTで取得したIDを使って、get_userdata() というWordPressの関数を使ってユーザー情報をまとめて取得します。
ユーザーのフォロー情報は、例えば「2,3,17」のようにカンマ区切りでユーザーIDが並んでいるだけのテキストデータです。
その最後に、カンマと先程取得したユーザーIDをくっつけて、update_user_meta() というWordPressの関数を使ってユーザー情報を更新、つまり上書きします。
SQL文など書く必要はないので、かなり簡単ですね。
フォローしたユーザーのユーザー情報も、同じ方法を使って更新します。
そして、最後に「フォローしました」という文字を表示します。
ちなみに、今思えばフォローユーザーの一覧は単なるカンマ区切りの文字列ではなく、配列で serialize() というPHPの関数を使って配列を文字列化した上で持っておいたほうが良かったなと反省しています。カンマ区切りの文字列を何度も更新していると、たまにカンマが2重になったりとかおかしくなることがありました。
Ajax用のjQuery(common.js)の内容
ボタンを押したらフォローするユーザーのIDをPOSTで送り、「フォローしました」という文字を受け取ってページに表示するなどの機能は jQuery で実装しています。
ソースは先ほどと比べるとそれほど長くはないのですが、エッセンス部分に絞って説明しようと思います。
/* フォローボタンをクリックするとPOSTでフォローorリムーブ */
jQuery( '.follow_button' ).click( function() {
// 変数定義
var thisbox = $(this);
var msgbox = $(this).next();
var target_id = $(this).text();
var token = $(this).next().next().text();
// .sidebar_follow_done が付いていれば remove.php をたたきリムーブさせる
if ( $(this).is( '.follow_button_done' ) ) {
$.post( '/ajax/remove.php', { target: target_id, token: token }, function(data) {
$( msgbox ).html(data).slideToggle();
$( msgbox ).delay(3000).slideToggle();
// sidebar_follow_msgの内容によってボタンの状態を変更
if ( $( msgbox ).text() == 'フォロー解除済みです' || $( msgbox ).text() == 'フォロー解除しました' ) {
$( thisbox ).removeClass( 'follow_button_done' );
}
});
} else {
// .sidebar_follow_done がなければ follow.php をたたきフォローさせる
$.post( '/ajax/follow.php', { target: target_id, token: token }, function(data) {
$( msgbox ).html(data).slideToggle();
$( msgbox ).delay(3000).slideToggle();
// sidebar_follow_msgの内容によってボタンの状態を変更
if ( $( msgbox ).text() == 'フォロー済みです' || $( msgbox ).text() == 'フォローしました' ) {
$( thisbox ).addClass( 'follow_button_done' );
}
});
}
});
さきほどの「フォローする」ボタンをクリックすると、この無名関数を実行します。
以下のソースは、すべてこの無名関数内のものです。
もし、フォローボタンに「follow_button_done」というclassがついていれば、remove.php というPHPにユーザーIDをPOSTします。
remove.php は、先ほどの follow.php とは逆で、POSTで受け取ったIDをユーザー情報のフォローの中から削除します。
逆に、「follow_button_done」というclassがついていない場合、つまりまだそのユーザーをフォローしていない場合は follow.php にユーザーIDをPOSTします。
以下、2つめの /*(省略)*/ 部分の解説です。
follow.php から返ってきた文字列が変数 data に入るので、変数 data の内容を、フォローボタンの下にある msgbox という空のdivの中に入れ、スライドアニメーションしながら表示させます。
表示後、3000ミリセカンド、つまり3秒後に msgbox をスライドアニメーションさせて非表示にします。
そして、msgbox 内のテキストが「フォローしました」であれば、つまり follow.php から正常にフォローできたと返ってきていれば、フォローボタンに follow_button_done という class をつけます。
フォロー機能に関しては以上です。
※数値などではなく、「フォローしました」などの文字列を返すようにしたのは個人的に分かりやすいと思ったからです。
管理者以外は管理画面に入れないようにする
ユーザー情報にフォロー、フォロワー一覧を追加したのは良いのですが、TwitterやFacebookでログインしたユーザーはWordPressのユーザーであるため、そのままだとWordPressにログインしてユーザー一覧から自分のフォロー、フォロワー一覧を書き換えたりといったことができてしまいます。
このままではよろしくないので、管理者以外はWordPressの管理画面に入れないようにしてしまいます。
テーマのfunctions.phpに、グローバル変数 $current_user の user_level が10未満であればトップページにリダイレクトするようなフックを書いておきます。
// 管理者以外には管理画面見せない
function forbid_admin_page() {
// グローバル宣言
global $current_user;
// 現在のユーザー情報を取得
get_currentuserinfo();
// ユーザーレベルが 10(管理者)未満であれば
if ( $current_user->user_level
このソースコードはそのままコピペで使えます。よろしければお使いください。
管理画面以外からWordPressに記事を追加する
WordPress管理画面に入らずにどうやって投稿させるかというと、wp_insert_post() というWordPressの関数を使います。
テーマフォルダではない場所に新しくPHPを作成し、先程と同じく wp-load.php を読み込んでおきます。
その上で、 wp_insert_post() を実行すると、記事を投稿することができます。
wp_insert_post() の例。このサンプルは WordPress でゲストに記事投稿させるフォームが作れる wp_insert_post() | ウェブル より。
$postarr = Array(
'post_status' => 'publish',
'post_category' => '1',
'post_title' => '記事タイトル',
'post_content' => '記事の内容'
);
wp_insert_post($postarr);
先ほどお話した通り、Croppyは良いと思ったページ全体を追加するのではなく、ページの一部を切り抜いて追加するWebアプリです。
なぜページの一部を切り抜いて追加ということにしたかというと、
ページ全体を追加する場合、なんとなく全体の雰囲気が良いサイトを追加すると思いますが、ページの一部を切り抜いてということであれば、特に良いパーツばかりが集まりクオリティの高いサイトになるのではないかということと、私はデザインに煮詰まって他のサイトを参考にするときは、サイト全体の雰囲気を参考にしたいのではなく細かいパーツの作り方などを参考にしたい場合が多いこと、そして、ギャラリーサイトのトップページに、幅・高さ240px程度のサムネイルを並べた時に、ページ全体のサムネイルでは細かい部分がどうなっているかよく分かりませんが、パーツ別のサムネイルだと細かい部分までよくわかると考えたからです。
どうやって切り抜いたパーツをアップロードしてもらうか
問題は、どうやってページの一部を切り抜いて投稿してもらうかということです。
実装が簡単そうなのは、WindowsやMacのスクリーンキャプチャ機能を使ってユーザーにスクリーンショットを撮ってもらい、それをアップロードしてもらうことですが、Windowsの場合ちょっと面倒な事になります。
ですので、「ユーザー側でキャプチャしてアップロードしてもらう方法」はボツにしました。
Webアプリとしての理想は、ユーザーに画像をアップロードしてもらうのではなく、「今見ているページをそのまま切り抜く」ことだと思いました。
つまり、サーバ側でページ全体をキャプチャしてブラウザに表示させ、ユーザーに「どの部分を切り抜くか」をブラウザ上で指定してもらう方法です。
どうやって指定されたページをキャプチャさせるかということですが、最も簡単なのは、WordPress.com などが提供している「URLとサイズを指定するだけでキャプチャした画像を返してくれるサービス」を利用することですが、それらを片っ端から試してみたところ、キャプチャした画像が返ってくるまでの時間が毎回まばらで、2分以上待たされることも多く、しかもほとんどのサービスがHTMLのテキスト部分の日本語フォントが非常に汚いものでした。
私はCroppyを作るにあたり、URLを指定してから平均5秒以内に画像を返してくれ、さらに日本語部分は美しくレンダリングされることを目標にしたいと思っていました。
よって、この方法はボツです。
そこで、自前で専用サーバを用意し、そこにキャプチャ用のパッケージをインストールしておくしかないと思いました。
専用サーバを普通に借りると月々1万円前後の費用がかかりますが、最近はVPSなら月々980円程度で借りられ、好きなOSやパッケージを入れられるのでこちらを選ぶことにしました。
VPSとは「Virtual Private Server」つまり仮想専用サーバのことです。
まず、サーバを1台まるごと借りると高くつくので、個人サイトの場合はサーバをまるごと1台借りるということはほぼなく、月数百円で共用レンタルサーバやVPSを借りることが多いと思います。
共用レンタルサーバの場合は、サーバの中にたくさんあるフォルダを1つ借りるイメージでしょうか。
1台のサーバをたくさんの方が共同で借りる事により、月額数百円でサイトを維持することができます。
初期設定もほとんど必要なくすぐに使い始めることが出来ますが、サーバの設定を変えることはほぼできませんし、サーバが混雑する時間帯になると自分のサイトへのアクセスはほとんど無くても表示に時間がかかるようになってしまうことがあります。
それに対して、VPSはサーバの中にたくさんの仮想のサーバが立ち上がっており、その中の1台を貸りるというイメージです。
「サーバの中にたくさんの仮想サーバが…」というのはピンと来ない方もいらっしゃるかもしれませんが、例えば Mac で Web制作されている方ならIE確認のために「VMware」や「Parallels」というソフトを使って Mac 上に仮想の Windows を立ち上げている方もいらっしゃると思います。そのようなものをイメージしてみてください。
それぞれの仮想コンピュータは、勝手にアプリをインストールできるのはもちろん、OSを入れ替えたりといったこともできます。
つまり、専用サーバほど性能が高いわけではありませんが、共用レンタルサーバと比べて圧倒的に自由度が高いです。
VPSのセットアップは、基本的なLinuxコマンドを知っている必要があります。
やったことがない方には難しそうに感じるかもしれませんが、今はドットインストールに さくらのVPSの基礎 というレッスンがあるので、そちらを観れば基本的なセットアップはできると思います。
Croppyで使用しているVPSには、基本的なセットアップの後、wkhtmltoimage というバイナリーパッケージをインストールしました。
wkhtmltoimageは、URLとサイズを指定すると、そのページを取得し、WebKitエンジンでレンダリングし、PNGなどの画像に変換して保存することができます。
画像をキャプチャする方法
wkhtmltoimage の使い方は簡単で、 サーバ上で「wkhtmltoimage スペース URL スペース 保存ファイル名」を入力してEnterを押して実行するだけです。
widthやheightで画像サイズを指定することもできます。
すると、一瞬で完了しました。Yahoo!のトップページの場合、約1秒ほどです。
このように、綺麗なキャプチャが一瞬でとれて、サーバにPNG形式で保存されました。
日本語表示も問題ありません。
これをPHPから実行すれば、指定したURLのキャプチャ画像のできあがりです。
画像の一部を切り抜く方法
画像の一部を切り抜く時は、Photoshopの切り抜きツールのようにブラウザ上で直感的に操作できるようにしたいと思いました。そのために使ったのが jCrop という jQuery プラグインです。
jCrop を使うと、Photoshopの切り抜きツール風に画像の一部を範囲指定することができます。
といっても、jCrop そのもので画像を切り抜けるわけではありません。
jCropでは、ユーザーが指定した座標の取得ができるだけです。
CroppyではCSSで非表示していますが、実は画像の下にはフォームがあり、jCrop で指定した座標が入っています。
ユーザーが「完了」ボタンを押すと、座標が post.php というファイルに送られます。
post.php は画像を切り抜いて記事を投稿するためのPHPです。
画像を切り抜き、トップページなどの記事一覧用に、長辺240pxのサムネイル画像を作成し、記事を投稿に、ユーザーがTwitterでログインして、かつ「Twitterに投稿する」にチェックが入っていた場合はTwitterにも投稿します。
post.php では、PHPThumb というライブラリを使用して、受け取った座標をもとに画像を切り抜きます。
見ての通り、座標を指定して切り抜き、保存先を指定しているだけです。とても簡単です。
ついでに、先程切り抜いた画像をリサイズしてサムネイルを作成しておきます。
(240, 240)と指定しておけば、縦横比は変わらず長辺240pxのサムネイルを作ってくれるので非常に楽です。
実際のソースの PHPThumb 部分〜投稿完了あたりはこんな感じです。
/* 画像の切り抜き
PHPThumb
https://github.com/masterexploder/PHPThumb/wiki/Basic-Usage
*/
// ライブラリの読み込み
require_once '../lib/phpthumb/ThumbLib.inc.php';
// 例外処理
try {
$thumb = PhpThumbFactory::create('./tmp/' . $imgname . $ext);
} catch (Exception $e) {
// 例外
$err = "画像が正常に読み込めませんでした。";
}
// エラーがなければ
if (!$err) {
// 投稿し、返ってきた Post id を代入
$postid = wp_insert_post($postarr);
// 切り抜き(crop)
$thumb->crop($x1, $y1, $w, $h);
$thumb->save('/var/www/html/croppy/img/l/' . $postid . $ext);
// サムネイルの作成
$thumb->resize(240, 240);
$thumb->save('/var/www/html/croppy/img/s/' . $postid . $ext);
/* カスタムフィードの投稿 */
// カスタムフィードを挿入(Post id, カスタムフィード名, )
$cusinfo = update_post_meta($postid, 'img', $postid );
$cusinfo = update_post_meta($postid, 'url', $url );
$cusinfo = update_post_meta($postid, 'title', $title2 );
$cusinfo = update_post_meta($postid, 'ext', $ext );
if ($cusinfo) {
// 投稿成功 記事に転送
header( 'Location: ' . $posturl );
var_dump($posturl);
}
} else {
// エラーメッセージ
echo
画像を切り抜き
$err
EOF;
}
Croppyの作り方は以上です。