WordOnsen Sapporo 2014 と pre_get_posts による記事のソート処理

5/17 〜 5/18 に WordOnsen Sapporo 2014 が開催されました。定山渓温泉にてゆったり WordPress の勉強会という企画であります。 🙂

sacss_wordOnsen_560160

セミナー講師を担当させていただきましたが、温泉ということでいつもの SaCSS の勉強会と趣を変えようかと、モニターにコードを映しながら、お題を「functions.php を使い、pre_get_posts フックを用いてカスタム投稿をソートする」と決めてライブコーディングする形式でしゃべらせて頂きました。

134_large

東京から来られましたまがりん様に相談しながら、ライブコーディングしていたら夢中になってしまい、気がついたら 90分も演っていたということで、すいませんすいません。。(←本人はとても楽しかったらしい…

ということで、申し訳ないので、このブログにてソースなどまとめてみたいと思います。 お役立ていただけたらと思います。

functions.php について

  • functions.php は WordPress の初期化後すぐに評価されるテーマ内のファイル。
  • functions.php にフックの追加処理を記述しておくことにより、WordPress 初期化後に実行されるメイン処理の挙動をテーマから変更することができる。
  • WordPress には処理の変更が行えるように、たくさんのフックが定義されている。
  • 今回はテンプレートファイルのループで出力される記事の抽出条件を変えることができる pre_get_posts フックを活用して、カスタムフィールドの値で記事の順番をソートすることをお題としました。

フィルターフックの基本形

add_filterに関数名(ここでは change_query)を設定する。

function change_query($query) {
    $query->set('posts_per_page', 1);
}

add_filter('pre_get_posts', 'change_query');

PHP 5.3 以降では無名関数が利用でき、フックが関数定義なしでかけるので、直感的で便利。(以下のコードはこの形式でかきます)

add_filter('pre_get_posts', function($query) {
    $query->set('posts_per_page', 1);
});

pre_get_posts フィルターフックによるメインクエリー改変

pre_get_posts フックから渡されてくる引数($queryオブジェクト)に set メソッドでパラメータを上書き設定すると記事の抽出条件が変更ができる。

例えば posts_per_page パラメータには出力する記事数が設定できる。

add_filter('pre_get_posts', function($query) {
    // var_dump($query);
    $query->set('posts_per_page', 1);
});

存在するパラメータは Codex の WP_Query をみるか、var_dump($query); をすると知ることができる。

pre_get_posts で改変できるクエリーは、サイト上の表示だけでなく WordPress の管理画面の一覧表示などでも使われるため、テンプレートファイルからのメインクエリー出力時のみ変更するように、次のような if で条件を絞る。

add_filter('pre_get_posts', function($query) {
    if(!is_admin() && $query->is_main_query()) {
        $query->set('posts_per_page', 1);
    }
});

pre_get_posts を使ったカスタムフィールド値でのソート処理

サイト上に配置された「昇順」「降順」などのリンククリックから、記事をカスタムフィールドに設定された値でソートを行う。例えば、カスタムフィールド PRICE に設定された値段で、商品の表示順をソートする場合。

商品を登録するカスタム投稿(post_menu)を定義。

add_action('init', function() {
    register_post_type('post_menu', array(
        // 管理画面ラベル名
        'label' => '商品',
        'public' => true,
        'supports' => array(
            'title', 'editor', 'thumbnail', 'custom-fields'
        ),
    ))
});

「昇順」「降順」のリンクとアーカイブページを作成するため、カスタム分類(sort)を定義。

add_action('init', function() {
    register_taxonomy('sort',
        // この分類はカスタム投稿 post_menu に属する
        array('post_menu'),
        array(
            // 管理画面ラベル名
            'label' => 'ソート',
            // 管理画面に表示する
            'show_ui' => true,
        )
    );
});

カスタム分類ができたら、「昇順」「降順」という分類を管理画面から登録し、スラッグをそれぞれ asc と desc に設定。(設定後、このカスタム分類は管理画面上に表示しなくてもよいため、show_ui を false にしてもOK)

functions.php でカスタム投稿・分類の設定を行った後は、管理画面から「パーマリンク」を一度ひらいてあげて、設定をリフレッシュしましょう。(←現地でわすれてまがりん様につっこまれる)

サイト上にカスタム分類(sort)のリンクを出力するため、テーマのテンプレートファイルに wp_list_categories テンプレートタグを配置。この分類には記事が属せず、そのままだとリンクが出力されないため、hide_empty を false に設定。(← true にしようとしてまがりん様につっこまれる)

<?php wp_list_categories(array(
    'title_li' => '',
    'taxonomy' => 'sort',
    'hide_empty' => false )); ?>

「昇順」「降順」リンクがサイトに出力されるも、そのままのクエリーだと、単純に分類に属する記事を抽出しようとするので、何もでてこない。

ここで pre_get_posts の出番。クエリーを改変し、「カスタム分類 sort 出力時は、カスタム投稿(post_menu)に属している記事」という条件にまずは変更。$query が持つ条件分岐タグ is_tax でクエリー改変条件を絞り込み。

add_filter('pre_get_posts', function($query) {
    // メインクエリーでカスタム分類 sort 時のクエリーを変更
    if(!is_admin() && $query->is_main_query() && $query->is_tax('sort')) {
        // カスタム投稿 post_menu の記事を抽出対象にする
        $query->set('post_type', 'post_menu');
        // カスタム分類 sort による抽出を削除してしまう
        $query->set('sort', '');
    }
});

この処理で「昇順」「降順」リンクをクリックで表示されるカスタム分類アーカイブページで、商品が日付順で出力されるようになる(まだどちらのリンクも同じ抽出結果)。

カスタムフィールド PRICE によるソート処理を追加。

add_filter('pre_get_posts', function($query) {
    // メインクエリーでカスタム分類 sort 時のクエリーを変更
    if(!is_admin() && $query->is_main_query() && $query->is_tax('sort')) {
        // カスタム投稿 post_menu の記事を抽出対象にする
        $query->set('post_type', 'post_menu');
        // カスタムフィールド PRICE でソートするようにクエリーを設定
        $query->set('meta_key', 'PRICE');
        $query->set('orderby', 'meta_value_num');
        // 押されたリンクにより昇順か降順のソートをクエリーに設定
        if($query->get('sort') == 'asc') {
            $query->set('order', 'ASC');
        } else {
            $query->set('order', 'DESC');
        }
        // カスタム分類 sort による抽出を削除してしまう
        $query->set('sort', '');
    }
});

これにて完成です。 🙂

まとめると、カスタム分類 sort を作成することにより、カスタム分類アーカイブページの URL(クエリー)を WordPress 上に作成し、リンククリック時のクエリーを pre_get_posts により改変しカスタムフィールドでソートするという合わせ技となります。

この方法は、アーカイブページのクエリーに相乗りすることでページネーションも普通に行うことができますので、便利なのではないかと思います。

なお、セミナー中でも少しふれましたが、カスタムフィールドを使ったソートは、WP_Query で対応しているものの、WordPress の実装上、DB インデックスがあたらなかったり、value カラムが varchar になっている関係もあり、表示速度はそれほど期待できません。 記事が多いサイトで利用する場合は、事前に速度をテストしたり、もし遅い場合は、キャッシュ系の技術を併用するなどの対策を行ってください。

というわけで、WordOnsen Sapporo はセミナー後、開発合宿及び懇親会に突入。

まがりん様がもくもくと Stop the Bokettch のバージョンをあげ、マリメロ様が wp-otenki のカスタムお天気画像をつくり、気がつけばマミリンがぐーすか寝ているなど、盛り上がりをみせておりました。

71_large

翌日は円山公園散策。まがりん様のリス写真。

健康的! 楽しかったです(^ ^アリガトー

baserCMS テーマの github 公開と Gradle による Sass ビルド

baserCMS テーマコンテスト受賞作品の「Cafe Debut」と「basercart」テーマのソースコードを github に公開しました。 🙂

h1romas4/basercart

basercart

h1romas4/cafedebut

cafedebut

普通に baserCMS に導入してみたい方は、baserマーケットから .zip をダウンロードしていただければと思います。「Cafe Debut」は先日 baserCMS 3.0.4 に対応され、プラグイン同梱機能や、新しいコーディングスタンダードに対応した新板になっています。(調整していただきまして、どうもありがとうございました!)

今回公開した github のソースコードは、テーマの修正や開発用を想定したものです。

basercart のソースツリーには開発時に用いた sass ファイルと、gradle によるビルド定義を加えています。( .sass ファイルウォッチから .css への自動コンパイルができるように準備しています)

本テーマの製作時は grunt で sass のコンパイルを行っていたのですが、nodejs や Ruby の環境を導入したり、それらのバージョンを開発メンバー間で合わせたりするのが大変と感じましたので、github 公開版ではビルダーを Gradle に変更しました。

Gradle はウェブ制作の方にはあまり馴染みがないかとは思いますが、最近の Java 界隈で良く採用されているビルドツールです。ここでは、grunt と同じようなタスクランナーとして考えてもらって良いと思います。

Gradle が依存するのは Java の環境だけですので、Windows、Mac、Linux ともにほとんど準備なしに(Java が入ってなければ入れるだけ、nodejs や Ruby、各周辺ツールの導入なしに)動作させられ、いつでもポータブルに同一の結果が得られるのが、grunt から変更したポイントになります。

github に公開されているソースには Gradle から生成できる gradlew と呼ばれる Gradle 自体の環境を自動で準備するラッパープログラムもコミットされています。なので事前に Gradle を導入することすら不要です。

というわけで、github から git clone するか .zip をダウンロードしていただいた後、./gradlew watch するだけで .sass のウォッチ・ビルドが開始できます。(初回起動時のみ、環境をオートでつくるため時間が少々かかります)

cd basercart
./gradlew watch
#Windows の場合は、gradlew.bat watch

簡単あるね。 🙂

build.gradle では、次のプラグインを使わせて頂いています。

A SASS / Compass plugin for Gradle は、JRuby を用いて gem の取得や処理を行い、Sass / Compass のコンパイルを行います。この動きにより、ビルドを行う PC には事前に Ruby の導入が不要になる仕掛けになっています。

その他にも同様な動きで Java に含まれる JavaScript の実装(Rhino)を用い、CoffeeScript をコンパイルするプラグインもあるようです。(TypeScript も同じ実装、早くでないかなぁ。Nashorn だと面白そう :))

Gradle Watch Plugin は Java の NIO を用いて、各 OS のファイル監視 API からの通知を元に変更時のタスクが定義できます。(ちなみに、A SASS / Compass plugin だけでも watch はできそうでした)

Gradle の build.gradle 定義は、Groovy が持つ AST 変換や各種シンタックスシュガーなどの効果で、非常に簡潔にかくことができます。 これに慣れてしまうと、JS の (function() { }) とか .pipe(“”) などがずいぶん冗長に見えてしまいますね。 😛

というわけで、ウェブ制作にも Gradle いかがでしょうか。配布先に環境をつくらなくてよいのは、大きなメリットのように思います。

ちなみに、あんまり関係ないですが Groovy 版 Ruby on Rails の新板である Grails 2.3 からは同梱される Asset Pipeline Plugin により sass、less、coffeescript が標準サポートされるようです。ごくり。

ぐる。

Eclipse EGit の使い方(2/2)

前回に続き、EGit の使い方です。

目次

  1. 導入
  2. ローカルリポジトリの作成
  3. ファイル操作
  4. リモートリポジトリに接続
  5. ローカルブランチの作成
  6. リベースインタラクティブ
  7. ブランチからのマージ1
  8. ブランチからのマージ2
  9. コンフリクトの解消
  10. リベース
  11. コミットコメント修正
  12. コミットリセット
  13. リバートコミット

このページでは、7〜13までを記載しています。

ブランチからのマージ1

develp ローカルブランチのコミットを origin/master に接続している master ブランチにマージしてプッシュする操作です。この操作では、devel に行ったコミット操作が master に統合されます。

develp ローカルブランチは次のようになっています。

egit-rebasei05

1. master ブランチにスイッチします。

egit-margea01

2. プロジェクト右クリックから、Team -> Marge を選択します。

egit-margea02

3. devel ブランチを指定し Merge ボタンを押下すると、master ブランチに devel ブランチのコミットがマージされます。devel と master でコンフリクトがなく、Fast forward option で、If a fast-foward, only update the branch pointer を選択しているため、master のおしりに devel のコミットが結合されます。

egit-margea03

egit-margea04

4. リモートリポジトリにプッシュされていないコミットがあると↑印で分かります。 Team -> Push to Upstream でプッシュします。( devel ブランチで行った rebase -i で修正したコミットはプッシュされていないのが分かります)

egit-margea05

egit-margea07

egit-margea08

b03

5. 不要になったローカルブランチは、Git Repository View から右クリック Delete Branch で削除できます。

egit-margea09

ブランチからのマージ2

develp ローカルブランチのコミットを origin/master に接続している master ブランチにSquash マージしてプッシュする操作です。この操作では、devel ブランチに行ったコミット全てをひとつにまとめて master にコミットできます。

1. Team -> Swicth To -> New Branch から devel2 ローカルブランチを作成します。

egit-margeb01

2. devel2 で作業を行い、(恥ずかしい)コミットをいれます。

egit-margeb02

3. Team -> Swich To -> master から master ブランチに切り替えます。

egit-margea03

4. Team -> Merge を選択します。

egit-margeb03

5. devel2 ブランチを選択し、Merge options で Squash を選択し、Merge ボタンを押下すると、ソースコードがマージされます。(コミットはされません)

egit-margeb04

egit-margeb05

egit-margeb06

6. マージされたソースを master ブランチにコミットすると、devel2 の修正が新しいコミットとして作成できます。なお、Commit Changes ダイアログでは Commit and Push を押下すると、コミット後自動的にリモートブランチにプッシュが行えます。

egit-margeb07

egit-margeb08

b04

コンフリクトの解消

マージやプル操作などで、ソースに衝突が発生した場合は、次の手順でマージを行います。

master からブランチした devel3 ローカルブランチで作業中に、起点とした master に別な修正がかかりコンフリクトした例です。

egit-confrict01

master にスイッチして devel3 をマージしたところ衝突が起きました。該当のモジュールに赤印がつき、プロジェクトのマーカーに Conficts がつきます。

egit-confrict02

egit-confrict03

1. 衝突したモジュールを右クリックし、Maege Tool を選択します。

egit-confrict04

2. Select a Merge Mode で Use HEAD を選択します。

egit-confrict05

3. Merge Tool でソースコードのマージを解消します。

egit-confrict06

4. マージして赤くなっているモジュールを右クリックから、Team -> Add to Index を選択し、インデックスに戻してあげると、プロジェクトのマーカーが Merged に変わります。

egit-confrict07

egit-confrict08

5. コミットを行います。(マージコミットが作成されます)

egit-confrict09

egit-confrict10

b05

リベース

ローカルブランチで作業中に、ブランチの起点となったコミットを動かし、なるべくマージ(コミット)しないようにリベースする手順です。

master からブランチしたローカルブランチの devel4 をリベースし、最新の master を起点に追従します。

1. devel4 で作業中の master にリベースしたいタイミングで、Team -> Rebase を選択します。

egit-rebase01

2. master ブランチを選択し、Rebase を押下します。

egit-rebase02

3. rebase 時に不運にもソースコードにコンフリクトが起きた場合は、Start Merge Tool to resolve conflicts を選択し OK ボタンを押下します。

egit-rebase03

4. コンフリクトを解消するとマージ印がつきます。プロジェクトのマーカーは Rebase interractive になっており、まだ rebase 操作中であることが分かります。

egit-rebase04

5. Team -> Rebase -> Continue Rebase を選択し、全ての衝突を解消すると、リベースが完了します。

egit-rebase05

egit-rebase06

コミットコメント修正

コミットした直前のコミットコメントは修正できます。

egit-amend01

1. モジュールに修正をかけない状態で、再びコミットを行おうとすると、No files to commit ダイアログが表示されるので、はいを押下します。

egit-amend02

2. コミットダイアログが表示されるので、コミットコメントを修正し、Commit を押下すると直前のコミットコメントが修正されます。

egit-amend03

egit-amend04

コミットリセット

プッシュ前であれば、コミットを取り消す(HEADを移動させる)ことができます。

egit-reset01

1. ヒストリービューからいらないコミットの前(HEADにしたい)のコミットにカーソルを合わせて、Reset -> Mixed を選択すると、そこまでのコミットがなかったことになります。(Mixed を選択した場合、ワーキングディレクトリのファイルの内容はそのままになります。ファイルの内容も抹消したい場合は Hard を選択します)

egit-reset02

egit-reset03

egit-reset04

リバートコミット

プッシュしてしまったコミットを取り消すには、打ち消しのコミットを入れてプッシュします。

機能G追加のコミットが不要でしたが、リモートリポジトリにプッシュしてしまったので取り消します。

b06

egit-revert01

1. ヒストリービューから、取り消したいコミットを右クリックして Revert Commit を選択すると、取り消しとなるリバートコミットが行われます。

egit-revert02

egit-revert03

3. リバーとコミットをプッシュします。

egit-revert04

b07

以上、Egit の操作系の紹介でした。(実はプルを書き忘れていたりしますがそのうちなおしておきます…)

EGit ではその他にも、stash とかタグうちとか、ブランチ同士の比較なども同じような操作でできますので試してがってん。3.3.1 にて自分がやりそうな操作はひと通りできるようになっているようです。(3.4 も後少しで Eclipse Luna とともにリリースされるハズです)

気になっているのが JGit のコマンドラインインターフェースがあるような雰囲気なのですが、どうやって使うんでしょうか。

…てなわけで、オチがないので滝にうたれてきます。