Scriptorama.nl

Header image showing a keyboard, mouse, laptop and books on design patterns

Hoe maak ik een File Upload progress bar met PHP ?

We hebben al eerder gelinkt naar artikelen die vertelden hoe je een file upload progressbar kon maken, maar deze vertelden dit op een enigzins hoog niveau zonder er bij te vertellen hoe alles nu precies in elkaar stak. Dus vandaag implementeren we op Scriptorama from-(bijna)-scratch een file upload progress bar met PHP, jQuery en een PHP extensie.

Update: Heb je niet de mogelijkheid om een nieuwe PHP module te installeren? Probeer het dan eens een file upload progress bar te maken met Google Gears!

De benodigheden

Allereerst moet je natuurlijk bekend zijn met het verwerken van file uploads binnen PHP. Is dit nog niet het geval, lees dan Scriptorama's PHP file upload tutorial eens door. Verder hebben we de volgende software nodig:

  • PHP 5.2+ ~ PHP 5.2 is de eerste versie van PHP die de benodigde hooks levert waarmee de voortgang van een fileupload bijgehouden kan worden.
    [ download ]
  • PHP extensie uit PECL: uploadprogress ~ Een simpele extensie die de bovengenoemde hooks implementeert en ons de mogelijkheid biedt om deze voortgang uit te lezen.
    [ download ]
  • Javascript library: jQuery ~ Om alles aan elkaar te lijmen hebben we Javascript nodig en jQuery maakt ons leven daarbij een stuk makkelijker.
    [ download ]

Zoals je ziet hebben de PHP ontwikkelaars gekozen om extensies de daadwerkelijke implementatie van de upload hooks te laten bieden en dat is voor mensen die met virtual hosting zitten toch een beetje jammer. Veel webhosters zijn namelijk niet snel geneigd om zo'n extensie te installeren en het feit dat deze extensie nog steeds als beta versie te boek staat helpt dan natuurlijk al helemaal niet.

Linux gebruikers kunnen het volgende command gebruiken om de extensie te installeren of anders de extensie zelf compileren.

CODE:
  1. $ pecl install uploadprogress-beta

Windows gebruikers kunnen een binary versie van de uploadprogress extensie downloaden bij PHP.net zelf. Zie de Downloads genaamd 'PECL binaries'.

De werking van uploadprogress

Op het moment dat je een upload begint treedt de uploadprogress extensie in werking. Aan de hand van een identifier die je zelf meegeeft in je formulier (zoals je later zult zien) creert de extensie een logboek. Dit logboek is in feite een simpele .txt file met daarin de laatste gegevens over de upload.

Wanneer je een bestand upload krijgt PHP het bestand niet ineens binnen maar in blokken. Iedere keer wanneer PHP zo'n blok ontvangen heeft roept PHP de uploadprogress extensie aan zodat deze zijn logboek kan bijwerken met de meest recente gegevens.

Maar, first things first, we zullen toch eerst een HTML document moeten hebben.

De HTML

We hebben natuurlijk een upload formulier nodig. Als we alleen een simpel formulier zouden gebruiken komen we in de problemen, want op het moment dat jij op de submit knop klikt zal de browser de pagina verversen. Dat is natuurlijk niet wat we willen want op die manier kunnen we geen progress bar tonen. Om dit probleem op te lossen zullen we gebruik maken van een verborgen iframe. Op deze manier kunnen we het formulier versturen naar het iframe zodat onze hoofdpagina gewoon actief blijft. We verbergen dit iframe zodat de resultaten van het upload script niet zichtbaar zijn.

PHP:
  1. <form target="upload_iframe" id="upload_form" action="upload.php" method="post" enctype="multipart/form-data">
  2.         <input type="hidden" id="UPLOAD_IDENTIFIER" name="UPLOAD_IDENTIFIER" value="<?php echo uniqid("upload"); ?>">
  3.  
  4.         <input type="file" name="upload_bestand">
  5.         <input type="submit" value="Uploaden">
  6.  
  7.         <div class="progress-bar">
  8.             <div class="progress-bar-bg"></div>
  9.             <div class="progress-bar-text">0%</div>
  10.         </div>
  11.  
  12.     </form>
  13.  
  14.     <iframe name="upload_iframe" src="leeg.html" id="upload_iframe"></iframe>

Zoals je ziet is er een hidden field met de naam UPLOAD_IDENTIFIER meegegeven. Dit is een speciale variabele die door de uploadprogress extensie gebruikt wordt om het logboek mee te identificeren. Later zullen we deze identifier gebruiken om informatie over de upload op te vragen.

De upload voortgang opvragen

Zo'n progress bar begint natuurlijk niet uit zichzelf te bewegen en dus zullen we alles aan elkaar moeten lijmen met Javascript. Allereerst zullen we het versturen van het formulier moeten afvangen zodat we onze animatie in gang kunnen zetten, we moeten kunnen bepalen wanneer onze upload klaar is en we moeten de voortgangs informatie gaan opvragen.

Voordat we ook maar iets kunnen doen zullen we een script moeten hebben dat de voortgangs informatie kan aanleveren. Met de functie uploadprogress_getinfo kunnen we deze informatie op halen in de vorm van een PHP array. Dat is 1 stap maar uiteindelijk moet onze Javascript toegang hebben tot deze informatie. De oplossing is om de informatie aan te leveren in een formaat dat Javascript begrijpt.

Hiervoor kunnen we JSON gebruiken (lees meer over JSON) en gelukkig biedt PHP sinds versie 5.2 ook standaard een functie waarmee we een PHP array om kunnen zetten naar een Javascript array: json_encode().

report-progress.php:

PHP:
  1. define ('ERROR_NO_ID',   1);
  2. define ('ERROR_NO_INFO', 2);
  3.  
  4. $responseArray = array (
  5.     'errorCode'    => 0,
  6.     'progressData' => array()
  7. );
  8.  
  9. if (!isset($_GET['id']) || !preg_match('~^[a-z0-9]+$~i', $_GET['id']))
  10. {
  11.     $responseArray['errorCode']    = ERROR_NO_ID;
  12. } else {
  13.     $progressInfo = uploadprogress_get_info($_GET['id']);
  14.  
  15.     if (is_null($progressInfo))
  16.     {
  17.         /* Er werd geen voortgangs informatie gevonden. Dit kan betekenen dat
  18.          * het ID onjuist is, maar ook dat de upload nu gewoon klaar is. */
  19.         $responseArray['errorCode'] = ERROR_NO_INFO;
  20.     } else {
  21.         $responseArray['progressData'] = $progressInfo;
  22.     }
  23. }
  24.  
  25. $jsonResponse = json_encode($responseArray);
  26. $jsonLength   = strlen($jsonResponse);
  27.  
  28. header("Content-Type: application/json");
  29. header("Content-Length: $jsonLength");
  30.  
  31. echo $jsonResponse;

Om deze informatie te gebruiken zullen we in Javascript vanaf het moment dat het formulier verstuurd wordt iedere seconde gaan vragen hoe ver onze upload is. Je zou dit vaker kunnen vragen om zo een meer preciezere inschatting te krijgen maar de keerzijde hiervan is dat je veel communicatie veroorzaakt en de vraag is dan of die extra load de extra precisie waard is.

De progressbar animeren

Om alles aan elkaar te lijmen hebben we dus de nodige javascript nodig en deze staat hieronder:

JAVASCRIPT:
  1. var uploading = false;
  2. var colorSwitchThreshold = 50;
  3.  
  4. function attachUploadHandlers()
  5. {
  6.     $("#upload_form").submit ( beginUpload );
  7.  
  8.     if ($.browser.msie)
  9.         $("#upload_iframe").bind('readystatechange', completeUpload );
  10.     else
  11.         $("#upload_iframe").bind('load', completeUpload);
  12.  
  13. }
  14.  
  15. /*
  16. * Update de visuele kant van de progress bar met de waarde in 'percentage'. Als
  17. * deze waarde groter is dan de waarde in colorSwitchThreshold (standaard 50) dan
  18. * wordt de klasse .progress-50-percent toegepast op de tekst. Zo kan de kleur
  19. * wijzigen van wit naar zwart, wat duidelijker is.
  20. */
  21. function updateProgressBarGUI(percentage)
  22. {
  23.     $(".progress-bar .progress-bar-bg").width(percentage + "%");
  24.     $(".progress-bar .progress-bar-text").text(percentage + "%");
  25.  
  26.     if (percentage> colorSwitchThreshold)
  27.     {
  28.         if (!$(".progress-bar .progress-bar-text").is(".progress-50-percent"))
  29.         {
  30.             $(".progress-bar .progress-bar-text").addClass('progress-50-percent');
  31.         }
  32.     } else if (percentage == 0) {
  33.         $(".progress-bar .progress-bar-text").removeClass('progress-50-percent');
  34.     }
  35. }
  36.  
  37.  
  38. /**
  39. * Initialiseer het upload systeem en zet de upload progress bar op 0.
  40. */
  41. function beginUpload()
  42. {
  43.     uploading = true;
  44.  
  45.     updateProgressBarGUI(0);
  46.     getUploadProgress();
  47.  
  48.     return true;
  49. }
  50.  
  51. function completeUpload(ev)
  52. {
  53.     if ( uploading && ( ev.type && ( ev.type == 'load' || ev.type == "readystatechange" ) ) )
  54.     {
  55.         uploading = false;
  56.         updateProgressBarGUI(100);
  57.     }
  58. }
  59.  
  60. function getUploadProgress()
  61. {
  62.     var upload_id = $('#UPLOAD_IDENTIFIER').val();
  63.     var timestamp = new Date().getTime();
  64.  
  65.     $.getJSON (
  66.         "report-progress.php",
  67.         { id: upload_id, t: timestamp },
  68.         updateProgressBar
  69.     );
  70.  
  71.     if (uploading)
  72.     {
  73.         window.setTimeout(getUploadProgress, 1000);
  74.     }
  75. }
  76.  
  77. function updateProgressBar(jsonData)
  78. {
  79.  
  80.     if (jsonData != null) {
  81.  
  82.         if (jsonData.errorCode == 0)
  83.         {
  84.             var progress_text  = $(".progress-bar .progress-bar-text");
  85.             var progress_bg    = $(".progress-bar .progress-bar-bg");
  86.  
  87.             var bytes_uploaded = parseInt(jsonData.progressData.bytes_uploaded);
  88.             var bytes_total    = parseInt(jsonData.progressData.bytes_total);
  89.             var percentage     = parseInt( (bytes_uploaded / bytes_total) * 100 );
  90.  
  91.             updateProgressBarGUI(percentage);
  92.         }
  93.     }
  94. }
  95.  
  96. $(document).ready(attachUploadHandlers);

  1. attachUploadHandlers() zorgt ervoor dat beginUpload() wordt aangeroepen op het moment dat het formulier verstuurd wordt en dat completeUpload() wordt aangeroepen op het moment dat het iframe klaar is met laden.
  2. beginUpload() zet de globale variabele uploading naar TRUE en zorgt er op zijn beurt voor dat getUploadProgress() wordt aangeroepen.
  3. Zolang de upload nog niet compleet is haalt getUploadProgress() de voortgangsinformatie op bij report-progress.php en zorgt daarmee voor het updaten van de progress bar
  4. Op het moment dat het iframe klaar is met laden (het bestand is binnen) wordt completeUpload() aangeroepen welke uploading op FALSE zet en daarmee is de cirkel rond.

Enkele bijzonderheden in onze javascript

1. Het aanroepen van ons PHP script dat de voortgangs informatie levert. Dit wordt gedaan in de functie getUploadProgress():

JAVASCRIPT:
  1. function getUploadProgress()
  2. {
  3.     var upload_id = $('#UPLOAD_IDENTIFIER').val();
  4.     var timestamp = new Date().getTime();
  5.  
  6.     $.getJSON (
  7.         "report-progress.php",
  8.         { id: upload_id, t: timestamp },
  9.         updateProgressBar
  10.     );
  11.  
  12.     if (uploading)
  13.     {
  14.         window.setTimeout(getUploadProgress, 1000);
  15.     }
  16. }

jQuery biedt via $.getJSON de mogelijkheid om een XMLHttpRequest te doen en de resultaten daarvan meteen te evalueren. Wanneer deze XMLHttpRequest compleet is en de JSON string verwerkt naar een javascript array roept $.getJSON de functie updateProgressBar aan. Deze functie controleert of er geen fout is opgetreden en update vervolgens onze progress bar op het scherm. Zoals je ziet geven we het ID dat in onze hidden field UPLOAD_IDENTIFIER mee en ook een timestamp. Deze timestamp is er om ervoor te zorgen dat IE niet onze antwoorden gaat proberen te cachen, al zouden we dit eventueel ook in ons PHP script kunnen afhandelen.

2. Aangeven wanneer de upload klaar is. De aanroep naar completeUpload() wordt gedaan op het moment dat het iframe aangeeft klaar te zijn met laden. Helaas werkt dit, hoe kan het ook anders, verschillend in Internet Explorer en Firefox, dus moeten we daarin een verschil maken. Gelukkig biedt jQuery een gemakkelijke manier om te bepalen of we ons momenteel in IE bevinden:

JAVASCRIPT:
  1. if ($.browser.msie)
  2.         $("#upload_iframe").bind('readystatechange', completeUpload );
  3.     else
  4.         $("#upload_iframe").bind('load', completeUpload);

Hoe nu verder?

Met dit stuk javascript is onze progressbar bijna compleet. Ik zeg bijna, want er wordt nog niets gedaan met het geuploadde bestand en de de foutafhandeling kan beter en dat laat ik over aan de lezer:

Reageer ook!

Interessant artikel, maar:

[quote]Zoals je ziet hebben de PHP ontwikkelaars gekozen om extensies de daadwerkelijke implementatie van de upload hooks te laten bieden en dat is voor mensen die met virtual hosting zitten toch een beetje jammer.[/quote]

Inderdaad erg jammer..

Ik zal eens kijken of ik hem geïnstalleerd kan krijgen. :P

Dankje voor de uitgebreide tutorial!
Als de extensie is geïnstalleerd ga ik er zeker eens wat mee stoeien; bedankt.

Ik heb nog geen tijd gehad hem te kunnen testen.
Maar aangezien ik ook webhosting aanbied is zeker een mooie toevoeging op de ondersteuning.

Maar zelfs als je host dit niet aanbied heb ik een hele mooi alternatief gevonden welke werkt met flash.

Ik zal hem morgen even opzoeken en hier plaatsen.

je bedoelt: http://swfupload.mammon.se/ ?

Congratulations for your tutorial.
Could you tell us which definition(s) should be added into php.ini (except extension uploadprogress), in order to make your sample working. I'm working under Windows XP and it seems to me that report-progress.php receives no data from uploadprogress_get_info($_GET['id']) while id is OK. $progressInfo is still empty. I'am using PHP 2.2.2 and IE 7. Thanks in advance for your anwser

Hi Jean-Claude,

I assume you mean PHP 5.2.2.

Please make sure the uploadprogress extension is loaded properly through the output of phpinfo(). If it's not, verify that the module for your platform (uploadprogress.so or uploadprogress.dll) is in the directory specified by the 'extension_dir' ini setting and that an 'extension=uploadprogress.dll' (or uploadprogress.so, depending on your platform) was specified in php.ini.

Hi Mathieu,
Yes, it's PHP 5.2.2.
Extension is well loaded. I add this test in your index.php:
if (extension_loaded("uploadprogress"))...
it's successfull. I take the standard extension from PHP 5.2.2. Should I take your dll ? Which parms are mandatory under Windows in php.ini ?

Well, if it's loaded, it's loaded :).

When uploadprogress_get_info() returns NULL this can mean 2 things: the upload is done, or no info was found.

What behaviour are you seeing? Are you getting errors or does the progress bar jump to 100% directly?

As far as I can see it: progress start at 0 and jump to 100% after 1 or 2 sec. File is uploaded (3 MB) and is OK. I can see that report-progress.php was called 3 times, and each time $responseArray[errorCode] is 2 ($progressInfo NULL)

Hello Mathieu,

I am sorry, it works now: file I tried to upload was too small, so I didin't see the progression because time of execution was too short, when uploading in the same computer.
Thanks for your help.

Very nice example.
However I cannot get it to work on Windows XP running Apache.

During the upload I keep getting Error 2.
After the upload is complete,

PHP version is 5.2.3 (Windows with Apache2)
PECL version is 5.2.3

Module IS loaded according to PHPINFO();
But it still doesn't return anything.
(uploadprogress
uploadprogress support enabled
Version 0.3.1-dev
)

When the download is completed it jumps to 100%, which would be correct.

Any tips?

Seemed a problem in the config file.

File Upload temp dir should be added in windows format.

e.g. c:\\temp

then it works

Hmm... I still have problems.
When I get it to work (it stopped working again:( ), I still receive te following problem:

as soon as I call the report php, I get data, but after that the upload just hangs.

When it is "completed" PHP returns error 8 for the transfer. Any ideas?

Werkt deze progress bar ook als je een php FTP upload doet?

Neat!

ik heb het programma met een mail mee ge kregen. wat is het en wat doet het???

Netjes, hier was ik al een tijdje op naar zoek. Ga het eens doornemen! :)

Bij WAMP schijnt die plugin automatisch al te zitten.

BTW Die velden zie ik amper hier :S

is er ook een mogelijkheid om via ajax zonder iframe te werken?

elgato ,

helaas kan dat niet. Je kan niet via javascript een file versturen als parameter naar je server. het enige wat kan is een string van de lokatie versturen. dit is gedaan voor de veiligheid.

iframe is momenteel helaas de enige oplossing.

Gaya

Gaaf! Ik krijg het visueel echter niet zo mooi aan de praat. Ik denk dat ik wat css mis... en progress-bar.zip is er niet meer! Kun je het naar me sturen of weer online zetten?

Million thanx!!!
Chris

Kan iemand me zeggen waar ik dit command kan plaatsen?
$ pecl install uploadprogress-beta

Groeten

Hoi Molly,

Dit is iets wat op de server moet gebeuren. Je hebt grote kans dat je een virtual hosting pakket hebt waarbij jij een server deelt met meerdere klanten. Dan is het voor jou waarschijnlijk niet mogelijk om dit commando uit te (laten) voeren.

Wellicht vind je hoster het interessant om zoiets te installeren maar doorgaans installeren hosters niet zomaar dergelijke extensies.

Mathieu

Persoonlijk ben ik grote fan van 'FancyUpload'. Hiervoor zijn geen speciale PHP extensies nodig (volgens mij is PHP4 al ruimvoldoende).
De upload wordt hierbij gedaan via een 1x1px SWF-je, waarna de progressbar via JS (Mootools) wordt weergegeven.
http://digitarald.de/project/fancyupload/

Heya, thanks for tutorial,

but I have a problem, the progress bar jump to 100% directly...
no matter what size..
uploadprogress_get_info() will load,
but I get NULL..

I have the same problem:
the progress bar jumps to 100% directly...
no matter what size..

Does anyone know the solution to this problem?

Erik
The Netherlands

Don't forget that if you're testing this on your local machine, the 'connection' is so fast that hardly takes ANY time to upload the file. Make sure you test with a sufficiently large file.

Hi Mathieu,

tnx for your reply.
I used your progress-bar.zip as is. Installed it on my webserver. I tested it with a 3MB file over the internet. From local to remote. The upload takes about 30 seconds but the progress bar doesn't change. I'm running PHP 5.2.6 with the correct extensions on a Win2003 machine running IIS.
Any ideas?

Erik

Do you have the uploadprogress extension loaded? Check with firebug what the PHP script is reporting.

Another, perhaps easier, way to create file upload progress bars is using Gears: http://www.scriptorama.nl/javascript/gears-04-httprequest-met-progress-events

Mathieu, werkelijk een prachtig artikel! Ik hoop eerlijk gezegd dat webmasters deze feature standaard in hun website opnemen op het moment dat er een groot bestanden geupload dient te worden. Er is immers maar één ding vervelender dan moeten wachten: het moeten wachten zonder te weten hoe lang je nog moet wachten.

Nogmaals, ongelofelijk bedankt! Wat mij betreft draagt dit artikel weer een steentje bij aan de ontwikkeling in de webdevelopment. Nu maar hopen dat de hosting business zich ook gaat aanpassen en de uploadprogress extentie standaard installeert. Heb hem in elk geval direct op mijn server gegooid!

Hi Martijn, bedankt voor je vriendelijke woorden :).

Nog beter zou zijn als de browser ontwikkelaars dit op zouden pakken, want hoe ver een upload nu precies is, weet de versturende partij ( de browser ) eigenlijk nog beter en met meer zekerheid dan dat de ontvangende partij (PHP) dat kan weten.

Gelukkig lijkt daar enig schot in te komen, want de HttpRequest API die Gears gebruikt ( zie ook http://www.scriptorama.nl/javascript/gears-04-httprequest-met-progress-events ) wordt gestandardiseerd en binnen kort ook in Firefox opgenomen.

Hallo, ik heb het script en het werkt ook maar waar staan de bestanden later?
srry maar ik ben niet zo goed in php

Hey Mathieu,

Mooi stukje werk.
Heb PECL geinstalleerd op m'n server.
Nu ben ik met de progress bar bezig, en hij schiet (net als een aantal andere direct naar 100%) door na de upload.
Het bestand verschijnt op m'n server (co-located) op de juiste manier. Maar hij geeft het dus niet in stappen aan. Als ik in FireBug kijk volgt hij het netjes.
Geeft om de x-aantal ms een return naar report-progress.php maarja. Blijft toch wat steken schijnbaar ergens.

Hoop dat je een tip hebt..

Hoi Geert,

Ik ben benieuwd wat de response is van de verschillende aanroepen naar report-progress.php, kun je dat eens laten zien?

de functie "uploadprogress_get_info" is nergens gedefinieerd in de php en deze maakt geen deel uit van de php librairies, Hoe zit dat?

Een heel mooi voorbeeld! Dank je wel! Wat ik me wel afvraag is hoe, als de upload volledig afgelopen is, de verdere verwerking kan doen? Want hij post kennelijk enkel naar de uploadprogress maar niet meer naar een verdere verwerking...
Zou je eens kunnen laten zien hoe ik het geuploade bestanden kan oppikken voor verdere bewerking?

@Justin: Zoals je kunt lezen in het artikel moet je een aparte PHP module installeren.

@Hendrik: Uiteindelijk zou het bestand gewoon benaderbaar moeten zijn in $_FILES vanuit 'upload.php'. Het verwerken van het bestand moet je dus niet proberen te doen in report-progress.php.

Ik heb de scripts geïmplementeerd in een eigen website en daar werkte de progressbar. Ik zie 'm mooi naar 100% gaan. Als hij daar is dan wordt er helaas niet verder gepost... en ik kom dus nooit in upload.php terecht. Aan wat zou dat kunnen liggen?
Ik begrijp ook niet goed hoe hij daar wel zou kunnen terechtkomen. De target van de form is naar de iframe gelegd wordt daar dan ook niet de action uitgevoerd?

Leave a comment
Line and paragraph breaks automatic, e-mail address never displayed, HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>