Fix a file upload (Drop Zone) issue
New here? Learn about Bountify and follow @bountify to get notified of new bounties! x

Fix a file upload (Drop Zone) issue

I'm using a combination of Dropzone.js and PHP to drag folders into the browser window and upload that folder and maintain the subfile/subfolder structure. However, the script fails by distributing the contents of the folders into two separate folders on the server if there are too many files. I need someone to fix the code so it allows everything to upload into a single folder. Below is my code.

index.html



    <form action="upload.php" class="dropzone" id="uploadFile" name="uploadFile" method="POST">
        <span id="tmp-path"></span>
    </form>

    <script>
        $(document).ready(function () {
            Dropzone.autoDiscover = false;
            $("#uploadFile").attr("class","dropzone");
            Dropzone.options.uploadFile = {
                init: function() {
                    this.on("success", function(file, responseText) {
                        file.previewTemplate.appendChild(document.createTextNode(responseText));
                    });

                    this.on("sending", function(file) {
                        if(typeof file.fullPath != "undefined"){                                
                            var changePath = file.fullPath.split("/");
                            changePath[0] = md5(new Date());
                            changePath = changePath.join("/");
                            $("form").append('<input type="hidden" name="path" value="'+changePath+'" />')
                            var el1 = document.getElementById('insertHere1')
                            el1.innerHTML = changePath
                            var el2 = document.getElementById('insertHere2')
                            el2.innerHTML = '<a href="http://example1.com/'+md5(new Date())+'" target="_blank">Open HTTP URL in a new window</a>*'
                            var el3 = document.getElementById('insertHere3')
                            el3.innerHTML = '<a href="http://example2.com/'+md5(new Date())+'" target="_blank">Open HTTP/2 URL in a new window</a>*'
                            var el4 = document.getElementById('insertHere4')
                            el4.innerHTML = '<b>*</b>Temporary URLs will no longer be availabe to copy once you leave this page. URLs and files will be delete from the server in 30 minutes.'

                        }
                    });            
                }
            }; 

            var myDropzone = new Dropzone("#uploadFile", { 
                url: "upload.php"
            });

        });
    </script>

    <div class="test-urls">
        <div id="insertHere1"></div>
        <div id="insertHere2"></div>
        <div id="insertHere3"></div>
        <div id="insertHere4"></div>
    </div>

</body>

upload.php
<?php

$ds = DIRECTORY_SEPARATOR;
//$storeFolder = '../uploads/';
$storeFolder1 = '../../example1.com/';
$storeFolder2 = '../../example2.com/';
$rand = substr(md5(time()),0,8);

if (!empty($_FILES)) {

    $tempFile = $_FILES['file']['tmp_name'];

    $targetPath1 = dirname( __FILE__ ) . $ds . $storeFolder1 . $ds;
    $targetPath2 = dirname( __FILE__ ) . $ds . $storeFolder2 . $ds;

    $fullPath1 = $storeFolder1.rtrim($_POST['path'], "/.");
    $fullPath2 = $storeFolder2.rtrim($_POST['path'], "/.");

    $folder1 = substr($fullPath1, 0, strrpos($fullPath1, "/"));
    $folder2 = substr($fullPath2, 0, strrpos($fullPath2, "/"));

    if (!is_dir($folder1)) {
        $old1 = umask(0);
        $old2 = umask(0);
        mkdir($folder1, 0777, true);
        mkdir($folder2, 0777, true);
        umask($old1);
        umask($old2);
    }    

    if (move_uploaded_file($tempFile, $fullPath1)) {
        copy($fullPath1, $fullPath2);
        die('Uploaded');
    } else {
        die('Error');
    }    
}

?>

Here is a link to the rest of the files on Github.

dropzone.js
https://gist.github.com/garyrozanc/7623b834ecbc9106f9300146017b3a86

md5.js
https://gist.github.com/garyrozanc/69d94380c029d7fd6802d37d4ddc8505

dropzone.css
https://gist.github.com/garyrozanc/87b9d0b8adf183ad03e2fdcdb7d9e85f

Link to video of issue
https://www.dropbox.com/s/snlnvjgzvvl6pup/error.mp4?dl=0

If you need more information to troubleshoot this, please let me know.

awarded to Chlegou

Crowdsource coding tasks.

2 Solutions

Winning solution

The bug was looking obvious from my first time reading the bounty. actually you mentioned it in the bounty without noticing.

From your code, i understood that you are calculating the container folder in the server, which might be a solution for keeping same folder structure in server also. (what you are looking for actually)

You said in bounty:

However, the script fails by distributing the contents of the folders
into two separate folders on the server if there are too many files.

This means, the problem occurs only when there are many files only. We know, that dropzonejs, start uploading one by one, this means he will take files one by one, and for each one (file) he will execute the onSending event listener.

The Bug

In your code, you are changing the path with an md5 calculation for the date 3 times in the onSending listener.

its giving the same answer for the same file, since only few instruction, but even that is wrong.

Since there are many dropped files to upload, calculating the new Date() every time is wrong, and giving different hashes for files.

Solution

since we like al the same files together, for single drop (upload), we need to give the dropzone a singleton upload location (folder to upload in it).

we could achieve that by fixing the hash from the beginning.

here is my code:

$(document).ready(function () {
    Dropzone.autoDiscover = false;
    $("#uploadFile").attr("class","dropzone");
    // calculating the dropzone singleton
    var dropzoneSingelton = md5(new Date());
    Dropzone.options.uploadFile = {
        init: function() {
            this.on("success", function(file, responseText) {
                file.previewTemplate.appendChild(document.createTextNode(responseText));
            });

            this.on("sending", function(file) {
                if(typeof file.fullPath != "undefined"){
                    var changePath = file.fullPath.split("/");
                    changePath[0] = dropzoneSingelton;
                    changePath = changePath.join("/");

                    $("form").append('<input type="hidden" name="path" value="'+changePath+'" />')
                    var el1 = document.getElementById('insertHere1')
                    el1.innerHTML = changePath
                    var el2 = document.getElementById('insertHere2')
                    el2.innerHTML = '<a href="http://example1.com/'+ dropzoneSingelton +'" target="_blank">Open HTTP URL in a new window</a>*'
                    var el3 = document.getElementById('insertHere3')
                    el3.innerHTML = '<a href="http://example2.com/'+ dropzoneSingelton +'" target="_blank">Open HTTP/2 URL in a new window</a>*'
                    var el4 = document.getElementById('insertHere4')
                    el4.innerHTML = '<b>*</b>Temporary URLs will no longer be availabe to copy once you leave this page. URLs and files will be delete from the server in 30 minutes.'

                }
            });
        }
    };

    var myDropzone = new Dropzone("#uploadFile", { 
        url: "upload.php"
    });               

});

Weakness

Since the last solution will upload all the dropped files in the same location (in the server) (it's obviously since all the files will have the same folder hash), this means even files from different folders drop will be gathered together in the server

As a solution, i suggest we keep the local parent folder name in the path. so we keep the same structure and we differentiate the server files also, so we know the original structure also.

Changing the file path by this way, will help us achieving it:

var changePath = dropzoneSingelton + "/" + file.fullPath;

Credit

i noticed that you used nuhil code: https://github.com/nuhil/structured-folder-uploader-dropzonejs

i have worked in it directly also.

since upload.php is ok, didn't play with it, i added changes only on index file.

Here is the complete code containing both solutions for tests (no need to create a github repository, just upload nuhil code, and update index with my pastebin code)

link: https://pastebin.com/pNnBRpuH

Set the hash once at the initialization and reuse that every time? That's even worse than how it originally was!
CyteBode 11 days ago
both solutions looks the same, yours is calculating the seed for every drop, and mine is calculating the seed for the dropzone instance. i already spoke about my solution weakness in the bounty. no need to point out if you look wisely, by logic, i'm creating a root for every dropzone instance. so all the files uploaded by every dropzone, will be kept in the same server root folder. this would give wise sense, if we track user uploads. think about it more ;) as @imokyourok said in the bounty, "I need someone to fix the code so it allows everything to upload into a single folder", he want everything to be uploaded in a single folder, not only folders as they are! as you are thinking maybe and as your code tells. i was following the bounty, and provided what he is saying he is looking for.
Chlegou 11 days ago
doing that, you need to move the code in the onDrop event listener, to the init function.
Chlegou 11 days ago
They want all the files of a single batch to be in the same folder, instead of being split if the time was long enough. Your solution puts all the files of every batch uploaded during the same page load in the same folder. Not the same.
CyteBode 11 days ago
otherwise, if he didn't drops from different locations kept in single folder, yours could be the solution. as i said firstly, it's a matter of logic, but both codes are the same.
Chlegou 11 days ago
yes, this is what i meant by "i'm creating a root for every dropzone instance". even if there is a big payload, it still the same dropzone instance.
Chlegou 11 days ago
I'll look into your solution later today. Don't have time to test right now.
imokyourok 11 days ago
@Chlegou I gave some further thoughts to our little debate. If the intent is to have a single upload per page load, then our two solutions indeed accomplish the same thing. However, if subsequent uploads are to be expected on the same page load, then your solution puts everything in the same folder, possibly overwriting files, and creating a union of originally disjoint folders, whereas mine creates a new folder for every new batch of files. It might not have been written in black and white in the bounty, but I derived that intent from the usage of md5(new Date()) which correctly created a new hash per batch, as long as the batch was small enough that the time didn't change, but incorrectly created different hash if it was too big.
CyteBode 11 days ago
since we are negotiating a debate, we could talk now. it could be nice, if you looked on it from the beginning instead of saying "worst solution". what you are saying, is totally legit, now you are getting it right. for batch issue, since the main goal is uploading files (for now at least, since the code given is too basic and not doing anything else), i couldn't see any difference (as i said, both solutions works), for duplicated drops (files and folders) you spoke about it, overwriting files is a server issue, we could keep them if we want, it's not a problem as you know (no need to point, since our debate is the seed) , even original code isn't looking on it. either way, it's a test before creating new files (still server side debate as i mentioned) .
Chlegou 11 days ago
If we are looking for a clever solution, it must be handled by server side. As developers, we mustn't trust user inputs (first rule), all the seed thing will be deprecated, and seeds should be calculated by server side as i said.
Chlegou 11 days ago
Both of the solutions work as I need them too. I read through the discussion and I don't understand the benefit of one solution over the other since they both slove my need. Since your solution was posted first, I'm going to award you the bounty.
imokyourok 11 days ago
@imokyourok, thanks for rewarding the bounty for me, as i mentioned in the weakness area in my solution, all files dropped in the drop zone, will be accumulated in the same folder till the webpage is refreshed, using var changePath = dropzoneSingelton + "/" + file.fullPath; will keep the local file name in it, so you could differentiate between files in the server (good explanation in the bounty). in other words, until you refresh the webpage, uploads will be kept in the same folder. but @cydebode code will generate an md5 hash for every drop (new server folder).
Chlegou 11 days ago

The problem is that md5(new Date()) is called once for every single file. If the number of files uploaded is small enough, the date and the hash stay the same, but once it gets big enough, the date changes and so does the hash. How quickly this happens will depend on timer granularity.

The solution is to set a single hash initially and reuse it for every file that's part of the same batch. This can be done with the drop event, which is triggered once a file or a folder has been dropped.

<body>
    <form action="upload.php" class="dropzone" id="uploadFile" name="uploadFile" method="POST">
        <span id="tmp-path"></span>
        <input type="hidden" name="seed" value="" />
    </form>

    <script>
        Dropzone.autoDiscover = false;

        $(document).ready(function () {
            Dropzone.options.uploadFile = {
                init: function() {
                    this.on("success", function(file, responseText) {
                        file.previewTemplate.appendChild(document.createTextNode(responseText));
                    });

                    this.on("drop", function() {
                        $('input[name="seed"]').val(md5(new Date()));
                    });

                    this.on("sending", function(file, xhr, formData) {
                        if (typeof file.fullPath != "undefined") {
                            var changePath = file.fullPath.split("/");
                            changePath[0] = $('input[name="seed"]').val();
                            changePath = changePath.join("/");
                            formData.set("path", changePath);
                            formData.delete("seed");

                            var el1 = document.getElementById('insertHere1')
                            el1.innerHTML = changePath
                            var el2 = document.getElementById('insertHere2')
                            el2.innerHTML = '<a href="http://example1.com/'+md5(new Date())+'" target="_blank">Open HTTP URL in a new window</a>*'
                            var el3 = document.getElementById('insertHere3')
                            el3.innerHTML = '<a href="http://example2.com/'+md5(new Date())+'" target="_blank">Open HTTP/2 URL in a new window</a>*'
                            var el4 = document.getElementById('insertHere4')
                            el4.innerHTML = '<b>*</b>Temporary URLs will no longer be availabe to copy once you leave this page. URLs and files will be delete from the server in 30 minutes.'
                        }
                    });
                }
            }; 

            var myDropzone = new Dropzone("#uploadFile", { 
                url: "upload.php",
            });
        });
    </script>

    <div class="test-urls">
        <div id="insertHere1"></div>
        <div id="insertHere2"></div>
        <div id="insertHere3"></div>
        <div id="insertHere4"></div>
    </div>
</body>

Note: Generating the hash client-side is a bad idea and so is not sanitizing the input.

Edit: Typos.

I'll look into your solution later today. Don't have time to test right now.
imokyourok 11 days ago
Both of the solutions work as I need them too. I read through the discussion and I don't understand the benefit of one solution over the other since they both slove my need. Since your solution was posted second, I'm going to tip you because I value your efforts and I'm probably going to need some work on this in the near future.
imokyourok 11 days ago
Thanks for the tip. I was actually the first to post a solution (you can see it in the timeline). The solution only appears as second because of the edit i did to fix the typos.
CyteBode 11 days ago
View Timeline