File Upload Progress CakePHP with PHP 5.4

I’ve been working on a nifty project, which is aimed at making it easy to manage art calls for entry software. I’ve implemented the great plugin by Mile Johnson which can be found here. It handles some of the tasks of thumb-nailing and validating your file uploads. What is doesn’t do is track progress and give that feedback to the user.

I found a few different solutions, but ran into a few problems which had me hung up for quite some time. I’ll share my process here in hopes to help folks out who are stuck on something similar. There are a few gotchas when pulling this off.

Pre-req’s

PHP 5.4 – You could use APC or the ‘upload_progress’ php modules to accomplish the same thing though.
CakePHP 2.3.x – I’ve only tested this on 2.3 so if your running below that, you’re on your own.
jQuery – Your layout template file should be loading jQuery

Gotchas

There are a few Gotchas in this tutorial, which mainly revolve around using the “Security” component in CakePHP. If you’re running that, pay special attention.

Overview

Here is the high level overview of what we are going to be doing.

  1. Set up a submission form with a file upload
  2. Set up a hidden iFrame to query for file progress on file submit (with jQuery Ajax Calls)
  3. Display a percentage and move the ‘progress loader’ bar along while the file is being submitted

Caveat

I’m not a ‘developer’ and the JavaScript here needs to be cleaned up and optimized with more features/error detection. I’ll leave that for you fancy devs to do.

Step 1 – Build Our Submission Form

We’ll include just the bare defaults here, obviously you’ll need to customize the form for your own project.  1st the code, then the ‘gotchas’.

-Create your submission view file (add.ctp)

Line 1 – This line is simply getting the value of the session.upload_progress setting in your PHP.ini file and setting it to a variable.  This tutorial assumes that you have the Session Upload progress allowed in your PHP.ini file.  More info can be found here: http://php.net/manual/en/session.upload-progress.php

Line 2 – This line is using the Form helper in CakePHP to build the opening form tags.  The important thing is to remember to add the ‘file’ type to the parameters array.

Line 3Here is a gotcha.  If you are using the Security Component in CakePHP, Cake will throw a blackhole error if you don’t ‘unlock’ our hidden field (created on the next line) from the Security Component.  Unlocking the field means that Cake won’t apply its strict rules and won’t error check or check the values of the field.  I couldn’t get mine working without unlocking the field.  My initial belief is that because we are overriding the ‘name’ paramater of the field in our paramaters array, the security component squawks if we don’t unlock it.

Line 4 – Here we add a hidden field (which we unlocked on line 3) who’s name must be the value of our PHP variable created on line 1.  When a form is submitted with a POSTED name of that variable, PHP will automatically add a new key to the SESSION array containing the details of the upload progress of our file.  The value of this field is just a randomly generated hash, which PHP appends to our SESSION file progress key name.  Again, read up on the link above to php.net for more info on how PHP handles this.

Line 5 – Add our file upload field.  Nothing fancy here

Line 6 – Use the form helper again to close the form.

Line 8 – Add our hidden iFrame which will eventually show the upload progress bar (more on this later).

Line 10 – Add the JS helper buffer (more on this later)

Notes:  It is important to remember that if you are using the Security Component, you must use the Form Helper when building your forms, else you will get black-hole errors.  You can’t manually code the HTML for the form.

 

Step 2 – Create the ‘add()’ action in your controller

Notes:  This code will be in your controller.  My controller is called “Submission”.  You will need to adjust accordingly to your set-up.  The add action will take the data submitted in the form, sanitize & validate it (according to your Models rules) and add the new record in the DB.  Nothing fancy here.

 

Step 3 – Add our Javascript code to our add.ctp View file

Quick intro:  You’ll notice that our iFrame we added in the first step of the add.ctp View file didn’t have any ‘src’ value.  This is on purpose.  I got this idea initially from www.johnboyproductions.com/phpuploadprogress-bar/  We will use JavaScript to attatch the src attribute to our iFrame, thus triggering the JS Ajax progress call when the form is submitted.  Here is the JS code that will need to be on your add.ctp page.  (You could either add this in the main template, or use the JS helper as I’m doing here and buffer it.  This way, the JS code will only be added to the add.ctp page.  You’ll need to make sure your main template file has a place to output the buffered JS.

-Add this code to the top of your add.ctp template file

Gotcha: I’ve added the Javascript code in a PHP HEREDOC syntax.  You need to make sure it is all the way left indented for HEREDOC to work.

Line 3 – This is where we create our unique ID that we will later use to query for progress.  It essentially uniquely identifies our uploaded file.

Line 10 – Make sure you change the jQuery selector to whatever your forms ID value is…

Line 17 – Hide the submit button (preventing them from ‘double’ pushing the button

Line 18 – Show the hidden iFrame

Line 20 – This function just sets the SRC to our iFrame, triggering any JS that is on the iFrame.  We also pass a GET variable to the iFrame which is the unique hash we created for our upload on line 3.

We already added the code that will buffer the JS in this code snippet, we just need to remember to add the output butter in our main template file.  Here is that code snippet:

-Add to your layout template file

That is pretty much it for our add.ctp template file.  Next we’ll create the iFrame code, and Ajax progress action.

 

Step 4 – Create the iFrame code HTML

Create a file called ‘progress_frame.php’ add the following code, and place it in your webroot.  We called it progress_frame.php in our JS above, so if you change the name, you’ll need to change the JS as well..

 -Create progress_frame.php in your webroot dir

 

Line 2 – Get the value of the GET variable and assign it to a local variable (This is our file upload unique ID)

Lines 6 to 35 – The CSS to style our progress bar, customize to liking.

Lines 36 – Add jQuery to our iFrame (via CDN here)

Lines 38 to 55 – Set up a function that fires every 500 ms (setInterval).  When that function fires, send a POST request with our unique upload ID via Ajax to /submissions/progress.  (You’ll need to change this to whatever your controller/action names are)

The result of this query is just a number (which our action will return).  We take this number and update the CSS of our upload progress bar to reflect the percentage number returned, and change the bar width.

 

Step 5 – Create the ‘progress()’ action in the controller

One of the final steps is to create the progress action.

– Add ‘progress()’ action to your controller

 

This code could be cleaned up a bit, but the important parts are:

Line 3 – Don’t render a view file for this action

Lines 4 & 5 – Create our Session Key from the PHP ini value name appended with our unique upload ID

Lines 6 to 10 – If our Key exists, do a little math to give us a whole percentage number of our upload progress

Line 12 – If the key doesn’t exist, the upload is complete, so we return ‘100’

 

Step 6 – Make CakePHP Work with PHP’s 5.4 Upload Progress features

By far the biggest gotcha in this tutorial, and perhaps what this is all about, is making CakePHP play nice with the upload progress feature of PHP 5.4.  This one had me stumped until I saw some answer on stack exchange with a similar issue but in regards to a different framework.  Turns out it is the same issue with CakePHP.  It has to do with the cookie name that CakePHP uses.

You’ll need to modify the config value in your core.php file to make cakePHP cookies/sessions have the same name as PHP expects.  Otherwise PHP won’t automatically inject the file upload progress into the SESSION super global like it is supposed to.

in Config/core.php make sure:

You can change the value of the cookie to whatever your app name is.  The default is CAKEPHP.  If you decide to change this value, you can’t use ini_set within PHP for some reason (I have no idea why).  You’ll need to set that Cookie name value via .htaccess.  Then you can change the value in the core.php file to reflect whatever you want.  PHPSESSID is the default for PHP.  You’ll likely want to change this, as you probably don’t want your cookies called ‘PHPSESSID’.  See the stack overflow link for more info.

Step 7 – Unlock the progress() action

If your using the Security Component, you’ll also want to unlock the progress() action so the security component doesn’t squawk.

-Add this if your controllers ‘beforeFilter’ function

 

And we’re done!  Phew.  That was a mess, and quite a few different gotchas to work around.  Hope it helps someone out!

4 Comments

  1. Michasko
    May 29, 2014 @ 15:54:48

    Big, big “thank you”!
    Didn’t really use this yet, but I learned some interesting things thanks to this article.

    Cheers:)

    Reply

  2. Bill Chalmers
    Mar 03, 2015 @ 19:08:08

    Great article!, one thing to note is that I had to use the built in cakephp session component, i.e. $this->Session->read() etc, as the superglobal did not work for me

    Reply

  3. Renaud
    Mar 19, 2015 @ 00:17:28

    Thank you so much !!!!!

    It was a struggle, but after ready through your example again and again I finally managed !

    Reply

  4. hardsingh
    May 26, 2017 @ 14:21:27

    Hi, i tried the code you provided, but didn’t work.

    Reply

Leave a Reply

*