Tutorial: Anzeige des Upload-Status mit Ajax und PHP (PECL :: uploadprogress)

Lange Zeit galt es als unmöglich, den Status eines Uploads nur mit Hilfe von PHP zu ermitteln. Die PECL Erweiterung uploadprogress ermöglicht diese heiß ersehnte Möglichkeit.

Leider ist es ziemlich schwierig, eine Dokumentation zu finden. Daher möchte ich euch hier kurz erklären, wie man diese Erweiterung nutzen kann. Das Ergebnis sieht dann in etwa so aus:

Screenshot Upload Progress

Installation

Das Kernstück ist natürlich die Erweiterung. Diese kannst du unter http://pecl.php.net/package/uploadprogress herunterladen. Wenn du auf einem Linux-Server php5-dev installiert hast, kannst du die Erweiterung einfach mit folgendem Befehl installieren:

pecl install uploadprogress

Anschließend muss die Erweiterung noch in der php.ini aktiviert werden:

extension=uploadprogress.so

Falls du Windoofs nutzt oder Probleme bei der Installation hast, findest du unter http://freestylesystems.co.uk/blog/installng-pecl-uploadprogress-extension-drupal-filefield-module eine gute Installations-Anleitung.

Upload-Formular

Um die Datei identifizieren zu können, die gerade hochgeladen wird, muss diese eine ID bekommen. Diese wird in einem Hidden-Feld mit dem namen „UPLOAD_IDENTIFIER“ definiert. Dieses Hiddenfeld muss zwingend vor dem Upload-Feld kommen, sonst kann nicht darauf zugegriffen werden.

Außerdem benötigen wir ein leeres DIV, wo wir später die Progressbar einbinden.

<form enctype="multipart/form-data" action="" method="post">
    <input type="hidden" name="UPLOAD_IDENTIFIER" id="UPLOAD_IDENTIFIER" value="<?php echo $id; ?>" />
    Select file: <input name="thefile" type="file">
    <input type="submit" value="upload" onclick="setTimeout('updateProgress()', 1000);">
</form>

<div id="progress">
    <b>Dateiupload:</b> <span id="uploadFileName"></span><br />
    <div id='progressbarWrapper'>
        <div id='progressbar' style='width:0%;'> </div>
        <div id='progressbarInfo'>
            <span id="uploadedBytes"></span> / <span id="uploadTotalBytes"></span> <span id="uploadUnit"></span> (<span id="uploadPercent"></span> %)
        </div>
    </div>
    <b>Geschwindigkeit:</b> <span id="uploadSpeed"></span> kB/s<br />
    <b>Geschätzte Dauer:  Sekunden
</div>

Ermittlung des Fortschritts

Der Upload-Status wird durch ein kleines PHP-Script ermittelt. Diesem Script wird die ID des Uploads übermittelt. Mit Hilfe dieser ID kann man mit der Funktion uploadprogress_get_info($id) alle wichtigen Informationen zum laufenden Upload ermitteln. Rückgabewert ist ein Assoziatives Array mit Dateinamen, Gesamtgröße der Datei, bereits hochgeladene Menge, geschätzte Dauer und Durchschnittsgeschwindigkeit.

<?php

header("Content-Type: application/x-json");

$id = $_GET["id"];
$info = uploadprogress_get_info($id);

if (!$info) die("{}");

$name = $info["filename"];
$speed = number_format(($info["speed_average"] / 1024), 1, ",", ".");
$percent = intval(100 * $info["bytes_uploaded"] / $info["bytes_total"]);
$time_left = $info["est_sec"];
$total = $info["bytes_total"];
$uploaded = $info["bytes_uploaded"];

if ($total >= (1024*1024)) {
	$unit = "MB";
	$total = $total / (1024*1024);
	$uploaded = $uploaded / (1024*1024);
} else {
	$unit = "kB";
	$total = $total / 1024;
	$uploaded = $uploaded / 1024;
}

$uploaded = number_format($uploaded, 1, ",", ".");
$total = number_format($total, 1, ",", ".");

echo '{"file":"'.$name.'","percent":'.$percent.',"uploaded":"'.$uploaded.'","total":"'.$total.'","unit":"'.$unit.'","speed":"'.$speed.'","left":"'.$time_left.'"}';
?>

Start der Statusermittlung durch ein Ajax-Request

Die Ermittlung des Upload-Fortschritts wird beim Start des Uploads (etwas zeitverzögert) durch ein Ajax-Request gestartet. Dazu erweitern wir den Absendebutton um den Event-Handler:

<input type="submit" value="senden" onclick="setTimeout("updateProgress()", 1000);">

Mein dazugehöriges Javascript sieht so aus:

<script type="text/javascript">
var xmlHttp = null;
var work = 0;
var stamp;
if (window.ActiveXObject)  {
        try {
                xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
        } catch(e) {
                try {
                        xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
                } catch (e) {
                }
        }
} else if (window.XMLHttpRequest) {
        try {
                xmlHttp = new XMLHttpRequest();
        } catch (e) {
        }
}

var uploadID = '';


function $(obj) {
	return document.getElementById(obj);
}

function updateProgress() {
	stamp = new Date() * getRandom();
	if (work == 0) {
		work = 1;
		xmlHttp.open('GET', './ajax.php?id=' + uploadID + "&" + stamp, true);
		xmlHttp.onreadystatechange = reloadProgress;
		xmlHttp.send(null);
	} else setTimeout("updateProgress()", 100);
}

function reloadProgress() {
	if (xmlHttp.readyState==4) {
		work = 0;
		text = xmlHttp.responseText;
		var upload = eval("("+text+")");
		if (upload["percent"] < 100) {
			$('progress').style.display="block";
			$('progressbar').style.width = upload["percent"] + "%";
			$('uploadFileName').innerHTML = upload["file"];
			$('uploadedBytes').innerHTML = upload["uploaded"];
			$('uploadTotalBytes').innerHTML = upload["total"];
			$('uploadUnit').innerHTML = upload["unit"];
			$('uploadPercent').innerHTML = upload["percent"];
			$('uploadSpeed').innerHTML = upload["speed"];
			$('uploadTimeLeft').innerHTML = upload["left"];

			setTimeout("updateProgress()", 1000);
		} else $('progress').style.display="none";
	}
}

function getRandom() {
        return parseInt(Math.random()*1000000)+1;
}

</script>

Download

Ein funktionierendes Beispiel gibt es natürlich auch zum Download. Du kannst dieses Beispiel und die darin enthaltenen Grafiken (von mir erstellt) gern verwenden. Natürlich wäre ich dir im Gegenzug für eine Verlinkung auf diesen Beitrag dankbar 😉

progress.zip (5,1 kB)
progress.tar.gz (4,4 kB)
Bitte teile den Beitrag, wenn er dir gefällt.
Share on FacebookTweet about this on TwitterPin on PinterestShare on Google+Share on LinkedInEmail this to someone

30 Gedanken zu „Tutorial: Anzeige des Upload-Status mit Ajax und PHP (PECL :: uploadprogress)

  1. Hallo Stefan,

    leider bringt dein Script keine Anzeige!
    Hab PECL Uploadprogress installiert und auf der Seite upload.php
    „move_uploaded_file….“ eingebaut.
    Die Daten landen auch im Ordner, doch leider erfolgt keine Anzeige während des Upload.
    Kannst du mir vielleicht sagen wo der Fehler liegt?

    1. Gib mir mal die URL, wo ich den HTML-Quelltext des Upload-Formulars sehen kann.
      Ohne Quelltext ist eine Fehlersuche unmöglich 😉

        1. Aus irgendeinem Grund gibt die Funktion
          uploadprogress_get_info($id)
          in der ajax.php kein Ergebnis zurück. Der Ajax-Request wird gestartet, aber in Zeile 29 wird er beendet:
          if (!$info) die(„{}“);

          Da muss irgendeine Fehleinstellung in der php.ini sein, so dass nicht auf die gerade hochgeladene Datei zugegriffen werden kann.

          1. Hallo Stefan,

            das mit APC ist schon richtig.
            APC kann schon aktiv sein, nur muss der Eintrag in der php.ini „apc.rfc1867 = on“ auf „apc.rfc1867 = off“ stehen, dann läuft dein Script einwandfrei
            So geht es bei meinem System mit Centos 5.
            Danke dir.

            PS: Könntest du bitte die Links oben entfernen.

          2. Hallo Stefan,

            hab dein Script erweitert.
            Es wird nun bei Upload über 60 Sekunden die Zeit in Minuten angezeigt und wenn die Zeit unter 60 Sekunden ligt dann in Sekunden.
            Hier der Erweiterte Code in der Datei ajax.php:
            $uploaded = $info[„bytes_uploaded“];

            if($time_left >= 60) {
            $time_left = $time_left / 60;
            $time_left = round($time_left,0);
            $zeit = „Minuten“;
            }
            else {
            $zeit = „Sekunden“;
            }

            if ($total >= (1024*1024)) {
            unten dann folgende Erweiterung:
            $speed.'“,“left“:“‚.$time_left.'“,“zeit“:“‚.$zeit.'“}‘;

            auf der Seite upload.php:
            $(‚uploadSpeed‘).innerHTML = upload[„speed“];
            $(‚uploadTimeLeft‘).innerHTML = upload[„left“];
            $(‚uploadzeit‘).innerHTML = upload[„zeit“];

            setTimeout(„updateProgress()“, 750);
            unten:

            Versuche gerade noch eine Weiterleitung nach erfolgreichem Upload auf eine andere Seite zu machen.

          3. header(„Location: ./zielseite.php“);
            Das funktioniert aber nur, wenn noch keine Ausgabe gemacht wurde.

  2. Hallo Stefan,

    hab das ganze nun so gelöst.
    Sende über ein HIDDEN-Feld eine Variable mit, und wenn diese auf der upload.php
    aktiv ist habe ich folgenden Code eingefügt, welcher echt Klasse läuft.
    if ($laden) {
    move_uploaded_file($_FILES[‚thefile‘][‚tmp_name‘],“/var/www/vhosts/xxxxxxxx.de/httpsdocs/daten/“.$_FILES[‚thefile‘][’name‘]);
    if (!move_uploaded_file($_FILES[„thefile“][„tmp_name“], $_FILES[„thefile“][„name“])) {
    header („Location: https://www.xxxxxx.de/test/daten_upload.inc.php„);
    exit;
    }
    Da ich auf dem Land wohne habe ich nur eine Internetverbindung mit ca. 360 kb.
    Somit dauern die Uploads von größeren Daten extrem lange.

    Habe es gestern und heute schon mal gestet und festgestellt, das die Anzeige
    immer nach einer Weile einfach stehen bleibt. Der Upload läuft aber weiter.
    Hast du eine Idee?

      1. Hallo Stefan,

        das Script zeigt mir nach längerer Laufzeit einen Fehler in der Zeile 45, Zeichen 3 an.
        Das müsste diese Zeile sein:
        var upload = eval(„(„+text+“)“);
        Siggi

        1. Keine Ahnung, liegt evtl. an deiner Erweiterung, dass kein valides JSON-Objekt zurückkommt, wenn die Zeit unter 1 Minute geht?
          Oder Verbindungsproblem, ajax.php kurz nicht erreichbar gewesen?

          1. an dem kann es nicht liegen.
            Denn die Zeit läuft noch weit über 1 Minute.
            Gibt es eine Möglichkeit, wenn der Fehler auftritt, den Aufruf des Scriptes neu zu starten, damit vielleicht der Fehler weggeht?

  3. Hey,

    ich habe eine kurze Frage. Dein Script wird wohl ausgeführt, aber es gibt anscheinend ein problem in der javascriptsyntax. Meine Fehlerkonsole meldet hier einen Bug: var upload = eval(„(„+text+“)“);. Woran könnte das liegen?

    1. Hallo Stefan,

      der Fehler hat sich mittlerweile von selber behoben.
      Hab dein Script so erweitert, das es nun die Startzeit anzeigt und wie lange der Upload noch ungefähr dauert.
      Wenn du willst kann ich dir die Daten senden.

      Siggi

  4. Hallo,

    ich konnte leider (aus unwissenheit ) nicht feststellen, was zurückgegeben wird. Verrätst du mir auch wo ich das einsehen kann?

    Mein Problem ist beim uploaden einer .mp3 Datei.
    Auf der Seite www.*****.de kannst du dich ja mal mit ***** und ***** anmelden, um zu sehen, was ich meine. Vielen Dank

  5. Mein Fehler, sorry.
    ajax.php ist drauf, es werden keine Fehler mehr angezeigt, aber leider auch kein Fortschrittsbalken. Was nun?

  6. Hey,

    seltsam, als ich es mir anschaute, war kein Balken da! Aber ist ja egal, da es nun geht! Ich werde einen Linkverweis auf die Seite machen, sobald sie fertig ist. Könntest du bitte die Seite ausblenden, wie bei Siggi?

    Vielen Dank!

    1. Flattr werde ich auf meiner Seite nicht nutzen.
      Aber wenn dir der Artikel geholfen hat, kannst du ihn gern auf deiner Seite verlinken 😉

  7. Er hat mir sehr geholfen. Leider habe ich keine Seite auf der ein Link Sinn machen würde.

    Ein kleines Problem hatte ich dennoch mit dem Script. Ich muss ein Ereignis setzen, sobald die 100% erreicht sind. (Um dann die Serverseitige Bearbeitungszeit der Dateien auszugeben).

    meine Lösung:

    if (!$info) {
    die(‚{„all“:“done“}‘);
    }

    und im reloadProgress() else { :

    if (upload[„all“] == „done“) {
    do(’stuff‘);
    }

    vielleicht hilf’s ja irgendwem.

  8. Hey,

    also ich habe nun versucht dein Script und alles was dazu gehört einzubauen. Allerdings habe ich nur Fehler drin. Ich hab das Tutorial 10 mal gelesen und alles genau so gemacht. Wäre es möglich dass du mir das in meine index.php einbindest?

    gruß

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.