WordPressでカテゴリーの並び順を任意に変更したい場合、「Category Order and Taxonomy Terms Order」等のプラグインを導入するのが一般的だが、私のように管理画面に管理系のUIを増やしたくないと思っている人向けに、元からあるカテゴリー編集画面の「カテゴリーの説明」欄を使って並び順を指定する処理を作ってみた。
 呼称がないとこの記事上不便なので、この処理を「Category Description Order」と呼ぶことにする。

事前準備

 まずは、下記のソースをテーマの functions.php に追加しよう。

defined( 'USE_CATEGORY_DESCRIPTION_ORDER' ) or define( 'USE_CATEGORY_DESCRIPTION_ORDER', true );
defined( 'CATEGORY_DESCRIPTION_ORDER_ON_ADMIN' ) or define( 'CATEGORY_DESCRIPTION_ORDER_ON_ADMIN', true );

function optimize_category_description( $description ) {
  if ( ! empty( $description ) && strpos( $description, ';' ) !== false ) {
    list( ,$_parse_description ) = explode( ';', $description, 2 );
    $description = trim( $_parse_description );
  }
  return $description;
}

add_filter( 'get_terms', function( $terms, $taxonomies, $args, $term_query ) {
  $_is_run = is_admin() ? ( CATEGORY_DESCRIPTION_ORDER_ON_ADMIN ? true : false ) : true;
  if ( $_is_run && in_array( 'category', $taxonomies ) ) {
    // Category order by prepended number in description
    $_tempOrder = [];
    foreach ( $terms as $_idx => $term_obj ) {
      if ( ! empty( $term_obj->description ) && strpos( $term_obj->description, ';' ) !== false ) {
        list( $_order_num, $_parse_description ) = explode( ';', $term_obj->description, 2 );
        $_tempOrder[] = intval( trim( $_order_num ) );
        if ( ! is_admin() ) {
          $terms[$_idx]->description = trim( $_parse_description );
        }
      } else {
        $_tempOrder[] = 0;
      }
    }
    $_maxOrder = count( $_tempOrder ) > 0 ? max( $_tempOrder ) : 1;
    if ( USE_CATEGORY_DESCRIPTION_ORDER && $_maxOrder > 0 ) {
      foreach ( $_tempOrder as $_idx => $_order ) {
        if ( $_order == 0 ) {
          $_maxOrder++;
          $_tempOrder[$_idx] = $_maxOrder;
        } else {
          $_tempOrder[$_idx] = $_order;
        }
      }
      array_multisort( $_tempOrder, $terms );
      // Remove categories if it has minus ordered
      if ( ! is_admin() ) {
        foreach ( $_tempOrder as $_idx => $_order ) {
          if ( $_order < 0 ) {
            unset( $terms[$_idx] );
          }
        }
      }
    }
  }
  return $terms;
}, PHP_INT_MAX, 4 );

 仕組みとしては、カテゴリーリストを取得・出力する関数 wp_list_categories()widget_categories_args() 等の内部にて共通的に処理されている、ユーザー指定条件から生成したSQLクエリを投げ、DBから取得したデータをWP_Termオブジェクトに格納した直後のフィルターにフックして、出力前のカテゴリーリストの並び順を変更している。そして、その時に参照する並び順は、管理画面でカテゴリーの説明欄に入力した数字を元にする──という建付けだ。
 これだと、カテゴリー編集時に同時に並び順も指定できて、さらにその場でどう並ぶのかも確認できる。別途並び順を変更するだけの管理UIを開く必要もなく、かなり便利だ。

 また、このカテゴリーの説明欄を使用した並び順変更を使いたくない場合は1行目の USE_CATEGORY_DESCRIPTION_ORDERfalse にすれば良い。
 さらに、管理画面でのみこの機能をOFFにしたい場合は、2行目の CATEGORY_DESCRIPTION_ORDER_ON_ADMINfalse にすれば良い。

 あと、この機能をONにした状況下で、テーマのテンプレート等で category_description() を実行すると、並び順番号が含まれたカテゴリーの説明が取得されてしまうので、そんな時は同梱の optimize_category_description() 関数を使って最適化してほしい(使用例は下記を参照)。

echo optimize_category_description( category_description( 1 ) );

実際の使い方

 では、この処理を使ってカテゴリーの並び替えを実践してみる。
 インストールした素の WordPress 4.9.4 と「Twenty Seventeen」テーマ(バージョン1.4)にて、下記のようにカテゴリーを登録してみた。

 

管理画面・並び替え前

 管理画面のカテゴリー編集画面での並び順は、 get_categories() 関数のデフォルトである name 順の昇順(かつ hierarchical が有効)となる。つまり、まず親カテゴリーに対して名前昇順で並び、それぞれの子カテゴリーも名前昇順で並んでいるのがわかる。
 では、さっそく「Category Description Order」を有効にしてみる(定数の USE_CATEGORY_DESCRIPTION_ORDERCATEGORY_DESCRIPTION_ORDER_ON_ADMINtrue にする)。

管理画面・並び替え後

 並びが変わった。
 Descriptionのセミコロン( ; )で区切られた数字の順で並んでいることがわかるだろうか。並び順の仕組みとしては、まず親のないトップレベルのカテゴリーに対してDescriptionの先頭数字順の昇順で並び、そのトップレベルカテゴリーにぶら下がる子カテゴリーに対しても改めてDescriptionの先頭数字順の昇順で並ぶ。その子たちについても同様だ。
 もし同レベルのカテゴリーにおいて同一の先頭数字があった場合は、初期値である name の昇順となる。また、Descriptionに先頭数字がなかった場合は同レベルの末尾から name の昇順で並ぶ。Descriptionの先頭数字として並び順の指定に有効な数字は、0埋めされた数値や浮動小数点などでもOKだ。セミコロンで区切られた数字が複数指定された場合は、先頭の区切り数字のみが並び順として採用される。
 なお、Descriptionの1つ目のセミコロンより後の文字列はすべて説明テキストとして取り扱われる。
 Descriptionの先頭数字がマイナスの場合、管理画面側ではカテゴリーが表示されているが、フロントエンド側では表示されなくなる。カテゴリーリスト用の関数で get_categories( [ 'exclude' => '1,13' ] ); のようにいちいちカテゴリーIDを調べて除外用の条件を指定する必要もなく、Descriptionの先頭に -1; を付けるだけで非表示にすることが可能だ(これが地味に重宝する機能だったりする)。

 では、次にフロントエンド側での表示を見てみよう。「Twenty Seventeen」テーマではデフォルトでサイドバー・ウィジェットにカテゴリーリストが表示されているので、それを「Category Description Order」を有効にして並びを変えてみよう(ウィジェットの設定で hierarchical を有効にしている)。
 まず並び替える前の状態。

フロントエンド・並び替え前

 デフォルトの並び順である name 順の昇順(かつ hierarchical が有効)で表示されている。
 では「Category Description Order」を有効にしてみよう。

フロントエンド・並び替え後

 Descriptionの先頭番号順に並び替えが行われた。また、マイナス番号を設定したカテゴリーは非表示になっていることもわかる。
 また、例の画像では「Novels」カテゴリにオンマウス状態で、ツールチップにカテゴリーの説明文が表示されているのだが、この説明文にはセミコロン区切りの先頭番号は含まれていないことも確認できる。

気をつける点

 ウィジェット以外でのカテゴリーリスト出力時の動作の確認を兼ねて、もう少し見てみよう。前述のウィジェットのカテゴリーリスト出力では、内部で wp_list_categories() が呼ばれているので、それ以外のカテゴリーリスト出力を試してみる。
 テンプレートの本文中に下記のような処理を挿入してみた。

wp_dropdown_categories();

 カテゴリーリストをドロップダウンリストで出力するテンプレート関数だ。今回は hierarchical の指定をしていないので、カテゴリーの入れ子構造は無視されてリスト化される。

フロントエンド・並び替え後(入れ子無視)

 カテゴリーの入れ子構造を無視する出力を行うと、純粋にDescriptionのセミコロン区切りの先頭番号の順番で並ぶことになり、並び順が指定されていないカテゴリーや重複する番号を持つカテゴリーは、末尾に追加されていくことになる。
 今回の例では、カテゴリーの入れ子構造ありなしで表示順が変わってしまった。こういうことを防ぐためには、Descriptionの先頭に付ける並び順番号はカテゴリー全体でユニークとなるように割り振っておくと意図しない並びになることはない。

発展的に

 この処理をカスタマイズすれば、カテゴリー以外のタクソノミー(タグとかカスタムタクソノミー等)に対しても独自に並び順を設定することは容易だ。
 また、各カテゴリー内の記事のPVを定期的に集計してoptionsテーブルとかtermmetaテーブルあたりにキャッシュしておいて、総PV値が多いカテゴリー順に並べ替えるとか、カテゴリーの並び順を動的に変えることも難しくないだろう(こういうことやるならプラグイン化した方が良いだろうな)。

 私的に、WordPressのプラグインって結構コストとリスクが高いものなので、導入するプラグインは厳選したいと考えている。そういう質なので、フィルターフック一発で対応できるようなカテゴリーの並べ替え処理に管理画面のUIを拡張するようなプラグインを導入するのには抵抗を感じてしまうのだ。WEB担が運営するような商業サイトであれば話はまた変わってくるんだろうけど。

──というか、カテゴリーの並び順設定の機能って、WordPress本体にビルドインされていても良いように思うんだよねぇ…。