WordPressメディア一覧に「使用数」「フォルダ」を表示する、目的と使い方

CMS
この記事は約46分で読めます。

メディアライブラリ(一覧)に「使用数(その画像が使われているページ数)」「フォルダ(保存場所)」の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');
        }
    });
PHP

mlc_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 に設置――押さえるのはそれだけです。