2

I'm trying to build-in an Image Uplaoder to my CMS for the gallery. I've done some research and found what I need to build it. The uploader uses three files. The first one is where to select the images for upload and showing some progress. Connected to this is a js file for resizing the selected images first and upload them afterwards. And last but not least a file to process the images on server via php and for writing data into sql-database.

The good point is: Everything works as it should. BUT I have a problem with sorting the images. Because they are getting a md5 generated filename, and the uploader handles multiple images at a time, some images that I took for example at the end of a day are showed first and the first pictures of the day are for example anywhere between them.

So here comes my question: Is there a way to keep the orignal filename and name the uploaded image for example anything like "1234md5randomdigits_ORIGINALFILENAME.jpg"?

I've tried a lot of $_FILES and other php parameters, but they were empty...

Here is my upload file for selecting images:

<!DOCTYPE html>
    <html>
        <head>
            <title>multiple.php</title>
            <link rel="stylesheet" href="./style.css" />
        <head>
        <body>

            <h1>Upload Images...</h1>

            <form>
                <input type="file" multiple />
                <div class="photos">

                </div>
            </form>

            <script src="./upload.js"></script>
        </body>
    </html>

Here comes the upload.js file

// Once files have been selected
document.querySelector('form input[type=file]').addEventListener('change', function(event){

    // Read files
    var files = event.target.files;

    // Iterate through files
    for (var i = 0; i < files.length; i++) {

        // Ensure it's an image
        if (files[i].type.match(/image.*/)) {

            // Load image
            var reader = new FileReader();
            reader.onload = function (readerEvent) {
                var image = new Image();
                image.onload = function (imageEvent) {

                    // Add elemnt to page
                    var imageElement = document.createElement('div');
                    imageElement.classList.add('uploading');
                    imageElement.innerHTML = '<span class="progress"><span></span></span>';
                    var progressElement = imageElement.querySelector('span.progress span');
                    progressElement.style.width = 0;
                    document.querySelector('form div.photos').appendChild(imageElement);

                    // Resize image
                    var canvas = document.createElement('canvas'),
                        max_size = 1200,
                        width = image.width,
                        height = image.height;
                    if (width > height) {
                        if (width > max_size) {
                            height *= max_size / width;
                            width = max_size;
                        }
                    } else {
                        if (height > max_size) {
                            width *= max_size / height;
                            height = max_size;
                        }
                    }
                    canvas.width = width;
                    canvas.height = height;
                    canvas.getContext('2d').drawImage(image, 0, 0, width, height);

                    // Upload image
                    var xhr = new XMLHttpRequest();
                    if (xhr.upload) {

                        // Update progress
                        xhr.upload.addEventListener('progress', function(event) {
                            var percent = parseInt(event.loaded / event.total * 100);
                            progressElement.style.width = percent+'%';
                        }, false);

                        // File uploaded / failed
                        xhr.onreadystatechange = function(event) {
                            if (xhr.readyState == 4) {
                                if (xhr.status == 200) {

                                    imageElement.classList.remove('uploading');
                                    imageElement.classList.add('uploaded');
                                    imageElement.style.backgroundImage = 'url('+xhr.responseText+')';

                                    console.log('Image uploaded: '+xhr.responseText);

                                } else {
                                    imageElement.parentNode.removeChild(imageElement);
                                }
                            }
                        }

                        // Start upload
                        xhr.open('post', 'process.php', true);
                        xhr.send(canvas.toDataURL('image/jpeg'));

                    }

                }

                image.src = readerEvent.target.result;

            }
            reader.readAsDataURL(files[i]);
        }

    }

    // Clear files
    event.target.value = '';

});

And this my "process.php" to process the uploaded data:

<?php
$save_path="/images";
// Generate filename
$filename = md5(mt_rand()).".jpg";

// Read RAW data
$data = file_get_contents("php://input");

// Read string as an image file
$image = file_get_contents("data://".substr($data, 5));

// Save to disk
if ( ! file_put_contents($save_path.$filename, $image)) {
    exit();
}

// Clean up memory
unset($data);
unset($image);

//Includes and SQL go after that

// Return file URL
echo $save_path.$filename;
?>

I'd be very happy about some help! :)

Professor Abronsius
  • 33,063
  • 5
  • 32
  • 46

2 Answers2

0

This is what I use to process potentially any number of upload files if you leave the file name as it is here it will leave it the same as the users file name. Make your form element name an array and it will loop through it uploading all the files. You'll also have to set your form type to multipart. This should be a lot easier to manage than what it looks like you're trying to do though.

    $target_dir = "images/";
    extract($_POST);
    $error=array();
    $extension=array("jpg", "gif");
    $i=0;
    foreach($_FILES["filetoupload"]["tmp_name"] as $key=>$tmp_name) {
        $file_name = $_FILES["filetoupload"]["name"][$key];
        $ext = strtolower(pathinfo($file_name,PATHINFO_EXTENSION));
        $file_tmp=$_FILES["filetoupload"]["tmp_name"][$key];
        if(in_array($ext,$extension))   {
            if ($_FILES["filetoupload"]["size"][$key] < 5000000) {
                if ($_FILES["filetoupload"]["size"][$key] != 0) {
                    if(!file_exists( $target_dir . $file_name ))  {
                        move_uploaded_file($file_tmp , $target_dir . $file_name );
                    }
                }
            }
        } else {
            array_push($error,"$file_name, ");
        }
    }

in this example the name attribute of all of the file input fields is name="filetoupload[]"

Mason Stedman
  • 613
  • 5
  • 12
  • Thanks for your suggestion! Appreciate it as well! But I wanted to process and resize the images via javascript first, so the upload won't take too long and to spare some work to the server. – tim_baland212 Mar 16 '18 at 16:24
  • It would be the same solution, you're just sending post data whether it's via form or ajax. – Mason Stedman Mar 16 '18 at 18:20
0

In support of my comment above if you send a custom header in the ajax function you can process that server side. I think I got the syntax right for accessing the filename from the files collection

/* ajax: add custom header */
xhr.open('post', 'process.php', true);
xhr.setRequestHeader( 'filename', files[i].name );
xhr.send(canvas.toDataURL('image/jpeg'));


/* php: resort to original md5 name if header failed  */
$filename=!empty( $_SERVER['HTTP_FILENAME'] ) ? $_SERVER['HTTP_FILENAME'] : md5(mt_rand()).".jpg";

As I originally forgot to add HTTP_ to the beginning of the custom header ( php ) it initially would not have worked - a simple oversight on my behalf. To correct that I put together a quick demo of using the custom headers idea from end to end, though the code that follows does not entirely emulate your original code with the image processing, canvas, FileReader etc it does show the important aspect of assigning the custom request header and how to process that server-side in php so I hope it will give you an idea how you can implement the original filename feature.

<?php
    /* 
        emulate server side processing of uploaded file - 
        here it simply sends back the custom headers and
        a single POST variable but this would be processing
        the image data and saving the file  
    */
    if( $_SERVER['REQUEST_METHOD']=='POST' ){
        ob_clean();

        $filename = !empty( $_SERVER['HTTP_FILENAME'] ) ? $_SERVER['HTTP_FILENAME'] : md5( mt_rand() ).".jpg";
        $filetype = !empty( $_SERVER['HTTP_FILETYPE'] ) ? $_SERVER['HTTP_FILETYPE'] : 'M.I.A';
        $filesize = !empty( $_SERVER['HTTP_FILESIZE'] ) ? $_SERVER['HTTP_FILESIZE'] : 'M.I.A';

        $action   = !empty( $_POST['action'] ) ? $_POST['action'] : 'M.I.A';

        /* send proper content type response header */
        header( 'Content-Type: application/json' );

        /* add some custom response headers to show how you can pass headers and process them */
        header( sprintf( 'Uploaded-Filename: %s', $filename ) );
        header( sprintf( 'Uploaded-Filesize: %s', $filesize ) );
        header( sprintf( 'Uploaded-Filetype: %s', $filetype ) );

        /* send payload back to ajax callback */
        exit( json_encode( array(
            'filename'  =>  $filename,
            'filesize'  =>  $filesize,
            'filetype'  =>  $filetype,
            'action'    =>  $action
        )));
    }
?>
<!doctype html>
<html>
    <head>
        <title>ajax custom headers</title>
        <style>
            body,body *{
                font-family:calibri,verdana,arial;
                font-size:0.9rem;
            }
        </style>
        <script>
            function bindEvents(){
                /* DOM elements */
                var oDiv=document.getElementById('results');
                var oPre=document.getElementById('headers');
                var oBttn=document.getElementById('bttn');
                var oFile=document.querySelector('form input[type="file"]');


                /* basic callback function to show response */
                var callback=function(r,h){
                    oDiv.innerHTML=r;
                    oPre.innerHTML=h;
                }

                oBttn.onclick=function(){
                    /* as there is only a single file we know the index is zero */
                    var oCol=oFile.files;
                    var file=oCol.item(0).name;
                    var size=oCol.item(0).size;
                    var type=oCol.item(0).type;


                    /* ultra basic ajax request with custom request headers */
                    var xhr=new XMLHttpRequest();
                    xhr.onreadystatechange=function(){
                        if( this.readyState==4 && this.status==200 ){
                            /*
                                The callback can take whatever arguments we want - here simply
                                the response and some headers - could easily process specific
                                response headers rather than all
                            */
                            callback.call( this, this.response, this.getAllResponseHeaders() );
                        }
                    };
                    xhr.open( 'POST', location.href, true );
                    xhr.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' );
                    /* add custom request headers - original file details */
                    xhr.setRequestHeader( 'filename', file );
                    xhr.setRequestHeader( 'filetype', type );
                    xhr.setRequestHeader( 'filesize', size );

                    xhr.send( 'action=headers-test' );                  
                }
            }

            document.addEventListener( 'DOMContentLoaded', bindEvents, false );
        </script>
    </head>
    <body>
        <div id='results'></div>
        <pre id='headers'></pre>
        <form method='post'>
            <input type='file' />
            <input type='button' id='bttn' value='Send Ajax Request with custom headers' />
        </form>
    </body>
</html>

Following on from your comment regarding multiple files all sharing the same name hopefully the following might help.

<form method='post'>
    <input type='file' multiple=true />
    <input type='button' id='bttn' value='Send Ajax Request with custom headers' />
    <div class='photos'></div>
</form>


<script>

    document.querySelector( 'form input[type="file"]' ).addEventListener( 'change', function( event ){

        // Read files
        var files = event.target.files;

        // Iterate through files
        for( var i = 0; i < files.length; i++ ) {

            // Ensure it's an image
            if ( files[i].type.match( /image.*/ ) ) {


                // Load image
                var reader = new FileReader();
                    /* 
                        assign custom properties to the reader 
                        object which will allow you to access 
                        them within callbacks 
                    */
                    reader.filename=files[i].name;
                    reader.filesize=files[i].size;
                    reader.filetype=files[i].type;

                reader.onload = function( readerEvent ) {


                    /*
                        assign each new image with the properties from the reader
                        - these will be available within the ajax function so you
                        can set the custom headers
                    */
                    var image = new Image();
                        image.filename=this.filename;
                        image.filesize=this.filesize;
                        image.filetype=this.filetype;

                    image.onload = function( imageEvent ) {

                        console.log('image onload - - - > > > > > %s -> %s',this.filename,this.filesize);


                        // Add element to page
                        var imageElement = document.createElement('div');
                            imageElement.classList.add('uploading');
                            imageElement.innerHTML = '<span class="progress"><span></span></span>';

                        var progressElement = imageElement.querySelector('span.progress span');
                            progressElement.style.width = 0;

                        document.querySelector('form div.photos').appendChild( imageElement );

                        // Resize image
                        var canvas = document.createElement('canvas'),
                            max_size = 1200,
                            width = image.width,
                            height = image.height;
                        if ( width > height ) {
                            if( width > max_size ) {
                                height *= max_size / width;
                                width = max_size;
                            }
                        } else {
                            if( height > max_size ) {
                                width *= max_size / height;
                                height = max_size;
                            }
                        }
                        canvas.width = width;
                        canvas.height = height;
                        canvas.getContext('2d').drawImage( image, 0, 0, width, height );

                        // Upload image
                        var xhr = new XMLHttpRequest();
                        if( xhr.upload ) {


                            xhr.upload.addEventListener('progress', function(event) {
                                var percent = parseInt( event.loaded / event.total * 100 );
                                progressElement.style.width = percent+'%';
                            }, false);


                            xhr.onreadystatechange = function(event) {
                                if( xhr.readyState == 4 ) {
                                    if( xhr.status == 200 ) {

                                        imageElement.classList.remove('uploading');
                                        imageElement.classList.add('uploaded');
                                        imageElement.style.backgroundImage = 'url('+xhr.responseText+')';

                                    } else {
                                        imageElement.parentNode.removeChild( imageElement );
                                    }
                                }
                            }
                            xhr.open( 'post', location.href, true ); //'process.php'
                            xhr.setRequestHeader( 'filename', image.filename );
                            xhr.setRequestHeader( 'filetype', image.filetype );
                            xhr.setRequestHeader( 'filesize', image.filesize );
                            xhr.send( canvas.toDataURL('image/jpeg') );
                        }
                    };
                    image.src = readerEvent.target.result;
                };
                reader.readAsDataURL( files[i] );
            }
        }
        // Clear files
        event.target.value = '';
    });
</script>
Professor Abronsius
  • 33,063
  • 5
  • 32
  • 46
  • Thanks for your quick help! Really appreciate it! I used the custom header idea and it worked perfectly fine - thanks for that! But unfortunately, yet there is another problem now. Everything works great as long as only one single image is uploaded. By uploading multiple images, every following image is named like the very first one. Is there a way to unset or clear the custom header after uploading the image? – tim_baland212 Mar 16 '18 at 16:00
  • The ajax request should be sending a custom header with each file so the index used in `oCol.item(0).name` needs to be a variable - in your code you used `i` so it would be `files[i].name` – Professor Abronsius Mar 16 '18 at 17:16
  • There must be something wrong with my for-loop but I don't get what. Even if I use `i` instead of the filename, all I get is the total number of images being uploaded. If only one image is uploaded, I get by using `i` the correct number 1. But if I'm uploading f.e. 10 images, `i` has the value 10 in every string im writing to the database with my process.php, even if the correct image is saved and all the images are different... What's wrong? – tim_baland212 Mar 17 '18 at 11:31
  • Sure, here you go... [process.php](https://pastebin.com/D63ZXEaT) - [upload.js](https://pastebin.com/ivDtzvF7) The html for file-selection is unchanged like in my post above. I've changed FILENAME to FILENUM since I've wanted to get the imagenumber. Thanks for your effort! – tim_baland212 Mar 19 '18 at 10:09
  • see update to answer above - works for me in test - perhaps could be simplified – Professor Abronsius Mar 19 '18 at 11:26
  • WORKS! Thank you very much! – tim_baland212 Mar 19 '18 at 12:14