WEBDESIGN

Multiupload mit HTML5 und PHP

Unter einem Multiupload versteht man das Hochladen von mehreren Dateien gleichzeitig. Dies ist vor allem dann nützlich, wenn man eine Bildergallery online stellen will. Mit HTML5 fähigen Browsern sind Multiuploads genauso einfach zu erstellen, wie Standarduploads einer einzelnen Datei. In diesem kleinen Tutorial geht es um die Erstellung eines simplen Uploadscripts mit PHP und HTML5.

Die Form

Eine Standard HTML – Form mit der Dateien hochgeladen werden können, besteht aus dem Formelement selbst, einem Inputelement vom Typ file und einem Submit – Button.

<form action="includes/upload.php" method="post" enctype="multipart/form-data">
	<input name="upload" type="file">
	<input type="submit">
</form>

Wichtig ist hierbei den richtigen ENCTYPE zu setzen, dieser Wert steht für das Übertragungsformat des Formulars. Wer bereits mit anderen Formelementen wie z.B. Checkboxen gearbeitet hat, weiß dass man Daten als Array übertragen kann, indem man dem Namen des Elements eckige Klammern anhängt. Die HTML5 Variante des Uploadformulars unterscheidet sich nur durch ein weiteres Attribut beim File-Inputelement, da es sich um mehrere Dateien handelt sollen diese in einem Array übertragen werden.

<form action="includes/upload.php" method="post" enctype="multipart/form-data">
	 <input name="uploads[]" type="file" multiple>
	 <input type="submit">
</form>

Browser die diese HTML5 Eigenschaft unterstützen erlauben nun die Auswahl mehrerer Dateien gleichzeitig. Mit gedrückter STRG – Taste können die einzelnen Dateien markiert werden, welche hochgeladen werden sollen.

Das PHP Script

Damit die Dateien nicht ins Nirvana gesendet werden, brauchen wir noch ein PHP Script, dass die Dateien auf dem Server speichert. Doch bevor wir Anfangen werfen wir einen Blick darauf, wie die Dateien überhaupt auf den Server übertragen werden beziehungsweise vorliegen. Mit Hilfe der Superglobalen Variable $_FILES kann auf alle wichtigen Informationen der Dateien zugegriffen werden.

Beispiel:

<?php
echo '<pre>';
print_r($_FILES);
echo '</pre>';
?>
Array
(
    [uploads] => Array
        (
            [name] => Array
                (
                    [0] => 08_26.jpeg
                    [1] => 08_28.jpeg
                    [2] => 111.jpg
                    [3] => picture.png
                )

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

            [tmp_name] => Array
                (
                    [0] => D:\Programme\xampp\tmp\php67DA.tmp
                    [1] => D:\Programme\xampp\tmp\php67DB.tmp
                    [2] => D:\Programme\xampp\tmp\php67EC.tmp
                    [3] => D:\Programme\xampp\tmp\php682B.tmp
                )

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

            [size] => Array
                (
                    [0] => 217297
                    [1] => 53600
                    [2] => 6511924
                    [3] => 127662
                )

        )

)

In diesem mehrdimensionalen Array sind alle wichtigen Informationen zu den hochgeladenen Dateien gespeichert. Zum Beispiel die Größe der Datei in Bytes, ein Fehlercode (Datei zu groß? Etc.), der MIME Type der Datei und noch der Name sowie der temporäre Pfad auf dem die Datei liegt.

Um die Datei endgültig auf dem Server zu speichern, gibt es die Funktion move_uploaded_files(). Da wir aus Sicherheitsgründen nicht möchten, dass Benutzer beliebige Dateien hochladen können, müssen wir die Dateien noch verifizieren bevor sie endgültig gespeichert werden. Der MIME – Type einer Datei liefert einen Anhaltspunkt, um was für eine Datei es sich handelt, jedoch ist es keineswegs ratsam sich auf diesen zu verlassen. Die Dateiendung entscheidet letztendlich über die Ausführbarkeit einer Datei, deswegen muss in erster Linie diese überprüft werden.

Dateiendung mit PHP prüfen:

function getExtension($name) 
{ 
  return (false === ( $p = strrpos($name, '.') ) ? '' : substr($name, ++$p));
}

Es kann weiterhin auch noch explizit die Größe der Datei überprüft werden, um dem Benutzer direkt Feedback zu geben ob eine Datei zu groß ist oder nicht. Aber auch wenn das nicht gemacht wird, kann der Benutzer nicht einfach beliebig große Dateien hochladen, da die maximale Größe der per POST übertragenen Daten in der php.ini festgelegt ist (post_max_size).

Das fertige PHP Script mit Überprüfung von Größe und Dateiendung sieht dann so aus:

// Erlaubte Dateiendungen
$allowedExtensions = array('png', 'jpg', 'jpeg');
// Maximale Größe der Datei
$maxSize = 2097152;
// Hilfsvariable für Array Index
$i = 0;
foreach($_FILES as $file) :
	foreach($file['name'] as $filename) :
		$extension = getExtension($filename);
		if(!in_array($extension, $allowedExtensions)) {
			echo 'FORBIDDEN EXTENSION', $filename;
			++$i;
			// Datei überspringen falls Endung nicht erlaubt
			continue;
		}
		
		if($file['size'][$i] > $maxSize) {
			echo 'FILE TOO BIG';
			++$i;
			continue;			
		}
		// Eindeutiger Dateiname
		$save_as_name = uniqid().'_'.$filename;
		// Datei auf Server speichern
		move_uploaded_file($file['tmp_name'][$i], '../upload/'.$save_as_name);	
	
		++$i;
	endforeach;
endforeach;


Kommentare ( 40 Kommentare )

  • cracker Antworten ↓

    Sehr gute Anleitung und alles sehr gut verstanden,
    aber wie schreibe ich jetzt jede einzelne datei in meiner datenbank herein ?
    lg

    • m0d Antworten ↓

      Nunja, du kannst den Dateinamen in der Datenbank speichern, auf den kannst du ja innerhalb der Schleife zugreifen. Die ganze Datei speichert man normal nicht in der Datenbank, sondern nur den Namen.

      • cracker Antworten ↓

        Jo genauso habe ich es auch gemacht, also der schreibt den path in der datenbank und dann kann ich in der view datei mit img src drauf zugreifen so...

        Aber wie mache ich das denn das der die hochgeladenen bilder in einen neu erstellten ordner reinhaut, das heißt das sozusagen ein neues album erstellt wird und dort dann auch alle bilder reinkommen, dazu habe ich mir das hier überlegt, aber es klappt bisher nur der ordner erstellen :(

        einmal das html formular:

        albumname:

        und mein php teil :
        //erstellen des ordners mit dem namen des albums
        $upload_path = "uploads/album/";
        if (mkdir($upload_path.$_POST['albumname'])) {
        echo "Das Album mit dem Namen ".$_POST['albumname']." wurde erfolgreich erstellt" ;

        Danach dein Script aber halt diese zeilen verändert:

        move_uploaded_file($file['tmp_name'][$i], $upload_path.$_POST['albumname'].$save_as_name);

        Jedoch nachdem der den ordner erstellt hat mit den namen bricht der den script ab :( also er macht dann nicht weiter die dateien hochzuladne :(

        • m0d Antworten ↓

          Hi,
          wenn du den Ordner innerhalb der Schleife erstellst, musst du dafür sorgen, dass das auch nur 1 mal passiert.

             $name = date('y-m'); // $name = Ordnername
             $upload_path = '../upload/';
             if(!file_exists($upload_path)) {
          	mkdir($upload_path.$name);
             }
          

          Dann fehlt zusätzlich noch ein Schrägstrich, so wie du es momentan stehen hast, ist der $_POST['albumname'].$save_as_name der Dateiname.

          move_uploaded_file($file['tmp_name'][$i], $upload_path.$name.'/'.$save_as_name);
          

          • cracker

            Der ordner erstellen ist auch nicht bei mir in der schleife oder übersehe ich hier etwas ?
            also so sieht mein code aus :

            if(!isset($_POST['album_new'])) return;

            if(!isset($_POST['albumname']) || empty($_POST['albumname'])) {
            $error_msg = "Das Formular wurde nicht vollständig ausgefüllt.";
            return;
            }

            if (mkdir($upload_path.$_POST['albumname'])) {
            echo "Das Album mit dem Namen ".$_POST['albumname']." wurde erfolgreich erstellt" ;

            function getExtension($name)
            {
            return (false === ( $p = strrpos($name, '.') ) ? '' : substr($name, ++$p));
            }

            // Erlaubte Dateiendungen
            $allowedExtensions = array('png', 'jpg', 'jpeg', 'JPG');
            // Maximale Größe der Datei
            $maxSize = 8097152;
            // Hilfsvariable für Array Index

            $i = 0;
            foreach($_FILES as $file) :
            foreach($file['name'] as $filename) :
            $extension = getExtension($filename);
            if(!in_array($extension, $allowedExtensions)) {
            echo 'FORBIDDEN EXTENSION', $filename;
            ++$i;
            // Datei überspringen falls Endung nicht erlaubt
            continue;
            }
            if($file['size'][$i] > $maxSize) {
            echo 'FILE TO BIG';
            ++$i;
            continue;
            }
            // Eindeutiger Dateiname
            $save_as_name = uniqid().'_'.$i.'_'.$filename;
            // Datei auf Server speichern

            move_uploaded_file($file['tmp_name'][$i], $upload_path.$_POST['albumname'].$save_as_name);
            ++$i;
            echo "Die Datei $filename wurde erfolgreich hochgeladen";

            Und mit dem schrägstrich brauch ich doch da nicht oder ? weil ich habe ja $upload_path = "uploads/album/"; also ist da doch der schrägstrich schon drin ?

          • m0d

            hi,

            ne der Schrägstrich mich hinter den Namen des Albums.

            move_uploaded_file($file['tmp_name'][$i], $upload_path.$_POST['albumname'].'/'.$save_as_name);
            

            Außerdem musst du noch prüfen, ob der Ordner bereits existiert, sonst kann es auch zu Fehlern kommen.

            if(!file_exists($upload_path.$_POST['albumname'])) {
            	if(mkdir($upload_path.$_POST['albumname'])) {
                    // ......
                  }
               }
            

            Außerdem solltest du die Eingabedaten noch validieren, sonst können auch illegale Namen für den Ordner angeben werden.

  • cracker Antworten ↓

    "ne der Schrägstrich muss hinter den Namen des Albums." zitat

    Ja stimmt, habe ich korrigiert danke, aber es klappt immer noch nicht, er erstellt nur den ordner aber er stellt nicht direkt die dateien in den erstellten ordner rein, ich habe dir hier mal meine dateien hochgeladen, dann kannst du sie dir mal angucken, vll findest du dann meinen fehler,weil ich finde ihn nicht :(

    downloadlink :
    http://ul.to/utpm9k0f

    • m0d Antworten ↓

      Du musst die Datenbank schon instanziieren.

      $db= new mysqli('host', 'user', 'pass', 'dbname'); 
      $stmt = $db->prepare($sql);
      

      • cracker Antworten ↓

        hab ich ;)
        aber hab ich vorher rausgenommen...
        also die verbindung zur datenbank besteht korrekt...

        • m0d Antworten ↓

          http://pastebin.com/j9Ctduf8 der Code funktioniert definitiv bei mir.

  • cracker Antworten ↓

    Okay die Fehlermeldung wurde behoben,weil ich oben uploadpath falsch angegeben hatte...jetz funktioniert ordner erstellen, aber da bilder direkt rein wieder nicht :(

    Als ausgabe gibt der mir das hier nur :
    Das Album mit dem Namen haha wurde erfolgreich erstellt
    Array
    (
    )

    Teamviewer ist immer noch an..

    • m0d Antworten ↓

      Teamviewer hab ich nicht, aber wenn der Array leer ist, ist das eher ein Zeichen dafür, dass überhaupt nichts übertragen wird. Überprüf mal den Namen des Dateifelds im Formular ob der richtig ist.

      • cracker Antworten ↓

        Das steht in meinem Formular, müsste doch stimmen oder ?

        form action="" method="post" enctype="multipart/form-data"
        input name="uploads[]" type="file" multiple

        • m0d Antworten ↓

          ja sollte stimmen. Welchen Browser verwendest du?

          • cracker

            Ahh mein Fehler :(
            Ich hab vergessen form auch wieder zuzumachen..kein wunder...
            Alles klappt perfekt...
            Dankeschön für deine ganze und auch geduldsame hilfe..

  • cracker Antworten ↓

    Sry das ich nochmal störe, aber ich will jetz aus dem album ein Bild löschen und dazu hab ich halt sql abfrage und hole mir den datei pfad dann hier :

    echo $row['piclink'] = $löschen ;

    und dann halt mein löschen-befehl :

    unlink($löschen)

    Jedoch hat der probleme mit dem pfad...also in diesem fall würde zum beispiel in $row piclink stehen "uploads/album/bildertest/max.jpg"

    und dann würde das ja heißen:
    unlink(uploads/album/bildertest/max.jpg)

    aber das klappt nicht..

    • m0d Antworten ↓

      du solltest nur den Namen der Datei in der Datenbank speichern und nicht den ganzen Pfad.

      • cracker Antworten ↓

        Aber wenn ich nur max.jpg zum beispiel habe, dann weiß der doch ganich in welchem ordner er die datei findet und löschen soll ? :o

        • m0d Antworten ↓

          Für solche allgemeinen Dinge, wie Pfade, Datenbankverbindung etc. legt man sich normalerweise eine extra Config Datei an. Zum Beispiel config.php und in der definierst du dann alle deinen Umgebungsvariablen. Diese Datei musst du dann natürlich auch per include oder require einbinden.

          • cracker

            Dankeschön ich habe es jetz in der datenbank auch jeweils reingeschrieben album pfad und bildname sobald bild hochgeladen wird und dann diese zeilen :

            $bildordner = $row['album_pfad'];
            $bildname = $row['pic_name'];

            $bildlink_delete = "$bildordner$bildname";

            unlink("$bildlink_delete") ;

            Dankeschön an dich nochmal :)

  • cracker Antworten ↓

    Ich habe mir jetz ein Album gebastelt...
    Jedes Bild hat seine eigene id...
    pro bild wird die id +1 gemacht beim upload...

    also bilder werden angezeigt:
    bild1,bild2,bild3,bild4,bild5

    Mein problem, ich habe auch per script löschen eingebaut, das heißt das der dann zb. Bild 3 löscht und auch aus der datenbank,

    und wenn ich jetz in meinem Album bin und auf bild 2 bin und auf nächstes bild drücke, dann versucht der halt id+1 (in diesem fall bild3) zu öffne, aber der datensatz bild3 existiert ja gar nicht mehr...

    Wie mach ich das, das wenn die id nicht existiert sozusagen id+2 gemacht wird und sozusagen das gelöschte übersprungen wird ?

    lg

    • m0d Antworten ↓

      du legst eine weitere Tabelle an, in der die Relation Bild <=> Album gespeichert wird.

      BildID | AlbumID

      Über die Tabelle kannst du dann leicht die Bilder zu einem Album auslesen.

      • cracker Antworten ↓

        Jo danke, so hab ich's jetzt auch gemacht und dann hab ich das hier gemacht, damit der immer die nächsthöhere id nimmt wenn man auf nächstes klickt:

        $bild = $_GET['bild'];
        SELECT id, dateiname FROM bild WHERE id > "'.$bild.'" ORDER BY id LIMIT 1

        lg

        • m0d Antworten ↓

          mit <pre lang="PHP">HIER DER CODE</pre> kannst du übrigens den Syntaxhighlighter ansprechen.

          Dein Code ist SQL Injection gefährdet.

          $bild = (int)$_GET['bild'];
          SELECT id, dateiname FROM bild WHERE id > “‘.$bild.’” ORDER BY id LIMIT 1
          

          Wäre sicher.

          • cracker

            Dankeschön :) Und mit Syntaxhighlighter auch :) vll könntest du ja in dem kommentar feld hier auch iwo nen Button machen wo man drauf klickt und dann automatisch der syntax so reingeschrieben wird ;) wäre einfacher :p

  • moneywasher Antworten ↓

    Beim überfliegen des Quellcodes fällt auf, das die Funktion getExtension() richtig schön durchgestylt ist.
    Allerdings berücksichtigt diese vermutlich nicht das Vorkommen mehrerer Punkte im Namen der Datei, die hochgeladen werden soll. Entscheidend ist das letztmalige Vorkommen eines Punktes im Dateinamen.
    Solche Dateinamen sind nämlich ohne weiteres möglich, zumindest in Windows 7 (an XP kann ich mich schon nicht mehr erinnern). ;-)
    Als Beispiel ein Screenshot mit mit dazugehöriger Adresse :
    www.meine-website.de.jpg

    • m0d Antworten ↓

      Hey,
      doch die Funktion berücksichtigt dies, strrpos findet das letzte Vorkommen eines Zeichens innerhalb einer Zeichenkette. In diesem Fall wird also der letzte (entscheidende) Punkt gefunden.

  • moneywasher Antworten ↓

    Und weil's so schön war, gleich nochmal den Beisieldateinamen als Link umfunktioniert. ;-)
    Hab' die strrpos jetzt nicht nachgelesen, aber wenn du es sagst, wird es wohl auch so sein.
    Wie gesagt, hab' den Quelltext nur mal eben halt überflogen.

  • David Antworten ↓

    Ich will das Script auch benutzen, allerdings funktioniert es nicht. Es wird nichts hochgeladen.

    In einer Variable $ordnername habe ich einen existierenden Ordnernamen gespeichert.

    Das ist mein Script:

    [SCRIPT]

    Siehst du da vielleicht einen Fehler?

    • m0d Antworten ↓

      Hi,

      auf die Schnelle hab ich jetzt nichts gesehen, sind bei dem Ordner die nötigen Schreibrechte gesetzt? Normalerweise sollte doch ein Fehler ausgegeben werden.

  • David Antworten ↓

    Ich habe das Problem jetzt gelöst, indem ich in die Funktion move_uploaded_file(); den absoluten Pfad angegeben habe.

    Jetzt klappt alles!

    • Gast Antworten ↓

      Ich habe das gleiche Problem, der Pfad funktioniert nicht. Leider weiss ich nicht, wo ich den absoluten Pfad in meinem Mac finden kann. Habe verschiedene Suchoptionen versucht, finde aber nix. :(

  • sisko Antworten ↓

    wieder mal ich. :)
    der upload funktioniert hervorragend. ebenfalls das ordner erstellen. auch kann ich informationen zum upload in die db schreiben. unter anderem möchte ich den dateinamen speichern. lade ich aber mehrere dateien gleichzeitig hoch, dann wird nur der erste dateiname gespeichert.
    ich habe gelesen das man den array serialisieren kann, weis aber nicht genau ob und wenn ja, wo das in diesem fall sinn macht.

    ich hab es mit spaltentype als varchar und als text versucht. aber es klappt beides nicht. sicher fällt dir sofort auf woran es liegt. :)
    mein script sieht so aus:

    $i = 0;
    foreach($_FILES as $file) :
    foreach($file['name'] as $filename) :
    $extension = getExtension($filename);
    if(!in_array($extension, $allowedExtensions)) {
    echo 'FORBIDDEN EXTENSION', $filename;
    ++$i;
    // Datei überspringen falls Endung nicht erlaubt
    continue;
    }

    if($file['size'][$i] > $maxSize) {
    echo 'FILE TO BIG';
    ++$i;
    continue;
    }

    $path = '../storrage/';
    if(!file_exists($upload_path)) {
    mkdir($path.$fname);
    }

    // Eindeutiger Dateiname
    $save_as_name = $filename;
    // Datei auf Server speichern
    move_uploaded_file($file['tmp_name'][$i], '../storrage/'.$fname.'/'.$save_as_name);

    ++$i;

    $_POST['file'] = $filename;
    $_POST['fname'] = $fname;

    $mysqli = 'INSERT INTO upload (username, file_name, direction) VALUES (?, ?, ?)';
    $stmt = $db->prepare($mysqli);
    $stmt->bind_param('sss', $_SESSION['username'], $_POST['file'], $_POST['fname']);

    if(!$stmt->execute())

    $stmt->close();
    $db->close();

    endforeach;

    endforeach;

    $fname steht für foldername, den ich vorher generiere. so bekommt jeder upload seinen individuellen ordner.

    • m0d Antworten ↓

      Hier steht etwas dazu, wie man mehrere Datensätze "auf einmal" effektiv in DB schreiben kann.

  • sisko Antworten ↓

    so. ich hab was bei mir entdeckt was mich beunruhigt, finde aber den fehler nicht. und zwar erhalte ich ständig folgende warnung:

    Warning: mkdir() [function.mkdir]: File exists in ****/upload_inc.php on line 58

    ich weis echt nicht wieso. der ordner existiert vor dem upload noch gar nicht, sondern wird erst erstellt (siehe generator) und die dateien sind vorher auch nicht da.
    der upload an sich klappt. ordner wird erstellt und dateien sind drin. nur die warnung macht mir sorgen.

    kannst du bitte mal nachsehen wo ich da einen fehler habe?
    btw: das mit alle dateinamen in die db schreiben hab ich auch noch nicht hin bekommen.

    // Prüfe ob Benutzer eingeloggt ist.
    if(check_login() === false) exit;

    $fname_length = 9;

    function make_seed() {
    list($usec, $sec) = explode(' ', microtime());
    return (float) $sec + ((float) $usec * 100000);
    }

    srand(make_seed());

    $alfa = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM";
    $token = "";
    for($i = 0; $i $maxSize) {
    echo 'FILE TO BIG';
    ++$i;
    continue;
    }

    $path = '../storrage/';
    if(!file_exists($upload_path)) {
    mkdir($path.$fname);
    }

    // Eindeutiger Dateiname
    $save_as_name = $filename;
    // Datei auf Server speichern
    move_uploaded_file($file['tmp_name'][$i], '../storrage/'.$fname.'/'.$save_as_name);

    ++$i;

    $_POST['file'] = $filename;
    $_POST['fname'] = $fname;

    endforeach;
    endforeach;

    $mysqli = 'INSERT INTO upload (nickname, file_name, direction) VALUES (?, ?, ?)';
    $stmt = $db->prepare($mysqli);
    $stmt->bind_param('sss', $_SESSION['nickname'], $_POST['file'], $_POST['fname']);

    if(!$stmt->execute())
    $stmt->close();
    $db->close();

    ?>

    • m0d Antworten ↓

      Du solltest vorher prüfen, ob der Ordner auch wirklich nicht existiert.
      Das geht z.B. mit file_exists()

  • sisko Antworten ↓

    danke. ich habe das mkdir aus der schleife geholt. nun gehts ohne warnung. :)

  • Tobias Heyl Antworten ↓

    Hat mir sehr geholfen, vielen Dank für die sehr ausführliche Beschreibung!

    Kleiner Hinweis: Im Englischen heißt es "too big" und nicht "to big" - zu groß, siehe Zeile 19 im Source beim Upload-Check (echo 'FILE TO BIG';).

Schreib einen Kommentar