MeioUpload Behavior – An improved File Upload Behavior for CakePHP
MeioUpload Behavior is an improved File Upload Behavior for the CakePHP framework. It’s based on the Digital Spaghetti’s Upload Behavior.
Juan Basso created a project hosted at github. I am unable to keep working on this behavior an he is making some updates to the code. I think github is a great tool because you can simply fork the project and make your own changes. Go check his work: http://github.com/jrbasso/MeioUpload/tree/master
Download
MeioUploadBehavior version 1.0.1
Related Articles
- Documentation translated to French
- Documentation translated to Ukrainian
- Junnan’s post
- Dave Rupert’s comment about MeioUpload Behavior
Features
- Can be used for any kind of files;
- Accepts custom directory for files to be uploaded;
- Validates the file extension and mime-type due to the behavior configuration;
- Validates the max file size;
- Allow custom validation rules;
- Allow as many thumbnails formats as you want;
- Allow more then one field to be uploadable, with custom options per field;
- Stores the directory, filesize, and mime-type in the database if the table has these fields. Also allows to customize these fields names;
- Allow the use of default files and deleting files without deleting the entire record;
- Delete files when the record is deleted or updated with a new file;
- Also works in the $model->saveAll method.
Usage
- Place the meio_upload.php file in your app/models/behaviors folder;
- If you want to use thumbnails, download Nate’s phpThumb Component and place it in your app/controllers/components folder;
- Insert in your model table a character varying field:
CREATE TABLE `products` ( `id` int(8) unsigned NOT NULL auto_increment, `name` varchar(255) default NULL, `description` text default NULL, `price` double default NULL, `picture` varchar(255) default NULL, `dir` varchar(255) default NULL, `mimetype` varchar(255) NULL, `filesize` int(11) unsigned default NULL, `created` datetime default NULL, `modified` datetime default NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
- Add the MeioUpload in the $actsAs of your model:
var $actsAs = array( 'MeioUpload' => array( 'picture' => array( 'dir' => 'img{DS}{model}{DS}{field}', 'create_directory' => true, 'allowed_mime' => array('image/jpeg', 'image/pjpeg', 'image/png'), 'allowed_ext' => array('.jpg', '.jpeg', '.png'), 'thumbsizes' => array( 'normal' => array('width'=>200, 'height'=>200), ), 'default' => 'default.jpg', ) ) ); - Use a form file input to the field:
echo $form->input('picture', array('type' => 'file')); - Make sure your form has multipart/form-data enctype:
echo $form->create('Product',array('type' => 'file')); - Make sure the PHP has permission to write in the folder yous specified and the php.ini allow the MAX_FILE_SIZE you specified.
Options
When you apply the MeioUpload Behavior to a Model, you pass an array with your options. Here are the options you can change and it’s defaults.
-
dir
The folder where the uploaded files will be saved. It must be inside the app/webroot directory and defaults to an empty string, what means that it will be the app/webroot itself. If you specify some path, then it will be app/webroot/the_path_you/specified. In this option you may use the {DS} pattern, what will be converted to ‘/’ in UNIX systems and to ‘\’ in Windows systems. -
allowed_mime
This option specifies the allowed mime-types for the files. It defaults to an empty array. What means that every mime-type will be accepted. But if you specify an array with the mime-types allowed, only files of these mime-types will be accepted.var $actsAs = array( 'picture' => array( 'allowed_mime' => array('image/jpeg', 'image/pjpeg', 'image/png') ) );The code above says that the picture field will be a file uploaded, and it allows ‘image/jpeg’, ‘image/pjpeg’ and ‘image/png’ mime-types. -
allowed_ext
This option specifies the allowed file extensions. It defaults to an empty array. What means that every extension will be accepted. But if you specify an array with the extensions allowed, only files with these extensions will be accepted.var $actsAs = array( 'picture' => array( 'allowed_ext' => array('.jpg', '.jpeg', '.png') ) );The code above says that the picture field will be a file uploaded, and it allows ‘.jpg’, ‘.jpeg’ and ‘.png’ extensions. -
create_directory
If you want to create the directory specified in the dir option if it don’t exists, then set this option to true. It already defaults to true, but you can set it to false. -
max_size
The max file size allowed in the upload. Be sure it’s lower then the php.ini configuration. You can set a numeric value of bytes, or set it in other unit like ‘8 Kb’, only ‘kb’, ‘mb’, ‘gb’ and ‘tb’ units are allowed and it’s not case-sensitive. It defaults to 2 Mb.var $actsAs = array( 'picture' => array( 'max_size' => '4 Mb' ) ); -
default
If you have a file that you want to use if the field is left blank, like a default picture for users who don’t have one, set this option to the name of this file. Make sure it is inside the folder specified in the ‘dir’ option.var $actsAs = array( 'picture' => array( 'default' => 'default.jpg' ) ); -
fields
With this option you can customize the field inside your model table, where the data will be saved. The filename is the key of the options array, and you can setup fields to store the filesize, mime-type and directory. It defaults to ‘filesize’, ‘mimetype’ and ‘dir’ respectively, but you can customize with an array:var $actsAs = array( 'picture' => array( 'fields' => array( 'filesize' => 'picture_filesize', 'mimetype' => '{field}_mimetype', 'dir' => 'pictures_folder' ) ) );The code above says that your filesize, mimetype and dir fields will be ‘picture_filesize’, ‘picture_mimetype’ and ‘picture_folder’ respectively. And the filename field will be pictures. You may notice that in mimetype case, i used the constant {field} that will be replaced with the field name(picture in this case).
Validations
The behavior automatically include some validations to your field. Here they are:
- FieldName Checks if the field has been setup to be and uploadable.
- Dir Checks if the directory exists or if it can be created. This validation depends on the option ‘create_directory’.
- Empty Checks if the filename is not empty. It defaults to be used only on create.
- UploadError Checks if ocurred erros in the upload.
- MaxSize Checks if the file isn’t bigger then the max file size option.
- InvalidMime Checks if the file is of an allowed mime-type.
- InvalidExt Checks if the file has an allowed extension.
You can overwrite any of the parameters of these default validation rules. Directly in the model $validate var:
var $validate = array(
'picture' => array(
'Empty' => array(
'check' => false
),
'InvalidExt => array(
'message' => 'This file extension isn't allowed.'
)
)
);
In the code above, i changed the message for the ‘InvalidExt’ default validation. You can also disable any of the default validations by setting the ‘check’ atribute fo false as is shown in the example above for the ‘Empty’ default validation. You can change any of the other parameters of the built-in cake validation:
var $validate = array(
'picture' => array(
'Empty' => array(
'rule' => array('YourDefaultValidation'),
'on' => 'update',
'required' => true
),
)
);
You can also change the validation rule for one you have created. But use this functionality carefully.
Setting up file removing
If you send a form data with as field named data[Model][field][remove] not empty, then the behavior will automatically delete the file associated with the record and set it’s value to the default, if it is set, or to empty. To achieve this you can add a checkbox to your form:
echo $form->input('Product.picture.remove', array('type' => 'checkbox'));


April 16th, 2009 at 04:19
@Calvin Can you post those error messages in english. Thank you!
Any example of multiple upload yet?
Thanks for the great behavior.
April 16th, 2009 at 17:44
Sean, I’ve posted a copy of my translation here: http://rottenrecords.com/tmp/meio_upload_v1.0.1_english.rar The readme file has a list of the changes.
April 20th, 2009 at 06:45
[...] desde una aplicación web, en el caso de usar CakePHP, se facilita mucho gracias al behavior MeioUpload de Vinicius [...]
April 21st, 2009 at 07:24
@Calvin
Thanks for sharing!
May 16th, 2009 at 07:59
@Adam Can you explain better your solution to the problem of array errors?
May 17th, 2009 at 18:50
Hi, i have the following problem:
I have 3 models, terms, images and sounds. A term hasmany images and sounds. I put my code:
Models:———————————————————–
class Image extends AppModel { var $name = ‘Image’; var $components = array(‘Thumb’); var $actsAs = array( ‘MeioUpload’ => array( ‘filename’ => array( ‘dir’ => ‘files{DS}terms{DS}images’, ‘create_directory’ => true, ‘allowed_mime’ => array(‘image/jpeg’, ‘image/pjpeg’, ‘image/png’), ‘allowed_ext’ => array(‘.jpg’ ‘.jpeg’, ‘.png’), ‘thumbsizes’ => array( ‘med’ => array(‘width’=>300, ‘height’=>300), ‘peq’ => array(‘width’=>100, ‘height’=>100) ) ) ) );
class Sound extends AppModel { var $name = ‘Sound’; var $actsAs = array( ‘MeioUpload’ => array( ’sound’ => array( ‘dir’ => ‘files{DS}terms{DS}sounds’, ‘create_directory’ => true, ‘allowed_mime’ => array(‘audio/mpeg’), ‘allowed_ext’ => array(‘.midi’, ‘.mp3′, ‘.wma’), ) ) ); }
class Term extends AppModel { var $actsAs = array(‘Containable’);
var $hasAndBelongsToMany = array( ... ... );
var $hasMany = array( 'Sound', 'Image' );
}
In the controller i have a function like this:
function admin_add() { if (!empty($this->data)) { if ($this->Term->save($this->data)) { if (isset($this->data['Image'])){ $this->data['Image']['term_id']=$this->Term->id; $this->Term->Image->save($this->data); } if (isset($this->data['Sound'])){ $this->data['Sound']['term_id']=$this->Term->id; $this->Term->Sound->save($this->data); } $this->flash(__(‘The term has been saved.’,true), ‘index’); } } }
And in the view i have:
echo $form->create(‘Term’,array(‘type’=>’file’)); echo $form->input(‘Term.name’); echo $form->input(‘Term.locale’); echo $form->input(‘Term.definition’, array(‘rows’ => ‘3′); echo $form->input(‘Sound.sound’,array(‘type’ => ‘file’); echo $form->input(‘Image.filename’,array(‘type’ => ‘file’); echo $form->end(__(‘Save’,true));
When i try to save the form i have this error:
Notice (8): Array to string conversion [CORE/cake/libs/model/datasources/dbo_source.php, line 582]
Code | Context
$model = Sound Sound::$name = “Sound” Sound::$actsAs = array Sound::$useDbConfig = “default” Sound::$useTable = “sounds” Sound::$displayField = “id” Sound::$id = false Sound::$data = array Sound::$table = “sounds” Sound::$primaryKey = “id” Sound::$schema = array Sound::$validate = array Sound::$validationErrors = array Sound::$tablePrefix = “” Sound::$alias = “Sound” Sound::$tableToModel = array Sound::$logTransactions = false Sound::$transactional = false Sound::$cacheQueries = false Sound::$belongsTo = array Sound::$hasOne = array Sound::$hasMany = array Sound::$hasAndBelongsToMany = array Sound::$Behaviors = BehaviorCollection object Sound::$whitelist = array Sound::$cacheSources = true Sound::$findQueryType = NULL Sound::$recursive = 1 Sound::$order = NULL Sound::$_exists = NULL Sound::$__associationKeys = array Sound::$__associations = array Sound::$__backAssociation = array Sound::$__insertID = NULL Sound::$__numRows = NULL Sound::$__affectedRows = NULL Sound::$_findMethods = array Sound::$_log = NULL $fields = array( “sound”, “term_id”, “modified”, “created” ) $values = array( array( “name” => “FX-009.mp3″, “type” => “audio/mpeg”, “tmp_name” => “/tmp/phpTd6h5f”, “error” => 0, “size” => 19644 ), “86″, “2009-05-17 16:30:31″, “2009-05-17 16:30:31″ ) $id = null $count = 4 $i = 4 $valueInsert = array( array( “‘FX-009.mp3′”, “‘audio/mpeg’”, “‘/tmp/phpTd6h5f’”, “‘0′”, “‘19644′” ), “86″, “‘2009-05-17 16:30:31′”, “‘2009-05-17 16:30:31′” ) $fieldInsert = array( “
sound“, “term_id“, “modified“, “created” )Debugger::handleError() – CORE/cake/libs/debugger.php, line 221 join – [internal], line ?? DboSource::create() – CORE/cake/libs/model/datasources/dbo_source.php, line 582 Model::save() – CORE/cake/libs/model/model.php, line 1223 TermsController::admin_add() – APP/controllers/terms_controller.php, line 65 Object::dispatchMethod() – CORE/cake/libs/object.php, line 115 Dispatcher::_invoke() – CORE/cake/dispatcher.php, line 227 Dispatcher::dispatch() – CORE/cake/dispatcher.php, line 194 [main] – APP/webroot/index.php, line 88
Warning (512): SQL Error: 1054: Unknown column ‘Array’ in ‘field list’ [CORE/cake/libs/model/datasources/dbo_source.php, line 525]
Code | Context
$sql = “INSERT INTO
sounds(sound,term_id,modified,created) VALUES (Array, 86, ‘2009-05-17 16:30:31′, ‘2009-05-17 16:30:31′)” $error = “1054: Unknown column ‘Array’ in ‘field list’” $out = nullDebugger::handleError() – CORE/cake/libs/debugger.php, line 221 DboSource::showQuery() – CORE/cake/libs/model/datasources/dbo_source.php, line 525 DboSource::execute() – CORE/cake/libs/model/datasources/dbo_source.php, line 201 DboSource::create() – CORE/cake/libs/model/datasources/dbo_source.php, line 585 Model::save() – CORE/cake/libs/model/model.php, line 1223 TermsController::admin_add() – APP/controllers/terms_controller.php, line 65 Object::dispatchMethod() – CORE/cake/libs/object.php, line 115 Dispatcher::_invoke() – CORE/cake/dispatcher.php, line 227 Dispatcher::dispatch() – CORE/cake/dispatcher.php, line 194 [main] – APP/webroot/index.php, line 88
Query: INSERT INTO
sounds(sound,term_id,modified,created) VALUES (Array, 86, ‘2009-05-17 16:30:31′, ‘2009-05-17 16:30:31′)I’ve noticed that if I put a debug() in the beforesave of meiouploadBehavior, this always has the configuration data for the latest model added to hasmany Terms associations (in this case sound).
Any idea to fix this? Thanks
May 25th, 2009 at 12:50
Конечно, как все говорят, занимательное рядом!
May 27th, 2009 at 17:32
@esezako I’m having the exact same issue and I think I’ve discovered that the problem is actually with the current release of cake. I recently upgraded to 1.2.3.8166 and then noticed that all of the my file uploads stopped working. I switched the cake folder back to the old version and everything worked fine again.
This behavior is awesome and super useful for my project, any chance of looking into this bug? Thanks!
June 5th, 2009 at 13:10
Hi, I have a problem using this behaviour. when I try to upload my image, I have an error message:
Notice (8): Undefined index: Director meio_upload.php, line 551
It seems to be a problem when trying to fix the name of the file. However the file is correctly uploaded !!!
My model’s name is: Director My filed’s name is: background_img
My code for my model:
var $actsAs = array( ‘MeioUpload’ => array( ‘background_img’ => array( ‘dir’ => ‘img{DS}{model}’, ‘create_directory’ => true, ‘allowed_mime’ => array(‘image/jpeg’, ‘image/pjpeg’, ‘image/png’), ‘allowed_ext’ => array(‘.jpg’, ‘.jpeg’, ‘.png’), ‘thumbsizes’ => array( ‘normal’ => array(‘width’=>200, ‘height’=>200) ) ) ) );
My code in the vue: create(‘Director’, array(‘type’=>’file’));?> echo $form->input(‘background_img’, array(‘type’=>’file’));
Please Help ! thinks
June 8th, 2009 at 09:28
Hi, The problem was caused because of my PHP version. It was PHP 4 on my server. Meio_upload seems to work only with PHP 5. It could be great if we can have a PHP 4 version. I’ll try to adapt this behaviour for PHP 4 when I’ll have time.
Regards.