WordPress 固定ページ上(表示側)からajaxでメディアをアップロード(ドラッグ&ドロップ)

固定ページ上の要素に画像やファイルをドラッグ&ドロップして、それをWordpressのメディアへ登録(アップロード)する機会がありましたのでメモしておきます。

私の場合は、社内サイトで利用したものですが、一般公開するサイトでやるのはセキュリティ面であまりよろしくない内容かと思いますので、自己責任で。

処理の流れ

大きく分けて、2つの処理があります。一つは、HTML/javascript側でドラッグ&ドロップ処理を実装。そして二つ目は、それをWordpressのメディアに追加する処理です。

ドラッグ&ドロップ(HTML/javascript)

HTML

HTMLはドラッグされるエリア要素と、ファイルをボタンから選択した場合も想定して以下のようにしました。 inputのcssを弄る場合はlabelなどで包むなどしてください。

<div id="drag_area">
<input type="file" name="file">
</div>

JS

dragover(ファイルをエリアに乗せたとき)、dragleave(ファイルをエリアから離したとき)、drop(ファイルをエリアにドロップしたとき)の3つのイベントを追加していきます。jquery離れができていないですが、ご了承ください;

let dragArea = $('#drag_area');
dragArea
.on('dragover', e => {
      e.stopPropagation();
      e.preventDefault();
      $(e.target).css('background', 'red');
})
.on('dragleave', e => {
      e.stopPropagation();
      e.preventDefault();
      $(e.target).css('background', '');
})
.on('drop', e => {
      let files = e.originalEvent.dataTransfer.files,
            file = files[0],
            fd = new FormData(),
            fr = new FileReader();
      if(files.length > 1){
            alert('アップロードできるファイルは1つだけです。');
            return;
      }
      fr.addEventListener("load", e => {
            //jsで生成したformデータに設定していく
            fd.append( 'action', 'upload-attachment' );
            fd.append( 'async-upload', file);
            fd.append( 'name', file.name);
            fd.append( '_wpnonce', LOCALIZE_DATA.nonce );//後述

            $.ajax({
                  url: LOCALIZE_DATA.upload_url,//後述
                  type: 'POST',
                  dataType: 'json',
                  cache: false,
                  processData: false,
                  contentType: false,
                  data: fd
            }).done( response => {
                  if(response.success){
                        //trueが返ってくれば成功
                  }
            }).fail( err => {

            });

      });
      fr.readAsArrayBuffer(file);
});
//ファイルをボタンから選択した場合
$('input[type="file"]', dragArea).on('change', e => {
      let file = e.target.files[0];
      //dropと同様の処理。今回は省略

});

PHP

PHP側からjs側に変数を渡すため、functions.phpに以下を追加し、 上記のjsで参照しています。 upload_urlとしているのは、ajaxのurlに設定するための送信先。 nonceでやっているのは、通常この処理は管理画面から行うものなので、セキュリティ対策としてajaxリクエスト時にこのnonceを送信する必要があるみたいです。

function is_enqueue_scripts() {
    $data = array(
        'upload_url' => admin_url( 'async-upload.php' ),
        'nonce'      => wp_create_nonce( 'media-form' ),
    );
    wp_localize_script( 'page_handle', 'LOCALIZE_DATA', $data );
    //'page_handle'の部分はこれを書き出すjsのハンドル名
    //例えば以下のように固定ページの場合にpage.jsを読み込んでいる前提
    if(is_page()){
      wp_enqueue_script( 'page_handle', get_template_directory_uri() . '/js/page.js', array('jquery'), '0.1.0', false );
    }
}
add_action( 'wp_enqueue_scripts', 'is_enqueue_scripts' );

当初、php側で面倒な処理が発生する想定でしたがWordpressの機能として出来たので助かりましたb

日本語ファイル名が文字化けする問題

WP Multibyte Patchプラグインを有効化していると、日本語ファイルのデータをアップロードした際に、 md5ハッシュ値に変換、サニタイズ化されてしまうため、半角英数字の乱数になってしまいます。 対策として、
/wp-content/plugins/wp-multibyte-patch/wpmp-config-sample-ja.php
上記のファイルを/wp-content/ 直下にコピーし、ファイル名を「wpmp-config.php」に変更。

さらに、ファイルの内容を編集します。
$wpmp_conf['patch_sanitize_file_name'] = false; //trueからfalseに変更
これで無事、日本語ファイル名のままアップロードされると思います。

ユーザ権限によるエラー

冒頭で述べた通り、私の場合社内サイト(ログインユーザのみ閲覧可能)での実装としており 各ログインユーザを「サイト権限なし」としておりました。
[参考]WordPress プラグインなしで簡易的な会員(メンバー)専用ページをつくる

このため、アップロードでエラーが出てしまいました。 解決策としては、ユーザ権限を「購読者」に変更し、functions.phpに以下を追記
//権限「購読者」のファイルアップロード権限をON
function add_theme_caps(){
    $role = get_role( 'subscriber' );//subscriberは購読者
    $role->add_cap( 'upload_files' );
}
add_action( 'admin_init', 'add_theme_caps' );
また、権限を購読者にしてしまうと、ログイン時に管理画面へ遷移してしまうので トップページへリダイレクトさせなければなりません。 ということで、以下を記述してみました。しかし、この方法だと管理画面への遷移は防げるものの 今度は、アップロードのajax処理で返ってくるレスポンスで何故かサイトのHTMLが一式返ってくるため エラーになります。
//この方法だとアップロードのajax処理で問題発生
add_action( 'auth_redirect', 'subscriber_go_to_home' );
function subscriber_go_to_home( $user_id ) {
    $user = get_userdata( $user_id );
    $user_roles  = $user->roles;
    $target_role = 'subscriber';
    if ( in_array($target_role, $user_roles) ){
        wp_redirect( get_home_url() );
        exit();
    }
}
なので、正しいやり方なのかどうかわかりませんが、以下の記述で一応動作しました
add_action( 'auth_redirect', 'subscriber_go_to_home' );
function subscriber_go_to_home( $user_id ) {
    if(isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
        //ajaxの場合、なにもしない
    }else{
        //管理画面かつユーザ権限が購読者の場合、ホームにリダイレクト
        if(is_admin() && current_user_can('subscriber')){
            wp_redirect( get_home_url() );
        }
    }
}
*ajax通信を除外しないと、上と同じようにエラーになってしまった為、「ajaxの場合、なにもしない」としています

セキュリティ上、アップロードできないファイル

私の場合、マクロなどを含むエクセルで「.xlsm」という拡張子のファイルもアップロードする必要があったのですが これが「セキュリティ上、アップロードできない」というエラーになってしまいました。 このため、まずはWordpressの設定ファイル(wp-config.php)に以下を記述してみました。
define('ALLOW_UNFILTERED_UPLOADS', true);
ちなみに、これはアップロードするファイルをすべて許可するという記述なので 自己判断で。

ただし、この記述だと管理者権限のみ有効なようで、購読者はダメでした。いろいろ調べた結果、 「Disable Real MIME Check」というプラグインをインストールし、無事解決しました。
ご依頼・お問い合わせ