ActAs Image column behavior
This behavior add new column to your model and allow to store images in file system. It can resize images, create several versions, and thubnails. After find model you got link to file for each record in model.
Samples of using this behavior you see there:
image_bahavior_example.rar
Download code
image_bahavior_example.rar
Download code
<?php
/*
* Image for cakePHP
* comments, bug reports are welcome skie AT mail DOT ru
* @author Yevgeny Tomenko aka SkieDr
* @version 1.0.0.5
files stored in structure
/images/{models}/{$id}/{field}.ext
*/
class ImageBehavior extends ModelBehavior {
var $settings = null;
function setup(&$model, $config = array()) {
$this->imageSetup(&$model, $config);
}
function imageSetup(&$model, $config = array()) {
$settings = Set::merge(array(
'baseDir'=> '',
), $config);
if (!isset($settings['fields'])) $settings['fields']=array();
$fields=array();
foreach($settings['fields'] as $key=>$value) {
$field = ife(is_numeric($key), $value, $key);
$conf = ife(is_numeric($key), array(), ife(is_array($value),$value,array()));
$conf=Set::merge(
array (
'thumbnail' => array('prefix'=>'thumb',
'create'=>false,
'width'=>'100',
'height'=>'100',
'aspect'=>true,
'allow_enlarge'=>true,
),
'resize'=>null, // array('width'=>'100','heigth'=>'100'),
'versions' => array(
),
), $conf);
foreach ($conf['versions'] as $id=>$version) {
$conf['versions'][$id]=Set::merge(array(
'aspect'=>true,
'allow_enlarge'=>false,
),$version);
}
if (is_array($conf['resize'])) {
if (!isset($conf['resize']['aspect'])) $conf['resize']['aspect']=true;
if (!isset($conf['resize']['allow_enlarge'])) $conf['resize']['allow_enlarge']=false;
}
$fields[$field]=$conf;
}
$settings['fields']=$fields;
$this->settings[$model->name] = $settings;
}
/**
* Before save method. Called before all saves
*
* Overriden to transparently manage setting the item position to the end of the list
*
* @param AppModel $model
* @return boolean True to continue, false to abort the save
*/
function beforeSave(&$model) {
extract($this->settings[$model->name]);
if (empty($model->data[$model->name][$model->primaryKey])) {
}
$tempData = array();
foreach ($fields as $key=>$value) {
$field = ife(is_numeric($key), $value, $key);
if (isset($model->data[$model->name][$field])) {
if ($this->__isUploadFile($model->data[$model->name][$field])) {
$tempData[$field] = $model->data[$model->name][$field];
$model->data[$model->name][$field]=$this->__getContent($model->data[$model->name][$field]);
} else {
unset($model->data[$model->name][$field]);
}
}
}
$this->runtime[$model->name]['beforeSave'] = $tempData;
return true;
}
function afterSave(&$model) {
extract($this->settings[$model->name]);
if (empty($model->data[$model->name][$model->primaryKey])) {
}
$tempData = $this->runtime[$model->name]['beforeSave'];
unset($this->runtime[$model->name]['beforeSave']);
foreach($tempData as $field=>$value) {
$this->__saveFile(&$model, $field, $value);
}
return true;
}
function afterFind(&$model, &$results, $primary) {
extract($this->settings[$model->name]);
if ( is_array( $results ) ) {
$i=0;
if (isset($results[0])) {
while ( isset( $results[$i][$model->name] ) && is_array( $results[$i][$model->name] ) ) {
foreach ($fields as $field => $fieldParams) {
if (isset($results[$i][$model->name][$field]) && ($results[$i][$model->name][$field]!='')) {
$value=$results[$i][$model->name][$field];
$results[$i][$model->name][$field]=$this->__getParams(&$model, $field, $value,$fieldParams, $results[$i][$model->name]);
}
}
$i++;
}
} else {
foreach ($fields as $field => $fieldParams) {
if (isset($results[$model->name][$field]) && ($results[$i][$model->name][$field]!='')) {
$value=$results[$i][$model->name][$field];
$results[$model->name][$field]=$this->__getParams(&$model, $field, $value, $fieldParams, $results[$model->name]);
}
}
}
}
return true;
}
function __getParams(&$model, $field, $value, $fieldParams, $record) {
extract($this->settings[$model->name]);
$result=array();
if ($value!='') {
$folderName = $this->__getFolder(&$model, $record);
$ext=$this->decodeContent($value);
$fileName=$field .'.'. $ext;
$result['path']=$folderName.$fileName;
$thumb=$fields[$field]['thumbnail'];
if ($thumb['create']) {
$result['thumb']=$folderName.$this->__getPrefix($thumb).'_'.$fileName;
}
foreach($fields[$field]['versions'] as $version) {
$result[$this->__getPrefix($version)]=$folderName.$this->__getPrefix($version).'_'.$fileName;
}
}
return $result;
}
/**
* Before delete method. Called before all deletes
*
* Will delete the current item from list and update position of all items after one
*
* @param AppModel $model
* @return boolean True to continue, false to abort the delete
*/
function beforeDelete(&$model) {
$this->runtime[$model->name]['ignoreUserAbort'] = ignore_user_abort();
@ignore_user_abort(true);
return true;
}
function afterDelete(&$model) {
extract($this->settings[$model->name]);
foreach ($fields as $field=>$fieldParams) {
$folderPath=$this->__getFullFolder(&$model, $field);
uses ('folder');
$folder = &new Folder($path = $folderPath, $create = false);
if ($folder!==false) {
@$folder->delete($folder->pwd());
}
}
@ignore_user_abort((bool) $this->runtime[$model->name]['ignoreUserAbort']);
unset($this->runtime[$model->name]['ignoreUserAbort']);
return true;
}
function __isUploadFile($file) {
if (!isset($file['tmp_name'])) return false;
return (file_exists($file['tmp_name']) && $file['error']==0);
}
function __getContent($file) {
return $file['type'];
}
function decodeContent($content) {
$contentsMaping=array(
"image/gif" => "gif",
"image/jpeg" => "jpg",
"image/pjpeg" => "jpg",
"image/x-png" => "png",
"image/jpg" => "jpg",
"image/png" => "png",
"application/x-shockwave-flash" => "swf",
"application/pdf" => "pdf",
"application/pgp-signature" => "sig",
"application/futuresplash" => "spl",
"application/msword" => "doc",
"application/postscript" => "ps",
"application/x-bittorrent" => "torrent",
"application/x-dvi" => "dvi",
"application/x-gzip" => "gz",
"application/x-ns-proxy-autoconfig" => "pac",
"application/x-shockwave-flash" => "swf",
"application/x-tgz" => "tar.gz",
"application/x-tar" => "tar",
"application/zip" => "zip",
"audio/mpeg" => "mp3",
"audio/x-mpegurl" => "m3u",
"audio/x-ms-wma" => "wma",
"audio/x-ms-wax" => "wax",
"audio/x-wav" => "wav",
"image/x-xbitmap" => "xbm",
"image/x-xpixmap" => "xpm",
"image/x-xwindowdump" => "xwd",
"text/css" => "css",
"text/html" => "html",
"text/javascript" => "js",
"text/plain" => "txt",
"text/xml" => "xml",
"video/mpeg" => "mpeg",
"video/quicktime" => "mov",
"video/x-msvideo" => "avi",
"video/x-ms-asf" => "asf",
"video/x-ms-wmv" => "wmv"
);
if (isset($contentsMaping[$content]))
return $contentsMaping[$content];
else return $content;
}
function __saveAs($fileData, $fileName=null, $folder) {
if (is_writable($folder)) {
if (is_uploaded_file($_FILES[$fileData]['tmp_name']))
{
if (empty($fileName)) $fileName = $_FILES[$fileData]['name'];
copy($_FILES[$fileData]['tmp_name'], $folder.$fileName);
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
function __getFolder(&$model, $record) {
extract($this->settings[$model->name]);
return $baseDir .'/'. Inflector::camelize($model->name) .'/'. $record[$model->primaryKey] . '/';
}
function __getFullFolder(&$model, $field) {
extract($this->settings[$model->name]);
return WWW_ROOT . IMAGES_URL. $baseDir .DS. Inflector::camelize($model->name) .DS. $model->id .DS;
}
function __saveFile(&$model, $field, $fileData) {
extract($this->settings[$model->name]);
$folderName = $this->__getFullFolder(&$model, $field);
$ext=$this->decodeContent($this->__getContent($fileData));
$fileName=$field .'.'. $ext;
uses ('folder');
uses ('file');
$folder = &new Folder($path = $folderName, $create = true, $mode = '777');
$files=$folder->find($fileName);
$file= &new File($folder->pwd().DS.$fileName);
$fileExists=($file!==false);
if ($fileExists) {
@$file->delete();
}
if (isset($fields[$field]['resize']['width']) && isset($fields[$field]['resize']['height'])) {
$file=$folder->pwd().DS.'tmp_'.$fileName;
copy($fileData['tmp_name'], $file);
$this->__resize($folder->pwd(),'tmp_'.$fileName,$fileName,$field, $fields[$field]['resize']);
@unlink($file);
} else {
$file=$folder->pwd().DS.$fileName;
copy($fileData['tmp_name'], $file);
}
if ($fields[$field]['thumbnail']['create']) {
$fieldParams=$fields[$field]['thumbnail'];
$newFile=$this->__getPrefix($fieldParams).'_'.basename($fileName);
$this->__resize($folder->pwd(),$fileName,$newFile, $field, $fieldParams);
}
foreach($fields[$field]['versions'] as $version) {
$fieldParams=$fields[$field]['thumbnail'];
$newFile=$this->__getPrefix($version).'_'.basename($fileName);
$this->__resize($folder->pwd(),$fileName,$newFile,$field, $version);
}
}
function __getPrefix($fieldParams) {
if (isset($fieldParams['prefix'])) {
return $fieldParams['prefix'];
} else {
return $fieldParams['width'].'x'.$fieldParams['height'];
}
}
/**
* Automatically resizes an image and returns formatted IMG tag
*
* @param string $path Path to the image file, relative to the webroot/img/ directory.
* @param integer $width Image of returned image
* @param integer $height Height of returned image
* @param boolean $aspect Maintain aspect ratio (default: true)
* @param array $htmlAttributes Array of HTML attributes.
* @param boolean $return Wheter this method should return a value or output it. This overrides AUTO_OUTPUT.
* @return mixed Either string or echos the value, depends on AUTO_OUTPUT and $return.
* @access public
*/
function __resize($folder, $originalName, $newName, $field, $fieldParams) {
$types = array(1 => "gif", "jpeg", "png", "swf", "psd", "wbmp"); // used to determine image type
$fullpath = $folder;
$url = $folder.DS.$originalName;
if (!($size = getimagesize($url)))
return; // image doesn't exist
$width=$fieldParams['width'];
$height=$fieldParams['height'];
if ($fieldParams['allow_enlarge']===false) { // don't enlarge image
if (($width>$size[0])||($height>$size[1])) {
$width=$size[0];
$height=$size[1];
}
} else {
if ($fieldParams['aspect']) { // adjust to aspect.
if (($size[1]/$height) > ($size[0]/$width))
$width = ceil(($size[0]/$size[1]) * $height);
else
$height = ceil($width / ($size[0]/$size[1]));
}
}
$cachefile = $fullpath.DS.$newName; // location on server
if (file_exists($cachefile)) {
$csize = getimagesize($cachefile);
$cached = ($csize[0] == $width && $csize[1] == $height); // image is cached
if (@filemtime($cachefile) < @filemtime($url)) // check if up to date
$cached = false;
} else {
$cached = false;
}
if (!$cached) {
$resize = ($size[0] > $width || $size[1] > $height) || ($size[0] < $width || $size[1] < $height || ($fieldParams['allow_enlarge']===false));
} else {
$resize = false;
}
if ($resize) {
$image = call_user_func('imagecreatefrom'.$types[$size[2]], $url);
if (function_exists("imagecreatetruecolor") && ($temp = imagecreatetruecolor ($width, $height))) {
imagecopyresampled ($temp, $image, 0, 0, 0, 0, $width, $height, $size[0], $size[1]);
} else {
$temp = imagecreate ($width, $height);
imagecopyresized ($temp, $image, 0, 0, 0, 0, $width, $height, $size[0], $size[1]);
}
call_user_func("image".$types[$size[2]], $temp, $cachefile);
imagedestroy ($image);
imagedestroy ($temp);
}
}
}
?>
Comments
Comment
1 examples
http://cakeexplorersamples.googlecode.com/files/image_bahavior_example.rar
Comment
2 Very nice
I was also able to modify the behavior to make it work for both image and file uploads.
Thanks!
Question
3 How to check extensions
Comment
4 Aspect ratio
How do I go about passing just one of the dimensions and having the behavior create the image such that the aspect ratio is preserved?
I tried not passing 'height' for the versions, but I got errors and the version images were not created.
Thanks in advance!
Comment
5 Nice but
Question
6 License
There is no info about the license and I would like to modify this behaviour to use it in a personal project, may I?
BTW, good job mate, this code is very useful :-)
Comment
7 Validation only image types
Just paste it somewhere in the behavior above.
Haven't tested it that much but it works fine for me!
http://www.pastebin.be/6872
cheers!
Comment
8 imagemagick
This would allow the use of the far superior imagemagick over gd, and would also be easily extend with other image lib functions if needed.
p.s. Awesome code, thanks!
Comment
9 specify upload folder
function __getFolder(&$model, $record) {
extract($this->settings[$model->name]);
return $baseDir .'/'. Inflector::camelize($model->name) .'/'. $record[$model->primaryKey] . '/';
}
function __getFullFolder(&$model, $field) {
extract($this->settings[$model->name]);
return WWW_ROOT . IMAGES_URL. $baseDir .DS. Inflector::camelize($model->name) .DS. $model->id .DS;
}
Where do $baseDir and IMAGES_URL get defined?
Bug
10 Storing file type
Comment
11 Preserving aspect ratio
It is quite easy to modify the behavior to make it generate the other dimension when only one is passed. In __resize() method just before the $width and $height declaration add this code:
if (!isset($fieldParams['height']) or !$fieldParams['height']) {
$fieldParams['height'] = (int) (($fieldParams['width'] / $size[0]) * $size[1]);
} elseif (!isset($fieldParams['width']) or !$fieldParams['width']) {
$fieldParams['width'] = (int) (($fieldParams['height'] / $size[1]) * $size[0]);
}
Question
12 How can I get the images when the model comes in as a recursive association
Bug
13 Incorrect afterFind declaration
This method should return $results instead of boolean.
Change method declaration from:
function afterFind(&$model, &$results, $primary)to:function afterFind(&$model, $results, $primary)And the last statement in it from:return true;to:return $results;Bug
14 Reduntant cycle counter in afterFind()
} else {
foreach ($fields as $field => $fieldParams) {
if (isset($results[$model->name][$field]) && ($results[$i][$model->name][$field]!='')) {
$value=$results[$i][$model->name][$field];
$results[$model->name][$field]=$this->__getParams(&$model, $field, $value, $fieldParams, $results[$model->name]);
}
}
}
should be changed to this:
} else {
foreach ($fields as $field => $fieldParams) {
if (isset($results[$model->name][$field]) && ($results[$model->name][$field]!='')) {
$value=$results[$model->name][$field];
$results[$model->name][$field]=$this->__getParams(&$model, $field, $value, $fieldParams, $results[$model->name]);
}
}
}
Comment
15 deleting an image
echo $form->input('delete_fot1',array('type' => 'checkbox'));
and the beforeSave() could be like this:
function beforeSave(&$model) {
//$model->log($this->settings);
extract($this->settings[$model->name]);
if (empty($model->data[$model->name][$model->primaryKey])) {
//$this->__addToListBottom(&$model);
}
$tempData = array();
foreach ($fields as $key=>$value) {
$field = ife(is_numeric($key), $value, $key);
if (isset($model->data[$model->name][$field])) {
if ($this->__isUploadFile($model->data[$model->name][$field])) {
$tempData[$field] = $model->data[$model->name][$field];
$model->data[$model->name][$field]=$this->__getContent($model->data[$model->name][$field]);
} else {
//delete image
if (isset($model->data[$model->name]["delete_".$field])) {
if($model->data[$model->name]["delete_".$field]==1){
@unlink($this->__getFullFolder(&$model, $field).'/'.$field.'.png');
@unlink($this->__getFullFolder(&$model, $field).'/thumb_'.$field.'.png');
$model->data[$model->name][$field]="";
}else{
unset($model->data[$model->name][$field]);
}
}else{
unset($model->data[$model->name][$field]);
}
}
}
}
//debug($model->data);
//debug($tempData);
$this->runtime[$model->name]['beforeSave'] = $tempData;
return true;
}
Comment
16 Just a little orthographic error
I can't find any other error: your behavior is great
Comment
17 Version Compatibility
1.2.0.7125
perhaps add a little note at the top to see what version this was written for.
Comment
18 Storing Image with prefix that is the same size
Although this is efficient, it is not really what's intended by most users. I'm not going to run checks on my versions to see if they exist or not, and if the original is the same size as my version before outputting it to the browser. So I added a bit of code to simply copy the original if it happens to be the same size as the version I am creating.
Add this ELSE to the final IF($resize) statement in the __resize function:
else {
// we still want our image w/prefix, even if it is the same size
copy($url, $cachefile);
}
and that solves the problem, all versions are created and you can use the version you created for a specific use without worry that the image won't exist.
Bug
19 type instead of name
I have just the same problem - any solutions?
Comment
20 solution
was an easy one, just changing
function __getContent($file) {return $file['type'];
}
to
function __getContent($file) {return $file['name'];
}
did the trick!
Bye
Comment
21 Call-time pass-by-reference
Call-time pass-by-reference has been deprecated; If you would like to pass it by reference, modify the declaration of [runtime function name](). If you would like to enable call-time pass-by-reference, you can set allow_call_time_pass_reference to true in your INI fileAs a newbie to php, i'm not really sure what it means or how to fix that. Any tips?
Comment
22 bugfixing the bugfix of Comment #20
So this is the right version again for all who have changed the behavior according to comment #20
function __getContent($file) {
return $file['type'];
}
Bug
23 Permission to create directory
the line:
$folder = &new Folder($path = $folderName, $create = true, $mode = '777');
has to be changed in this way:
$folder = &new Folder($path = $folderName, $create = true, $mode = 0777);
$mode has to be an integer. Better if 0755
Marco
Question
24 Show imgs View.ctp
But when I try to show this images in the view, returns a blank result.
Code:
<?php echo $html->image($item['Item']['avatar']['thumb']) ?>HTML:
<img src="/car_stuff/Item/34/thumb_avatar.jpg" alt="" />"car_stuff" is my app name
Any ideas? Cheers!
Comment
25 Solved.
My imgs /app/webroot/img/MODEL_NAME/ITEM_ID
change the code:
function __getFolder(&$model, $record) {
extract($this->settings[$model->name]);
return $baseDir .'/'. Inflector::camelize($model->name) .'/'. $record[$model->primaryKey] . '/';
to:
function __getFolder(&$model, $record) {
extract($this->settings[$model->name]);
return $baseDir .'/img/'. Inflector::camelize($model->name) .'/'. $record[$model->primaryKey] . '/';
Comment
26 Updates for CakePHP 1.2.2.8120
Comment
27 Cake 1.2.2
Comment
28 Cake 1.2.2.8120
Comment
29 CakePHP mess