wordpress WordPressで親子関係のカテゴリーを一画面で作り保存させる機能を付与するコード 作成日: 2025年9月2日
WordPressでサイト制作を行う際に、予めカテゴリーの階層構造などが分かっている場合(普通設計に要るから解るはずだけど)、親作ってから子を作ってその際に都度親選ぶやり方=通常のやり方でも良いのだが、特にカテゴリーの加増等をユーザーサイドで行う場合などUI上よろしくないのでfunction.php追記するコードを作った。
当スタジオのWPテーマ『Initialize』では、カテゴリー設定ページの『カテゴリーの説明』をカテゴリーページのmeta descriptionに呼び出しているが、keywordsフィールドに相当するものはデフォルトでは無いのでこのフィールドを追加。
整合性のためデフォルトのカテゴリー編集画面にも、このkeywords入力欄を設置。
現状meta keywordsはSEO上無用とされているが、いつあった方が良いになるか判らないし、Google1強(YahooがGoogleのエンジンだし)といえる現状が変わるかもしれないので、ネガティブ事象を作らない・msnなど向けまたはアルゴリズム変化への担保として、UI向上によるカテゴリー設定の一部となるように配置した。
WP6.8.2環境で開発、PHP8で多分動くと思うが、REST APIを使ってるので、4.4以降は必須。テーマなどによりREST API関連のエラーが出るかもしれません。
コード
<?php
/**
* カスタムカテゴリ編集UIの管理画面とAPIエンドポイントを登録
*
* このコードは、WordPressのテーマ(または子テーマ)のfunctions.phpファイルに追記してください。
*/
// 1. カスタム管理画面を登録する
add_action('admin_menu', 'my_plugin_add_admin_menu');
function my_plugin_add_admin_menu() {
add_menu_page(
'カテゴリー&子カテゴリー管理', // ページタイトル
'カテゴリー&子カテゴリー', // メニュータイトル
'manage_categories', // 必要な権限
'event-class-manager', // スラッグ
'my_plugin_render_admin_page', // ページの内容を表示する関数
'dashicons-category', // メニューアイコン
25 // 表示位置
);
}
// 2. カスタム管理画面の内容を表示する関数
function my_plugin_render_admin_page() {
?>
<div class="wrap">
<h2>カテゴリー&子カテゴリー管理</h2>
<!-- UIが表示される場所 -->
<div id="custom-ui-container"></div>
</div>
<script>
// UIのHTML内容をJavaScriptで挿入
document.addEventListener('DOMContentLoaded', () => {
const container = document.getElementById('custom-ui-container');
if (container) {
container.innerHTML = `
<div class="container bg-white p-8 rounded-lg shadow-lg">
<h1 class="text-2xl font-bold mb-6">カテゴリー&子カテゴリーを編集</h1>
<!-- カテゴリー名の編集フォーム -->
<div id="parent-category-form" class="mb-6 border-b pb-4">
<label for="event-name" class="block text-gray-700 font-semibold mb-2">カテゴリー名</label>
<input type="text" id="event-name" placeholder="例: カテゴリー"
class="w-full p-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<!-- カテゴリーの説明フォーム -->
<div id="category-description-form" class="mb-6 border-b pb-4">
<label for="category-description" class="block text-gray-700 font-semibold mb-2">カテゴリーの説明</label>
<textarea id="category-description" placeholder="例: このカテゴリーはホゲホゲです" rows="4"
class="w-full p-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"></textarea>
</div>
<!-- メタキーワードのフォーム -->
<div id="meta-keywords-form" class="mb-6 border-b pb-4">
<label for="meta-keywords" class="block text-gray-700 font-semibold mb-2">メタキーワード</label>
<input type="text" id="meta-keywords" placeholder="例: カテゴリー, ホゲホゲ, 2023"
class="w-full p-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<!-- 子カテゴリーの追加エリア -->
<div id="child-categories-container" class="mb-6">
<h2 class="text-xl font-bold mb-4">子カテゴリー</h2>
</div>
<!-- 子カテゴリー追加ボタン -->
<button id="add-child-btn" class="w-full bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded-lg transition-colors duration-200">
+ 子カテゴリーを追加
</button>
<!-- 保存ボタン -->
<button id="save-categories-btn" class="w-full mt-6 bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-4 rounded-lg transition-colors duration-200">
保存
</button>
<!-- 動作確認用のメッセージエリア -->
<div id="message-area" class="mt-6 p-4 rounded-lg bg-gray-200 text-gray-800 hidden"></div>
</div>
`;
}
// Tailwind CSS の読み込み
const tailwindScript = document.createElement('script');
tailwindScript.src = "https://cdn.tailwindcss.com";
document.head.appendChild(tailwindScript);
// UIを動作させるJavaScriptコード
const addJs = () => {
const addChildBtn = document.getElementById('add-child-btn');
const saveBtn = document.getElementById('save-categories-btn');
const childContainer = document.getElementById('child-categories-container');
const messageArea = document.getElementById('message-area');
let childCount = 0;
const addChildField = (childData = { name: '', description: '', keywords: '' }) => {
const childId = `child-${childCount++}`;
const div = document.createElement('div');
div.id = childId;
div.className = 'flex flex-col space-y-2 mb-4 p-4 border rounded-lg shadow-sm';
div.innerHTML = `
<div class="flex items-center space-x-2">
<input type="text" value="${childData.name}" placeholder="子カテゴリー名"
class="flex-grow p-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 child-input-name">
<button type="button" class="remove-child-btn bg-red-500 text-white rounded-full w-8 h-8 flex items-center justify-center font-bold">
-
</button>
</div>
<textarea placeholder="子カテゴリーの説明" rows="2"
class="w-full p-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 child-input-desc">${childData.description}</textarea>
<input type="text" value="${childData.keywords}" placeholder="メタキーワード"
class="w-full p-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 child-input-keywords">
`;
childContainer.appendChild(div);
div.querySelector('.remove-child-btn').addEventListener('click', () => {
div.remove();
});
};
addChildBtn.addEventListener('click', () => {
addChildField();
});
saveBtn.addEventListener('click', () => {
const parentName = document.getElementById('event-name').value.trim();
const categoryDescription = document.getElementById('category-description').value.trim();
const metaKeywords = document.getElementById('meta-keywords').value.trim();
const childElements = document.querySelectorAll('#child-categories-container > div');
const children = Array.from(childElements).map(element => {
const name = element.querySelector('.child-input-name').value.trim();
const description = element.querySelector('.child-input-desc').value.trim();
const keywords = element.querySelector('.child-input-keywords').value.trim();
return { name, description, keywords };
}).filter(child => child.name.length > 0);
if (!parentName && children.length === 0) {
showMessage('カテゴリー名か子カテゴリー名を最低1つ入力してください。');
return;
}
// 実際のWordPressへのデータ送信
fetch('/wp-json/my-plugin/v1/save-categories', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': '<?php echo wp_create_nonce('wp_rest'); ?>'
},
body: JSON.stringify({
parent: parentName,
children: children,
description: categoryDescription,
keywords: metaKeywords
})
})
.then(response => response.json())
.then(data => {
showMessage(data.message, 'success');
})
.catch(error => {
console.error('Error:', error);
showMessage('データの保存に失敗しました。', 'error');
});
});
const showMessage = (message, type = 'error') => {
messageArea.textContent = message;
messageArea.classList.remove('hidden');
messageArea.classList.remove('bg-red-200', 'bg-green-200');
if (type === 'success') {
messageArea.classList.add('bg-green-200');
} else {
messageArea.classList.add('bg-red-200');
}
};
// 初期表示として子カテゴリの入力欄を1つ追加
addChildField();
};
// スクリプトの読み込み後に実行
tailwindScript.onload = addJs;
});
</script>
<?php
}
// 3. REST APIエンドポイント
add_action('rest_api_init', 'my_plugin_register_category_route');
function my_plugin_register_category_route() {
register_rest_route('my-plugin/v1', '/save-categories', array(
'methods' => 'POST',
'callback' => 'my_plugin_save_categories',
'permission_callback' => function () {
return current_user_can('manage_categories');
}
));
}
function my_plugin_save_categories(WP_REST_Request $request) {
$data = $request->get_json_params();
$parent_name = sanitize_text_field($data['parent']);
$description = sanitize_textarea_field($data['description']);
$keywords = sanitize_text_field($data['keywords']); // キーワードを取得
$child_data = $data['children'];
// 親カテゴリが存在するか確認し、なければ作成
$parent_term = term_exists($parent_name, 'category');
if ($parent_term === 0 || $parent_term === null) {
$parent_term = wp_insert_term($parent_name, 'category', array(
'description' => $description,
));
}
if (is_wp_error($parent_term)) {
return new WP_REST_Response(array('message' => 'Failed to create parent category.'), 500);
}
$parent_id = $parent_term['term_id'];
// 親カテゴリのキーワードをメタフィールドとして保存
update_term_meta($parent_id, 'cat_keyword', $keywords);
// 子カテゴリをループで処理
foreach ($child_data as $child) {
$child_name = sanitize_text_field($child['name']);
$child_description = sanitize_textarea_field($child['description']);
$child_keywords = sanitize_text_field($child['keywords']); // 子カテゴリのキーワードを取得
if (!empty($child_name)) {
$child_term = term_exists($child_name, 'category', $parent_id);
if ($child_term === 0 || $child_term === null) {
$inserted_child = wp_insert_term($child_name, 'category', array(
'parent' => $parent_id,
'description' => $child_description,
));
if (!is_wp_error($inserted_child)) {
update_term_meta($inserted_child['term_id'], 'cat_keyword', $child_keywords);
}
} else {
update_term_meta($child_term['term_id'], 'cat_keyword', $child_keywords);
}
}
}
return new WP_REST_Response(array('message' => 'Categories saved successfully!'), 200);
}
// 4. デフォルトのカテゴリ追加画面にキーワード入力フィールドを追加
function my_plugin_add_keyword_field() {
?>
<div class="form-field term-keywords-wrap">
<label for="cat_keyword">メタキーワード</label>
<input type="text" name="cat_keyword" id="cat_keyword" value="" placeholder="例: カテゴリー, キーワード, 2023" />
<p class="description">カテゴリページ用のメタキーワードを入力します(ACFフィールドと同様に動作します)。</p>
</div>
<?php
}
add_action('category_add_form_fields', 'my_plugin_add_keyword_field');
// 5. デフォルトのカテゴリ編集画面にキーワード入力フィールドを追加
function my_plugin_edit_keyword_field($term) {
// 既存のキーワードを取得
$keywords = get_term_meta($term->term_id, 'cat_keyword', true);
?>
<tr class="form-field term-keywords-wrap">
<th scope="row"><label for="cat_keyword">メタキーワード</label></th>
<td>
<input type="text" name="cat_keyword" id="cat_keyword" value="<?php echo esc_attr($keywords); ?>" placeholder="例: カテゴリー, キーワード, 2023" />
<p class="description">カテゴリページ用のメタキーワードを入力します(ACFフィールドと同様に動作します)。</p>
</td>
</tr>
<?php
}
add_action('category_edit_form_fields', 'my_plugin_edit_keyword_field');
// 6. メタキーワードを保存する
function my_plugin_save_keyword($term_id) {
if (isset($_POST['cat_keyword'])) {
$keywords = sanitize_text_field($_POST['cat_keyword']);
update_term_meta($term_id, 'cat_keyword', $keywords);
}
}
add_action('edited_category', 'my_plugin_save_keyword');
add_action('create_category', 'my_plugin_save_keyword');