WordPress3.0未満でのカスタムタクソノミーの使い方

taxonomy-0
WordPress の3大使われない機能のひとつにカスタムタクソノミーがあります。タクソノミーという言葉に馴染みが無くハマる日本語も見当たらないため長らくマイナーな立場に甘んじてきましたが、WordPress 3 で UI が充実しカスタム投稿タイプと絡んでようやく陽の当たる場所へ出てきそうなので予習しておきましょう。
タクソノミーは分類ってことです。カテゴリーもタグもタクソノミーなんですよ。カテゴリーは階層(親子関係)有りのタクソノミー、タグは階層無しのタクソノミー。混乱してきましたね。そんなキミのためにタクソノミーとは何なのか、どうやって使うのか。ココだけ読んどけばオール OK、にしてやるぜ。

ことばのせつめい

生涯 WordPress のカスタマイズ1本と謳っている wpxtreme でも、ブログの記事は「TUTORIALS」「PORTFOLIO」などのカテゴリーに分類されています。「メイク」「グルメ」「わんこ」と記事のカテゴリーを分けているえびちゃんもいることでしょう。
ちょっと待って。「カテゴリー」と何気なく言ってますけど、デヴェロッパー的にはこれは「カテゴリーのターム」です。「TUTORIALS」「メイク」など、これらはカテゴリーじゃなくてカテゴリーのタームなんです。共学の私立ですね。通ってみたいです。
同じく「投稿のタグ」も[新規タグの追加]で追加した「ADMIN」「プラグイン」などが「タグのターム」です。どちらも今まで通り「わんこカテゴリー」「ADMIN タグ」と言ってて OK ですが、タームであることを知ってて言うのと知らないで言うのとではやはり他人に気づかれない程度の差があると思います。
そしてタームをグルーピングしている「カテゴリー」や「タグ」をタクソノミーと言います。なので「カテゴリーというタクソノミーで TUTORIALS,PORTFOLIO などのタームに分類する」が正確。女性相手なら「わんこカテゴリーだね☆」が正解。
以上、ネチネチな言葉狩りではなく、AT がオートマなら MT はミッションじゃなくてマニュアルだろうが、っていうどうでもいい話でもなくて、誤解による混乱防止のため正しい理解の下に話を進めるのでよろしくお願いするぜ。
なお、MT は MovableType では?っていうコメントは不要だ。AT って AUTOMATTIC ですよね!っていうコメントは歓迎する。

具体的な分類を考えよう

そもそもカテゴリー,タグなんてので説明するからわかり難いのだ。ケーススタディとして「グルメーイタリアン|フレンチ|和食」「動物ー犬|猫」とブログ記事を分けたいえびちゃんを例に説明する。一般的な wper なら以下のようにカテゴリーを作るだろう。

  • カテゴリー
    • グルメ
      • イタリアン
      • フレンチ
      • 和食
    • 動物

だが、えびちゃんの反応はこうだ。「何?!カテゴリー?意味わかんない!グルメと動物がなんで一緒になってんのよ!」一方、wpist ならタクソノミーでスマートに解決するはずだ。

  • グルメ
    • イタリアン
    • フレンチ
    • 和食
  • 動物

そして、えびちゃんの反応はこうだ。「あなたってステキね。私なんだか酔っちゃったみたい。」 説明するとは言ったが説明するまでもないだろう。なりたい自分になれるかどうかは自分次第だ。

では、次にそれぞれの記事に「おいしさーまた食べたい|二度とイラねぇ」「かわいさーゲキカワ|ブサイク」のようなラベル付けをしたいとえびちゃんにお願いされたらどうする?一般的な wper なら以下のようにタグを作ってしまうだろう。

  • タグ
    • また食べたい
    • 二度とイラねぇ
    • ゲキカワ
    • ブサイク

さらに「グルメカテゴリーのときは また食べたい|二度とイラねぇ のどちらかを選んでください。」とかメンドクサイこと言ってまたドヤされるわけだ。そして wpist はまたしてもタクソノミーで鮮やかに解決する。

  • おいしさ
    • また食べたい
    • 二度とイラねぇ
  • かわいさ
    • ゲキカワ
    • ブサイク

えびちゃんの頬は紅潮し瞳はウルウルしている。そう、タクソノミーを制する者は人生の勝利者となるのだ。

現実に戻ろう

では早速タクソノミーを使ってみよう。使い方は簡単、以下を functions.php に追加すればよろしい。

function my_init(){
  register_taxonomy('animal', 'post', array('hierarchical' => true, 'label' => '動物',
    'update_count_callback' => '_update_post_term_count'));
  register_taxonomy('taste', 'post', array('hierarchical' => false, 'label' => 'おいしさ',
    'update_count_callback' => '_update_post_term_count'));
}
add_action('init', 'my_init', 0);

各タクソノミーの管理画面でターム一覧に表示される投稿数を正しい値にするには 'update_count_callback' => '_update_post_term_count' が必要です。

また、投稿数の値は下書きを含みませんがビルトインの「カテゴリー」タクソノミー,「タグ」タクソノミーのみ、投稿数のリンクをクリックして表示される投稿編集画面での投稿一覧に下書きが含まれており、ここでの表示件数と先の投稿数は一致しません。不具合なのかまだ作り中だからなのかは不明。

タクソノミーの追加は何よりも優先しろってことなので add_action('init', 'my_init', 0); のアクションで実行します。'動物' などの表示名は .mo ファイルを作成して __('Animal') のように指定するとデキルヤツを演出できますね。register_taxonomy() の詳細は Codex を参照のこと — Function Reference/register taxonomy とは言ってもメンドクサがって見ない人のために未検証のままテキトウに解説します。必要になったら各自検証すること。

使い方:
<?php register_taxonomy($taxonomy, $object_type, $args); ?>
  • $taxonomy
    (文字列) (必須) タクソノミー名。スラッグ的なもの。
    デフォルト:なし
  • $object_type というより $post_type(s) のがシックリくる
    (配列/文字列) (必須) 投稿/ページ/メディアなど、どの投稿タイプで使うかの指定。複数なら配列でね。
    デフォルト:なし
    例:'post', array('post', 'page')
    カスタム投稿タイプが使えるようになったら、さらに夢が広がりますね。
  • $args
    (配列/文字列) (任意) 各種引数の指定。
    デフォルト:なし
    引数:
    • label
      (文字列) 管理画面のメニューなどに表示されるタクソノミーの名前。
      例:'おいしさ'
    • hierarchical
      (boolean) タクソノミーが階層を持つかどうかを指定。
      例:
       false:階層無し=タグ形式
       true:階層有り=カテゴリー形式
    • update_count_callback
      (文字列) このタクソノミーに属するタームが増減したときに呼ばれる関数名。
    • rewrite
      (配列|false) タクソノミーアーカイブとかのパーマリンク名(スラッグ部分)の指定。指定するならキーが 'slug' の配列でね。
      デフォルト:$taxonomy を使う。
      例:
       デフォルト:http://example.com/taste/matatabetai
       false:rewrite 無し(意味不明)
       array('slug' => "my-tax-$taxonomy") :http://example.com/my-tax-taste/matatabetai
    • query_var
      (文字列|false) ?query_var=term 形式のクエリ変数名の指定。
      デフォルト:$taxonomy を使う。
      例:
       デフォルト: ?taste=matatabetai
       false:クエリ無し(意味不明)
       "my-tax-$taxonomy": ?my-tax-taste=matatabetai
    ま、hierarchical と label だけを指定すれば OK です。

    うそでしたごめん。
    'update_count_callback' => '_update_post_term_count' が無いと ターム一覧で表示される「投稿」の数が正しく更新されないのでこれも必要。

参考:「カテゴリー」タクソノミーと「タグ」タクソノミーの定義(wp-includes/taxonomy.php 17行目〜)

register_taxonomy( 'category', 'post', array('hierarchical' => true, 'update_count_callback' => '_update_post_term_count', 
  'label' => __('Categories'), 'query_var' => false, 'rewrite' => false) ) ;
register_taxonomy( 'post_tag', 'post', array('hierarchical' => false, 'update_count_callback' => '_update_post_term_count',
  'label' => __('Post Tags'), 'query_var' => false, 'rewrite' => false) ) ;

Codex に

WordPress 2.8 will automatically build an admin interface for custom taxonomies. Allowing end users to add terms and associate posts with the taxonomy terms.
と書いてあるとおり、管理画面で「おいしさ」タクソノミーを満喫できます。 「おいしさ」タクソノミー管理画面新規投稿の「おいしさ」タクソノミー行頭に ー が来てるのはワザとなのでコメントは不要だ。これだけで「おいしさ」タクソノミーを利用できるんだから、今後の案件に積極的に取り入れていこうぜ。ちなみに「おいしさ」タクソノミーのタグクラウドを表示させたい場合は以下のようにすれば OK。

wp_tag_cloud(array('taxonomy' => 'taste'))

指定したカテゴリーに含まれるタグのみ表示したい的なことはこれでスマートに解決するわけです yeah!

階層有りのタクソノミーの管理画面 UI を作る

前述の Codex ではカスタムタクソノミーの管理画面は自動で出てくるよ、と言ってますが実は UI が対応済みなのはタグ形式=階層無しのタクソノミーだけなので気をつけましょう。カテゴリー形式=階層有りのタクソノミーは バージョン 2.9.2 の時点では UI はサポートされていません。3.0 ではサポートされると思います(知らんけど)ので開発版をダウンロードしてお試しいただくと良いかもです。どこでダウンロードするのかは自分で調べるのだ。

もの好きな方は引き続きついてきていただいて、階層有りのタクソノミーの UI を作りましょう。コア開発してる気になるのできっと楽しいですよ。
まずは管理画面の[投稿]トップメニューのサブメニューとして[ 動物]メニューを追加します。functions.php に以下を追加。

function my_admin_menu() {
  global $submenu;
  $i = 55;
  $submenu['edit.php'][$i++] = array( esc_attr('動物'), 'manage_categories', 'my-edit-categories.php?taxonomy=' . 'animal' );
}
add_action('admin_menu', 'my_admin_menu');

普通なら[投稿]トップメニューにサブメニューを追加する場合は add_posts_page(Codex:Adding Administration Menus)を使います。

add_posts_page('動物', '動物', 'manage_categories', 'animal', 'my_animal_page');

これはメニューのリンク先が http://example.com/wp-admin/edit.php?page=animal となり、実際に画面を出力する際にコールバック関数として指定した 'my_animal_page' が呼ばれます。んが、WordPress のタグ,カテゴリーの編集の実装では redirect を多用しているので、コールバック関数内で同じことをしてもうまく動作しません。そんな時はお隣のサブメニューがどうやっているかを調べましょう。

[投稿のタグ]サブメニューの URL:http://example.com/wp-admin/edit-tags.php?taxonomy=post_tag
[カテゴリー]サブメニューの URL:http://example.com/wp-admin/categories.php
タグはタクソノミー対応してるけどカテゴリーはまだよ、てのが良くわかります。ちゅうことはですよ、
[動物]サブメニューの URL:http://example.com/wp-admin/my-edit-categories.php?taxonomy=animal

とすれば良さそうですね。categories.php をコピって edit-tags.php を真似れば OK な気がします。できたファイルは my-edit-categories.php と名付けて wp-admin フォルダに放り込みましょう。おぉ、コア開発な気分。
で、このようなオリジナルなリンクを持つサブメニューを作る方法が先のコードです。[カテゴリー]サブメニューは $submenu['edit.php'][50] に入ってるのでそれより下に表示させるために 55 番とかに入れちゃいます。詳細は、wp-admin/menu.php 46行目辺りをご覧いただくときっとなるほど納得です。

my-edit-categories.php を作る

今回は異様に長い内容になるので頑張ってついてくるように。ちなみにこのような不毛な作業を車輪の再発明と言いますが、コア開発気分を味わうのが目的なので心配無用です。俺にもできそう、と思った方は WordPress の発展に深いところで貢献していきましょう。キミ達がコア開発メンバーとして名を連ねるのが先生の夢です。私にもできそうだわ!と思った方は kz にコンタクトしましょう。キミ達をはべらせるのが俺様の夢なのさ。

wp-admin/my-edit-categories.php (categories.php との差分のみ)

タクソノミー対応
- wp_reset_vars( array('action', 'cat') );
+ global $action, $cat, $cat_ID, $taxonomy; 
+ wp_reset_vars( array('action', 'cat', 'cat_ID', 'taxonomy', 'page') );
+
+ if ( empty($taxonomy) ){
+  if(isset($_POST['taxonomy']))
+    $taxonomy = $_POST['taxonomy'];
+  else
+    $taxonomy = 'category';
+ }
+
+ if ( !is_taxonomy($taxonomy) )
+   wp_die(__('Invalid taxonomy'));

タクソノミー対応のカテゴリー追加
- if ( wp_insert_category($_POST) )
-   wp_safe_redirect( add_query_arg( 'message', 1, wp_get_referer() ) . '#addcat' );
- else
-   wp_safe_redirect( add_query_arg( 'message', 4, wp_get_referer() ) . '#addcat' );
+ $ret = my_insert_category($_POST);
+ if ( $ret && !is_wp_error( $ret ) ) {
+   wp_redirect("my-edit-categories.php?taxonomy=$taxonomy&message=1#addcat");
+ } else {
+   wp_redirect("my-edit-categories.php?taxonomy=$taxonomy&message=4#addcat");
+ }

- wp_redirect('categories.php');
+ wp_redirect("my-edit-categories.php?taxonomy=$taxonomy");

タクソノミー対応のカテゴリー削除
- wp_delete_category($cat_ID);
- wp_safe_redirect( add_query_arg( 'message', 2, wp_get_referer() ) );
+ wp_delete_term($cat_ID, $taxonomy);
+	
+ $location = 'my-edit-categories.php';
+ if ( $referer = wp_get_referer() ) {
+   if ( false !== strpos($referer, 'my-edit-categories.php'))
+     $location = $referer;
+ }
+
+ $location = add_query_arg('message', 2, $location);
+ wp_redirect($location);

タクソノミー対応のカテゴリー削除
- wp_delete_category($cat_ID);
+ wp_delete_term($cat_ID, $taxonomy);
	
- wp_safe_redirect( wp_get_referer() );
+ $location = 'my-edit-categories.php';
+ if ( $referer = wp_get_referer() ) {
+   if ( false !== strpos($referer, 'my-edit-categories.php') )
+     	$location = $referer;
+ }
+
+ $location = add_query_arg('message', 6, $location);
+ wp_redirect($location);

タクソノミー対応の「カテゴリーの編集」フォーム
- $category = get_category_to_edit($cat_ID);
- include('edit-category-form.php');
+ $category = get_term( $cat_ID, $taxonomy, OBJECT, 'edit' );
+ if (! is_wp_error( $category ) )
+    _make_cat_compat( $category );
+ include('my-edit-category-form.php');
 
タクソノミー対応のカテゴリー更新
- if ( wp_update_category($_POST) )
+ $ret = my_update_category($_POST);
+ if ( $ret && !is_wp_error( $ret ) )

ajax 関連スクリプト無し
- wp_enqueue_script('admin-categories');
- if ( current_user_can('manage_categories') )
-   wp_enqueue_script('inline-edit-tax');

メッセージ追加
+ $messages[6] = __('Categories deleted.');

フォームに追加
+ <input type="hidden" name="taxonomy" value="<?php echo esc_attr($taxonomy); ?>" />

- $num_cats = wp_count_terms('category');
+ $num_cats = wp_count_terms($taxonomy);

タクソノミー対応のカテゴリー一覧表示
- cat_rows(0, 0, 0, $pagenum, $cats_per_page);
+ my_cat_rows(0, 0, 0, $pagenum, $cats_per_page, $taxonomy);

タクソノミー対応のフォーム
- <form name="addcat" id="addcat" method="post" action="categories.php" class="add:the-list: validate">
+ <form name="addcat" id="addcat" method="post" action="my-edit-categories.php" class="add:the-list: validate">

タクソノミー対応の「親カテゴリー」ドロップダウン
- wp_dropdown_categories(array('hide_empty' => 0, 'name' => 'category_parent', 'orderby' => 'name', 
  'selected' => $category->parent, 'hierarchical' => true, 'show_option_none' => __('None'))); 
+ wp_dropdown_categories(array('taxonomy' => $taxonomy, 'hide_empty' => 0, 'name' => 'category_parent', 
  'orderby' => 'name', 'selected' => $category->parent, 'hierarchical' => true, 'show_option_none' => __('None'))); 

ajax 編集は無し
- inline_edit_term_row('categories');

見難い。なお、ajax 関連処理はヤリ始めるとさらにオオゴトになるので今回は未対応です。



あ、途中で公開してしもた。続きは明朝!

ここ以降はうっかり公開した後に追記されたものです。見るなって言ったのにここまで見ちゃってしまってた人は改めて始めから読み直しましょう。新たな気づきがあるかもしれません。隠れキャラとか。

各種カテゴリー関連ファンクションをタクソノミー対応にする

続きは明朝(ミンチョウ)ってなんだよフォントかよ serif なのかよ。うっかり公開は気にせずガンガン行くぜ!では管理画面の完成イメージをどうぞ。階層有りタクソノミーの管理画面 タームの一覧表示は cat_rows() / wp-admin/includes/template.php で処理されてるので下請けファンクションと共にタクソノミー対応します。赤丸部分のデカイ方はアイコンを CSS で指定します。ちっさい方は当該の li 要素に class="current" を付加するとメニューの左側が > って凹みます。これは皆の宿題にしておくぜ。

まずはフィルタで OK なものから。
get_categories() / wp-includes/category.php

function &get_categories( $args = '' ) {
  $defaults = array( 'type' => 'category' );
  $args = wp_parse_args( $args, $defaults );

  $taxonomy = apply_filters( 'get_categories_taxonomy', 'category', $args );

get_categories_taxonomy フィルタには何も add されてないので $taxonomy = '' になります。$taxonomy が設定されていないと、これ以降各種ファンクションはタクソノミーを 'category' として処理します。なので functions.php で get_categories_taxonomy にフィルタを与えてやりましょう。

function my_get_categories_taxonomy($taxonomy, $args){
  return isset($args['taxonomy']) ? $args['taxonomy'] : $taxonomy;
}
add_filter( 'get_categories_taxonomy', 'my_get_categories_taxonomy', 10, 2 );

get_categories(array('taxonomy' => 'taste')) と呼べば OK です。

はい、次。cat_rows() 達をタクソノミー対応にします。functions.php に書くんだけどもマイブームの差分コードでお楽しみください。見難い。

cat_rows(), _cat_rows(), _cat_row() / wp-admin/includes/template.php

- function cat_rows( $parent = 0, $level = 0, $categories = 0, $page = 1, $per_page = 20 ) {
+ function my_cat_rows( $parent = 0, $level = 0, $categories = 0, $page = 1, $per_page = 20, $taxonomy = 'category' ) {

- $args = array('hide_empty' => 0);
+ $args = array('hide_empty' =$gt; 0, 'taxonomy' =$gt; $taxonomy);

- $children = _get_term_hierarchy('category');
+ $children = _get_term_hierarchy($taxonomy);

- _cat_rows( $parent, $level, $categories, $children, $page, $per_page, $count );
+ _my_cat_rows( $parent, $level, $categories, $children, $page, $per_page, $count, $taxonomy );


- function _cat_rows( $parent = 0, $level = 0, $categories, &$children, $page = 1, $per_page = 20, &$count ) {
+ function _my_cat_rows( $parent = 0, $level = 0, $categories, &$children, $page = 1, $per_page = 20, &$count, $taxonomy ) {

- echo "\t" . _cat_row( $my_parent, $level - $num_parents );
+ echo "\t" . _my_cat_row( $my_parent, $level - $num_parents, $taxonomy );

- echo "\t" . _cat_row( $category, $level );
+ echo "\t" . _my_cat_row( $category, $level, $taxonomy );

- _cat_rows( $category->term_id, $level + 1, $categories, $children, $page, $per_page, $count );
+ _my_cat_rows( $category->term_id, $level + 1, $categories, $children, $page, $per_page, $count, $taxonomy );


- function _cat_row( $category, $level, $name_override = false ) {
+ function _my_cat_row( $category, $level, $taxonomy, $name_override = false ) {

- $category = get_category( $category, OBJECT, 'display' );
+ $category = get_term( $category, $taxonomy, OBJECT, 'display' );
+ if (! is_wp_error( $category ) )
+   _make_cat_compat( $category );

- $edit_link = "categories.php?action=edit&cat_ID=$category->term_id";
+ $edit_link = "my-edit-categories.php?action=edit&cat_ID=$category->term_id&taxonomy=$taxonomy";

- $actions['inline hide-if-no-js'] = '<a href="#" class="editinline">' . __('Quick Edit') . '<a>';

- $actions['delete'] = "<a class='delete:the-list:cat-$category->term_id submitdelete' href='" . 
   wp_nonce_url("categories.php?action=delete&cat_ID=$category->term_id", 'delete-category_' . $category->term_id) . "'>" . __('Delete') . "</a>";
+ $actions['delete'] = "<a class='delete:the-list:cat-$category->term_id submitdelete' href='" . 
  wp_nonce_url("my-edit-categories.php?action=delete&cat_ID=$category->term_id&taxonomy=$taxonomy", 'delete-category_' .
  $category->term_id) . "'>" . __('Delete') . "";

- $posts_count = ( $category->count > 0 ) ? "<a href='edit.php?cat=$category->term_id'>$category->count" : $category->count;
+ $posts_count = ( $category->count > 0 ) ? "<a href='edit.php?$taxonomy=$category->slug'>$category->count</a>" : $category->count;

次は、タームの追加/更新ファンクション。これもアレンジしたファンクションは functions.php に書きます。

元ファンクション wp_insert_category() / wp-admin/includes/taxonomy.php

- function wp_insert_category($catarr, $wp_error = false) {
- $cat_defaults = array('cat_ID' => 0, 'cat_name' => '', 'category_description' => '', 'category_nicename' => '', 'category_parent' => '');
+ function my_insert_category($catarr, $wp_error = false) {
+ $cat_defaults = array('cat_ID' => 0, 'taxonomy' => 'category', 'cat_name' => '', 'category_description' => '', 'category_nicename' => '',
  'category_parent' => '');

- if ( empty($parent) || !category_exists( $parent ) || ($cat_ID && cat_is_ancestor_of($cat_ID, $parent) ) )
+ $id = is_term($parent, $taxonomy, 0);
+ if ( is_array($id) )
+   $id = $id['term_id'];
+ if ( empty($parent) || !$id || ($cat_ID && cat_is_ancestor_of($cat_ID, $parent) ) )

- $cat_ID = wp_update_term($cat_ID, 'category', $args);
+ $cat_ID = wp_update_term($cat_ID, $taxonomy, $args);

- $cat_ID = wp_insert_term($cat_name, 'category', $args);
+ $cat_ID = wp_insert_term($cat_name, $taxonomy, $args);


元ファンクション wp_update_category() / wp-admin/includes/taxonomy.php

- function wp_update_category($catarr) {
+ function my_update_category($catarr) {

- $category = get_category($cat_ID, ARRAY_A);
+ $category = get_term( $cat_ID, $catarr['taxonomy'], ARRAY_A, 'raw' );
+ if (! is_wp_error( $category ) )
+   _make_cat_compat( $category );

- return wp_insert_category($catarr);
+ return my_insert_category($catarr);

最後にデカイ赤丸部分のアイコンを表示させましょう。

function my_head(){
  echo '
<style type="text/css">
#icon-my-edit-categories{background:url(/wp-admin/images/icons32.png) no-repeat -552px -5px;}
</style>';
}
add_action('admin_head', 'my_head');

はい、以上で階層有りタクソノミーの管理画面が完成です。おつかれさまでした。

「階層有りタクソノミーの編集」画面

ところが UI の作成はまだまだ終わらんのだよ。ヤリ始めたことをちょっと公開してないか?そう、うっかり[変更をプレビュー]と[更新]を押し間違えるアレだよ。投稿したよとつぶやいたのを無かったことにするプラグインはありませんか?

ターム一覧で、ターム名または[編集]のリンクをクリックすると表示されるこの画面を作ります。「動物」タクソノミーの編集この画面は my-edit-categories.php?action=edit&〜 のときにインクルードされる my-edit-category-form.php で出力します。これはもちろん edit-category-form.php をコピってタクソノミー対応したファイルです。これも wp-admin に放り込んでやります。

+ <input type="hidden" name="taxonomy" value="<?php echo esc_attr($taxonomy) ?>" />

タイトルの「カテゴリーの編集」をタクソノミー名に変更するなら
- <h2><?php _e('Edit Category'); ?></h2>
+ <h2><?php echo get_taxonomy($taxonomy)->label; ?></h2>

えええー!こんだけー?はい、こんだけです。なんという富豪プログラミング。

投稿画面のメタボックス

階層無しタクソノミーは何もしなくても表示されてるのがうらめしい完成イメージです。早速メタボックスを作りましょう。なお、[すべてのカテゴリー][よく使われるもの][+新規カテゴリー追加]に相当する UI は省略します。javascript がらみなのでね許しておくれよ。では、先ほど追加した my_admin_menu() / functions.php 内に以下を追加します。

add_meta_box('taxonomydiv-animal', '動物', 'my_animal_meta_box', 'post', 'side', 'low');

taxonomydiv-animal はメタボックスの id です。テキトウにつけます。投稿画面の右側の下の方に '動物' というタイトルのメタボックスを my_animal_meta_box() ファンクションを使って出力するよ、という意味です。
その my_animal_meta_box() と下請けファンクションなども同様に functions.php に追加します。メンドクサくなったので差分はやめるよ。

function my_animal_meta_box($post, $box){
?>
<div class="tabs-panel">
  <ul class="list:category categorychecklist form-no-clear">
    <?php my_category_checklist($post->ID, array('taxonomy' => 'animal', 'checked_ontop' => false)); ?>
  </ul>
</div>
<?php
}

function my_category_checklist($post_id = 0, $args = array()) {
  $defaults = array(
    'taxonomy' => 'category',
    'checked_ontop' => true
  );
  extract( wp_parse_args($args, $defaults), EXTR_SKIP );
   
  $walker = new Walker_Taxonomy_Checklist;

  $args['selected_cats'] = wp_get_object_terms($post_id, $taxonomy, array_merge($args, array('fields' => 'ids')));
  $args['popular_cats'] = get_terms( $taxonomy, array( 
    'fields' => 'ids', 'orderby' => 'count', 'order' => 'DESC', 'number' => 10, 'hierarchical' => false ) );
  $categories = (array) get_terms($taxonomy, array('get' => 'all'));
	  
  if ( $checked_ontop ) {
    $checked_categories = array();
    $keys = array_keys( $categories );
  
    foreach( $keys as $k ) {
      if ( in_array( $categories[$k]->term_id, $args['selected_cats'] ) ) {
        $checked_categories[] = $categories[$k];
        unset( $categories[$k] );
      }
    }
   
    echo call_user_func_array(array(&$walker, 'walk'), array($checked_categories, 0, $args));
  }
  echo call_user_func_array(array(&$walker, 'walk'), array($categories, 0, $args));
}

require_once(ABSPATH . '/wp-admin/includes/template.php');
class Walker_Taxonomy_Checklist extends Walker_Category_Checklist {
  function start_el(&$output, $category, $depth, $args) {
    extract($args);

    $class = in_array( $category->term_id, $popular_cats ) ? ' class="popular-category"' : '';
    $output .= "\n<li id='" . $args['taxonomy'] . "-$category->term_id'$class>" . '<label class="selectit"><input value="' .
      $category->term_id . '" type="checkbox" name="post_' . $args['taxonomy'] . '[]" id="in-' . $args['taxonomy'] . '-' . 
      $category->term_id . '"' . (in_array( $category->term_id, $selected_cats ) ? ' checked="checked"' : "" ) . '/> ' .
      esc_html( apply_filters('the_category', $category->name )) . '';
  }
}

function my_save_post($post_id){
  if(defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) 
    return $post_id;

  if('post' == $_POST['post_type']){
    if(!current_user_can('edit_post', $post_id))
      return $post_id;
  }else{
    return $post_id;
  }

  $post_animal = $_POST['post_animal'];
  if ( empty($post_animal) || 0 == count($post_animal) || !is_array($post_animal) )
    return $post_id;
	
  $post_animal = array_map('intval', $post_animal);
  $post_animal = array_unique($post_animal);
  wp_set_object_terms($post_id, $post_animal, 'animal');
}
add_action('save_post', 'my_save_post');

my_category_checklist() の引数に 'checked_ontop' => true を与えるとチェックの入っているタームが先(上)に表示されます。親子関係のタームをインデント表示するには先に作成した my_head() / functions.php で以下のスタイルを追加します。

ul.categorychecklist ul{margin-left:18px;}

こんだけやればカスタムタクソノミーはお腹いっぱいですね。ボクはもう今年は要らないです。皆さんは WordPress 3.0 を心待ちにしていてください。きっと edit-tags.php,edit-categories.php なんてのは作らずに edit-taxonomies.php とかに一本化していることでしょう。各種ファンクションも tag,category なんて名前はもう使わずに taxonomy で統一してますね きっと。さすが。

そして!なんと今回の内容をダウンロードできるようにしましたので、皆様もぜひ車輪作りにチャレンジしてください。あなたのコードは全部持っておきたいの、というえびちゃんもご遠慮なくダウンロードしてくださいね。
以上、今回の投稿は最近明朝づいている kz からでした。

ダウンロード
  • taxonomy-with-wordpress-2.9.2-or-lower  version: 1.0 (.zip  8.92 KB)
  • 含まれるファイル:

    • functions.php
    • my-edit-category.php
    • my-edit-category-form.php

    40 Comments

    • BLOG: WordPress3.0未満でのカスタムタクソノミーの使い方 http://bit.ly/d4U5Tl

    • カスタムタクソノミーの謎が全て解決☆あざーっす RT @kzxtreme WordPress3.0未満でのカスタムタクソノミーの使い方 http://bit.ly/awGuRv

    • できた寝る: WordPress3.0未満でのカスタムタクソノミーの使い方 http://bit.ly/d4U5Tl

    • RT @kzxtreme: WordPress3.0未満でのカスタムタクソノミーの使い方 http://bit.ly/d4U5Tl

    • 目から鱗な力作解説。えびちゃん口説けるなら頑張って読破しよう。 RT @kzxtreme: WordPress3.0未満でのカスタムタクソノミーの使い方 http://bit.ly/d4U5Tl

    • show555 show555

      さすがkzさん!とても丁寧な解説ありがとうございます!
      今までカスタムタクソノミーの詳しい解説に出会えず悶々としておりましたが、これでよくわかりました!
      しかもサンプルダウンロードまでつけていただき…感激です。えびちゃんも嬉しさで泣いてると思います。

      • kz kz

        「ことばのせつめい」だけで済む内容なのにものすごく長くてびっくりですね。
        途中から主旨が管理画面を作ることに変わってる辺りは自分でもかわいいなと思います。

        ダウンロードする変態さんは 2名と予想。

    • カスタムタクソノミー

    • 実は、これ求めていたのです。早速使わせていただきまッス。

    • よけいなお世話ですが、私のような変態さんが、階層無しタクソノミーを2つ(以上?)使う場合、my_save_post)0 で条件分岐させて、my_xxx_meta_box() を必用なだけ複製してやれば良いわけですよね。
      実際にそれで動いているのでそれでいいやと思っているのですけど。

      • kz kz

        それで良いです。お好みにより、
        my_xxx_save_post() を増やして
        add_action(‘save_post’, ‘my_xxx_save_post’); しても OK です。

      • 了解です。
        add_action(’save_post’, ‘my_xxx_save_post’);
        よりは今のやり方の方が良さそうなので、そうさせてもらいます。

        ありがとうございました。

      • moc moc

        safari4なのですがコメント記入欄がなぜか表示されないので、Replyから失礼します。
        あと、GRAVATARなくてすみません。

        さっそく使わせていただこうかと思ってます。
        ちょっと気になったのですがこのタクソノミーの設定などは、3.0に移行するときはそのまま引き継がれるんですか?
        問題なく移行できるなら、本格的に使ってみようかなと思ってます。

        あと、標準のカテゴリー(正確にはカテゴリーというタクソノミーですか)を未選択にできないのは、正式対応してないからなんでしょうか。

        kzさんに聞くことでもないかもしれませんが、このブログが好きなえびちゃんの一人として質問します。(あ、男ですので勘違いしないでください。言葉のあやです。)

    • WordPress3.0未満でのカスタムタクソノミーの使い方  |  wpxtreme: 【ブクマ】分かりやすいなー。凄い http://bit.ly/ajUArR

    • RT @kachibito: WordPress3.0未満でのカスタムタクソノミーの使い方  |  wpxtreme: 【ブクマ】分かりやすいなー。凄い http://bit.ly/ajUArR

    • RT @kachibito WordPress3.0未満でのカスタムタクソノミーの使い方  |  wpxtreme: 【ブクマ】分かりやすいなー。凄い http://bit.ly/ajUArR

    • RT @kachibito: WordPress3.0未満でのカスタムタクソノミーの使い方  |  wpxtreme: 【ブクマ】分かりやすいなー。凄い http://bit.ly/ajUArR

    • RT @wordpressbot RT @kachibito WordPress3.0未満でのカスタムタクソノミーの使い方  |  wpxtreme: 【ブクマ】分かりやすいなー。凄い http://bit.ly/ajUArR

    • RT @kachibito: WordPress3.0未満でのカスタムタクソノミーの使い方  |  wpxtreme: 【ブクマ】分かりやすいなー。凄い http://bit.ly/ajUArR

    • ほほー。WordPressにこんな機能があったとは。 http://bit.ly/9baNdq

    • kz kz

      Safari での表示確認を怠っておりました。WebKit 仲間なのに Chrome での確認だけでは安心できない世の中になってしまったのですね。
      WordPress 開発メンバーがイジワルをしない限り 3.0にアップグレードしてもタクソノミーの設定はそのまま使えるはずです。
      2.9.2 まではカテゴリーが未選択の場合は強制的に未分類に分類されます。そこを無理矢理未選択にできるかどうかは明日明後日辺り確認してみます今日はもう寝ますお花見疲れです。GRAVATAR がえびちゃんなら即日対応したのですが残念です。
      3.0 でカテゴリー未選択時の扱いがどうなるかは、明日の WordBench 名古屋で識者の方々に問い質してみようと思います。明日ちゃんと起きれますように。

      • moc moc

        お疲れのところいろいろありがとうございます。
        標準のカテゴリーは別のことに使って、カスタムタクソノミーをBlog用ののカテゴリにしたりできないかな、とか考えてます。
        移行に関しては、WordPressの開発メンバーには嫌われてないと思いますので大丈夫そうですね。

      • kz kz

        3.0β版のソースを見たところ、これまでと同様カテゴリー未指定の場合はデフォルトのカテゴリー(未分類)に設定されます。
        んが、デフォルトカテゴリーの取得に get_option を使っているのでそこをフックして空文字返せばOKな気がします。2.9.2でもいけるはず。
        function my_option_default_category($option){
        return ”;
        }
        add_filter(‘pre_option_default_category’, ‘my_option_default_category’);

      • moc moc

        上記ソースをfunctions.phpにコピペしましたが、チェックなしで更新しても元にもどってしまいました。
        実はWordPressの開発メンバーに嫌われていたのかもしれません…。
        3.0でもカテゴリー無効化できないということはtaxsonomyの位置づけは、タグ付け用のカスタムフィールド的なものなのですかね。

      • kz kz

        んあー どうやら他にもいろいろイジらないとですね。ムリムリですがカテゴリーをまるっと非表示にして封印しちゃって、カテゴリーもどき階層有りタクソノミーを作ればお望みな感じを実現できます。

    • moc moc

      やっぱりそれが一番手軽ですか。
      いろいろ難しいこと言ってすみませんでした。

    • moc moc

      コメント投稿時にwp-comments-post.phpが表示されちゃいます。
      「んなこと、わっかてるよ!」だったらごめんなさい。

      • kz kz

        マジですか!当方では再現いたしません。ブラウザまたはOSを再インストールしてみてください。

    • moc moc

      すみません、もう一回試してみますね。

    • moc moc

      あ、直ってます。なんか一時的な問題だったようです。

    • kz kz

      よくテキトウにイジった副作用でワヤなことになってるようなので
      今後とも何かありましたらご遠慮なくお知らせくださいねん

    • [memo][wordpress]カテゴリもタクソノミー http://bit.ly/cmoIQ6

    • RT @gorton: [memo][wordpress]カテゴリもタクソノミー http://bit.ly/cmoIQ6

    • 少しすっきりしそう。。。 RT @kzxtreme WordPress3.0未満でのカスタムタクソノミーの使い方 http://bit.ly/awGuRv

    • WordPress3.0未満でのカスタムタクソノミーの使い方  |  wpxtreme http://htn.to/4GxG8Q

    • RT @kzxtreme WordPress3.0

    • タクソノミーは分類ってことです。カテゴリーもタグもタクソノミーなんですよ。カテゴリーは階層(親子関係)有りのタクソノミー、タグは階層無しのタクソノミー。混乱してきましたね。そんなキミのためにタクソノミーとは何なのか、どうやって使うのか。ココだけ読んどけばオール OK、にしてやるぜ。