The Upload Plugin is an attempt to sanely upload files using techniques garnered packages such as MeioUpload , UploadPack and PHP documentation.
Media Plugin is too complicated, and it was a PITA to merge the latest updates into MeioUpload, so here I am, building yet another upload plugin. I'll build another in a month and call it "YAUP".
- CakePHP 2.x
- Imagick/GD PHP Extension (for Thumbnail Creation)
- PHP5
- Patience
For CakePHP 1.3 support, please see the 1.3 branch.
[Manual]
- Download this: http://github.com/josegonzalez/upload/zipball/master
- Unzip that download.
- Copy the resulting folder to
app/Plugin
- Rename the folder you just copied to
Upload
[GIT Submodule]
In your app directory type:
git submodule add -b master git://github.com/josegonzalez/upload.git Plugin/Upload
git submodule init
git submodule update
[GIT Clone]
In your Plugin
directory type:
git clone -b master git://github.com/josegonzalez/upload.git Upload
In 2.0 you need to enable the plugin your app/Config/bootstrap.php
file:
CakePlugin::load('Upload');
If you are already using CakePlugin::loadAll();
, then this is not necessary.
CREATE table users (
id int(10) unsigned NOT NULL auto_increment,
username varchar(20) NOT NULL,
photo varchar(255)
);
<?php
class User extends AppModel {
public $actsAs = array(
'Upload.Upload' => array(
'photo'
)
);
}
<?php echo $this->Form->create('User', array('type' => 'file')); ?>
<?php echo $this->Form->input('User.username'); ?>
<?php echo $this->Form->input('User.photo', array('type' => 'file')); ?>
<?php echo $this->Form->end(); ?>
Using the above setup, uploaded files cannot be deleted. To do so, a field must be added to store the directory of the file as follows:
CREATE table users (
`id` int(10) unsigned NOT NULL auto_increment,
`username` varchar(20) NOT NULL,
`photo` varchar(255) DEFAULT NULL,
`photo_dir` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
);
<?php
class User extends AppModel {
public $actsAs = array(
'Upload.Upload' => array(
'photo' => array(
'fields' => array(
'dir' => 'photo_dir'
)
)
)
);
}
<?php echo $this->Form->create('User', array('type' => 'file')); ?>
<?php echo $this->Form->input('User.username'); ?>
<?php echo $this->Form->input('User.photo', array('type' => 'file')); ?>
<?php echo $this->Form->input('User.photo_dir', array('type' => 'hidden')); ?>
<?php echo $this->Form->end(); ?>
Thumbnails are not automatically created. To do so, thumbnail sizes must be defined:
Note: by default thumbnails will be generated by imagick, if you want to use GD you need to set the thumbnailMethod attribute. Example: 'thumbnailMethod' => 'php'
.
<?php
class User extends AppModel {
public $actsAs = array(
'Upload.Upload' => array(
'photo' => array(
'fields' => array(
'dir' => 'photo_dir'
),
'thumbnailSizes' => array(
'xvga' => '1024x768',
'vga' => '640x480',
'thumb' => '80x80'
)
)
)
);
}
Multiple files can also be attached to a single record:
<?php
class User extends AppModel {
public $actsAs = array(
'Upload.Upload' => array(
'resume',
'photo' => array(
'fields' => array(
'dir' => 'profile_dir'
),
'thumbnailSizes' => array(
'xvga' => '1024x768',
'vga' => '640x480',
'thumb' => '80x80'
)
)
)
);
}
It is now possible to generate a thumbnail for the first page of a PDF file. (Only works with the imagick
thumbnailMethod
.)
Please read about the Behavior options for more details as to how to configure this plugin.
In some cases you will want to store multiple file uploads for a multiple models, but will not want to use multiple tables for some reason. For example, we might have a Post
model that can have many images for a gallery, and a Message
model that has many videos. In this case, we would use an Attachment
model:
Post hasMany Attachment
We could use the following database schema for the Attachment
model:
CREATE table attachments (
`id` int(10) unsigned NOT NULL auto_increment,
`model` varchar(20) NOT NULL,
`foreign_key` int(11) NOT NULL,
`name` varchar(32) NOT NULL,
`attachment` varchar(255) NOT NULL,
`dir` varchar(255) DEFAULT NULL,
`type` varchar(255) DEFAULT NULL,
`size` int(11) DEFAULT 0,
`active` tinyint(1) DEFAULT 1,
PRIMARY KEY (`id`)
);
Our attachment records would thus be able to have a name and be activated/de-activated on the fly. The schema is simply an example, and such functionality would need to be implemented within your application.
Once the attachments
table has been created, we would create the following model:
<?php
class Attachment extends AppModel {
public $actsAs = array(
'Upload.Upload' => array(
'attachment' => array(
'thumbnailSizes' => array(
'xvga' => '1024x768',
'vga' => '640x480',
'thumb' => '80x80',
),
),
),
);
public $belongsTo = array(
'Post' => array(
'className' => 'Post',
'foreignKey' => 'foreign_key',
),
'Message' => array(
'className' => 'Post',
'foreignKey' => 'foreign_key',
),
);
}
We would also need to present a valid counter-relationship in the Post
model:
<?php
class Post extends AppModel {
public $hasMany = array(
'Image' => array(
'className' => 'Attachment',
'foreignKey' => 'foreign_key',
'conditions' => array(
'Attachment.model' => 'Post',
),
),
);
}
The key thing to note here is the Post
model has some conditions on the relationship to the Attachment
model, where the Attachment.model
has to be Post
. Remember to set the model
field to Post
, or whatever model it is you'd like to attach it to, otherwise you may get incorrect relationship results when performing find queries.
We would also need a similar relationship in our Message
model:
<?php
class Message extends AppModel {
public $hasMany = array(
'Video' => array(
'className' => 'Attachment',
'foreignKey' => 'foreign_key',
'conditions' => array(
'Attachment.model' => 'Message',
),
),
);
}
Please note that this is not the only way to represent file uploads, but it is documented here for reference.
pathMethod
: The method to use for file paths. This is appended to thepath
option below- Default: (string)
primaryKey
- Options:
- flat: Does not create a path for each record. Files are moved to the value of the 'path' option
- primaryKey: Path based upon the record's primaryKey is generated. Persists across a record
- random: Random path is generated for each file upload. Does not persist across a record.
- Default: (string)
path
: A path relative to theAPP_PATH
. Should end in{DS}
- Default: (string)
'{ROOT}webroot{DS}files{DS}{model}{DS}{field}{DS}'
- Tokens:
- {ROOT}: Replaced by a
rootDir
option - {DS}: Replaced by a
DIRECTORY_SEPARATOR
- {model}: Replaced by the Model Alias
- {field}: Replaced by the field name
- {size}: Replaced by a zero-length string (the empty string) when used for the regular file upload path
- {geometry}: Replaced by a zero-length string (the empty string) when used for the regular file upload path
- {ROOT}: Replaced by a
- Default: (string)
fields
: An array of fields to use when uploading files- Default: (array)
array('dir' => 'dir', 'type' => 'type', 'size' => 'size')
- Options:
- dir: Field to use for storing the directory
- type: Field to use for storing the filetype
- size: Field to use for storing the filesize
- Default: (array)
rootDir
: Root directory for moving images. Auto-prepended topath
andthumbnailPath
where necessary- Default (string)
ROOT . DS . APP_DIR . DS
- Default (string)
mimetypes
: Array of mimetypes to use for validation- Default: (array) empty
extensions
: Array of extensions to use for validation- Default: (array) empty
maxSize
: Max filesize in bytes for validation- Default: (int)
2097152
- Default: (int)
minSize
: Minimum filesize in bytes for validation- Default: (int)
8
- Default: (int)
maxHeight
: Maximum image height for validation- Default: (int)
0
- Default: (int)
minHeight
: Minimum image height for validation- Default: (int)
0
- Default: (int)
maxWidth
: Maximum image width for validation- Default: (int)
0
- Default: (int)
minWidth
: Minimum image width for validation- Default: (int)
0
- Default: (int)
deleteOnUpdate
: Whether to delete files when uploading new versions (potentially dangerous due to naming conflicts)- Default: (boolean)
false
- Default: (boolean)
thumbnails
: Whether to create thumbnails or not- Default: (boolean)
true
- Default: (boolean)
thumbnailMethod
: The method to use for resizing thumbnails- Default: (string)
imagick
- Options:
- imagick: Uses the PHP
imagick
extension to generate thumbnails - php: Uses the built-in PHP methods (
GD
extension) to generate thumbnails
- imagick: Uses the PHP
- Default: (string)
thumbnailName
: Naming style for a thumbnail- Default:
NULL
- Note: The tokens
{size}
and{filename}
are both valid for naming and will be auto-replaced with the actual terms. - Note: As well, the extension of the file will be automatically added.
- Note: When left unspecified, will be set to
{size}_{filename}
or{filename}_{size}
depending upon the value ofthumbnailPrefixStyle
- Default:
thumbnailPath
: A path relative to therootDir
where thumbnails will be saved. Should end in{DS}
. If not set, thumbnails will be saved atpath
.- Default:
NULL
- Tokens:
- {ROOT}: Replaced by a
rootDir
option - {DS}: Replaced by a
DIRECTORY_SEPARATOR
- {model}: Replaced by the Model Alias
- {field}: Replaced by the field name
- {size}: Replaced by the size key specified by a given
thumbnailSize
- {geometry}: Replaced by the geometry value specified by a given
thumbnailSize
- {ROOT}: Replaced by a
- Default:
thumbnailPrefixStyle
: Whether to prefix or suffix the style onto thumbnails- Default: (boolean)
true
prefix the thumbnail - Note that this overrides
thumbnailName
whenthumbnailName
is not specified in your config
- Default: (boolean)
thumbnailQuality
: Quality of thumbnails that will be generated, on a scale of 0-100- Default: (int)
75
- Default: (int)
thumbnailSizes
: Array of thumbnail sizes, with the size-name mapping to a geometry- Default: (array) empty
thumbnailType
: Override the type of the generated thumbnail- Default: (mixed)
false
orpng
when the upload is a Media file - Options:
- Any valid image type
- Default: (mixed)
mediaThumbnailType
: Override the type of the generated thumbnail for a non-image media (pdfs
). OverridesthumbnailType
- Default: (mixed)
png
- Options:
- Any valid image type
- Default: (mixed)
saveDir
: Can be used to turn off saving the directory- Default: (boolean)
true
- Note: Because of the way in which the directory is saved, if you are using a
pathMethod
other than flat and you setsaveDir
to false, you may end up in situations where the file is in a location that you cannot predict. This is more of an issue for apathMethod
ofrandom
thanprimaryKey
, but keep this in mind when fiddling with this option
- Default: (boolean)
Styles are the definition of thumbnails that will be generated for original image. You can define as many as you want.
<?php
class User extends AppModel {
public $name = 'User';
public $actsAs = array(
'Upload.Upload' => array(
'photo' => array(
'thumbnailSizes' => array(
'big' => '200x200',
'small' => '120x120'
'thumb' => '80x80'
)
)
)
);
}
Styles only apply to images of the following types:
- image/bmp
- image/gif
- image/jpeg
- image/pjpeg
- image/png
- image/vnd.microsoft.icon
- image/x-icon
You can specify any of the following resize modes for your sizes:
100x80
- resize for best fit into these dimensions, with overlapping edges trimmed if original aspect ratio differs[100x80]
- resize to fit these dimensions, with white banding if original aspect ratio differs100w
- maintain original aspect ratio, resize to 100 pixels wide80h
- maintain original aspect ratio, resize to 80 pixels high80l
- maintain original aspect ratio, resize so that longest side is 80 pixels
By default, no validation rules are attached to the model. One must explicitly attach each rule if needed. Rules not referring to PHP upload errors are configurable but fallback to the behavior configuration.
Check that the file does not exceed the max file size specified by PHP
public $validate = array(
'photo' => array(
'rule' => 'isUnderPhpSizeLimit',
'message' => 'File exceeds upload filesize limit'
)
);
Check that the file does not exceed the max file size specified in the HTML Form
public $validate = array(
'photo' => array(
'rule' => 'isUnderFormSizeLimit',
'message' => 'File exceeds form upload filesize limit'
)
);
Check that the file was completely uploaded
public $validate = array(
'photo' => array(
'rule' => 'isCompletedUpload',
'message' => 'File was not successfully uploaded'
)
);
Check that a file was uploaded
public $validate = array(
'photo' => array(
'rule' => 'isFileUpload',
'message' => 'File was missing from submission'
)
);
Check that the PHP temporary directory is missing
public $validate = array(
'photo' => array(
'rule' => 'tempDirExists',
'message' => 'The system temporary directory is missing'
)
);
If the argument $requireUpload
is passed, we can skip this check when a file is not uploaded:
public $validate = array(
'photo' => array(
'rule' => array('tempDirExists', false),
'message' => 'The system temporary directory is missing'
)
);
Check that the file was successfully written to the server
public $validate = array(
'photo' => array(
'rule' => 'isSuccessfulWrite',
'message' => 'File was unsuccessfully written to the server'
)
);
If the argument $requireUpload
is passed, we can skip this check when a file is not uploaded:
public $validate = array(
'photo' => array(
'rule' => array('isSuccessfulWrite', false),
'message' => 'File was unsuccessfully written to the server'
)
);
Check that a PHP extension did not cause an error
public $validate = array(
'photo' => array(
'rule' => 'noPhpExtensionErrors',
'message' => 'File was not uploaded because of a faulty PHP extension'
)
);
If the argument $requireUpload
is passed, we can skip this check when a file is not uploaded:
public $validate = array(
'photo' => array(
'rule' => array('noPhpExtensionErrors', 1024, false),
'message' => 'File was not uploaded because of a faulty PHP extension'
)
);
Check that the file is of a valid mimetype
public $validate = array(
'photo' => array(
'rule' => array('isValidMimeType', array('valid/mimetypes', 'array/here')),
'message' => 'File is of an invalid mimetype'
)
);
If the argument $requireUpload
is passed, we can skip this check when a file is not uploaded:
public $validate = array(
'photo' => array(
'rule' => array('isValidMimeType', array('valid/mimetypes', 'array/here'), false),
'message' => 'File is of an invalid mimetype'
)
);
Check that the upload directory is writable
public $validate = array(
'photo' => array(
'rule' => array('isWritable'),
'message' => 'File upload directory was not writable'
)
);
If the argument $requireUpload
is passed, we can skip this check when a file is not uploaded:
public $validate = array(
'photo' => array(
'rule' => array('isWritable', false),
'message' => 'File upload directory was not writable'
)
);
Check that the upload directory exists
public $validate = array(
'photo' => array(
'rule' => array('isValidDir'),
'message' => 'File upload directory does not exist'
)
);
If the argument $requireUpload
is passed, we can skip this check when a file is not uploaded:
public $validate = array(
'photo' => array(
'rule' => array('isValidDir', false),
'message' => 'File upload directory does not exist'
)
);
Check that the file is below the maximum file upload size (checked in bytes)
public $validate = array(
'photo' => array(
'rule' => array('isBelowMaxSize', 1024),
'message' => 'File is larger than the maximum filesize'
)
);
If the argument $requireUpload
is passed, we can skip this check when a file is not uploaded:
public $validate = array(
'photo' => array(
'rule' => array('isBelowMaxSize', 1024, false),
'message' => 'File is larger than the maximum filesize'
)
);
Check that the file is above the minimum file upload size (checked in bytes)
public $validate = array(
'photo' => array(
'rule' => array('isAboveMinSize', 1024),
'message' => 'File is below the mimimum filesize'
)
);
If the argument $requireUpload
is passed, we can skip this check when a file is not uploaded:
public $validate = array(
'photo' => array(
'rule' => array('isAboveMinSize', 1024, false),
'message' => 'File is below the mimimum filesize'
)
);
Check that the file has a valid extension
public $validate = array(
'photo' => array(
'rule' => array('isValidExtension', array('ext', 'array', 'here')),
'message' => 'File has an invalid extension'
)
);
If the argument $requireUpload
is passed, we can skip this check when a file is not uploaded:
public $validate = array(
'photo' => array(
'rule' => array('isValidExtension', array('ext', 'array', 'here'), false),
'message' => 'File has an invalid extension'
)
);
Check that the file is above the minimum height requirement (checked in pixels)
public $validate = array(
'photo' => array(
'rule' => array('isAboveMinHeight' 150),
'message' => 'File is below the minimum height'
)
);
If the argument $requireUpload
is passed, we can skip this check when a file is not uploaded:
public $validate = array(
'photo' => array(
'rule' => array('isAboveMinHeight', 150, false),
'message' => 'File is below the minimum height'
)
);
Check that the file is below the maximum height requirement (checked in pixels)
public $validate = array(
'photo' => array(
'rule' => array('isBelowMaxHeight', 150),
'message' => 'File is above the maximum height'
)
);
If the argument $requireUpload
is passed, we can skip this check when a file is not uploaded:
public $validate = array(
'photo' => array(
'rule' => array('isBelowMaxHeight', 150, false),
'message' => 'File is above the maximum height'
)
);
Check that the file is above the minimum width requirement (checked in pixels)
public $validate = array(
'photo' => array(
'rule' => array('isAboveMinWidth', 150),
'message' => 'File is below the minimum width'
)
);
If the argument $requireUpload
is passed, we can skip this check when a file is not uploaded:
public $validate = array(
'photo' => array(
'rule' => array('isAboveMinWidth', 150, false),
'message' => 'File is below the minimum width'
)
);
Check that the file is below the maximum width requirement (checked in pixels) public $validate = array( 'photo' => array( 'rule' => array('isBelowMaxWidth', 150), 'message' => 'File is above the maximum width' ) );
If the argument $requireUpload
is passed, we can skip this check when a file is not uploaded:
public $validate = array(
'photo' => array(
'rule' => array('isBelowMaxWidth', 150, false),
'message' => 'File is above the maximum width'
)
);
Copyright (c) 2010-2012 Jose Diaz-Gonzalez
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.