I’ve been recently having plenty of fun shifting platforms like a mad man and I feel it’s time to share some goodness with you all. Let’s have ourselves some PHP (and even if that’s not your thing you still might want to check out the last part)! While we still stand behind our statement that the Ignite UI controls can run everywhere and are server-agnostic, there should really be a tiny asterisk on that one. And what it should state is… well, that you would need custom server side logic to use the File Upload as it only comes with ASP.NET support (handlers for IIS) out-of-the-box. With that in mind, it is true that the control itself is still 100% client-side jQuery, but highly dependent (for now) on communication with the server (I’ll explain why in a bit). So I’ve been on this PHP wave going for Ignite UI Controls with WordPress (for which I’m still gather opinions by the way!) and I thought it would be an awesome idea to have proper PHP wrappers for all Ignite UI controls to ease that up and also to be usable as standalone in other apps. Pretty much I’m making baby-steps towards the original goal (bear with me) and I thought I’d start with something useful like the File Upload:
Take into account that the best part of the control is the AJAX multiple file upload and the progress tracking. First part is taken care of on the client, but the latter needs serious back-end push in most cases where the latest XHR is not supported and I’ll mention those last, for now assume you can’t rely on that. I figured the File Upload progress can’t be that hard – boy, was I in for a ride ..
Limitations, limitations and then some..
So how is file progress handled without client-side access? Well, you can say it takes a lot of communication For the client side to provide a decent experience it needs to know the size of the file, and sadly no good browser will disclose such information to JavaScript (and thus jQuery). So what do you do when only the server can know the actual file size? You ask, of course! The default way of doing so is to fake the file post with special hint to the server (so the backend can grab the size) and reject it. That, however, only flies when you do have access to the actual incoming stream and control over it. The case with PHP is not such – files reach your code ready-to-use. Essentially all you have to do is pick the files you want from the $_FILES superglobal and move them to where you want them saved, for example:
$sucess = move_uploaded_file($_FILES["fileInput"]["tmp_name"] , 'upload/' . $_FILES["fileInput"]["name"]);
echo ($sucess ? "Stored " : "Failed to store") . "in: " . "upload/" . $_FILES["fileInput"]["name"];
Pretty simple! But PHP’s simplicity is both a blessing and a curse - it will upload files to temporary folder and assign temporary names for you, and sadly the actual PHP script you want to use to handle file uploads will only be executed after the file has been completely uploaded or an error has occurred. You can see the problem here, right? Imagine a 100MB file is to be uploaded..so wait for how long to get the size? And the whole file just to grab the size? And then dump it and upload again? It’s just pointless when you can’t immediately cancel the upload. So no initial size will be available, fine, but progress, what about that? Well cue the drums, in comes PHP 5.4 giving you a fairly usable way of tracking just that. The inner handling of the actual file upload will kindly set you some info in a special variable in the $_SESSION so you can check how the file is coming along. So far so good, you’d imagine, but as long as there’s actual progress going on being posted to one point, another would be best to be constantly polled. That ensures quite the additional work to synchronize between those.
There’s more - remember the part where the file actually makes it all the way to the server, before the realization it’s not welcome happens? Yeah, that also means the progress you are showing to the client is the temporary file being transferred, and only at the end of it will you know if it was successfully saved.
So you get no initial information on size and the outcome is only available after a short wait time? PHP also offers an additional MAX_FILE_SIZE field (see http://www.php.net/manual/en/features.file-upload.post-method.php) which truly confused the living hell out of me. I’m far from the illusion it’s a browser hint ( I mean I think we can almost take XMLHttpRequest Level 2 (and File API) as granted soon, as it looks like it’s already stable and spreading to even IE10!) so I would assume that fields feature is similar to the actual Session progress field and it somewhat is. While it says it’s supposed to save “users the trouble of waiting for a big file being transferred only to find that it was too large” from my tests(PHP 5.4.7) I find it doesn’t really abort the upload, but the session upload progress does hang. //end rant
While I have included this as optional functionality, it should be noted this is easily manipulated and while not very functional, it should definitely not be trusted to restrict the size. I could say the progress hang has the potential with some additional logic to be used to detect when the file has gone over the limit and cancel it(from either side), but I haven’t came around to doing that just yet.
Oh yeah, finally you have the session info disappearing from the superglobal as soon as the file is done loading, which is because of the auto-clean. That is again simpler to handle, but harder to manipulate. The thing is, it’s highly recommended to have it on, so I won’t ask you to do otherwise, instead more of the guessing game!
Workarounds, hacks, tricks, etc.
First things first, the settings (in the php.ini or some other way, depending on the hosting and what not..) upload_max_filesize should be set to something reasonable (keep in mind as default it’s just 2MB) and it should be less than post_max_size! Note that there is no workaround for those, other maximum sizes you see are ‘soft’ limits. Note that the Ignite UI File Upload will still do multiple requests for separate files, so the maximum post size can be almost the same as the maximum file size, you don’t need to worry about that. Also, session.upload_progress.enabled should be on (it is by default) and I've left the rest to defaults as well.
So to have a working file upload with PHP on the back-end some patching up of the Ignite UI control was required. Since the actual progress requires the special hidden field before the input (of type file) and we can’t really fake-post here – I have a handler removing the actual file input when registering the file with the server for file size (which for the reason is always initially 0) and adding the special hidden field(s) when the actual post does happen. This is done through scripts emitted from the wrapper. What they also do is play yet another trick on the widget and fix the size in the file information once it becomes available (which is only after the upload has actually started) so the user experience can be mostly unaffected. Oh and the auto-start upload feature totally plays on every weak side of the PHP handling – skipping the initial register and directly going for posting the file and asking for status – which again is unfortunately not really ideal since I can’t map the key from the post with its file name before the upload has ended…therefore requests with the key would return nothing. So I register the file before the widget gets a change to submit in that case, which might not be ideal, but does the job.
One last touch is trying had to maintain a minimal footprint in the session storage by deleting keys that have finished/threw error. However, the equivalent ASP.NET wrapper only holds to such information for a given period of time, which is quite a cumbersome task with PHP, so when you register(pick) a file, but then remove it - no more requests are made with that key and it sticks around. Here’s an optional way to deal with that :
$(document).delegate("#igUpload1", "iguploadfileuploadaborted", function (evt, ui) {
if (ui.status == 0) {
var key = $("#igUpload1").igUpload("getFileInfoData").filesInfo[ui.fileId].key;
$.ajax({
url: "uploadHandler.php",
type: "GET",
data: {'key': key, 'command': "clear" }
});
}
});
This is totally optional, of course, you could implement something better.
Usage
Why PHP wrappers , you’d ask? Well, so you can have this oh-so-very-nice experience inside your IDE:
Having pretty much every setting easily available and mostly described (I might’ve failed here) should make using the Ignite UI widgets a joy! And hey, while at it, figured it’s also nice to have the Loader PHP-ready as well! And the options on both classes are identical to the actual jQuery API (so you don’t need PHP specific documentation and so I can serialize them easily ) .
Note you still need to include jQuery and jQuery UI as usual, and then:
<?php
include_once'IG/igLoader.php';
include_once'IG/igUpload.php';
include_once'IG/config.php';
$igLoader = new ig\loader\igLoader("http://cdn-na.infragistics.com/jquery/20122/latest/css/", "http://cdn-na.infragistics.com/jquery/20122/latest/js/", "igUpload");
echo $igLoader;
$igUpload1 = new ig\upload\igUpload("igUpload1", "upload.php", "uploadHandler.php");
$igUpload1->allowedExtensions = ig\upload\Config::$allowedExts;
$igUpload1->mode = ig\upload\UploadMode::Multiple;
$igUpload1->maxSimultaneousFilesUploads = 2;
echo $igUpload1;
?>
The upload handler is the endpoint for requesting status updates, handled by the class I’ve also included. And the third main piece of the package is the upload module that handles file posts. The config contains:
- allowed extensions - set to the Upload control above and also verified by the module when saving.
- accepted Mime types – again verified by the module when saving.
- file upload path – this is the place the module will try to save to, make sure it exists with write rights
- max file size – used by the module, can also be assigned to the Upload control, but as explained above its uses are very limited.
And one last thing - security..yeah. it's that part again... well I'm no PHP master and many of the enforced restrictions are based on information that comes from the client. Therefore, they can't be trusted and just in case I've missed something(probably have) you can use the “fileUploaded” event to apply any other checks you want. So in the “upload.php” you can have:
<?php
include_once'IG/igUploadModule.php';
$uploadModule = new ig\upload\igUploadModule();
$uploadModule->fileUploaded("fileSaving");
$uploadModule->HandleRequests();
function fileSaving($sKey, $fKey){
$sessionInfo = $_SESSION[$sKey];
$fileInfo = $_FILES[$fKey];
//check if file should be accepted
returntrue;
}
?>
The function will receive two keys – one for the session info and one for the file info in their respective globals, so you can have the final say if the file should be saved by returning true or false.
Also note that while a blacklist is generally recommended I find a small whitelist to be much more restrictive and closer to the existing control behavior.
The future looks HTML5 bright…
… but not FLASH-y – and by that I mean HTML5 specifications are actively evolving to make any and all client side plugins a thing of the past and file uploading and general file access have been long overdue. Now, though, HTML5 (well, technically, XMLHttpRequest Level 2 is not part of HTML5, but still…) is to offer file access as well as upload progress - pretty much everything you need from the browser (one of the sides that already inherently knows the upload status, but never liked to share so far). So in that sense, I would like to proudly announce that all the hacks and what not are soon going to be completely unnecessary – the Ignite UI File Upload is going HTML5 mode with the next release (that is 13.1) so there will be no more file size requests and no more status requests!
But seriously, that’s not completely true – as I mentioned the standard is not finished and the browser support is not that broad. The point is that the Upload widget itself will fill in the gaps - and the current implementation of the upload module will still verify and accept files just fine, but there will no longer be requests for status. There will be some minor adjustments required to optimize it, but in a up-to-date browser your File Upload widget will produce much less traffic!
Try the PHP File Uplaod with Ignite UI for yourself and if you see somewhere I can improve something or you find a bug do let me know. PHP 5.4 is required. Also once you can get your hands on the new File Upload I will try to provide the PHP wrappers updated for 13.1. Perhaps some WordPress plugin and the same treatment are incoming for other controls (I also might be totally overdoing the promises right now ), but in the meantime, grab your version of the awesome Ignite UI toolset:
I’d love to hear some thoughts, so leave a comment down below or @DamyanPetev.
And as always, you can follow us on Twitter @Infragistics and stay in touch on Facebook, Google+ and LinkedIn!