メディアライブラリ(一覧)に「使用数(その画像が使われているページ数)」「フォルダ(保存場所)」の2カラムを追加し、差し替えや更新の判断を速くする方法と運用の型を説明します。
対象読者:サイト運用・ディレクション・編集担当。
コードの置き場所だけ:functions.php または wp-content/mu-plugins/
追加すると何が変わるか
- 使用数が見える → 影響の大きい素材が一目でわかり、どれから直すかが即決できる。
- フォルダが見える → 素材の所在が明確になり、探す時間と確認往復が減る。
結果として、差し替え・停止・更新の判断と依頼が短く・早くまとまります。
コード(そのまま使用可能)
コードは functions.php か mu-plugins に設置して下さい。
/**
* Media Library: 使用数 / フォルダ カラム追加(完成版)
* 置き場所: テーマ functions.php か mu-plugins/media-columns.php
*/
defined('ABSPATH') || exit;
/* ------------------------------
* カラム追加&描画
* ------------------------------ */
add_filter('manage_media_columns', function ($cols) {
$cols['usage_count'] = '使用数';
$cols['upload_folder'] = 'フォルダ';
return $cols;
});
add_action('manage_media_custom_column', function ($column_name, $attachment_id) {
if ($column_name === 'usage_count') {
echo esc_html( mlc_get_attachment_usage_count($attachment_id) );
} elseif ($column_name === 'upload_folder') {
echo esc_html( mlc_get_attachment_folder_label($attachment_id) );
}
}, 10, 2);
/* ------------------------------
* 使用数カウント(本文/URL/アイキャッチ/Wooギャラリー)
* ------------------------------ */
/**
* 添付ファイルが使われている “投稿数(重複なし)” を返す
* - 本文: `wp-image-{ID}` / 絶対URL
* - アイキャッチ: _thumbnail_id
* - Woo ギャラリー: _product_image_gallery(任意)
* - 12時間キャッシュ(?mlc_debug=1 で無視&ログ)
*/
function mlc_get_attachment_usage_count($attachment_id) {
global $wpdb;
// --- デバッグフラグ(?mlc_debug=1) ---
// $dbg = isset($_GET['mlc_debug']);
// --- キャッシュ(メタ) ---
$cache_key = '_mlc_usage_count_cache';
// if ($dbg) { // デバッグ時はキャッシュ無視
// $cached = '';
// } else {
$cached = get_post_meta($attachment_id, $cache_key, true);
$cached_at = (int) get_post_meta($attachment_id, $cache_key . '_time', true);
if ($cached !== '' && (time() - $cached_at) < 12 * HOUR_IN_SECONDS) {
return (int) $cached;
}
// }
// --- 添付URL(無い場合はメタから復元) ---
$url = wp_get_attachment_url($attachment_id);
if (!$url) {
// 例: 開発環境で guid/url が欠落している場合の保険
$relative = (string) get_post_meta($attachment_id, '_wp_attached_file', true);
if ($relative !== '') {
$uploads = wp_get_upload_dir();
if (empty($uploads['error']) && !empty($uploads['baseurl'])) {
$url = trailingslashit($uploads['baseurl']) . ltrim($relative, '/');
}
}
}
// LIKE パターンを組み立て
$like_url = $url ? '%' . $wpdb->esc_like($url) . '%' : null;
$like_id = '%' . $wpdb->esc_like('wp-image-' . $attachment_id) . '%';
// 対象ステータス/タイプ
$post_statuses = "('publish','future','draft','pending','private')";
$post_types_ex = "('revision','nav_menu_item')";
// 追加の検索対象メタキー(Elementor/ACFなど)を外から拡張可能に
// 例: add_filter('mlc_usage_meta_keys', fn($keys)=> array_merge($keys, ['_elementor_data']));
$extra_meta_keys = apply_filters('mlc_usage_meta_keys', []);
$extra_meta_sql = '';
$extra_params = [];
if (!empty($extra_meta_keys)) {
// JSONやテキスト内に URL での参照がある想定(必要に応じて wp-image-ID も追加可)
// FIND_IN_SET ではなく LIKE を使用(JSON等も対象にするため)
foreach ($extra_meta_keys as $mk) {
// URL ありの場合のみURLで当たりを取り、ID一致は別 UNION を追加してもOK
if ($like_url) {
$extra_meta_sql .= "
UNION
SELECT pm3.post_id as id
FROM {$wpdb->postmeta} pm3
INNER JOIN {$wpdb->posts} p5 ON p5.ID = pm3.post_id
WHERE pm3.meta_key = %s
AND pm3.meta_value LIKE %s
AND p5.post_status IN $post_statuses
AND p5.post_type NOT IN $post_types_ex
";
$extra_params[] = $mk;
$extra_params[] = $like_url;
}
// wp-image-{ID} でのヒットも見る(ビルダーがクラス持っているケース)
$extra_meta_sql .= "
UNION
SELECT pm4.post_id as id
FROM {$wpdb->postmeta} pm4
INNER JOIN {$wpdb->posts} p6 ON p6.ID = pm4.post_id
WHERE pm4.meta_key = %s
AND pm4.meta_value LIKE %s
AND p6.post_status IN $post_statuses
AND p6.post_type NOT IN $post_types_ex
";
$extra_params[] = $mk;
$extra_params[] = $like_id;
}
}
// 本体SQL
$sql = "
SELECT COUNT(DISTINCT t.id) FROM (
-- 本文: wp-image-{ID}
SELECT p.ID as id
FROM {$wpdb->posts} p
WHERE p.post_type NOT IN $post_types_ex
AND p.post_status IN $post_statuses
AND p.post_content LIKE %s
UNION
-- 本文: 絶対URL(URLがある場合のみ実行)
" . ($like_url ? "
SELECT p2.ID as id
FROM {$wpdb->posts} p2
WHERE p2.post_type NOT IN $post_types_ex
AND p2.post_status IN $post_statuses
AND p2.post_content LIKE %s
" : "
SELECT NULL AS id WHERE 1=0
") . "
UNION
-- アイキャッチ
SELECT pm.post_id as id
FROM {$wpdb->postmeta} pm
INNER JOIN {$wpdb->posts} p3 ON p3.ID = pm.post_id
WHERE pm.meta_key = '_thumbnail_id'
AND pm.meta_value = %d
AND p3.post_status IN $post_statuses
AND p3.post_type NOT IN $post_types_ex
UNION
-- Woo ギャラリー(任意)
SELECT pm2.post_id as id
FROM {$wpdb->postmeta} pm2
INNER JOIN {$wpdb->posts} p4 ON p4.ID = pm2.post_id
WHERE pm2.meta_key IN ('_product_image_gallery')
AND FIND_IN_SET(%d, REPLACE(pm2.meta_value, ' ', '')) > 0
AND p4.post_status IN $post_statuses
AND p4.post_type NOT IN $post_types_ex
{$extra_meta_sql}
) as t
";
// プレースホルダ整合(%s, [%s], %d, %d, [extra ...])
$params = [$like_id];
if ($like_url) { $params[] = $like_url; }
$params[] = $attachment_id;
$params[] = $attachment_id;
$params = array_merge($params, $extra_params);
// 実行
$prepared = $wpdb->prepare($sql, $params);
$count = (int) $wpdb->get_var($prepared);
// if ($dbg) {
// error_log("[MLC] id={$attachment_id}");
// error_log("[MLC] like_id={$like_id}");
// if ($like_url) { error_log("[MLC] like_url={$like_url}"); }
// error_log("[MLC] count={$count}");
// error_log("[MLC] last_error={$wpdb->last_error}");
// // 長大になりやすいので last_query は必要時のみ出して下さい
// // error_log("[MLC] last_query={$wpdb->last_query}");
// }
// キャッシュ保存
update_post_meta($attachment_id, $cache_key, $count);
update_post_meta($attachment_id, $cache_key . '_time', time());
return $count;
}
/* ------------------------------
* フォルダ表示(仮想フォルダ > 相対パス > '-')
* ------------------------------ */
function mlc_get_attachment_folder_label($attachment_id) {
// 仮想フォルダ系プラグイン
$folder_taxonomies = array('media_folder', 'rml_folder', 'wpmf_folder');
$folder_taxonomies = apply_filters('mlc_folder_taxonomies', $folder_taxonomies);
foreach ($folder_taxonomies as $tax) {
if (taxonomy_exists($tax)) {
$terms = get_the_terms($attachment_id, $tax);
if (!is_wp_error($terms) && !empty($terms)) {
return implode(', ', wp_list_pluck($terms, 'name'));
}
}
}
// コアの保存先(相対パス)
$relative = (string) get_post_meta($attachment_id, '_wp_attached_file', true);
if ($relative !== '') {
$dir = wp_normalize_path(dirname($relative)); // 例: '2025/09'
if ($dir !== '.' && $dir !== '/') {
return $dir;
}
}
return '-';
}
/* ------------------------------
* キャッシュ自動無効化(更新に追従)
* ------------------------------ */
/**
* 投稿保存時:本文に含まれる wp-image-{ID} を拾って、該当添付のキャッシュを削除
* (最小実装。ビルダー/ACF等は filter で meta 検索を足して対応)
*/
add_action('save_post', function ($post_id, $post, $update) {
if (wp_is_post_revision($post_id) || $post->post_type === 'revision') return;
$content = (string) $post->post_content;
if ($content === '') return;
if (preg_match_all('/wp-image-(\d+)/', $content, $m)) {
$ids = array_unique(array_map('intval', $m[1]));
foreach ($ids as $aid) {
delete_post_meta($aid, '_mlc_usage_count_cache');
delete_post_meta($aid, '_mlc_usage_count_cache_time');
}
}
}, 10, 3);
/**
* アイキャッチ変更時:対象添付のキャッシュを削除
*/
add_action('set_post_thumbnail', function ($post_id, $att_id) {
delete_post_meta($att_id, '_mlc_usage_count_cache');
delete_post_meta($att_id, '_mlc_usage_count_cache_time');
}, 10, 2);
/**
* 添付削除時:自身のキャッシュを削除
*/
add_action('delete_attachment', function ($att_id) {
delete_post_meta($att_id, '_mlc_usage_count_cache');
delete_post_meta($att_id, '_mlc_usage_count_cache_time');
}, 10, 1);
/* =========================================
* ソート対応(メディア一覧)
* ========================================= */
/* 1) 見出しを「ソート可能」にする */
add_filter('manage_upload_sortable_columns', function ($cols) {
$cols['usage_count'] = 'usage_count'; // 使用数
$cols['upload_folder'] = 'upload_folder'; // フォルダ
return $cols;
});
/* 2) 並び替えの実処理(metaでソート) */
add_action('pre_get_posts', function ($q) {
if (!is_admin() || !$q->is_main_query()) return;
if ($q->get('post_type') !== 'attachment') return;
$orderby = $q->get('orderby');
// 使用数:キャッシュメタ(数値)でソート
if ($orderby === 'usage_count') {
$q->set('meta_key', '_mlc_usage_count_cache');
$q->set('orderby', 'meta_value_num');
// 空メタを0扱いにしたいので、直前に足りないキャッシュを埋める
add_filter('the_posts', function ($posts) {
foreach ($posts as $p) {
if (get_post_meta($p->ID, '_mlc_usage_count_cache', true) === '') {
mlc_get_attachment_usage_count($p->ID); // 計算→メタ保存
}
}
return $posts;
}, 9);
}
// フォルダ:_wp_attached_file の文字列でソート(例: 2025/10 など)
if ($orderby === 'upload_folder') {
$q->set('meta_key', '_wp_attached_file');
$q->set('orderby', 'meta_value');
}
});PHPmlc_get_attachment_usage_count($attachment_id)
- 役割:その添付を使っている**投稿数(重複なし)**を返す。
- 仕組み:キャッシュ(12時間)→なければ
本文wp-image-{ID}/本文の絶対URL/アイキャッチ(_thumbnail_id)/(任意)WooギャラリーをUNION集計。 - 注意:独自フィールド等は対象外→必要なら検索対象を追加。重い場合はキャッシュ延長か対象縮小。
mlc_get_attachment_folder_label($attachment_id)
- 役割:一覧表示用のフォルダ名を返す。
- 仕組み:仮想フォルダ系タクソノミー(あればその名称)→なければ
_wp_attached_fileの相対ディレクトリ→なければ-。 - 備考:複数タームは「, 」連結。
設置場所
functions.php または wp-content/mu-plugins/。
デバッグで確認
記事内ではデバッグ用コードをコメントアウトしています。デバッグを行う場合は、該当箇所のコメントを解除してください。上手く表示されない時など、メディアページを読み込んでいただきます。wp-contentのdebug.logにエラー内容が表示されます。
// --- デバッグフラグ(?mlc_debug=1) ---
$dbg = isset($_GET['mlc_debug']);
// --- キャッシュ(メタ) ---
$cache_key = '_mlc_usage_count_cache';
if ($dbg) { // デバッグ時はキャッシュ無視
$cached = '';
} else {
...
}
if ($dbg) {
error_log("[MLC] id={$attachment_id}");
error_log("[MLC] like_id={$like_id}");
if ($like_url) { error_log("[MLC] like_url={$like_url}"); }
error_log("[MLC] count={$count}");
error_log("[MLC] last_error={$wpdb->last_error}");
// error_log("[MLC] last_query={$wpdb->last_query}");
}PHP「使用数」「フォルダ」を追加後
下記は追加前の初期のカラムです。

メディア一覧・全体
一覧の右側に使用数とフォルダの2つの列が増えています。

使用数
使用数…その画像が使われているページの本数。
数値が10なら、10本のページで使われている画像を示します。影響が大きい素材が一目で分かります。
数値が0なら、まだどこにも使っていない画像です。
フォルダ
フォルダ…保存先(例:2025/09)。フォルダ管理プラグインを使っている場合は、そのフォルダ名が表示されます。
2025/09 のように年月フォルダが表示されます。
プラグインでフォルダ管理している場合は、Top Banner, Summer Sale のようにフォルダ名が並びます。該当がなければ - と表示されます。
注意点
- 使用数、優先順位の目安です。先に使用数が多い素材を対応すると効果的。
- フォルダ名が分かれば、受け渡しや保存場所の食い違いが起きにくくなります。
- 実装コードは上記の場所に配置してください。
まとめ
この2カラムを足すだけで、「どれから直すか」と「どこにあるか」が同時に可視化されます。判断が迷わず、依頼が短文化され、更新のスピードが上がります。コードは functions.php か mu-plugins に設置――押さえるのはそれだけです。

