On a recent project I had to address the requirement that the admin user can "upload any type of file and associate it with any of the models in the system". In reality this seems to be a rather logical and common requirement. For quite a while now I've been collecting bits of info on how to upload files with cake and it's about time to begin sharing the knowledge. Did I say sharing? I meant I'll show you what I know, and hope you show me how to do it better :)
Partly because I am really loaded at the moment, but also due to the number of files which are 'supporting' the behavior are numerous, this blog will not follow the format of my previous posts. The code which to be discussed is in the noswad CakeForge project
Preamble
A massive shout out to both Tane Piper (Digitalspaghetti) for the original upload behavior and Jon Bennet (JBen) for his insightful Image helper which, as you may note from the download, forms the basis for the image manipulation code in the ImageUpload behavior.
The code has been taken from a running project, but I have not yet commented the code due to time constraints but in any case the code may change - Particularly as the code relies on the patch attached to Ticket 3178 to work.
I'll write a tutorial on the bakery if the code settles down after taking on board whatever suggestions come forth.
What's In it?
The Upload Behavior
There are a lot of files in the download but the most important inclusion is the upload behavior.
This file allows you to save meta data about your file uploads in the database and store the files wherever you configure the uploaded files to go.
Control of uploads by mime type, extension and size
Validation related to file uploading itself is handled by the behavior (dependent on ticket 3178).
The behavior will only be active for file uploads, as such if you wanted to edit the meta data that is collected you can via a normal form.
Image Upload Behavior
This file demonstrates clear separation from file upload handling and image manipulation. In addition to everything it inherits from the upload behavior it also saves width and height data to the database (meta) table if the fields are present and has two functions defined to permit resizing of a file that has been uploaded or any arbitrary image file which it is passed. After resizing a file either the file is written to the path defined in the parameters, or the image contents are returned to the calling method.
Generic App Model
The associations defined in this file permit a thumb image to be associated with any model instance, and any number of files in general to be associated and used as you wish.
Attachment administration controller and views
Lets you upload files and by default stores files in APP/uploads. Files are not directly stored in the webroot.
Using the attachment controller, you can add and view all files that are uploaded.
The model and id to which the attachment will be associated is dependent on the url parameters passed. To upload a file for Blog number 22 you would go to the url /admin/attachments/add/Blog/22. Submit the form and it's up there for use.
File and Image handling controller
The functionality within this controller could just as well be put in the attachments controller, but it is presented here separately.
The routes file that is in the download will route requests for /files/* and /img/* to this controller. On the fly image resizing is also built into this controller. "On the fly image resizing?" you say? Consider the following example, see the source for more info:
- As an admin user you go to the url /admin/attachments/add/Blog/22 and upload a file named "test.jpg" and give it a description of "what happens here". The file uploaded is 3000px by 5000px. Users :D!
- You go to, or include in a page, the url /img/Blog/22/test-what-happens-here.jpg . The image you would see in this case is served at the default size, which if you don't change the code means you will see your image restricted to 300px wide and 1000px high, as you don't want it changing size (most likely) what you will therefore get is your image served as 300px x 500px.
- You want to have a thumb of the same image so you go to, or include in a page, the url /img/50x50/Blog/22/test-what-happens-here.jpg. Tada, you have your thumb.
- You decide that the default size for this image is too small, so you go to, or include in a page, the url /img/600x1000/Blog/22/test-what-happens-here.jpg. Tada, you have your image as 600px x 1000px.
- For uses to be able to see/download the original file you can go to, or include in a page, the url /files/Blog/22/test-what-happens-here.jpg. Tada, you have your image as originally uploaded, 3000px x 5000px.
For none-image files the logic is slightly different:
- As an admin user you go to the url /admin/attachments/add/Blog/22 and upload a file named "music.mp3" and give it a description of "great tune this one".
- In this case it isn't an image, and if you include a link, or go to an 'image' url like /img/50x50/Blog/22/music-great-tune-this-one.mp3 you will get..... redirected to the url /img/types/mp3.png if the file exists or /img/types/generic.png if not
- To access the original file you include a link, or go to the url /files/Blog/22/music-great-tune-this-one.mp3
And also...
A few other things are there in the code ;) but I'll leave describing them for another opportunity.
Wrapping Up
The source files described here allow you to upload files, restricted by extension, mimetype or size and associate them with any model in your application. Meta data is stored in the database while the files are stored out of the webroot. Files can be dynamically served/copied to the webroot upon demand.
Bake on!











Excellent stuff! I'll use it in my next project and let you know what i run into.
How's that for a slice of fried gold:
Behavior methods can now be used as validation callbacks without any hacking. I wonder how else I can (ab)use that now :D
Hey AD7six,
I started working on combining the Image/Upload behavior just two days before I saw your post about your combination of them here (Whereas my behavior wasn't as innovative as yours).
It seems that you and I started from the same codebase but headed into different directions. I decided to rewrite the behavior once again. I really liked your idea of attaching documents to models and the way you access documents thru the routes.
I'm nearly finished with it right now but will be on vacation in venice for the next 7 days. If you would like to see the code just tell me.
-David
David: Sure, stick it in the paste bin (as a saved paste) and add a link to it here, maybe there can be some synergy found.
Jetpac: Good call, I'll update the controller with that.
PS. If you don't want to link to a site, just leave it blank ;).
Hey I'm really liking this functionality :)
Just another couple of lines I had to change to get things running well on windows in case anyone else is in the same situation:
1 - the dirFormat field in the upload behavior works best with / instead of DS because its used in the urls.
2 - then we need to convert the / back to DS in the images controller for viewing: (line16) $dir = str_replace(DS, '/', implode(DS, $args));
seems like url is a required field in the comments.
cheers,
J
Just one more thing Andy: your example app seems to be using two behaviors whodunnit and slug to handle converting urls and storing who made edits. I'm sure they're not too complicated but I think I would find them useful. If you could share them that would be great :)
Hi Jetpac, the slug behavior is on the bakery, I'll have a think about publishing the whoDunnit behavior (I don't like showing things where I break cake's rules :D)
Hi Cakefreak,
I see I really do need to fix that link field!
There is a controller method included which will clear out the webroot, so that stale files can be removed, as is it will clear out everything but could easily be adapted to just delete versions of one file at a time. I should point out that the controller/resizing solution is only a suggestion and demonstration of use. As you rightly point out it could be abused to form the basis for a DOS attack, I did think about taking some of the ideas from, for example the secure get component such that you can't just type whatever size you want and get it. Alternatively it could be made to work via requestAction only and after saving the data a call is made to create only the appropriate files.
I don't quite follow regarding the renaming, if you edit the filename, there is nothing in place to rename the existing file, is that what you mean?
Hi Cakefreak,
Are you experiencing that with the demo applicaiton or in your own code? What you describe is the concequence of the code being unable to determine what the var $filename is.
Hey andy,
in the demo app I downloaded from CakeForge
Hi Cakefreak,
Well I found you a reason: path info only returns the filename since 5.2.0. so you just need to substitute where that is used to be functionally equivalent.
Hey Andy, thanks a lot for the tip!
There were another couple of features not working properly (some paths to the images + the stuff/link to clean the directories), but I'll be back with more precision!
Have a nice weekend!
Dan
@Cakefreak: I don't usually use windows, but I'll see if I can install and check it out. The lack of a link to clean the webroot was deliberate - Didn't want to confuse anybody who didn't bother to read the code and hopefully after reading the code you know what it does.
@Mauricio: I think there is a simple mistake in the demo code which causes adds to insert a blank row. I'll update that when I get a minute (I was experimenting with means of not having an add method since it is often a needless duplication)
@Mariano: Oops. That was taken care of in a previous version. Looks like I optimized it out :D. I'll optimize it back in.
$filename = substr($info['basename'], 0, strlen($info['basename'])-4);//DAN HACKDanOf late, I'm getting "No Database table for model AppModel (expected app_models), create it first." error with recent 1.2 svn branch.
Do you have any comments on that?
Another note that the source of the above problem is AppModel::__construct() .
If we remove this definition app_model.php, the error disappears.
Hi Dan,
Whoops I hadn't put the fix which I discussed on the google group with R. Rajesh Jeba Anbiah in the download.
Did it just now, you might kick yourself as it's quite simple ;).
Cheers,
AD
if (mkdir($pathname, intval($mode, 8), true)) {Otherwise the dynamic creation of different filesizes doesn't work. (In windows!) No problems on my MAC with that though. I thought I share my experience with you. Thanks for all your great articles! -B.Hi Andy, I just wanna say thanks for the great job. Your approach for a generic Upload Behavior is really cool and far useful. I was traying to do something similar but I had trouble thinking the right model. This fits every need I had in an elegant and easy way. I'm extending it to support also video. I'll let you know how it works out!
Regards!