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

20260116

文字数[5224文字] この記事は7分32秒で読めます.

Logging

おはようございます.メディアライブラリにメディアをコピペ保存出来るプラグインを作りました.メディアライブラリの項目を開きコントロール+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));
    }
  });
})();

明日へ続く

3627番目の投稿です/34 回表示されています.

中の人🏠️

AIによるおすすめ記事

著者名  @taoka_toshiaki

※この記事は著者が40代後半に書いたものです.

Profile
高知県在住の@taoka_toshiakiです、記事を読んで頂きありがとうございます.
数十年前から息を吸うように日々記事を書いてます.たまに休んだりする日もありますがほぼ毎日投稿を心掛けています😅.
SNSも使っています、フォロー、いいね、シェア宜しくお願い致します🙇.
SNS::@taoka_toshiaki

OFUSEで応援を送る

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA


タグ

AI, github, アイキャッチ, いん, お裾分け, クリップ, クローン, コード, コピー, コピペ, コントロール, それ, フラグ, ブログ, ペースト, ボード, メディア, ライブラリ, 保存, 対応, 手間, 明日, 生成, 画像, 記事, 項目,