目次
拡張:管理画面からカテゴリーをドラッグ&ドロップで並び替えする方
独自の設定画面を設け、プラグイン「Category Order and Taxonomy Terms Order」のように、ドラッグ&ドロップでカテゴリーの順番を並び替えられるようにします。
完成イメージ

実装手順
以下のコードをfunctions.phpに追加します。
// 管理画面にメニューを追加
add_action('admin_menu', function () {
add_submenu_page(
'edit.php',
'カテゴリー並び替え',
'カテゴリー並び替え',
'manage_categories',
'category_order',
'render_category_order'
);
});
/**
* 管理画面ページを出力
*
* @return void
*/
if (!function_exists('render_category_order')):
function render_category_order() {
// カテゴリー情報を取得
$terms = get_terms(['taxonomy' => 'category', 'hide_empty' => false]);
// 取得エラー時は空配列を設定
if (is_wp_error($terms)) {
$terms = [];
}
// 保存済みの順序とカテゴリー名で並び順を変更
usort($terms, function ($a, $b) {
return ((int) get_term_meta($a->term_id, 'my_category_order', true) <=> (int) get_term_meta($b->term_id, 'my_category_order', true))
?: strcmp($a->name, $b->name);
});
// 親カテゴリーごとの分類情報を初期化
$map = [];
// 親カテゴリーIDをキーにしてカテゴリー情報を登録
foreach ($terms as $t) {
$map[$t->parent][] = $t;
}
// 階層ごとのカテゴリーを再帰的に表示する処理を設定
$print = function ($pid, $d) use (&$print, $map) {
// 指定された親カテゴリーに属するカテゴリーを取得
foreach ($map[$pid] ?? [] as $t) {
// 表示用のカテゴリーIDを取得
$term_id = (int) $t->term_id;
// 表示用の階層を取得
$depth = (int) $d;
// 階層に応じた左余白を設定
$margin_left = $depth * 30;
// カテゴリー名をエスケープして取得
$term_name = esc_html($t->name);
// カテゴリーID・階層・左余白を設定
echo '<li data-id="' . $term_id . '" data-depth="' . $depth . '" style="margin-left:' . $margin_left . 'px">';
echo '<span class="category-order__grip" aria-hidden="true">⋮⋮</span>';
echo '<span class="category-order__title">' . $term_name . '</span>';
echo '<small class="category-order__depth">depth: <b>' . $depth . '</b></small>';
echo '</li>';
// 子カテゴリーを次の階層で表示する処理を設定
$print($t->term_id, $d + 1);
}
};
?>
<div class="wrap">
<h1>カテゴリー 階層・並び順</h1>
<p>上下ドラッグで順序、左右ドラッグで階層を変更できます。子カテゴリーも一緒に移動します。</p>
<ul id="category-order-list"><?php $print(0, 0); ?></ul>
<p>
<button id="category-order-save" class="button button-primary">設定を保存</button>
<span id="category-order-msg"></span>
</p>
</div>
<?php
}
endif;
// 管理画面用JS・CSSを追加
add_action('admin_enqueue_scripts', function () {
if (($_GET['page'] ?? '') !== 'category_order') {
return;
}
ob_start();
?>
#category-order-list {
background-color: #fff;
border: 1px solid #ccd0d4;
border-radius: 4px;
list-style: none;
margin-top: 20px;
max-width: 700px;
padding: 10px;
}
#category-order-list li {
align-items: center;
background-color: #f6f7f7;
border: 1px solid #c3c4c7;
border-radius: 3px;
box-sizing: border-box;
cursor: move;
display: flex;
font-weight: bold;
margin-bottom: 8px;
min-height: 39px;
padding: 8px 12px;
}
#category-order-list li:last-child {
margin-bottom: 0;
}
#category-order-list li:hover {
background-color: #f0f0f1;
}
.category-order__grip {
color: #8c8f94;
flex-shrink: 0;
font-weight: normal;
letter-spacing: -1px;
margin-right: 8px;
user-select: none;
}
.category-order__title {
font-weight: normal;
overflow-wrap: anywhere;
}
.category-order__depth {
color: #787c82;
flex-shrink: 0;
font-size: 12px;
font-weight: normal;
margin-left: auto;
padding-left: 12px;
}
#category-order-list li.category-order__placeholder {
background-color: #e5f5fa;
border: 1px dashed #2271b1;
border-radius: 3px;
box-sizing: border-box;
height: 39px;
margin-bottom: 8px;
padding: 0;
}
#category-order-list li.ui-sortable-helper {
background-color: #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, .2);
}
<?php
wp_add_inline_style('wp-admin', ob_get_clean());
ob_start();
?>
jQuery(function ($) {
// ドラッグ幅・対象リスト・移動情報・子カテゴリーを設定
var I = 30, $l = $("#category-order-list"), x0, d0, C = $();
// カテゴリーの階層を取得
var D = function ($i) {
return +$i.attr("data-depth") || 0;
};
// カテゴリーの階層と表示位置を変更
var S = function ($i, d) {
$i.attr("data-depth", d).css("margin-left", d * I);
$i.find("b").text(d);
};
// カテゴリーリストのドラッグ操作を設定
$l.sortable({
// ドラッグ中のプレースホルダーと判定方法を設定
placeholder: "category-order__placeholder",
tolerance: "pointer",
// ドラッグ開始時の階層と子カテゴリーを取得
start: function (e, ui) {
// ドラッグ開始位置と対象カテゴリーの階層を取得
x0 = e.pageX;
d0 = D(ui.item);
// 移動対象の子カテゴリーを初期化
C = $();
// 対象カテゴリーに属する子孫カテゴリーを取得
ui.item.nextAll("li").not(ui.placeholder).each(function () {
// 同じ階層以下のカテゴリーで取得を終了
if (D($(this)) <= d0) return false;
// 移動対象の子孫カテゴリーを登録
C = C.add(this);
});
// 子孫カテゴリーを一時的にリストから切り離す
C.detach();
// ドラッグ位置の再計算を設定
$l.sortable("refreshPositions");
},
// ドラッグ中の移動先階層を変更
sort: function (e, ui) {
// プレースホルダーの直前にあるカテゴリーを取得
var p = ui.placeholder.prevAll("li").not(ui.item).first();
// 横方向の移動量から階層を計算して変更
var d = Math.max(0, Math.min(d0 + Math.round((e.pageX - x0) / I), p.length ? D(p) + 1 : 0));
// 移動先の階層を一時保存
ui.item.data("nd", d);
// プレースホルダーの表示位置を変更
ui.placeholder.css("margin-left", d * I);
},
// ドラッグ終了時に階層と子カテゴリーの位置を変更
stop: function (e, ui) {
// 移動先の直前にあるカテゴリーを取得
var p = ui.item.prev();
// ドラッグ中に設定した階層を取得
var d = ui.item.data("nd");
// 移動先の階層を有効な範囲に変更
d = Math.max(0, Math.min(d === undefined ? d0 : d, p.length ? D(p) + 1 : 0));
// 移動したカテゴリーの階層を変更
S(ui.item, d);
// 子孫カテゴリーの階層を親カテゴリーに合わせて変更
C.each(function () {
S($(this), D($(this)) + d - d0);
});
// 子孫カテゴリーを親カテゴリーの後ろに登録
ui.item.after(C);
// 子孫カテゴリーの一時情報を初期化
C = $();
// 階層の連続性を確認するための初期値を設定
var prev = -1;
// 各カテゴリーの階層を確認して変更
$l.children().each(function () {
// 直前のカテゴリーより深い階層になりすぎないように変更
var n = Math.min(D($(this)), prev + 1);
// 必要な場合は階層を変更
if (n !== D($(this))) {
S($(this), n);
}
// 次のカテゴリーの比較用階層を設定
prev = n;
});
}
});
// 保存ボタンのクリック処理を設定
$("#category-order-save").on("click", function () {
// 親カテゴリー情報と保存対象を初期化
var s = [], items = [];
// 画面上の順序からカテゴリーIDと親IDを取得
$l.children().each(function () {
// 現在の階層とカテゴリーIDを取得
var d = Math.min(D($(this)), s.length);
var id = $(this).data("id");
// 現在の階層に合わせて親情報を設定
s.length = d;
// カテゴリーIDと親カテゴリーIDを登録
items.push([id, s[d - 1] || 0]);
// 次のカテゴリーの親情報としてカテゴリーIDを登録
s.push(id);
});
// AJAXで保存処理を実行
$.post(ajaxurl, {
// 保存処理のアクションを設定
action: "category_order_save",
// カテゴリー情報をJSON形式で登録
items: JSON.stringify(items)
})
.done(function (r) {
// 保存成功時は画面を再読み込み
if (r.success) {
location.reload();
} else {
// 保存失敗時のメッセージを設定
$("#category-order-msg").text("保存に失敗しました。");
}
})
.fail(function () {
// 通信失敗時のメッセージを設定
$("#category-order-msg").text("通信エラー");
});
});
});
<?php
wp_enqueue_script('jquery-ui-sortable');
wp_add_inline_script('jquery-ui-sortable', ob_get_clean());
});
// タームのSQL句を並び順に合わせて変更
add_filter('terms_clauses', function ($c, $tax, $args) {
global $wpdb;
// 並び順の指定を取得
$o = $args['orderby'] ?? 'name';
// 対象タクソノミー以外、または対象の並び順以外は処理対象外
if (!in_array('category', (array) $tax, true) || $o !== 'name' || (is_admin() && isset($_GET['orderby']))) {
return $c;
}
// 並び順用タームメタとの結合条件を置換
$c['join'] .= " LEFT JOIN {$wpdb->termmeta} AS co ON co.term_id = t.term_id AND co.meta_key = 'my_category_order'";
// 並び順をタームメタとカテゴリー名の順に置換
$c['orderby'] = 'ORDER BY CAST(co.meta_value AS SIGNED), t.name';
// 既定の並び順指定を解除
$c['order'] = '';
// 変更後のSQL句を返却
return $c;
}, 10, 3);
// Ajaxアクションを追加
add_action('wp_ajax_category_order_save', function () {
// 権限チェック
if (!current_user_can('manage_categories')) {
wp_send_json_error();
}
// 送信されたカテゴリー情報をJSONから取得
$items = json_decode(wp_unslash($_POST['items'] ?? ''), true);
// 配列形式でない場合は処理対象外
if (!is_array($items)) {
wp_send_json_error();
}
// 更新件数とエラー件数を初期化
$n = $err = 0;
// 送信されたカテゴリー情報を順番に処理
foreach (array_values($items) as $order => $it) {
// カテゴリーIDを取得
$id = (int) ($it[0] ?? 0);
// 親カテゴリーIDを取得
$parent = (int) ($it[1] ?? 0);
// ターム情報を取得
$t = get_term($id, 'category');
// タームが存在しない場合や編集権限がない場合は処理対象外
if (!$t || is_wp_error($t) || !current_user_can('edit_term', $id)) {
continue;
}
// 親カテゴリーが存在しない場合はエラー件数を登録
if ($parent && !term_exists($parent, 'category')) {
$err++;
continue;
}
// 自分自身を親にする場合はエラー件数を登録
if ($parent === $id) {
$err++;
continue;
}
// 変更状態の初期値を設定
$changed = false;
// 親カテゴリーが変更されている場合は親情報を変更
if ((int) $t->parent !== $parent) {
// 親カテゴリーIDを変更
$result = wp_update_term($id, 'category', ['parent' => $parent]);
// 親カテゴリーの変更に失敗した場合はエラー件数を登録
if (is_wp_error($result)) {
$err++;
continue;
}
// 変更済みの状態を設定
$changed = true;
}
// 保存済みの並び順を取得
$old_order = get_term_meta($id, 'my_category_order', true);
// 並び順が変更されている場合は並び順を登録
if ((string) $old_order !== (string) $order) {
// 並び順をタームメタに登録
update_term_meta($id, 'my_category_order', (string) $order);
// 変更済みの状態を設定
$changed = true;
}
// 変更があった場合は更新件数を登録
if ($changed) {
$n++;
}
}
// タクソノミーのキャッシュをクリアして処理を終了
clean_taxonomy_cache('category');
// 保存結果の通知内容を設定
if (!$err) {
// 成功通知を一時データに登録
set_transient('category_order_admin_notice', [
'type' => 'success',
'message' => '設定を保存しました。',
], 30);
} else {
// 警告通知を一時データに登録
set_transient('category_order_admin_notice', [
'type' => 'warning',
'message' => '一部のカテゴリーを保存できませんでした。',
], 30);
}
// 更新件数とエラー件数をJSON形式で返却して処理を終了
wp_send_json_success(['n' => $n, 'err' => $err]);
});
// 通知メッセージを出力
add_action('admin_notices', function () {
if ($notice = get_transient('category_order_admin_notice')) {
echo '<div class="notice notice-' . esc_attr($notice['type']) . ' is-dismissible"><p>' . esc_html($notice['message']) . '</p></div>';
delete_transient('category_order_admin_notice');
}
});
さいごに
今回紹介した方法を使えば、プラグインなしでカテゴリーを好きな順番に並び替えることができます。
ネット上にはさまざまなコード例がありますが、get_terms()のorderbyにincludeを指定することで、ID順に並び替えることが可能です。
これにより、複雑なコードを書く必要がなく実現可能です。

