Attachments
Outdated
This article is outdated. The project has been packaged as a plugin and moved to http://github.com/davidpersson/media/tree/master. Please see the the wiki pages of the project for documentation until a new bakery article has been written.Thank you for all your feedback and support!
David
Features
- Association of any number of files with any record of any model
- Clean and documented code
- Easy to install and setup
- Transfer of files via HTTP, local file or from a HTML file form
- Access attachments and versions via url
- Creation and caching of file version (e.g. thumbnails, etc.) on the fly
- Extendable to support other file types
Download
Get latest Attachments packageInstall
The package basically contains an Attachmet behavior, an Attachment component, an Attachment element and some libraries.- Copy the files from the package to the appropriate folders of your cake app
- Run cake bake schema run schema run create attachments from the working directory of your app or init the table with the provided sql
Setup
Cache
Edit your config/core.php and setup an additional cache config:
Cache::config('binary', array('engine' => 'File',
'prefix' => 'binary_',
'serialize' => false)
);
You may choose a different cache engine but be warned that this cache has got to chew big chunks of binary data (depends on the size of the attached files you are requesting).
Behavior
Add this to the $actsAs parameter of the model you'd like to attach stuff to:Model Class:
<?php
var $actsAs = array('Attachment');
?>
No configuration options are required. See below on how to adjust the behavior.
Component
The Component allows you to dynamically render versions of files.!! This functionality is experimental and not intented to be used within
!! production setups! It may easily let your php script run out of memory and
!! put massive load upon your server.
Open up the controller file corresponding to your model and add the Attachment component:
Controller Class:
<?php
var $components = array('...','...','Attachment');
?>
Element
Edit the add and edit views of the controller make sure the form type is set to file.View Template:
<div class="examples form">
<?php echo $form->create('Example',array('type' => 'file'));?>
...
Still in the view add the statement for rendering the attachment element
View Template:
...
<?php echo $this->element('attachment');?>
<?php echo $form->end('Submit');?>
</div>
The element supplies you with a basic listing of attached files and fields to add more files to the record.
Usage in views
Assuming you attached an image file named freekevin.jpg to the record of the Example model with the id 23.To render the image you could simply do:NOTE: You've got to completely turn off debugging in order to make this work.
View Template:
echo $html->image('/examples/23/attachments/freekevin.jpg');
To render a resized version of the image within constraints of 300 width and 300 height:
View Template:
echo $html->image('/examples/23/attachments/freekevin.jpg/thumb');
- tiny: 16x16
- thumb: 100x100
- medium: 300x300
- large: 800x800
- port: 1000x550
Currently you can only generate version of file types that are supported by the GD extension.
For extending this feature you may have a look into the source code of vendors/XFile.php and vendors/XFile/XFileImageGd.php.
Adjusting the Behavior
You can customize how the file is going to be named and where it's stored by using special markers in the options.The markers {DS},{APP},{WWW_ROOT} and {UNIQUE_ID} are valid for base, dirname and basename.
Additionally {BASENAME},{FILENAME} and {EXTENSION} as well as any other field that is submitted with your attachment (e.g. {GROUP}) can be used within basename.
- base: Absolute path to base directory without trailing slash
- dirname: Relative path without trailing slash
- basename: Basename of the destination file
Checks are enforced onto a file being attached. All of these options are pretty self explanatory.
See the source of the behavior for the correct syntax and defaults.
- allowMimetype
- denyMimetype
- allowExtension
- denyExtension
- allowPaths
- maxSize
At least there are three more general options.
- infoLevel: Controls the verbosity of the output on find
- checksumAlgo
NOTE: You may also add additional columns to the attachments table.
Find&Save Operations
Assuming you already attached files to records, a find() issued on the Example Mode would result in (depends on verbosity set for behavior and file type):
Array
(
[Example] => Array
(
[id] => 1
[title] => Let Me Show You
[created] => 2008-01-21 16:28:33
[modified] => 2008-01-21 16:28:33
)
[ExampleAttachment] => Array
(
[0] => Array
(
[id] => 1
[model] => Example
[foreign_key] => 1
[base] => /home/davidpersson/Workspace/project/webroot/
[dirname] => files/examples
[basename] => freekevin.jpg
[filename] => freekevin
[extension] => jpg
[checksum] => 9e496bcf9f601a7501b3efaf2b19da15
[size] => 49160
[mimetype] => image/jpeg
[mediatype] => image
[width] => 640
[height] => 480
[ratio] => 4:3
[megapixel] => 0
[quality] => 0
[group] => demo
[created] => 2008-01-21 16:28:33
[modified] => 2008-01-21 16:28:33
)
...
)
)
If you'd like to attach a file directly to an existing record you would build:
Array
(
[Example] => Array
(
[id] => 1
)
[ExampleAttachment] => Array
(
[0] => Array
(
[file] => /var/log/kern.log
)
)
)
...then...
Controller Class:
<?php $this->Example->save($data);?>
Of course the save operation above is going to fail because the file is not within allowed paths.
By default all files below the app's temp, webroot and the systems temp directory are considered to have valid locations.
NOTE: Supplying an id for the attachment would cause the attached file to be substituted by the new file.
[p]NOTE: Supplying an delete which is set to true causes the record and file to be deleted permanently.[p]
You could even attach a remote file to a record by setting the file field to
e.g. http://www.cakephp.org/img/cake-logo.png.
This would cause the remote file to be downloaded, saved to your local filesystem and then attached to the record.

great stuff, thanks.
I just have a problem: MyModel hasOne Attachment, and i have a view with MyModel Fields plus the Attachment File Upload.
View Template:
echo $form->input('Attachment.file',array('type'=>'file'));If the file upload is empty MyModel gets saved and the Validation of Attachment isn't triggered (Validation of Attachment works fine when a file is selected)
How can i adjust my models to require a file to be attached and error on empty fileUpload?
But has anybody had the issue where xls files are recognised as application/msword mime type? It doesn't seem to be the code it seems to be how the magic.mime/php deals with mime types. Any ideas anyone?
BTW it happens on my Windows XP test server (WAMP) and my Centos 5.2 production server.
I have currently got as far as deploying the latest release of this on on 1.2 final and am currently successfully adding the fields 'model`, `group`, `alternative`, `cattery_id`, `modified`, `created`, but no file is uploaded and no dirname, basename or checksum are being written, with no errors being reported.
Any suggestions would be much appreciated.
Many thanks in advance,
Jon
I am trying to make Attachmet behavior work on my website with no success. When I try the Example Movie Module,Controller and Views I get
Fatal error: Class 'Imagick' not found in /home/....
I checked that ImageMagick is installed on my shared-web server.
Well I am kind of lost here. Please help me.
First of all installation steps are different in README file and here. So which document I have to follow for the working example.
For example in README file Step 4 and 5 aren't mentioned here.
Step 4
------
Add default media plugin configuration by appending this line to your app's core.php:
...
require_once(APP.'plugins'.DS.'media'.DS.'config'.DS.'core.php');
Step 5
------
Define MEDIA and MEDIA_URL in your config/bootstrap.php or config/core.php:
...
define('MEDIA','/path/to/your/app/webroot/media/');
define('MEDIA_URL','media/');
as well as var $actsAs = array('Attachment'); isn't mentioned in READ ME file.
Could anyone provide a simple example for uploading PDF, AVI , JPEG files.
Thanks for your efforts.
I want to use it with AjaxHelper. so that I have tried it using $ajax->form() and $ajax->submit(). but it doesn't work.
Normally(w/o Ajax) its working gr8.
Please reply ASAP.
In reply to kapcee above: You can't (easily) send file using Ajax. There are work arounds, but you won't be able to do it by simply using the AjaxHelper. This is not a bug about CakePHP or this behavior, but a browser "feature". Just google 'send file ajax' and you will understand.
line 511 should be:
if(!$this->Attachment->delete($attachment['id'])) {
I like the flexibility and the implementation, too.
I'm excited to see how development continues.
I'm new to cakephp (actually, this is my first comment on the bakery and all, so please be kind ^_^)
I had an hard time while getting this behaviour to work on windows;
i'll share my experience hoping it can help someone else in the same situation.
The test were made using Win XP and Wamp2.0 -> http://www.wampserver.com/en/ with Apache 2.2.8 - MySQL 5.0.51b - PHP 5.2.6
attm 0.42, cake core to the latest svn (6816), app beta 6311
I spent some time tracking down my fault (other than using Windows). And the fault was.. I was using Windows.
If you don't want to run every time into the "Could not determine temporary file type" error, you'll have to take into consideration some path issues.
First, in {APP}/models/behaviours/attachment.php:
edit the default settings property:
private $defaultSettings = array( [..]
adding the right path to the allowed paths key, i.e:
'allowPaths' => array(TMP,'/tmp/',WWW_ROOT,'C:\\wamp\\tmp\\', 'C:/wamp/tmp/' )
This will save you an headhache ^_^
check your php.ini for the actual value of tmp - the Transfer class (in {APP}/Vendors) will then able to get the right temp uploaded file.
Second - After that, you'll notice, there is another problem.
The isLocalFile method (and therefore the isUploadedLocalFile method too) always evaluates to false.
The problem lies -i think- in the
parse_url($file)
function used in conjunction with the windows filesystem.
$url['scheme'] shoul be set to "file" (or null) for a local file.
In my case (try pr($url['scheme'])..) it was set to "C", because the tmp file is something like "C:\wamp\tmp\phpB11.tmp" !!!
(the drive letter before the colon is interpreted as the scheme)
I didn't dig deeply into the issue; just came up with the following workaround.
$url['scheme'] may be set to any drive (A: trough Z:), so just accept any single char string ^_^:
(in yourAppName/vendors/transfer.php):
private function isLocalFile($file)
{
if(!is_string($file)) {
//pr("filename is not a string". $file);
return false;
}
$url = parse_url($file);
//pr("url " . $url . "<br>". $file);
if(isset($url['scheme']) && $url['scheme'] == 'file') {
$file = $file = substr($file,7);
//pr("scheme = file (substr 7)" . $file);
// CHANGE THIS /////////////////
//} elseif(!empty($url['scheme'])) {
// TO: //////////////
} elseif(!empty($url['scheme']) && strlen($url['scheme']) != 1) {
//pr("url scheme " . $url['scheme'] );
//pr("scheme not = file " . $file);
return false;
}
if(is_file($file) && is_executable($file)) {
$this->errors[] = 'File is a local file but executable bit is set.';
//pr('is executable'. $this->errors);
return false;
}
return true;
}
You're almost done. The temp files now pass the checks on windows too :)
But there is anoter problem
Third - make sure the PECL file_info extension is properly installed.
AND bug free..
see:
http://pecl.php.net/bugs/bug.php?id=7555
It's possible to set the second parameter of the finfo_open function, i.e.
finfo_open(FILEINFO_MIME,"C:/wamp/php/extras/magic"
for every occurrence in {APP}/vendors/mimetype.php,
but you don't want to have a class that would'nt work when you commit to the (probabably linux) production server, don't you?
THE solution that worked for me is there:
http://www.php.net/manual/en/fileinfo.installation.php
Note:
it seems that the php.ini setting
mime_magic.magicfile = "C:\wamp\bin\php\extras\magic.mime"
does not work on windows!
You HAVE TO set a (system) ENVIRONEMENT VARIABLE called MAGIC -
set it to
C:\wamp\bin\php\extras\magic
if the main magic database file is
C:\wamp\bin\php\extras\magic.mime
Restart the system (yes, we are on windows)
And you're done!
---
Other
You may also need/want to bypass the mimetype check, by setting the second parameter ($skip) to
array([..], 'mimetype')
on every call of the $Transfer->check method.
I.e.:
if(!$this->check('tmp',array('extension','mimetype'))) {
return false;
}
I had another problem with the security component; if included it gave me errors while updating a record with more than one attached file. (maybe this is not a windows only issue)
Finally, I think it's a good practice to set the configuration for the behaviour (see the examples provided with the behaviout)
'Attachment' => array(
'base' => '{APP}webroot',
'dirname' => 'files/organizations',
'filename' => '{BASENAME}',
'overwriteExisting' => true,
'maxSize' => '8M',
),
Thanks again David,
and to windows users, happy baking (with attachments)!
} elseif($cached = Cache::read($cacheKey,$this->cacheConfig)) {
$content = $cached;
to
} elseif($cached = Cache::read($cacheKey,$this->cacheConfig)) {
echo $content = $cached;
exit;
The error will disappear when you download the nightly build and the 0.42 attachments. At least this worked for me.
Later, I got another error about PATHINFO_FILENAME but fixed it with upgrading php from 5.1.x to 5.2.x ( http://www.php.net/pathinfo )
I hope tihs will help you.
However, I still get an error about the temporary file 'has incorrect mimetype'.
As far as I can see, the error occurs in the
when making the call:
Any help will be appreciated.
I'm getting this error as well while I have 'Attachment' defined in both my components and and actsAs arrays.
1.2.65981.2.0.6613I had to comment out lines 76-80 in the component as it was causing the app to timeout (possibly because modelClass isn't there, I believe it's modelNames (array))I also had to change the php opening tag from the short tag format in the element on line 50 as I purposely don't enable short tag support to maximize code portability.
I had better luck debugging with die(debug($foo)) because of timeouts and file permissions that are probably due to my particular setup - but if anyone else is having timeouts and issues of this behaviour not working, try replacing trigger_errors with dies/debugs and adding dies/debugs before function return values where you think you might be having problems. I'm sure there's more elegant ways, but I did get this working finally for my app using .42 and 6613 on php 5.2.5 with apache 2.2.8, thanks David for all your efforts.
I encounter similar problems that others reported. First, the code never enter this line:
if(isset($attachment['file'])) {
because of the errors that Abdullah Zainul Abidin reported.
Debugging attachment show this:
[ExmapleAttachment] => Array
(
[47e2d69465f77] => Array
(
[name] => Array
(
[file] => xxx.jpg
)
[type] => Array
(
[file] => image/jpeg
)
[tmp_name] => Array
(
[file] => \\xampplite\\tmp\\php237.tmp
)
[error] => Array
(
[file] => 0
)
[size] => Array
(
[file] => 22460
)
)
)
After implementing Abdullah fix (e.g then isset($attachment['file']) would eval to true), the code enters the loop, but then it complains about mimetype (e.g because it's \\xampplite\\tmp\php237.tmp) during setTemporary.
Even after I disable mimetype check, I got stuck during setDestination because of extension checking (e.g. c:\\xxx.jpg) failed because it didn't started with 'file://'???
I am running on Windows, Firefox.
Using this line of code:
input($assocAlias.'.'.$id.'.file',array('label' => false,'type' => 'file'));?>
generating this line on the view:
Anything suspicious? Does anyone get this componenet working? Please share your knowledge. Thanks.
This looks like the data send to the controller isn't parsed right. This was fixed (don't know the exact revision) in cake 1.2 svn. In 1.2 beta the problem was existent. So I would again suggest to update to latest cake version from svn.
After doing that the array should look like this:
[ExampleAttachment] => Array
(
[47e2d69465f77] => Array
(
[name] => xxx.jpg
[type] => image/jpeg
[tmp_name] => \\xampplite\\tmp\\php237.tmp
[error] => 0
[size] => 22460
)
)
By the way I dont't if it's just a typo you made in this text but I think it must be "ExampleAttachment" instead of "ExmapleAttachment"
Please also update to Attachments 0.42 this problem should already been fixed.
No your path is OK. You don't need to prepend "file://???" the support for this optional syntax may be removed in Attachments 0.50
The code isn't tested on windows ... yet ...
This is also related to your cake version.
I get this error:
I a working with the examples provided in the source and the latest beta: 1.2.0.6311
Any ideas?
So let's say you have an Examples Controller with the Attachment Component loaded. Then you should also have an Example Model with the Attachment Behavior in it's $actsAs array.
hope that helps
Thanks for your quick reply!
I do have the $actsAs array in the example model and the var $components = array('Attachment'); in the examples_controller.
I also upgraded from cakephp beta to the latest from svn and still get the same error.
The only thing i can think of is my php ver 5.1.4. Any other ideas?
Please download Attachments 0.42.
As long as cake 1.2 is not final the project is going to rely onto the latest cake version from svn.
I fixed the schema bug, the no config bug and also included the needed change suggested by Oliver B. Deland in 0.41. You can download the new version from cakeforge if you like. The Mimetype detection should work better in the new version as well because it won't rely exclusively onto the extension.
@Abdullah Zainul Abidin:
You can find a description on how to configure the behavior in the updated README or take a look on this message http://groups.google.com/group/cake-php/msg/dada642fda1ae7bf I could not find any wrong arrangements of the array in the beforeSave(). If you encounter wrong arrangements on html form file uploads try to check out the latest cake version from SVN.
The File was not accepted for transfer. Not going to save this record. Source file argument is invalid or file is of unknown type Error is not that serious and I'm maybe going to remove the message in the next release.
The behavior will iterate over the submitted data and checks if it finds a file to upload if it doesn't it show this message.
So e.g. if you edit a record with files already attached to it and you don't change anything and then submit it you will see the above message. This only means that there is nothing to upload/update.
P.s.: I'm currently not able to make edits to this article. Hope it's going to be possible again soon...
Behaviors have changed. They are no longer accessed as arrays in the model, but as BehaviorsCollection Object.
So you need to change components/attachment.php, line 76 to:
if(!isset($controller->{$controller->modelClass}->Behaviors->Attachment))
Hope this helps someone.
Im stuck in a mime type detection problem... since the files are being saved in the temp folder as "randomname.tmp", the detection is not made thru the extension, and cant get it working. I'll try in a production lamp server asap, since im testing in windows. :)
But one thing that I can think off the top of my head is that if you don't require mime type detection then why don't you just switch it off?
And about the error you got before
With my fix you might still get it too. Because after the remapping of the array, some of the arrays would be empty and the program would try to copy an empty file. No biggie though because if you just turn off debugging and suppress the message it should still reroute you back to your index page.
And more thing I forgot to mention, but you'll only realize this after you got the upload working and editing it again to add another file. In the tutorial it doesn't mention that the element actually require the Number helper. So in your controller you should have the line:
var $helpers=array('Number');
But there is a few things I would like to mention here on how to get it working. It took me a whole day just to figure it out.
The first part is the database. The script included in the download is missing a the auto increment extra. So you should change the file in config/sql/attachments.php so that the line with:
'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
is changed to:
'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'key' => 'primary', 'extra' => 'auto_increment'),
Note the auto increment part at the end. Once that is done you can create it with bake. The command is just (running from your current cake root directory):
cake/console/cake schema run create attachments
That should set up your database if all your configuration is right.
And then I find that I need to change the file models/behaviors/attachment.php. In there under the function setup I have to add:
Right at the beginning of the function so that there would be a default if there wasn't one passed by the user (And actually I don't have a clue how to pass one actually).if(!is_array($config)){
$config=$this->defaultSettings;
}
And then in the same file under the beforeSave function I have to add the following code:
foreach($attachment as $key=>$data){
if(is_array($data)){
foreach($data as $skey=>$sdata){
$uh[$skey][$key]=$sdata;
}
}
}
$attachment=$uh;
before line 365 just before the test:
if(isset($attachment['file'])) {
because it seems the arrangments of the array is wrong for the Transfer object. But apart from that it works pretty well. Thank you for this great work.
Thanks!
edit: couldn't make it work yet :(. I used the examples you provide, but nothing happened. I've been peaking in the code all afternoon, and found some things, but not sure how to handle them. Basically, I found the code never entered the conditional in line 365 in the the behavior file. Changed the "$attachment['file']" to "$attachment['name']['file']" in that line, and entered, but couldn't save yet... At least now I got a nice error (File was not accepted for transfer. Not going to save this record. Source file argument is invalid or file is of unknown type)...
I'll keep trying/seeing this and let you know. :)