2

I'm writing an image upload program and have it working perfectly with single files, but when I try and process multiple files it seems like nothing is getting passed through. I have a single HTML element to accept multiple files:

<input type="file" id="entryPhotos" multiple>

and then build my FormData:

var formData = new FormData();
var entryArray = new Array("entryName","entryFoodType","entryROI","entryOpeningHour","entryOpeningMinute","entryOpeningAMPM","entryClosingHour","entryClosingMinute","entryClosingAMPM","entryPhone","entryEmail","entryPhotos","entryDesc","entryLocLat","entryLocLong");
for(var temp = 0; temp < entryArray.length; temp++){
    //--I suspect my problem is here
    if($('#' + entryArray[temp])[0].files){
        formData.append(entryArray[temp], $('#' + entryArray[temp])[0].files);
    }
    else{
        formData.append(entryArray[temp], $('#' + entryArray[temp]).val());
    }
}

After that use jQuery.ajax to send it over:

$.ajax({
    url: '/scripts/spotEntry.php',
    data: formData,
    type: 'POST',
    contentType: false,
    processData: false,
    success: function(returnCode){
        entryReturnProc(returnCode);
    }
});

But when it gets to the server nothing is there:

echo count($_FILES["entryPhotos"]["name"]);   //returns 0

If I'm just doing a single image I can do $('#' + entryArray[temp])[0].files[0] and it will work fine, but shouldn't I then be able to remove the [0] and have it send the array, which from my understanding, FormData jQuery.ajax and PHP all support?

Marcin Orlowski
  • 72,056
  • 11
  • 123
  • 141
Xandor
  • 440
  • 1
  • 9
  • 22

2 Answers2

1

For anyone else who might have this issue. After some more searching, I found the answer here: uploading-multiple-files-using-formdata

I had to modify my formData.append command a little bit and iterate through the array instead of just passing the whole thing:

var entryArray = new Array("entryName","entryFoodType","entryROI","entryOpeningHour","entryOpeningMinute","entryOpeningAMPM","entryClosingHour","entryClosingMinute","entryClosingAMPM","entryPhone","entryEmail","entryPhotos","entryDesc","entryLocLat","entryLocLong");
for(var temp = 0; temp < entryArray.length; temp++){
    if($('#' + entryArray[temp])[0].files){
        for (var i = 0; i < $('#' + entryArray[temp])[0].files.length; i++) {
            formData.append(entryArray[temp] + "[]", $('#' + entryArray[temp])[0].files[i]);
        }
    }
    else{
        formData.append(entryArray[temp], $('#' + entryArray[temp]).val());
    }
}

And for completion's sake if it may help someone in the future, to process on the server in PHP:

for($i = 0; $i < count($_FILES["entryPhotos"]["name"]); $i++){
       $tmp_name = $_FILES["entryPhotos"]["tmp_name"][$i];
       if (!$tmp_name) continue;

       $name = basename($_FILES["entryPhotos"]["name"][$i]);

    if ($_FILES["entryPhotos"]["error"][$i] == UPLOAD_ERR_OK)
    {
        if ( move_uploaded_file($tmp_name, $uploaddir.$name) ){
            echo 'us';
        }
        else{
            echo 'uf';
        }
    }
    else{
        echo 'uf';
    }
}

EDIT/NOTE: This upload script is rather crude and has certain security risks. Always keep in mind when uploading files: Never trust what a user is uploading is safe. Always sanitize your uploads best you can. One thing that isn't shown in this snippet is that the files are kept in a folder behind the webroot. This helps prevent access from a potential hacker from a script they might upload.

It is also recommended to change the name of the file all together to something random and unique and at the same time, check the file extension(s). Some servers are set to allow multiple extensions and this can give an opening. There are a few ways I found online to do these things and after mixing them together this is what I came up with:

In order to get a random and unique name I used $prefix = str_replace(array('.', ' '), '' , microtime()); which pulls the current Unix time in microseconds, thus giving a number a potential hacker wouldn't be able to predict as easily, then to help ensure uniqueness since I am looping through multiple files I added the index of the loop to the end of the prefix.

Then I pulled the extension(s) of the basename. Instead of using the typical pathinfo() to pull the extension, I used strstr(basename($name), '.'). The advantage I find with this is it will pull all file extensions, not just the last. Then I check that against a list of accepted extensions. If it passes then it may be uploaded to a directory prior to the webroot. An extra step this inherently takes is if the file has multiple extensions it is automatically skipped, avoiding the possible security risk there.

One last note is that there are a couple of "sanitation" techniques that I did not use such as checking for an image size and checking the MIME type to verify the files are images. Actual images (thus returning a file size) can contain malicious coding and the MIME type is sent by the client and thus can be faked. These to me seemed to give no actual benefit so I did not use them but some may argue that there is no such thing as being too secure, and there are even further measures that could be taken. When dealing with user input, in any way, you should always do as much research as possible and take appropriate precautions to make sure you, your data and your users are protected.

Xandor
  • 440
  • 1
  • 9
  • 22
0

I believe you will need the $_FILES variable for PHP.

<form method="post" action="upload-page.php" enctype="multipart/form-data">
  <input name="filesToUpload[]" id="filesToUpload" type="file" multiple="" />
</form>

if(count($_FILES['uploads']['filesToUpload'])) {
    foreach ($_FILES['uploads']['filesToUpload'] as $file) {

        //do your upload stuff here
        echo $file;

    }
}

To do so with JavaScript would look like this:

//get the input and UL list
var input = document.getElementById('filesToUpload');
var list = document.getElementById('fileList');

//empty list for now...
while (list.hasChildNodes()) {
    list.removeChild(ul.firstChild);
}

//for every file...
for (var x = 0; x < input.files.length; x++) {
    //add to list
    var li = document.createElement('li');
    li.innerHTML = 'File ' + (x + 1) + ':  ' + input.files[x].name;
    list.append(li);
}
Mirza Sisic
  • 2,401
  • 4
  • 24
  • 38
  • I already am using $_FILES. It works perfectly for me as long as it's a single file. As far as your list object and element, why is this necessary? Does the `FormData` object not accept an array of the files? – Xandor Aug 13 '17 at 20:39