オリジナルプラグイン作成

ソースファイル

wp-content/
 ┣ plugins/
    ┣ simple-image-search/
       ┣ simple-image-search.php
       ┣ js/search.js
       ┗ css/style.css

simple-image-search.php

下記のようにすればひとまずプラグインとして登録できており、有効化すると編集画面でショートコード[simple-image-search]でフォームが表示されます。

<?php
/**
 * Plugin Name: Simple Image Search
 * Description: シンプルな画像検索プラグイン
 * Version: 0.1
 * Author: Your Name
 */

// 直接アクセス禁止
if (!defined('ABSPATH')) exit;

class SimpleImageSearch {
    public function __construct() {
        // 初期化フック
        add_action('init', array($this, 'init'));
        
        // Ajax処理の登録(ログイン・非ログインユーザー両方用)
        add_action('wp_ajax_simple_image_search', array($this, 'handle_search'));
        add_action('wp_ajax_nopriv_simple_image_search', array($this, 'handle_search'));
        
        // スクリプトとスタイルの読み込み
        add_action('wp_enqueue_scripts', array($this, 'enqueue_assets'));
    }

    /**
     * 初期化処理
     */
    public function init() {
        // ショートコードの登録
        add_shortcode('simple_image_search', array($this, 'render_search_form'));
    }

    /**
     * スクリプトとスタイルの読み込み
     */
    public function enqueue_assets() {
        // CSSの登録と読み込み
        wp_enqueue_style(
            'simple-image-search',
            plugins_url('css/style.css', __FILE__),
            array(),
            '0.1'
        );

        // JavaScriptの登録と読み込み
        wp_enqueue_script(
            'simple-image-search',
            plugins_url('js/search.js', __FILE__),
            array(), // 依存関係なし
            '0.1',
            true    // フッターで読み込み
        );

        // Ajax URLとnonceをJavaScriptに渡す
        wp_localize_script(
            'simple-image-search',
            'simpleImageSearch',
            array(
                'ajax_url' => admin_url('admin-ajax.php'),
                'nonce' => wp_create_nonce('simple_image_search_nonce')
            )
        );
    }

    /**
     * 検索フォームの表示
     */
    public function render_search_form() {
        ob_start();
        ?>
        <div class="simple-image-search">
            <div class="search-header">
                <input type="text" 
                    id="image-search-input" 
                    placeholder="画像を検索..."
                    autocomplete="off">
            </div>
            <div id="image-search-results" class="search-results"></div>
        </div>
        <?php
        return ob_get_clean();
    }

    /**
     * 検索処理のハンドラー(Ajax)
     */
    public function handle_search() {
        // nonceチェック
        check_ajax_referer('simple_image_search_nonce', 'nonce');

        // 検索キーワードを取得して整形
        $search_term = sanitize_text_field($_POST['search_term'] ?? '');

        // 検索クエリの設定
        $args = array(
            'post_type' => 'attachment',
            'post_mime_type' => array(
                'image/jpeg',
                'image/png',
                'image/gif'
            ),
            'post_status' => 'inherit',
            'posts_per_page' => 20,
            's' => $search_term,
            'orderby' => 'date',
            'order' => 'DESC'
        );

        // 検索実行
        $query = new WP_Query($args);
        $results = array();

        if ($query->have_posts()) {
            while ($query->have_posts()) {
                $query->the_post();
                $attachment_id = get_the_ID();
                
                // 画像情報の取得
                $thumbnail_url = wp_get_attachment_image_url($attachment_id, 'thumbnail');
                $full_url = wp_get_attachment_image_url($attachment_id, 'full');
                $metadata = wp_get_attachment_metadata($attachment_id);
                
                // ファイルサイズの取得
                $file_path = get_attached_file($attachment_id);
                $file_size = file_exists($file_path) ? filesize($file_path) : 0;
                
                $results[] = array(
                    'id' => $attachment_id,
                    'title' => get_the_title(),
                    'thumbnail_url' => $thumbnail_url,
                    'full_url' => $full_url,
                    'width' => $metadata['width'] ?? '',
                    'height' => $metadata['height'] ?? '',
                    'filesize' => size_format($file_size),
                    'date' => get_the_date()
                );
            }
        }

        wp_reset_postdata();
        wp_send_json_success($results);
    }
}

// プラグインのインスタンス化
new SimpleImageSearch();

search.js

/**
 * 画像検索用のJavaScript
 */
document.addEventListener("DOMContentLoaded", function () {
  const searchInput = document.getElementById("image-search-input");
  const resultsContainer = document.getElementById("image-search-results");

  let searchTimer = null;
  let isSearching = false;

  // 検索入力イベントの設定
  searchInput.addEventListener("input", function () {
    clearTimeout(searchTimer);

    const searchTerm = this.value.trim();

    if (searchTerm === "") {
      resultsContainer.innerHTML = "";
      return;
    }

    // 入力から300ms後に検索実行
    searchTimer = setTimeout(() => {
      performSearch(searchTerm);
    }, 300);
  });

  /**
   * 検索実行関数
   */
  async function performSearch(searchTerm) {
    if (isSearching) return;

    isSearching = true;
    resultsContainer.innerHTML = '<div class="searching">検索中...</div>';

    try {
      const formData = new FormData();
      formData.append("action", "simple_image_search");
      formData.append("nonce", simpleImageSearch.nonce);
      formData.append("search_term", searchTerm);

      const response = await fetch(simpleImageSearch.ajax_url, {
        method: "POST",
        body: formData,
      });

      const data = await response.json();

      if (data.success) {
        displayResults(data.data);
      } else {
        resultsContainer.innerHTML =
          '<div class="error">検索中にエラーが発生しました</div>';
      }
    } catch (error) {
      console.error("Search error:", error);
      resultsContainer.innerHTML =
        '<div class="error">検索中にエラーが発生しました</div>';
    } finally {
      isSearching = false;
    }
  }

  /**
   * 結果表示関数
   */
  function displayResults(results) {
    resultsContainer.innerHTML = "";

    if (results.length === 0) {
      resultsContainer.innerHTML =
        '<div class="no-results">画像が見つかりませんでした</div>';
      return;
    }

    const gridContainer = document.createElement("div");
    gridContainer.className = "image-grid";

    results.forEach((image) => {
      const imageItem = createImageItem(image);
      gridContainer.appendChild(imageItem);
    });

    resultsContainer.appendChild(gridContainer);
  }

  /**
   * 画像アイテム要素作成関数
   */
  function createImageItem(image) {
    const imageItem = document.createElement("div");
    imageItem.className = "image-item";

    // クリックでオリジナル画像を新しいタブで開く
    imageItem.addEventListener("click", () => {
      window.open(image.full_url, "_blank");
    });

    imageItem.innerHTML = `
            <div class="image-preview">
                <img src="${image.thumbnail_url}" 
                     alt="${image.title}"
                     loading="lazy">
            </div>
            <div class="image-info">
                <div class="title">${image.title}</div>
                <div class="meta">
                    ${image.width}x${image.height}px
                    <span class="separator">|</span>
                    ${image.filesize}
                </div>
            </div>
        `;

    return imageItem;
  }
});

style.css

.simple-image-search {
    max-width: 1200px;
    margin: 0 auto;
    padding: 20px;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}

/* 検索入力エリア */
.search-header {
    margin-bottom: 20px;
}

#image-search-input {
    width: 100%;
    padding: 10px;
    font-size: 16px;
    border: 2px solid #ddd;
    border-radius: 4px;
    transition: border-color 0.3s ease;
}

#image-search-input:focus {
    border-color: #0073aa;
    outline: none;
}

/* 検索結果グリッド */
.image-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 20px;
}

/* 画像アイテム */
.image-item {
    border: 1px solid #ddd;
    border-radius: 8px;
    overflow: hidden;
    cursor: pointer;
    transition: transform 0.2s ease, box-shadow 0.2s ease;
    background: #fff;
}

.image-item:hover {
    transform: translateY(-3px);
    box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}

/* 画像プレビュー */
.image-preview {
    position: relative;
    padding-top: 75%; /* 4:3のアスペクト比 */
    background: #f0f0f0;
}

.image-preview img {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* 画像情報 */
.image-info {
    padding: 10px;
}

.image-info .title {
    font-size: 14px;
    font-weight: 500;
    margin-bottom: 4px;
    color: #333;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.image-info .meta {
    font-size: 12px;
    color: #666;
}

.image-info .separator {
    margin: 0 5px;
    color: #ddd;
}

/* ステータスメッセージ */
.searching,
.error,
.no-results {
    text-align: center;
    padding: 20px;
    color: #666;
}

.error {
    color: #d63638;
}

/* レスポンシブ対応 */
@media (max-width: 600px) {
    .image-grid {
        grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
        gap: 10px;
    }
    
    .image-info .title {
        font-size: 12px;
    }
    
    .image-info .meta {
        font-size: 11px;
    }
}

検索のロジック

メディアライブラリに画像をアップロードしたときの流れ

1. 画像アップロード
   ↓
2. wp_postsテーブルにレコード作成
   - post_type = 'attachment'
   - post_mime_type = 'image/jpeg'(など)
   - post_title = ファイル名(自動設定)
   ↓
3. wp_postmetaテーブルにメタ情報を保存
   - _wp_attachment_metadata(サイズ情報など)
   - _wp_attachment_image_alt(Alt テキスト)
WordPressメディア管理フロー 画像アップロード メディアライブラリから サーバー処理 1. ファイル保存 2. サムネイル生成 データベース登録 wp_posts wp_postmeta 画像削除 メディアライブラリから ファイル削除 1. 元ファイル 2. サムネイル データベース削除 関連レコード削除 メタデータ削除 ファイル保存場所 wp-content/uploads/年/月/ 例: wp-content/uploads/2024/01/image.jpg データベース保存情報 タイトル、説明、Alt属性 サイズ情報、アップロード日時
FTP操作のリスク 正常な状態 サーバー 画像ファイル サムネイル データベース 画像情報 メタデータ データの整合性が保たれている メディアライブラリ 正常に表示・動作 すべての機能が利用可能 FTP操作後の問題 サーバー ファイル削除/追加 サムネイル欠落 データベース 古い情報のまま 不整合発生 データの整合性が失われる メディアライブラリの問題 – 404エラー – 壊れた画像リンク – サムネイル表示エラー

WordPressの基本的なデータベース構造

/**
 * wp_posts テーブル(メインテーブル)
 * すべての投稿、ページ、メディアファイルの基本情報を格納
 */
CREATE TABLE wp_posts (
    ID bigint(20) NOT NULL AUTO_INCREMENT,    -- 投稿/メディアのユニークID
    post_title text NOT NULL,                 -- タイトル
    post_name varchar(200) NOT NULL,          -- スラッグ(URL用)
    post_content longtext NOT NULL,           -- 本文/説明
    post_excerpt text NOT NULL,               -- 抜粋/キャプション
    post_type varchar(20) NOT NULL,           -- 投稿タイプ(attachment=メディア)
    post_mime_type varchar(100) NOT NULL,     -- メディアタイプ(image/jpeg等)
    post_status varchar(20) NOT NULL,         -- 状態
    /* その他のフィールド */
);

/**
 * wp_postmeta テーブル(メタ情報テーブル)
 * 追加の情報を格納(Alt テキスト、画像サイズなど)
 */
CREATE TABLE wp_postmeta (
    meta_id bigint(20) NOT NULL AUTO_INCREMENT,  -- メタ情報のID
    post_id bigint(20) NOT NULL,                 -- 関連する投稿/メディアのID
    meta_key varchar(255),                       -- メタデータの種類
    meta_value longtext,                         -- メタデータの値
);

参考サイト