ファイルアップローダー Ver.2.0
以前、Blog記事内で「自作のファイルアップローダー」をご紹介しましたが、今回、その仕様を少しバージョンアップしましたので、改めて「ファイルアップローダー Ver.2.0」としてご紹介します(※記事内でも説明しておりますが、当ファイルアップローダーを参考にされる場合、セキュリティ面に配慮した上でご活用ください)。
Index
― 目次 ―「ファイルアップローダー」の仕様について
「ファイルアップローダー」には、以下のような機能を実装します。
今回バージョンアップした点は、以下の3点です。
- 複数ファイルの一括アップロードに対応
- SP(モバイル端末)でのアップロードにも対応
- セキュリティ対策
- 「ファイルアップローダー」のディレクトリにBasic認証を設置
「ファイルアップローダー」にアクセスする際、セキュリティ面を考慮し、SSLで暗号化された領域内にBasic認証を設置。
※ユーザー名とパスワードを入力した場合のみアクセス可能とする - 「ファイルアップローダー」内にメッセージ機能を実装
アップロードしたファイルに関する補足説明を登録できるように「メッセージ入力欄」を設置。 - ファイル一覧に表示される内容
ファイル名、ダウンロードボタン、削除ボタンの3つを表示。
ファイル名には、アップロード日時が分かるように「ファイル名の先頭に年・月・日・時間・分・秒のタイムスタンプを自動的に付与」。
※表示順序は「新しいファイル順」とする - アップロードの方法:PCとSP(モバイル端末)でアップロード方法を切り分ける
PCの場合:「ファイルをドラッグ & ドロップしてアップロードする形」とする。
SP(モバイル端末)の場合:「ファイル参照ボタンよりファイルを選択してアップロードする形」とする。
※SP(モバイル端末)の場合「ファイルアップロードエリアをタップするとファイル選択画面が出る」仕様とする
※複数ファイルの一括アップロードに対応 - 検証ブラウザ
Google Chrome(※2026.6.2時点での最新バージョン:149.0.7827.54)
ファイル構成
「ファイルアップローダー」のファイル構成は、以下のような感じです。
▼ファイル構成のイメージ画像
uploaderディレクトリ内に「ファイルアップローダーに必要なファイル一式」を格納します。
- .htaccessファイル
Basic認証に必要なファイル
※セキュリティ面を考慮し、.htpasswdファイルは、ブラウザからアクセスできる領域とは別の領域に設置 - uploadディレクトリ
アップロードファイルの格納ディレクトリ
※このディレクトリ内にメッセージ文保存用の「message.txt」も格納 - index.html
ブラウザ表示用のhtmlファイル
※URL/uploader/にアクセス → ファイルアップローダーのページ - uploader.css
UI(ユーザーインターフェース)編集用のCSSファイル - ajax.js
Ajax通信用のJavaScripファイル - uploader.php
サーバー側の処理を行うphpファイル
※設定を変更したい場合はこちらを編集
HTML・CSS・JavaScript(Ajax)・PHPなどの参考ソース
「ファイルアップローダー」に必要なHTML・CSS・JavaScript(Ajax)・PHPなどについて説明していきます。
前提として、uploaderディレクトリ以下は、Basic認証でアクセスを制限してください。
Basic認証については、参考できるWebサイトが多数あり、Webサーバーを利用されている方向けに「指定ディレクトリ以下へのアクセス制限機能」が付加されている場合もありますので、本記事内での説明は割愛します。
セキュリティ面を考慮して「SSLで暗号化された領域内にBasic認証を設置」「.htpasswdファイルは、ブラウザからアクセスできる領域とは別の領域に設置」「1つのユーザー名とパスワードを複数名で使いまわすのではなく、各自に発行」するようにしてください。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 |
<!DOCTYPE html> <html lang="ja"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>ファイルアップローダー</title> <link rel="stylesheet" href="./uploader.css" media="all"> <script type="text/javascript" src="./ajax.js"></script> <script type="text/javascript"> window.onload = function(){ // テキストの読み込み sendData = createQueryParam( "flg", 1 ); ajaxNetConnect( "./uploads.php", sendData, "POST", 1, "text_load_complete" ); // SP(モバイル端末)判定と表示の切り替え initUploadArea(); } // テキストの読み込みを完了 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; sendData = createQueryParam( "flg", 0, "text_val", text_val ); ajaxNetConnect( "./uploads.php", sendData, "POST", 1, "text_save_complete" ); } // メッセージ用テキストの保存を完了 function text_save_complete(){ alert( "メッセージを保存しました" ); } // ドラッグ要素がドロップ要素に重なっている間の処理 function f_dragover(event){ // dragoverイベントをキャンセル → ドロップ先の要素がドロップを受け付け可能とする event.preventDefault(); } // ドロップ時の処理:PC用 function f_drop(event){ var files = event.dataTransfer.files; uploadFiles(files); // ドロップ処理の最後にdropイベントをキャンセル → エラー回避処理 event.preventDefault(); } // SP(モバイル端末)では[ファイル参照ボタン]に変更:SP/MOBILE用 function f_file_change(event){ var files = event.target.files; uploadFiles(files); } // 共通アップロード処理 function uploadFiles(files) { if (files.length > 0) { var formData = new FormData(); formData.append( "flg", "2" ); // すべてのファイルをFormDataに追加 for (var i = 0; i < files.length; i++) { formData.append( "file[]", files[i] ); } ajaxSendFormData( "./uploads.php", formData, "file_save_complete" ); } } // SP(モバイル端末)端末の判定とアップロードエリアの初期化 function initUploadArea() { var isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); var uploadDiv = document.getElementById('file_upload'); var mobileInput = document.getElementById('mobile_file_input'); var annotation = document.getElementById('upload_annotation'); if (isMobile) { // SP(モバイル端末)の場合は表示を変更 uploadDiv.textContent = "こちらをタップして「ファイルを選択」してください"; // HTML構造を維持:タグをそのまま埋め込まず要素を生成して挿入 annotation.textContent = ""; var asterisk = document.createElement("span"); asterisk.className = "asterisk"; asterisk.textContent = "※"; annotation.appendChild(asterisk); annotation.appendChild(document.createTextNode("複数ファイルの一括アップロードも可能です")); // イベント設定:エリアをタップすると隠れているinput type="file"が発火するようにする uploadDiv.addEventListener('click', function() { mobileInput.click(); }); } } // アップロードファイルの保存を完了 function file_save_complete(){ console.log( 'file_save_complete' ); // アップロードファイル一覧の取得を開始 getFileList(); } // アップロードファイル一覧の取得 function getFileList(){ sendData = createQueryParam( "flg", 3 ); ajaxNetConnect( "./uploads.php", sendData, "POST", 1, "file_list_complete" ); } // アップロードファイル一覧の取得を完了 function file_list_complete(){ var obj = document.getElementById( 'file_list' ); // 中身をクリア obj.textContent = ""; if( ajaxRecvData != "file_not_found" && ajaxRecvData.trim() !== "" ){ var fileNames = ajaxRecvData.split( "," ); // テーブル要素の生成 var table = document.createElement("table"); table.className = "base_style"; var tbody = document.createElement("tbody"); // ヘッダー行の作成 var trHead = document.createElement("tr"); var th1 = document.createElement("th"); th1.className = "w70"; th1.textContent = "ファイル名"; var th2 = document.createElement("th"); th2.className = "w15"; th2.textContent = "ダウンロード"; var th3 = document.createElement("th"); th3.className = "w15"; th3.textContent = "削除"; trHead.appendChild(th1); trHead.appendChild(th2); trHead.appendChild(th3); tbody.appendChild(trHead); // データ行の作成 for( var i=0; i < fileNames.length; i++ ){ if(!fileNames[i]) continue; // ファイル名からフォルダ名を除去 var buff = fileNames[i].split( "/" ); var fileName = buff[2] || fileNames[i]; var tr = document.createElement("tr"); tr.className = "display_none"; // ファイル名 var tdName = document.createElement("td"); tdName.className = "left"; tdName.textContent = fileName; // ダウンロードリンク var tdDl = document.createElement("td"); var aDl = document.createElement("a"); aDl.setAttribute("href", fileNames[i]); aDl.setAttribute("download", ""); aDl.textContent = "ダウンロードする"; tdDl.appendChild(aDl); // 削除リンク var tdDel = document.createElement("td"); var aDel = document.createElement("a"); aDel.setAttribute("href", "#"); aDel.textContent = "削除する"; // クロージャによる変数の固定 (function(currentPath) { aDel.addEventListener("click", function(e) { e.preventDefault(); fileDeleteStart(currentPath); }); })(fileNames[i]); tdDel.appendChild(aDel); // 各セルを行に追加 tr.appendChild(tdName); tr.appendChild(tdDl); tr.appendChild(tdDel); tbody.appendChild(tr); } table.appendChild(tbody); obj.appendChild(table); } else { var p = document.createElement("p"); p.style.color = "#ff0000"; p.style.marginBottom = "30px"; p.textContent = "「メッセージ保存用のテキストファイル」をアップロードしてください"; obj.appendChild(p); } } // ファイル削除 function fileDeleteStart( fileName ){ var buff = fileName.split( "/" ); var buffFileName = buff[2] || fileName; // ファイル削除確認ダイアログ表示 var value = window.confirm( "「"+buffFileName+"」を削除しても宜しいですか?" ); if( value ){ sendData = createQueryParam( "flg", 4, "file_name", fileName ); ajaxNetConnect( "./uploads.php", sendData, "POST", 1, "file_delete_complete" ); } } // ファイル削除完了 function file_delete_complete(){ if( ajaxRecvData == "file_delete_success" ){ alert( "ファイルを削除しました" ); // ファイル一覧取得通信開始 getFileList(); } else { alert( "削除ファイルが見つかりませんでした" ); } } </script> </head> <body> <h1 class="ttl">ファイルアップローダー</h1> <h2 class="sub_ttl">[メッセージ]</h2> <textarea id="save_text" name="save_text" class="ta_style"></textarea> <input type="button" id="button" name="button" onClick="textSave()" value="メッセージを保存する" class="save_button"> <h2 class="sub_ttl">[ファイル一覧]</h2><p class="annotation"><span class="asterisk">※</span>アップロードされたファイルの先頭には「アップロード日時(年・月・日・時間・分・秒)」が自動的に付与されます</p> <div id="file_list" name="file_list"></div> <h2 class="sub_ttl">[ファイルアップロード]</h2> <p id="upload_annotation" class="annotation"><span class="asterisk">※</span>複数ファイルの一括アップロードも可能です</p> <div id="file_upload" name="file_upload" class="dd_style" style="cursor: pointer;" ondragover="f_dragover(event);" ondrop="f_drop(event);"> こちらに「ファイルをドラッグ & ドロップ」してください </div> <input type="file" id="mobile_file_input" name="file[]" multiple style="display: none;" onChange="f_file_change(event);"> </body> </html> |
[補足]
6行目が外部CSSファイルへのパス指定(※UI編集用のCSSファイル)。
7行目が外部JavaScriptファイルのパス指定(※Ajax通信用のJavaScripファイル)。
8~220行目がJavaScriptの記述。
ここでの処理は、主に「メッセージ用テキストの処理」「端末の判定(PC or Mobile)と表示の切り替え処理」「アップロードしたファイルに関する処理」「セキュリティ対策としての処理」を行っています。
サーバーリクエストの際「flg=0」というようにパラメータを付与し、サーバーサイドでフラグの値を見て「0=読込」「1=保存」というように処理を分岐させています(※処理を分岐させることでファイル数を減らし、エコな作りにしています)。
また、HTML文字列の動的組み立てではなく、ブラウザの安全なAPI(document.createElementやtextContent)を使って安全にDOMを構築する方式を採用しています。
6行目が外部CSSファイルへのパス指定(※UI編集用のCSSファイル)。
7行目が外部JavaScriptファイルのパス指定(※Ajax通信用のJavaScripファイル)。
8~220行目がJavaScriptの記述。
ここでの処理は、主に「メッセージ用テキストの処理」「端末の判定(PC or Mobile)と表示の切り替え処理」「アップロードしたファイルに関する処理」「セキュリティ対策としての処理」を行っています。
サーバーリクエストの際「flg=0」というようにパラメータを付与し、サーバーサイドでフラグの値を見て「0=読込」「1=保存」というように処理を分岐させています(※処理を分岐させることでファイル数を減らし、エコな作りにしています)。
また、HTML文字列の動的組み立てではなく、ブラウザの安全なAPI(document.createElementやtextContent)を使って安全にDOMを構築する方式を採用しています。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
@charset "utf-8"; /*********************************************************** File Uploader UI ***********************************************************/ /*----------------------------------------------- Text Link -----------------------------------------------*/ /* Base */ a:link { color: #323232; text-decoration: underline; } a:visited { color: #323232; } a:hover { color: #f01e69; text-decoration: underline; } /*----------------------------------------------- border-boxを全ての要素に適用 ※横幅・高さのサイズ調整 -----------------------------------------------*/ *, *:before, *:after { -webkit-box-sizing: border-box; box-sizing: border-box; } /*----------------------------------------------- Title -----------------------------------------------*/ /* Page Title */ .ttl { font-size: 16px; font-weight: normal; color: #ffffff; background-color: #000000; margin: 0px 0px 35px 0px; padding: 15px 15px 20px 15px; width: 100%; border-radius: 5px; } /*----------------------------------------------- Sub Title -----------------------------------------------*/ .sub_ttl { font-size: 16px; font-weight: normal; margin: 0px 0px 5px 0px; } /*----------------------------------------------- Annotation -----------------------------------------------*/ .annotation { font-size: 13px; font-weight: normal; line-height: 1.0em; margin: 0px 0px 10px 0px; padding: 0px; } .asterisk { color: #ff0000; } /*----------------------------------------------- Table -----------------------------------------------*/ /* table幅の指定:外枠あり・隣接するセルのボーダーを重ねて表示 */ table.base_style { margin: 0px 0px 30px 0px; padding: 0px; width: 100%; border-collapse: collapse; border: none; } /* th base */ table.base_style th { padding: 13px; font-weight: normal; text-align: center; border: 1px solid #c3c3c3; background-color: #eaeaea; } /* tr td base */ table.base_style tr td { padding: 13px; text-align: center; border: 1px solid #c3c3c3; } /* tr td custom:align left */ table tr td.left { text-align: left; } /* th custom:width 70% */ table.base_style th.w70 { width: 70%; } /* th custom:width 15% */ table.base_style th.w15 { width: 15%; } /* message.txtの非表示 */ tr.display_none:nth-child(2) { display: none; } /*----------------------------------------------- Textarea & Drag & Drop Area -----------------------------------------------*/ /* Textarea */ .ta_style { font-family: "NotoSans-R","Hiragino Sans","Meiryo",sans-serif; font-size: 15px; color: #323232; letter-spacing: 0.07em; line-height: 1.6em; margin: 0px 0px 10px 0px; padding: 13px; border: 1px solid #c3c3c3; width: 100%; height: 200px; } /* Drag & Drop Area */ .dd_style { font-family: "NotoSans-R","Hiragino Sans","Meiryo",sans-serif; font-size: 15px; color: #323232; letter-spacing: 0.07em; line-height: 1.6em; margin: 0px; padding: 13px; border: 1px solid #c3c3c3; width: 100%; height: 200px; } /*----------------------------------------------- Button -----------------------------------------------*/ /* Save Button */ input.save_button { font-size: 15px; color: #ffffff; background-color: #000000; margin: 0px 0px 30px 0px; padding: 15px 30px 20px 30px; cursor: pointer; border-radius: 5px; } |
[補足]
上記のようなCSSを適応することで、最後に掲載している『「ファイルアップローダー」の画面イメージ』のようなUIに仕上がります。
※フォントに関しては、好みのWebフォントを使用したり、お好きなfont-familyを適応してください。
27~35行目が「border-box」プロパティに関する記述です。
テキストエリアなどにボーダーを適応すると「テキストエリアのサイズ=横幅(高さ)」ではなくなりますので(※ボーダー幅の分サイズが合わなくなる)、「サイズが合わないことにより横スクロールが発生する」原因となります。そのため「border-boxプロパティを全ての要素に適用し、横幅・高さのサイズ調整」を行います。
83~94行目が「隣接するセルのボーダーを重ねて表示」を指定。
これは、私が以前よりよく利用している手法で、合わせて「th・tr・tdのボーダー・横幅・表示位置などを調整」してやることで、table関連のタグを制御することができます。
127~130行目は「message.txt」の表示に関連してきます。
後程説明する「uploader.php」の記述を見ていただくと分かるかと思いますが、現状だと「メッセージ文の内容を保存するmessage.txtもファイル一覧に表示される」ようになっています。「message.txtファイルをダウンロード・削除できる」といったメリットもありますが、削除すると「メッセージ保存用のテキストファイル」をアップロードしてくださいとエラーが出力されるようにしていますので、CSSの類似クラスで非表示にしています。この場合「trに適応したクラスdisplay_noneに対して、nth-child(2)という類似クラスを使用」しています。これは「trの2つ目=テーブルの2行目」を意味しますので、「1つ目に表示されるファイル名(message.txt)を非表示にする」ということになります(※こちらに関しては、PHP側をカスタマイズしたり、CSS側をカスタマイズして削除ボタンだけ非表示にするなど、お好きに編集してみてください)。
上記のようなCSSを適応することで、最後に掲載している『「ファイルアップローダー」の画面イメージ』のようなUIに仕上がります。
※フォントに関しては、好みのWebフォントを使用したり、お好きなfont-familyを適応してください。
27~35行目が「border-box」プロパティに関する記述です。
テキストエリアなどにボーダーを適応すると「テキストエリアのサイズ=横幅(高さ)」ではなくなりますので(※ボーダー幅の分サイズが合わなくなる)、「サイズが合わないことにより横スクロールが発生する」原因となります。そのため「border-boxプロパティを全ての要素に適用し、横幅・高さのサイズ調整」を行います。
83~94行目が「隣接するセルのボーダーを重ねて表示」を指定。
これは、私が以前よりよく利用している手法で、合わせて「th・tr・tdのボーダー・横幅・表示位置などを調整」してやることで、table関連のタグを制御することができます。
127~130行目は「message.txt」の表示に関連してきます。
後程説明する「uploader.php」の記述を見ていただくと分かるかと思いますが、現状だと「メッセージ文の内容を保存するmessage.txtもファイル一覧に表示される」ようになっています。「message.txtファイルをダウンロード・削除できる」といったメリットもありますが、削除すると「メッセージ保存用のテキストファイル」をアップロードしてくださいとエラーが出力されるようにしていますので、CSSの類似クラスで非表示にしています。この場合「trに適応したクラスdisplay_noneに対して、nth-child(2)という類似クラスを使用」しています。これは「trの2つ目=テーブルの2行目」を意味しますので、「1つ目に表示されるファイル名(message.txt)を非表示にする」ということになります(※こちらに関しては、PHP側をカスタマイズしたり、CSS側をカスタマイズして削除ボタンだけ非表示にするなど、お好きに編集してみてください)。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
//======================================================================================= // AJAX //======================================================================================= var xmlhttp; // XMLHttpオブジェクト var ajaxRecvData; // 受信データ var HTTP_GET = "GET"; var HTTP_POST = "POST"; var HTTP_RECV = 1; //----------------------------------------------------------- // getXMLHttp : 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 : クエリパラメータ作成 //----------------------------------------------------------- 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 : データ送受信 //----------------------------------------------------------- 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通信 //----------------------------------------------------------- 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通信を行うためのスクリプトです
30~55行目がクエリパラメータの文字列を作成するメソッド。
※パラメータ数→通信種別により変化→引数が可変長引数(arguments変数)になる
57~107行目がAjax通信を実行するメソッド(※jQueryではなく、素のJavaScriptでAjax通信を処理)。
※第5引数funcNameでメソッド名を渡す→サーバーからのデータ受信後evalメソッドを実行→指定した引数(文字列)のメソッドを実行できる
Ajax通信を行うためのスクリプトです
30~55行目がクエリパラメータの文字列を作成するメソッド。
※パラメータ数→通信種別により変化→引数が可変長引数(arguments変数)になる
57~107行目がAjax通信を実行するメソッド(※jQueryではなく、素のJavaScriptでAjax通信を処理)。
※第5引数funcNameでメソッド名を渡す→サーバーからのデータ受信後evalメソッドを実行→指定した引数(文字列)のメソッドを実行できる
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
<?php $response = "responce_no_data"; // メッセージの保存先とファイル名 $textFileName = "./upload/message.txt"; // ファイルのアップロード先 $folderName = "./upload"; $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: // メッセージ用テキストの読み込み if (file_exists($textFileName)) { $response = file_get_contents($textFileName); $response = mb_convert_encoding($response, "UTF-8", "Shift-JIS"); } else { $response = ""; } break; case 2: // アップロードファイルの保存:複数ファイル一括アップロードに対応 if( isset($_FILES['file']) ){ $files = $_FILES['file']; // 送信されたファイルの数を取得:単一ファイルの場合でも配列として処理 $fileCount = is_array($files['name']) ? count($files['name']) : 1; for ($i = 0; $i < $fileCount; $i++) { // 配列構造(file['name'][$i])or 単一構造かにより取得方法を分岐:互換性維持 $fileName = is_array($files['name']) ? $files['name'][$i] : $files['name']; $tmpName = is_array($files['tmp_name']) ? $files['tmp_name'][$i] : $files['tmp_name']; $error = is_array($files['error']) ? $files['error'][$i] : $files['error']; // アップロードエラーがない場合のみ処理を実行 if ($error === UPLOAD_ERR_OK && !empty($fileName)) { // 「".date("Y.m.d-H:i:s")."_」でファイル名の先頭にアップロード日時を追加 $copyFolder = "".$folderName."/".date("Y.m.d-H:i:s")."_".$fileName.""; move_uploaded_file($tmpName, $copyFolder); } } } break; case 3: // アップロードファイル一覧の取得 $result = glob( "".$folderName."/*" ); rsort($result); // ファイルを降順(アップロード日時が新しい順)で表示 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; |
[補足]
3~4行目が「メッセージの保存先とファイル名」を指定。
5~6行目が「ファイルのアップロード先」を指定。
30~48行目が「アップロードファイル」の保存に関する処理(※複数ファイル一括アップロードに対応)。
42~43行目が「ファイル名の先頭にアップロード日時を追加」(※「年.月.日-時間:分:秒_ファイル名.拡張子」となります)。
※ファイル名の先頭にアップロード日時のタイムスタンプを自動的に付与することで「アップロード日時をファイルから知ることがでる(わざわざ管理画面を見なくても済みます)」「同名ファイルアップロード時の上書きを防ぐことができる」などのメリットがあります。
52行目が「ファイル一覧を降順(アップロード日時が新しい順)で表示」。
※ファイル名の先頭にアップロード日時のタイムスタンプを自動的に付与し、rsort()関数でファイル一覧を降順で表示することで、「アップロード日時が新しい順」に並びます。
※タイムスタンプがズレる場合、PHPのデフォルトタイムゾーンをご確認ください
→ php.iniを編集
3~4行目が「メッセージの保存先とファイル名」を指定。
5~6行目が「ファイルのアップロード先」を指定。
30~48行目が「アップロードファイル」の保存に関する処理(※複数ファイル一括アップロードに対応)。
42~43行目が「ファイル名の先頭にアップロード日時を追加」(※「年.月.日-時間:分:秒_ファイル名.拡張子」となります)。
※ファイル名の先頭にアップロード日時のタイムスタンプを自動的に付与することで「アップロード日時をファイルから知ることがでる(わざわざ管理画面を見なくても済みます)」「同名ファイルアップロード時の上書きを防ぐことができる」などのメリットがあります。
52行目が「ファイル一覧を降順(アップロード日時が新しい順)で表示」。
※ファイル名の先頭にアップロード日時のタイムスタンプを自動的に付与し、rsort()関数でファイル一覧を降順で表示することで、「アップロード日時が新しい順」に並びます。
※タイムスタンプがズレる場合、PHPのデフォルトタイムゾーンをご確認ください
→ php.iniを編集
|
1 2 |
; タイムゾーンの設定 date.timezone = Asia/Tokyo |
「ファイルアップローダー」の画面イメージ
「ファイルアップローダー」の完成イメージは、以下のような感じです。
▼ファイルアップローダーの画面イメージ
▼スマートフォンでのファイルアップロード箇所の画面イメージ
[仕様についての補足]
●アップロードできるファイルの種類について
現状、アップロードできるファイルの種類は指定していません。
そのため、セキュリティ対策として「upload」ディレクトリ内には、以下を記述した.htaccessをアップロードしておきましょう。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
# ---------------------------------------------------------------------- # 1.全てのハンドラを無効化:将来のPHPバージョン対策 # PHPとして実行される可能性のあるハンドラを完全に削除 # ---------------------------------------------------------------------- <FilesMatch "\.p?html?(\d+)?$"> RemoveHandler .php .phtml RemoveType .php .phtml </FilesMatch> # ---------------------------------------------------------------------- # 2.拡張子が.phpなどで終わるファイルへのアクセスを強制遮断 # サーバー側の設定で新しいPHP動作手法(FPMやFastCGI)が追加された場合、 # ブラウザから直接アクセスさせないことで実行を阻止 # ---------------------------------------------------------------------- <FilesMatch "\.(php|phtml|php\d+|cgi|pl|py|sh)$"> <IfModule mod_authz_core.c> Require all denied </IfModule> <IfModule !mod_authz_core.c> Order allow,deny Deny from all </IfModule> </FilesMatch> # ---------------------------------------------------------------------- # 3.CGIスクリプトの実行を禁止 # ---------------------------------------------------------------------- Options -ExecCGI |
[補足]
こちらは、PHPやCGIなどのファイルがアップロードされている場合、ブラウザ経由でスクリプトが実行されるのを禁止するためのものです。
1~9行目が「サーバー全体の設定やバグで『アクセス拒否』をすり抜けてファイルが読み込まれてしまった場合の対策」です。
RemoveHandlerとRemoveTypeで「サーバーに対して、このディレクトリ内では.phpという拡張子をプログラム(PHP)として扱わず、単なるテキストファイルとして扱う」ように命令しています。
この命令によって「ファイルが開かれてしまった場合にも、PHPのコードは実行されず、単なる文字列として表示されるだけ(無害化)」になります。
11~25行目が「ブラウザから指定した拡張子のファイルへ直接アクセスされた時点で、サーバーが『閲覧禁止(403 Forbidden)』」を返します。
また「将来どんな数字のPHPバージョンが登場したとしても自動的にマッチする仕組み」を有しています。
[参考]
・php:通常の.phpにマッチ
・php\d+:「\d+」は、1文字以上の数字=php9、php10、php100などにマッチ
その他「Require all denied」・・・Apache 2.4以降、「Deny from all」・・・Apache 2.2系(古いサーバー)に対して条件分岐を行っているため、サーバーが将来アップデートされた場合でもエラーを起こさず動き続けます。
27~31行目が「PHP以外のプログラム言語(Perl・Python・シェルスクリプトなど)がアップロードされ、CGI(サーバー上で動くプログラム)として実行されてしまうのを禁止する命令」です。
頭に「-(マイナス)」を付けることで、CGIの実行権限を完全にオフにします。
※上記に該当するのファイルは、ダウンロードもできなくなります
※zip圧縮したフォルダ内に上記に該当するファイルがある場合、ダウンロードは可能です
●複数ファイル一括アップロードについてこちらは、PHPやCGIなどのファイルがアップロードされている場合、ブラウザ経由でスクリプトが実行されるのを禁止するためのものです。
1~9行目が「サーバー全体の設定やバグで『アクセス拒否』をすり抜けてファイルが読み込まれてしまった場合の対策」です。
RemoveHandlerとRemoveTypeで「サーバーに対して、このディレクトリ内では.phpという拡張子をプログラム(PHP)として扱わず、単なるテキストファイルとして扱う」ように命令しています。
この命令によって「ファイルが開かれてしまった場合にも、PHPのコードは実行されず、単なる文字列として表示されるだけ(無害化)」になります。
11~25行目が「ブラウザから指定した拡張子のファイルへ直接アクセスされた時点で、サーバーが『閲覧禁止(403 Forbidden)』」を返します。
また「将来どんな数字のPHPバージョンが登場したとしても自動的にマッチする仕組み」を有しています。
[参考]
・php:通常の.phpにマッチ
・php\d+:「\d+」は、1文字以上の数字=php9、php10、php100などにマッチ
その他「Require all denied」・・・Apache 2.4以降、「Deny from all」・・・Apache 2.2系(古いサーバー)に対して条件分岐を行っているため、サーバーが将来アップデートされた場合でもエラーを起こさず動き続けます。
27~31行目が「PHP以外のプログラム言語(Perl・Python・シェルスクリプトなど)がアップロードされ、CGI(サーバー上で動くプログラム)として実行されてしまうのを禁止する命令」です。
頭に「-(マイナス)」を付けることで、CGIの実行権限を完全にオフにします。
※上記に該当するのファイルは、ダウンロードもできなくなります
※zip圧縮したフォルダ内に上記に該当するファイルがある場合、ダウンロードは可能です
画像などを大量に一括アップロードされるとファイル一覧が見ずらくなり管理も大変になります。
ファイル点数が多い場合は、全ファイルをフォルダ内に格納し「zip圧縮したファイル」をアップロードすることを推奨しています。
今回は「ファイルアップローダー Ver.2.0」についてご紹介しました。
制作時の参考になりましたら幸いです。
関連キーワード
