メディアライブラリにメディアをコピペ保存出来るプラグイン

2026年1月16日
文字数[5224文字] この記事は7分32秒で読めます.

おはようございます.メディアライブラリにメディアをコピペ保存出来るプラグインを作りました.メディアライブラリの項目を開きコントロール+V(ペースト)する事でコピーしたメディアをメディアライブラリに保存できるプラグインです.

この頃、ブログのアイキャッチ画像は生成AI画像をクリップボードでコピーして、そのまま記事に貼り付けてメディア保存していたのだけど、それだと記事を書かない時にもそういう対応しないといけないのが手間だったのでプラグインを生成AIと共に作りました.

そのお裾分けのコードになります.

コードをクローンしたい方はプラグインをgithub上に置いてあるのでお使い下さい.

<?php

/**
 * Plugin Name: Clipboard Media Saver Plus
 * Description: クリップボードから画像・音声・動画を貼り付けるだけでメディアライブラリに保存
 * Version: 1.1.0
 * Author: @toshiaki_taoka
 * Author URI: https://x.com/toshiaki_taoka
 */

if (!defined('ABSPATH')) exit;

class Clipboard_Media_Saver_Plus
{

    public function __construct()
    {
        add_action('admin_enqueue_scripts', [$this, 'enqueue']);
        add_action('wp_ajax_clipboard_media_upload', [$this, 'upload']);
    }

    public function enqueue($hook)
    {
        // 念のため二重ガード
        if ($hook !== 'upload.php') {
            return;
        }

        wp_enqueue_script(
            'clipboard-media-saver-plus',
            plugin_dir_url(__FILE__) . 'clipboard-media-saver-plus.js',
            [],
            '1.1.0',
            true
        );

        wp_localize_script('clipboard-media-saver-plus', 'CMSP', [
            'ajax'  => admin_url('admin-ajax.php'),
            'nonce' => wp_create_nonce('clipboard_media_upload')
        ]);
    }

    public function upload()
    {
        check_ajax_referer('clipboard_media_upload', 'nonce');

        if (!current_user_can('upload_files')) {
            wp_send_json_error('権限がありません');
        }

        if (empty($_FILES['file'])) {
            wp_send_json_error('ファイルがありません');
        }

        require_once ABSPATH . 'wp-admin/includes/file.php';
        require_once ABSPATH . 'wp-admin/includes/media.php';
        require_once ABSPATH . 'wp-admin/includes/image.php';

        $file = $_FILES['file'];

        // MIMEと拡張子をWP基準で再判定(超重要)
        $check = wp_check_filetype_and_ext(
            $file['tmp_name'],
            $file['name']
        );

        if (!$check['ext'] || !$check['type']) {
            wp_send_json_error('不正なファイル形式');
        }

        $upload = wp_handle_upload($file, [
            'test_form' => false,
            'mimes'     => get_allowed_mime_types()
        ]);

        if (isset($upload['error'])) {
            wp_send_json_error($upload['error']);
        }

        $attachment = [
            'post_mime_type' => $check['type'],
            'post_title'     => sanitize_file_name(pathinfo($file['name'], PATHINFO_FILENAME)),
            'post_status'    => 'inherit'
        ];

        $attach_id = wp_insert_attachment($attachment, $upload['file']);

        // 画像のみメタ生成
        if (strpos($check['type'], 'image/') === 0) {
            $meta = wp_generate_attachment_metadata($attach_id, $upload['file']);
            wp_update_attachment_metadata($attach_id, $meta);
        }

        wp_send_json_success([
            'id'  => $attach_id,
            'url' => wp_get_attachment_url($attach_id),
            'type' => $check['type']
        ]);
    }
}

new Clipboard_Media_Saver_Plus();
(function () {
  document.addEventListener('paste', function (e) {
    if (!document.body.classList.contains('upload-php')) {
      return;
    }

    const items = e.clipboardData?.items || [];

    for (const item of items) {
      if (item.kind !== 'file') continue;

      const file = item.getAsFile();
      if (!file) continue;

      let ext = '';
      if (file.type.startsWith('image/')) ext = '.png';
      else if (file.type.startsWith('image/')) ext = '.jpg';
      else if (file.type.startsWith('image/')) ext = '.gif';
      else if (file.type.startsWith('image/')) ext = '.webp';
      else if (file.type.startsWith('audio/')) ext = '.mp3';
      else if (file.type.startsWith('video/')) ext = '.mp4';
      else continue;

      const filename = file.name && file.name.includes('.')
        ? file.name
        : `clipboard-${Date.now()}${ext}`;

      const formData = new FormData();
      formData.append('action', 'clipboard_media_upload');
      formData.append('nonce', CMSP.nonce);
      formData.append('file', file, filename);

      fetch(CMSP.ajax, {
        method: 'POST',
        credentials: 'same-origin',
        body: formData
      })
        .then(r => r.json())
        .then(r => {
          if (!r.success) {
            alert(r.data || 'Upload failed');
          } else {
            alert('保存しました.リロードします.');
            console.log('Saved:', r.data.url);
            this.location.reload();
          }
        })
        .catch(err => console.error(err));
    }
  });
})();

明日へ続く