Link to home
Start Free TrialLog in
Avatar of Joey Drenth
Joey Drenth

asked on

Upload multiple files from multiple inputs with PHP and insert the filename into a database.

I want to upload multiple files to my server with PHP and insert the filename, given description and some other things to my database.

This is the form I am using. I am using the LI and UL elements because I am using jQuery to add more inputs dynamically.

            <form class="add-post-form" action="add-files-to-post.php?id=<?php echo $lastId; ?>" method="post">
              <ul id="fieldList">
                <li>
                  <input type='file' name='postFile'>
                </li>
                <li>
                  <input type='text' name='postName[]' placeholder='Titel van bestand'>
                </li>
                <li>
                  <input type='text' name='postDesc[]' placeholder='Beschrijving / instructie'>
                </li>
              </ul>
              <button type="button" id="addMore" name="button">Nog een bestand</button>
              <br>
              <input type="submit" name="" value="Bestanden uploaden en toevoegen">
            </form>

Open in new window


This is the jQuery code

$(function() {
  $("#addMore").click(function(e) {
    e.preventDefault();
    $("#fieldList").append("<li>&nbsp;</li>");
    $("#fieldList").append("<li><input type='file' name='postFile'></li>");
    $("#fieldList").append("<li><input type='text' name='postName[]' placeholder='Titel van bestand'></li>");
    $("#fieldList").append("<li><input type='text' name='postDesc[]' placeholder='Beschrijving / instructie'></li>");
  });
});

Open in new window


This is how my table looks



Along with this, I am of course using some CSS to remove the bullet points and do some additional styling.

My question

How can I upload the files, while ALSO inserting the filepath, file title and file description into my database?

Using a loop seems the most logical option? However I am unsure on how to implement it.

I have tried a script that would upload multiple files from one <input> tag with the attribute multiple but this is not what I am looking for since I need to be able to add a title and description for each individual file.

Any help and tips are greatly appreciated. I know it's a broad question, but I just need someone to give me a point to start at.
Avatar of noci
noci

With a file field, the content of the file is sent across as the field.
a Name needs to be provided in another text field.
 (And those need to be numbered) also all files need to be to fit in an upload. (size limit)
Avatar of Joey Drenth

ASKER


Here to start :
https://www.experts-exchange.com/questions/29076657/Multiple-upload-files-with-PHP-and-MySQL-save-data-and-showing-them.html
https://www.experts-exchange.com/questions/26874395/Submit-PHP-form-values-and-upload-multiple-image-files.html
You've lot of example here too :
https://www.experts-exchange.com/searchResults.jsp?searchTerms=upload+multiple+files+php&searchSubmit=&asSubmit=true&asIgnored=true

Thank you for your time and help.

I took a look at those links and in both of them, the amount of images is predetermined. But I need the amount of images to be variable, there could be only one file, eight or maybe even fifteen.

That's why running it in a loop for each individual file seems like the most logical option to me.

            <form class="add-post-form" action="add-files-to-post.php?id=<?php echo $lastId; ?>" method="post">
              <ul id="fieldList">
                <li>
                  <input type='file' name='postFile[]'>
                </li>
                <li>
                  <input type='text' name='postName[]' placeholder='Titel van bestand'>
                </li>
                <li>
                  <input type='text' name='postDesc[]' placeholder='Beschrijving / instructie'>
                </li>
              </ul>
              <button type="button" id="addMore" name="button">Nog een bestand</button>
              <br>
              <input type="submit" name="" value="Bestanden uploaden en toevoegen">
            </form>

Open in new window


In my form I am using the brackets so the files and data gets submitted as an array. But I am unsure how to then use this data and insert it into my database with a loop.

I am able to do the database connection, write all the queries and secure it.

Just the part of using the loop per file and the file upload is where I lose it.
All PHP installations have limits on the size of files that are uploaded and the number of uploads in a given POST.  Before you're done, you need to check your installation to see what the limits and try to keep it below those limits so it doesn't error out.

All PHP installations have limits on the size of files that are uploaded and the number of uploads in a given POST.  Before you're done, you need to check your installation to see what the limits and try to keep it below those limits so it doesn't error out.

But I could just increase this limit in my php.ini file?
ASKER CERTIFIED SOLUTION
Avatar of Julian Hansen
Julian Hansen
Flag of South Africa image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Julian, first of all thank you a lot for your efforts.

I have edited the code. See below.

However, I am now getting this error.

Call to undefined method mysqli_stmt::bind() ... on line 39

I changed it to bind_param instead of just bind, the error is gone, but it just shows a blank page now.

Any idea what might cause this?

<?php
// BASEPATH WHERE UPLOADED FILES WILL BE SAVED TO
define('BASEPATH','/user-uploads/');

// DB CONNECTION PARAMETERS
$host     = 'localhost';
$user     = 'root';
$password = '';
$db       = 'nnnext';

// SAFELY EXTRACT THE $_FILES DATA
$files = isset($_FILES['postFile']) ? $_FILES['postFile'] : array();

// START YOUR DB TRANSACTION HERE SO THAT IF ANY PART OF THIS
// FAILS YOU DON'T END UP WITH PARTIAL RECORDS
$db = new mysqli($host, $user, $password, $db);

// CHECK THAT YOUR DB CONNECT WAS SUCCESSFUL HERE (NOT INCLUDED)

$db->begin_transaction();
// A PLACE TO SAVE THE PATHS TO UPLOADED FILES IN CASE WE NEED TO REGRESS
$paths = array();

// START OUR try / catch BLOCK
try {
  // WE WILL USE PREPARED STATEMENTS SO SET UP THE BASE QUERY
  // NOTE: WE ARE USING HEREDOC NOTATION
  $query = <<< QUERY
    INSERT INTO `dias` (`dia_title`, `dia_desc`, `dia_path`) VALUES (?,?,?)
QUERY;

  // CREATE THE STATEMENT
  $stmt = $db->prepare($query);

  // BUG OUT IF NOT SUCCESSFUL
  if (!$stmt) throw new Exception('Failed to prepare query: ' . $db->error);

  // BIND OUR LOOP VARIABLES
  $stmt->bind("sss", $title, $desc, $path);

  // ITERATE OVER THE UPLOADED FILES
  foreach($files['name'] as  $k => $f) {

    // SAFELY EXTRACT POST VARIABLES INTO BOUND STATEMENT VARIABLES
    $title = isset($_POST['postName']) ? $_POST['postName'] : false;
    $desc = isset($_POST['postDesc']) ? $_POST['postDesc'] : false;

    // YOU MAY WANT TO BUG OUT AT THIS POINT IF title OR desc NOT SUPPLIED
    // YOU COULD DO THIS AS FOLLOWS
    // if (!$title || !$desc) throw new Exception('Invalid or missing data');

    // CREATE THE TARGET PATH
    $path = BASEPATH . $f;

    // ATTEMPT TO SAVE THE FILE - BUG OUT IF NOT SUCCESSFUL
    if (!move_uploaded_file($files['tmp_name'][$k], $path)) throw new Exception('Failed to move file ' . $f);

    // SAVE THE PATH IN CASE WE NEED TO REGRESS
    $paths[] = $path;

    // EXECUTE THE STATEMENT AND BUG OUT IF AN ERROR
    if (!$stmt->execute()) throw new Exception('Query failed with error ' . $db->error);
  }

  // GOT HERE SO ALL IS GOOD - COMMIT THE TRANSACTION
  $db->commit();
}
catch(Exception $e) {
   // ROLLBACK THE DB CHANGES
   $db->rollback();

   // YOU MAY WANT TO REMOVE YOUR UPLOADED FILES IF THERE WAS A FAILURE
   foreach($paths as $p) unlink($p);
}

Open in new window

But I could just increase this limit in my php.ini file?
Yes, I always do on my own machines.  If you are on shared hosting, you often can't.
Okay, so I feel silly now. I totally forgot to add the brackets to my jQuery code. I did that now and the code is running further.

It's now throwing this error:

Invalid argument supplied for foreach() ... on line 42

As you might have noticed, I am a complete noob at loops, so unlike the last error I have no clue how to solve this one.
As I stated in my post this is just out of my head so there may be a few typos
mysqli_stmt::bind() => mysqli_stmt::bind_param()

Open in new window

In your case
$stmt->bind() => $stmt->bind_param()

Open in new window

I have corrected in the original post

The loop error is saying that the value supplied for the loop was invalid - that is referring to the $files['name'] - which is indicating that your $_FILES value is not coming through correctly.

Place this at the top of the file
echo "<pre>" . print_r($_FILE, true) . "</pre>";

Open in new window

And report the results here.
Did you make the change to your form to include the enctype attribute
enctype="multipart/form-data"

Open in new window

This is my form at the moment
            <form class="add-post-form" enctype="multipart/form-data" action="file-handler.php?id=<?php echo $lastId; ?>" method="post">
              <ul id="fieldList">
                <li>
                  <input type='file' name='postFile[]'>
                </li>
                <li>
                  <input type='text' name='postName[]' placeholder='Titel van bestand'>
                </li>
                <li>
                  <input type='text' name='postDesc[]' placeholder='Beschrijving / instructie'>
                </li>
              </ul>
              <button type="button" id="addMore" name="button">Nog een bestand</button>
              <br>
              <input type="submit" name="" value="Bestanden uploaden en toevoegen">
            </form>

Open in new window


The line of code at the top results in this:

Array
(
    [postFile] => Array
        (
            [name] => TEST-IMAGE-2.jpg
            [type] => image/jpeg
            [tmp_name] => C:\server\temp\php435.tmp
            [error] => 0
            [size] => 146805
        )

)

Open in new window


It seems to be only uploading the last file.
Did you make the change in your jQuery to put the '[]' on the end of the postFile in the dynamic fields you are adding.

Here is a sample page with source that mimics your code and points to a reflect script. It demonstrates how the code should work

http://www.marcorpsa.com/ee/t3146.html

In the sample you will see that the results show the FILES coming through correctly.

I suspect it is your jQuery code that has not been updated.
I have found a typo (that I made myself), I forgot to place the dot in front of the basepath.

I did change some other things and the file upload is now working, files are being uploaded to my server in the folder ./user-uploads/ and there's a record being created.

There's one final error, the file title and description are not being inserted correctly.

It says "array" in both fields, instead of what I specified.


 Notice: Array to string conversion ... on line 64

print_r is now echoing this, which seems  to be working properly.

Array
(
    [postFile] => Array
        (
            [name] => Array
                (
                    [0] => latest001+pix.jpg
                    [1] => gun-city-offline.jpg
                )

            [type] => Array
                (
                    [0] => image/jpeg
                    [1] => image/jpeg
                )

            [tmp_name] => Array
                (
                    [0] => C:\server\temp\php98D3.tmp
                    [1] => C:\server\temp\php98D4.tmp
                )

            [error] => Array
                (
                    [0] => 0
                    [1] => 0
                )

            [size] => Array
                (
                    [0] => 43280
                    [1] => 190032
                )

        )

)

Open in new window

What is on line 64 - your code has changed since your last code post - which has a blank line for 64.
I added a ID to insert (I need it to be able to link images to created posts) this works.

The error is on line 65 according to the error message,


Notice: Array to string conversion in ... on line 65

The title and description that I give to the images do not seem to process properly, instead of the given string, the word "Array" gets inserted into the database record.

<?php
echo "<pre>" . print_r($_FILES, true) . "</pre>";

// BASEPATH WHERE UPLOADED FILES WILL BE SAVED TO
define('BASEPATH','./user-uploads/');

// DB CONNECTION PARAMETERS
$host     = 'localhost';
$user     = 'root';
$password = '';
$db       = 'nnnext';

// SAFELY EXTRACT THE $_FILES DATA
$files = isset($_FILES['postFile']) ? $_FILES['postFile'] : array();

// START YOUR DB TRANSACTION HERE SO THAT IF ANY PART OF THIS
// FAILS YOU DON'T END UP WITH PARTIAL RECORDS
$db = new mysqli($host, $user, $password, $db);

// CHECK THAT YOUR DB CONNECT WAS SUCCESSFUL HERE (NOT INCLUDED)

$db->begin_transaction();
// A PLACE TO SAVE THE PATHS TO UPLOADED FILES IN CASE WE NEED TO REGRESS
$paths = array();

// START OUR try / catch BLOCK
try {
  // WE WILL USE PREPARED STATEMENTS SO SET UP THE BASE QUERY
  // NOTE: WE ARE USING HEREDOC NOTATION
  $query = <<< QUERY
    INSERT INTO `dias` (`dia_title`, `diaserie_id`, `dia_desc`, `dia_path`) VALUES (?,?,?,?)
QUERY;

  // CREATE THE STATEMENT
  $stmt = $db->prepare($query);

  // BUG OUT IF NOT SUCCESSFUL
  if (!$stmt) throw new Exception('Failed to prepare query: ' . $db->error);

  // BIND OUR LOOP VARIABLES
  $stmt->bind_param("siss", $title, $id, $desc, $path);

  // ITERATE OVER THE UPLOADED FILES
  foreach($files['name'] as  $k => $f) {

    // SAFELY EXTRACT POST VARIABLES INTO BOUND STATEMENT VARIABLES
    $title = isset($_POST['postName']) ? $_POST['postName'] : false;
    $desc = isset($_POST['postDesc']) ? $_POST['postDesc'] : false;
    $id = $_GET['id'];

    // YOU MAY WANT TO BUG OUT AT THIS POINT IF title OR desc NOT SUPPLIED
    // YOU COULD DO THIS AS FOLLOWS
    // if (!$title || !$desc) throw new Exception('Invalid or missing data');

    // CREATE THE TARGET PATH
    $path = BASEPATH . $f;

    // ATTEMPT TO SAVE THE FILE - BUG OUT IF NOT SUCCESSFUL
    if (!move_uploaded_file($files['tmp_name'][$k], $path)) throw new Exception('Failed to move file ' . $f);

    // SAVE THE PATH IN CASE WE NEED TO REGRESS
    $paths[] = $path;

    // EXECUTE THE STATEMENT AND BUG OUT IF AN ERROR
    if (!$stmt->execute()) throw new Exception('Query failed with error ' . $db->error);
  }

  // GOT HERE SO ALL IS GOOD - COMMIT THE TRANSACTION
  $db->commit();
}
catch(Exception $e) {
   // ROLLBACK THE DB CHANGES
   $db->rollback();

   // YOU MAY WANT TO REMOVE YOUR UPLOADED FILES IF THERE WAS A FAILURE
   foreach($paths as $p) unlink($p);
}

Open in new window

A couple of typos on my part.
postName and postDesc are arrays so we need to do this

$title = isset($_POST['postName'][$k]) ? $_POST['postName'][$k] : false;
$desc = isset($_POST['postDesc'][$k]) ? $_POST['postDesc'][$k] : false;

Open in new window

The $k is the key of the $files array we are iterating over in the for loop - it tells us what item in each array to map to the file we are processing
So I just tested this and it works!

Thank you a ton for helping me out here, I really learned a lot today.
Together with the help of Julian I was able to resolve my problem.

Thank you a lot for helping me out with my problem and learning me a lot today.
You are welcome.
Hello Julian, sorry to come back to this question.

If I wanted to add a check to the script to see whether a file is an image or not.

I was thinking about something like getimagesize(); or pathinfo(); ?

What would I use in this case and how would I add it to the existing script, considering security and the script that I currently have?
Check out the function exif_imagetype()
First of all, sorry for my very late response, had two busy weeks.

I looked at the function that you suggested and this is what I'm thinking.

Instead of doing this: (Psuedo) IF filetype === GIF OR PNG OR JPG

Do this:
$allowed = array("IMAGETYPE_PNG", "IMAGETYPE_JPG", "IMAGETYPE_GIF");

$fileType = exif_imagetype($_FILES['']) //Not entirely sure what to put there

if(!in_array($fileType, $allowed)) {
   // Don't allow file and stop script
}

Open in new window


Is this correct? And how would I implement this in the loop ?
Run it against the uploaded file the path to which is stored in $_FILES['tmp_name'].
$fileType = exif_imagetype($_FILES['tmp_name'])

Open in new window

foreach($files['name'] as  $k => $f) {

    // SAFELY EXTRACT POST VARIABLES INTO BOUND STATEMENT VARIABLES
    $title = isset($_POST['postName']) ? $_POST['postName'] : false;
    $desc = isset($_POST['postDesc']) ? $_POST['postDesc'] : false;
    $id = $_GET['id'];

    // YOU MAY WANT TO BUG OUT AT THIS POINT IF title OR desc NOT SUPPLIED
    // YOU COULD DO THIS AS FOLLOWS
    // if (!$title || !$desc) throw new Exception('Invalid or missing data');

    // CREATE THE TARGET PATH
    $path = BASEPATH . $f;

    // ATTEMPT TO SAVE THE FILE - BUG OUT IF NOT SUCCESSFUL
    if (!move_uploaded_file($files['tmp_name'][$k], $path)) throw new Exception('Failed to move file ' . $f);

    // CHECK WHETHER FILE IS AN IMAGE OR NOT
    $allowed = array("IMAGETYPE_PNG", "IMAGETYPE_JPG", "IMAGETYPE_GIF");

    $fileType = exif_imagetype($_FILES['tmp_name'][$k]);

    if(!in_array($fileType, $allowed)) throw new Exception('File is not an image');

    // SAVE THE PATH IN CASE WE NEED TO REGRESS
    $paths[] = $path;

    // EXECUTE THE STATEMENT AND BUG OUT IF AN ERROR
    if (!$stmt->execute()) throw new Exception('Query failed with error ' . $db->error);
  }

  // GOT HERE SO ALL IS GOOD - COMMIT THE TRANSACTION
  $db->commit();
}

Open in new window

I added these lines:

    $allowed = array("IMAGETYPE_PNG", "IMAGETYPE_JPG", "IMAGETYPE_GIF");

    $fileType = exif_imagetype($_FILES['tmp_name'][$k]);

    if(!in_array($fileType, $allowed)) throw new Exception('File is not an image');

Open in new window


But it doesn't seem to work.

Is there something I'm missing here?

Please note that I did enable these settings in the php.ini file.
extension=php_mbstring.dll
extension=php_exif.dll

Open in new window