ログイン中のQiita Team
ログイン中のチームがありません

Qiita Team にログイン
コミュニティ
OrganizationイベントアドベントカレンダーQiitadon (β)
サービス
Qiita JobsQiita ZineQiita Blog
PHP
Chrome
javasctipt
2
どのような問題がありますか?
Organization

ファイルアップロードツールを作ってみた

はじめに

PHPとJavaScriptを使ってブラウザ上で動作するシンプルなファイルアップロードツールを作りました。
Chromeブラウザで動作します。

ツールイメージ

ツールのイメージはこんな感じになります。
01_img.png
①テキストを保存する領域です。テキストエリアになっているので複数行保存出来ます。
②上部のテキストの内容を保存するボタンです。
③アップロードされているファイル一覧が表示される領域です。
ファイルがアップロードされている場合は、ファイル名・ダウンロードボタン・削除ボタンが表示されます。
④ファイルアップロード領域です。ここにファイルをドラッグ&ドロップするとファイルがアップロードされます。

ファイル構成

ツールのファイル構成はこのようになっています。
02_img.png

fileフォルダ・・アップロードしたファイルが格納されるフォルダ
Ajax.inc・・Ajax処理をまとめているJavaScriptライブラリファイル
index.html・・ブラウザのページファイル※このファイルにアクセスしてツールを起動します
server.php・・サーバ側の処理を行うPHPファイル
text.txt・・テキストの入力欄に入力し保存された文字を保存するテキストファイル

クライアント側

クライアント側のソースは以下になります。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <title>file upload</title>
        <script type="text/javascript" src="Ajax.inc"></script>
        <script type="text/javascript">
            window.onload = function(){
                // テキスト読み込み
                sendData = createQueryParam( "flg", 1 );
                ajaxNetConnect( "./server.php", sendData, "POST", 1, "text_load_complete" );
            }
            // テキスト読み込み完了
            function text_load_complete(){
                // テキストエリアへ反映
                var dom = document.getElementById( 'save_text' );
                dom.value = ajaxRecvData;

                // ファイル一覧取得通信開始
                getFileList();
            }

            // テキスト保存実行
            function textSave(){
                var sendData;
                var text_val = document.getElementById('save_text').value;
                //console.log(dom);

                sendData = createQueryParam( "flg", 0, "text_val", text_val );
                //console.log( sendData );
                ajaxNetConnect( "./server.php", sendData, "POST", 1, "text_save_complete" );
            }
            // テキスト保存完了
            function text_save_complete(){
                //console.log( "受信データ:"+ajaxRecvData+"" );
                alert( "テキスト保存成功しました" );
            }

            // ドラッグ要素がドロップ要素に重なっている間の処理
            function f_dragover(event){
                // dragoverイベントをキャンセルして、ドロップ先の要素がドロップを受け付けるようにする
                event.preventDefault();
            }

            // ドロップ時の処理
            function f_drop(event){
                var file = event.dataTransfer.files[0];
                //console.log( file.name );

                // ファイル送信用AJAX
                var formData  = new FormData();
                formData.append( "flg", "2" );
                formData.append( "file", file );

                ajaxSendFormData( "./server.php", formData, "file_save_complete" );

                // エラー回避のため、ドロップ処理の最後にdropイベントをキャンセルしておく
                event.preventDefault();
            }

            // ファイル保存完了
            function file_save_complete(){
                console.log( 'file_save_complete' );
                // ファイル一覧取得通信開始
                getFileList();
            }

            // ファイル一覧取得
            function getFileList(){
                sendData = createQueryParam( "flg", 3 );
                ajaxNetConnect( "./server.php", sendData, "POST", 1, "file_list_complete" );
            }

            // ファイル一覧取得完了
            function file_list_complete(){
                //ajaxRecvData
                var obj = document.getElementById( 'file_list' );
                var str = "";
                if( ajaxRecvData != "file_not_found" ){
                    var fileNames = ajaxRecvData.split( "," );
                    str += "<table border='1'>";
                    str += "<tr>";
                    str += "<td width='500px'>ファイル名</td>";
                    str += "<td>ダウンロード</td>";
                    str += "<td>削除</td>";
                    str += "</tr>";
                    for( var i=0; i<fileNames.length; i++ ){
                        // ファイル名からフォルダ名を除く
                        var buff = fileNames[i].split( "/" );
                        var fileName = buff[2];

                        str += "<tr>";
                        str += "<td>"+fileName+"</td>";
                        str += "<td><a href='"+fileNames[i]+"' download>ダウンロード</a></td>";
                        str += "<td align='center'><a href='#' onClick='fileDeleteStart(\""+fileNames[i]+"\");'>■</a></td>";
                        str += "</tr>";
                    }

                    str += "</table>";
                    obj.innerHTML = str;

                } else {
                    str = "ファイルがありません";
                    obj.innerHTML = str;

                }
                //console.log( 'file_save_complete' );
            }

            // ファイル削除
            function fileDeleteStart( fileName ){
                var buff = fileName.split( "/" );
                var buffFileName = buff[2];
                // ファイル削除確認ダイアログ表示
                var value = window.confirm( ""+buffFileName+"」を削除します、よろしいですか?" );
                if( value ){
                    sendData = createQueryParam( "flg", 4, "file_name", fileName );
                    ajaxNetConnect( "./server.php", sendData, "POST", 1, "file_delete_complete" );
                }
            }

            // ファイル削除完了
            function file_delete_complete(){
                if( ajaxRecvData == "file_delete_success" ){
                    alert( "ファイル削除成功" );
                    // ファイル一覧取得通信開始
                    getFileList();
                } else {
                    alert( "削除ファイルが見つかりませんでした" );
                }
            }
        </script>
</head>
<body>

    [テキスト]<br>
    <textarea id="save_text" name="save_text" rows="10" cols="100"></textarea>
    <br>

    <input type='button' id='button' name='button' onClick='textSave()' value='保存'><br><br>

    [ファイル一覧]<br>
    <div id='file_list' name='file_list'>

    </div><br>

    [ファイルアップロード]<br>
    <div id='file_upload' name='file_upload' style='width:300px; height:100px; border:1px dotted #333333;' ondragover="f_dragover(event);" ondrop="f_drop(event);">
        <p>ここにドラッグ</p>
    </div>

</body>

</html>

コード解説

コードを見てもらえば分かるのですが、9割JavaScriptになっています。
また、このような小規模のシステムの時は極力ファイル数を増やしたくないので、サーバへのリクエスト時に「flg=0」といったパラメータを付与し、サーバ側ではフラグの値を見て「0:読み込み、1:保存」というように処理を分岐させるといったことをよくやります。
上記の観点でコードを見てもらえると処理が追いやすいかと思います。

続いて、Ajaxライブラリになります。

Ajax.inc
//=======================================================================================
//
//  AJAX
//
//=======================================================================================
var xmlhttp;        // XMLHttpオブジェクト
var ajaxRecvData;   // 受信データ

var HTTP_GET = "GET";
var HTTP_POST = "POST";
var HTTP_RECV = 1;

//-----------------------------------------------------------
//     getXMLHttp : XMLHttpオブジェクト取得
//           引数 : 無し
//         戻り値 : XMLHttpオブジェクト
//-----------------------------------------------------------
function getXMLHttp()
{
    // ブラウザによって処理を変える
    if( window.XMLHttpRequest ){
        xmlhttp = new XMLHttpRequest();
    } else if( window.ActiveXObject ){
        try {
            xmlhttp = new ActiveXObject( "Msxml2.XMLHTTP" );
        } catch( e ){
            xmlhttp = new ActiveXObject( "Microsoft.XMLHTTP" );
        }
    }
    return xmlhttp;
}

//-----------------------------------------------------------
//   createQueryParam : クエリパラメータ作成
//               引数 : 名前,パラメータ(可変長引数)
//             戻り値 : 成功・・クエリパラメータ
//             戻り値 : 失敗・・false
//-----------------------------------------------------------
function createQueryParam()
{
    var param = new Object();
    var key;
    var array = new Array();

    if( arguments.length % 2 != 0 ){    // 引数が2の倍数でなければエラー
        alert( "エラー:クエリパラメータは[名前:値]のセットで指定して下さい" );
        return false;
    }

    // クエリパラメータを作成
    for( i=0; i<arguments.length; i+=2 ){
        param[arguments[i]] = arguments[i+1];
    }

    for( key in param ){
        array.push( key + "=" + encodeURIComponent(param[key]) );
    }
    array = array.join( "&" );  // クエリパラメータを&で結合する

    return array;
}

//-----------------------------------------------------------
//  ajaxNetConnect : データ送受信
//            引数 : URL,送信データ,通信種別(GET,POST)
//            引数 : 送受信フラグ,コールバック関数名
//          戻り値 : 無し
//-----------------------------------------------------------
function ajaxNetConnect( url, sendData, sendKind, sendFlg, funcName )
{
    if( xmlhttp == null ){
        getXMLHttp();   // XMLHttpオブジェクト取得
        if( xmlhttp == null ){
            alert( "エラー:XMLHttpオブジェクトがありません" );
            return;
        }
    }

    // 通信種別によって処理を変更
    if( sendKind == HTTP_GET ){ // GET通信
        if( sendData != null )  url += "?"+sendData+"";     // URLの後ろにリクエストパラメータを付与
    }

    xmlhttp.open( sendKind, url, true );    // オープン
    if( sendKind == HTTP_POST ){    // POST通信
        xmlhttp.setRequestHeader( "Content-Type", "application/x-www-form-urlencoded" );    // ヘッダーセット
        xmlhttp.send( sendData );
    }

    if( sendFlg == HTTP_RECV ){ // 受信の時のみ処理
        ajaxRecvData = null;    // 受信データ初期化
    }

    xmlhttp.onreadystatechange=function()
    {
        if( xmlhttp.readyState == 4  ){ // 準備完了
            if( xmlhttp.status == 200 ){    // 成功

                if( sendFlg == HTTP_RECV ){ // 受信の時のみ処理
                    ajaxRecvData = xmlhttp.responseText;    // 受信データを保存
                }

            } else {    // 失敗

                if( sendFlg == HTTP_RECV ){ // 受信の時のみ処理
                    ajaxRecvData = "error";
                }
            }

            if( funcName != null ){ // コールバック関数名がある場合のみ処理
                eval( funcName + '()' );    // コールバック関数実行
            }
        }
    };
    // GET通信の場合はここで送信
    if( sendKind == HTTP_GET )  xmlhttp.send( null );
}

//-----------------------------------------------------------
// ajaxSendFormData : フォームデータ用Ajax通信
//             引数 : URL,送信データ,コールバック関数名
//           戻り値 : 無し
//-----------------------------------------------------------
function ajaxSendFormData( url, sendData, funcName )
{
    if( xmlhttp == null ){
        getXMLHttp();   // XMLHttpオブジェクト取得
        if( xmlhttp == null ){
            alert( "エラー:XMLHttpオブジェクトがありません" );
            return;
        }
    }
    ajaxRecvData = null;    // 受信データ初期化

    xmlhttp.open( 'POST', url );
    xmlhttp.send( sendData );

    xmlhttp.onreadystatechange=function()
    {
        if( xmlhttp.readyState == 4  ){ // 準備完了
            if( xmlhttp.status == 200 ){    // 成功
                ajaxRecvData = xmlhttp.responseText;    // 受信データを保存

            } else {    // 失敗
                ajaxRecvData = "error";
            }

            if( funcName != null ){ // コールバック関数名がある場合のみ処理
                eval( funcName + '()' );    // コールバック関数実行
            }
        }
    };
}

コード解説

Ajax通信を行うだけのライブラリなのですごくシンプルです。
少し特徴的な部分だけ解説します。
createQueryParamメソッド
クエリパラメータの文字列を作成するメソッドなのですが、パラメータ数は通信の種類によって変化するために引数が可変長引数(arguments変数)になっています。

ajaxNetConnectメソッド
Ajax通信を行っているメソッドです。
業務ではよくjQueryを使用してAjax通信処理を実行することが多いと思うのですが、素のJavaScriptでAjax通信を記述するとこんな感じになります。
第5引数の「funcName」でメソッド名を渡しているのですが、サーバからのデータ受信後にevalメソッドを実行することで、指定した引数(文字列)のメソッドを実行させることが出来ます。
こうすることで「通信A終了後はAメソッドを実行」「通信B終了後はBメソッドを実行」といった形でそれぞれ別メソッドに定義することが出来るようになります。

サーバ側

サーバ側のソースは以下になります。

server.php
<?php
$response = "responce_no_data";
$textFileName = "./file.txt";
$folderName = "./file";

$flg = $_REQUEST['flg'];

switch( $flg ){
case 0:     // テキスト保存
    $text_val = $_REQUEST['text_val'];
    $text_val = mb_convert_encoding( $text_val, "Shift-JIS", "UTF-8" );

    // テキストファイル保存
    if( file_exists($textFileName) ){
        unlink( $textFileName );
    }
    file_put_contents( $textFileName, $text_val );
    // レスポンス
    $response = "保存成功";
    break;

case 1:     // テキスト読み込み
    $response = file_get_contents( $textFileName );
    $response = mb_convert_encoding( $response, "UTF-8", "Shift-JIS" );
    break;

case 2:     // ファイル保存
    if( isset($_FILES['file']) ){
        $file = $_FILES['file'];
        $fileName = $file['name'];          // ファイル名

        $copyFolder = "".$folderName."/".$fileName."";   // コピーフォルダ名

        move_uploaded_file( $_FILES['file']['tmp_name'], $copyFolder );
    }
    break;

case 3:     // ファイル一覧取得

    $result = glob( "".$folderName."/*" );

    if( !empty($result) ){  // ファイルが存在する
        $response = "";
        for( $i=0; $i<count($result); $i++ ){
            if( $i > 0 )    $response .= ",";
            $response .= $result[$i];
        }

    } else {
        $response = "file_not_found";

    }
    break;

case 4:     // ファイル削除
    $deleteFileName = $_REQUEST['file_name'];

    if( file_exists($deleteFileName) ){
        unlink( $deleteFileName );
        $response = "file_delete_success";

    } else {
        $response = "file_not_found";
    }
    break;
}
echo $response;

コード解説

サーバ側のコードはとてもシンプルになっています。
特にPHPはファイル保存や移動などが数行のコードで実現出来るのでとても便利です。
クライアント側でも書きましたが「flg」変数で分岐させているので処理が見通しやすくなっていると思います。

最後に

とてもシンプルなツールですが、Ajax通信の連続実行(ページ読み込み時)やドラッグ&ドロップのイベント処理など、業務に利用出来るような部分もあるかなと思います。
また前回の内容と組み合わせると、外部からアクセス出来るツールにすることも出来るので是非チャレンジしてみて下さい。

ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
ari-group
Biz&Tech&Creative 三位一体型でサービス企画、UIデザイン、アプリ開発からクラウド基盤や音声基盤の構築、BIやRPAなどのソリューション導入、各種保守運用までをワンストップで提供するITコンサルティングとクラウドインテグレーションをやっている会社です。渋谷、大阪、名古屋の国内3拠点体制で、FAQチャットボットなど自社サービスも展開しています。仲間を絶賛募集中です。

コメント

(編集済み)
リンクをコピー
このコメントを報告

ディレクトリトラバーサル脆弱性が存在します。

server.php?flg=4&file_name=%2Fvar%2Flog%2Faccess_log

2
リンクをコピー
このコメントを報告

脆弱性を指摘されているにもかかわらず、コメントに返答もせず投稿内容も修正することなく放置という姿勢は、こういった場で技術情報を公開するには問題があるのではないでしょうか。
この記事の後も別の記事を投稿されているので、指摘に気付いていないということでもなさそうですし。
Organization設定もされているようですが、その会社の信用問題に繋がる可能性もあるように思います。

0
どのような問題がありますか?
あなたもコメントしてみませんか :)
ユーザー登録
すでにアカウントを持っている方はログイン
記事投稿イベント開催中
マイクロソフト認定資格を取得する際の学習方法や経験談、おすすめ学習リソースなどを紹介しよう!
~
2
どのような問題がありますか?
ユーザー登録して、Qiitaをもっと便利に使ってみませんか

この機能を利用するにはログインする必要があります。ログインするとさらに下記の機能が使えます。

  1. ユーザーやタグのフォロー機能であなたにマッチした記事をお届け
  2. ストック機能で便利な情報を後から効率的に読み返せる
ユーザー登録ログイン
ストックするカテゴリー