Yii 1.1: fileimagearbehavior

attach file(s) / image(s) with multiple formats to a CActiveRecord
33 followers

This extension intend to help to "attach" file(s) to a CActiveRecord, without the help of any database, by generating file names based on the concatenation of the primary key(s). File(s) can be processed in any way you want (look at Usage).

Here is the main idea:

  • A user send a file via a form linked to a model
  • [optional] you can process it, and create different files from the one sended
  • the file(s) is(are) saved
  • when the model is deleted, files associated are deleted too

For those who knows paperclip for rails, the purpose is nearly the same, but extended as you can create formats for any file by using processors (look at the example file provided in the archive).

Requirements

Yii 1.1., the php gd or imagemagick extension for ImageARBehavior.

Installation

Download the archive

FileARBehavior : copy FileARBehavior in your components folder.

ImageARBehavior : copy FileARBehavior and ImageARBehavior in your components folder, then copy the image folder in your extension directory.

Usage

Look at the example file provided in the archive.

Very briefly, you have to configure your behaviors() method in models. An example to generate 3 formats of image:

public function behaviors() {
  return array(
    'recipeImgBehavior' => array(
      'class' => 'ImageARBehavior',
      'attribute' => 'recipeImg', // this must exist
      'extension' => 'png, gif, jpg', // possible extensions, comma separated
      'prefix' => 'img_',
      'relativeWebRootFolder' => 'images/recipes', // this folder must exist
 
      'useImageMagick' => '/usr/bin', # I want to use imagemagick instead of GD, and
      // it is located in /usr/bin on my computer.
 
      // this will define formats for the image.
      // The format 'normal' always exist. This is the default format, by default no
      // suffix or no processing is enabled.
      'formats' => array(
        // create a thumbnail grayscale format
        'thumb' => array(
        'suffix' => '_thumb',
        'process' => array('resize' => array(60, 60), 'grayscale' => true),
      ),
      // create a large one (in fact, no resize is applied)
        'large' => array(
        'suffix' => '_large',
      ),
      // and override the default :
        'normal' => array(
        'process' => array('resize' => array(200, 200)),
      ),
    ),
 
    'defaultName' => 'default', // when no file is associated, this one is used
    // defaultName need to exist in the relativeWebRootFolder path, and prefixed by prefix,
    // and with one of the possible extensions. if multiple formats are used, a default file must exist
    // for each format. Name is constructed like this :
    //     {prefix}{name of the default file}{suffix}{one of the extension}
  )
);

Important

The file (or files if you use Images with multiple formats) are generated like this :

relativWebFolder/{prefix} + join('_', primaryKeys) + {suffix} + {extension}

The suffix is only provided for images to handle multiple format.

For example, with a dish with one primary key equal to 1, you will have the following files (if you send a png image):

  • files/recipes/img_1.png
  • files/recipes/img_1_thumb.png
  • files/recipes/img_1_large.png

Think about it to avoid name clashes if you put multiples files (from different CActiveRecord) in the same directories and be sure in this case to define different prefix for each CActiveRecord.

Changelog

version 0.7

  • FileARBehavior and ImageARBehavior can be placed where you want (they just need to be in the same directory)
  • modification of Image extension
    • getFilePath is now called getFilesPath (there is a getFilePath method which take an argument)
    • 3 new functions (emboss, negate, grayscale)
    • The ImageMagick Driver is optimized greatly by appending arguments for convert instead of reading / saving file for each function. The temp image copy in this driver is now useless and was removed too.
  • modification of FileARBehavior
    • can have formats, which can be be processed (similar to old ImageARBehavior but more generic): you can define your own processor class (look at the example file in the archive).
    • property moved from ImageARBehavior $formats -property added prcessor (set a yii import path to your own processor)
    • processing functions with no parameters (like grayscale and negate) can be set to true instead of array().
    • property added $forceExt to force saving files with a given extension.
    • property added $attributeSeparator to specify the separator used when mulitples primary keys. Default to '_'
    • getFilePath() now takes 1 optionnal argument, $format (default to 'normal')
    • getFilesPath() added, which returns an array of existing format => filePath
  • modification of ImageARBehavior
    • property added $useImageMagick to use ImageMagick instead of the default GD2 library.
    • the order of the processing functions is now important ! For example, use 'process' => array('resize' => array(60, 90), 'grayscale' => true) instead of 'process' => array('grayscale' => true, 'resize' => array(60, 90)) for better performance.

version 0.5:

  • allow multiple formats (not only a thumb one)
  • file research and suppression now works better, by using the glob function correctly.

version 0.6:

  • get file(s) path(s) in order to test if a file exists, delete when you want, etc

Resources

The image extension used is modified to remove the need of the CArray file (and there is some new functions). You can use the default extension if you want by installing it instead of copying the image folder provided in my archive.

There is a forum to discuss about this extension.

Total 20 comments

#14622 report it
Bogdan Savluk at 2013/08/29 09:34am
image-attachment extension, and some fixes form my fork

Recently, I have done work on extension intended to replace this one for image uploads, here it is: http://www.yiiframework.com/extension/image-attachment

Also I was using this extension for a long time, so I have made some fixes and improvements: https://bitbucket.org/z_bodya/fileimagearbehavior

I think, it would be good to merge them into original extension.

#11189 report it
undsoft at 2012/12/24 01:01pm
Cache?

Hi,

I'm using your extension. It's great. However, I believe its performance may be improved by storing directory file list in memory on the first access.

Consider the scenario of having a grid view with user photos. For 10 users, glob() function inside of your extension would be called for 10 times. Instead directory listing could be queried just once and then saved in memory.

#9035 report it
kmindi at 2012/07/15 07:36am
warning in deleteFile because of empty/null $fs (getAnyExistingFilesName)

Hi,

there is a warning because you don't check if $fs is null before passing it to foreach.

You should change it to this:

protected function deleteFile($path, $fname) {
        $fs = $this->getAnyExistingFilesName($path, $fname);
        if($fs) {
            foreach ($fs as $f) {
                unlink($f);
            }
        }
 
    }
#8552 report it
florin p at 2012/06/11 05:22pm
Watermarks

Hi,

It is possible to use image watermark for "large" format?

#8487 report it
warden at 2012/06/06 10:39pm
github

any chance you will put that on github? or should we do that? ;-)

#7680 report it
Edgar at 2012/04/09 03:00am
Not working when I create item in another model!

hi, very nice extension. Problem: I inserted this ext. to Photo model. When I create Photo item it works fine, but when I create Photo item in another model where have relation with Photo model it create item but not manipulation with file(

When I change method afterSave in FileARBehavior() to:

//$file = CUploadedFile::getInstance($this->owner, $this->attribute);
$file = CUploadedFile::getInstance(Category::model(), $this->attribute);

it work fine. But how to set model dynamically?

How to sent file to Photo model?

public function actionCreate()
    {
        $model=new Category;
        $this->performAjaxValidation($model);
 
        if(isset($_POST['Category']))
        {
            $model->attributes=$_POST['Category'];
 
            $file = CUploadedFile::getInstance($model, 'default_image');
 
            if(!is_null($file))
            {
                $image = new Photo;
 
                $image->fileimg = $file;
                $image->filename = $file->getName();
                $image->name = $file->getName();
                $image->extension = $file->getExtensionName();
                $image->byte_size = $file->getSize();
                $image->mime_type = $file->getType();
 
                if($image->save(true))
                {
                    $model->default_image = $image->getPrimaryKey();
                }
                else
                    throw new CHttpException(400, 'file not saved');
            }
 
 
 
            if(empty($_POST['Category']['parent_id']))
            {
                if ($model->saveNode())
                {
                    $this->redirect(array('view','id'=>$model->id));
                }
            }
            else
            {
                $parent_id = $this->loadModel((int)$_POST['Category']['parent_id']);
 
                if($model->appendTo($parent_id,true))
                {
 
                    $this->redirect(array('view','id'=>$model->id));
                }
 
            }
        }
 
        $this->render('create',array(
            'model'=>$model,
        ));
    }

I mode this problem so:

...
public $otherModel;
...
if (empty($this->otherModel))
    $file = CUploadedFile::getInstance($this->owner, $this->attribute);
else
    $file = CUploadedFile::getInstance($this->otherModel, $this->attribute);
...

and when I create Photo Item in other model I do that:

$file = CUploadedFile::getInstance($model, 'fileimg');
 
            if(!is_null($file))
            {
 
                $model->default_image = new Photo();
 
                $model->default_image->otherModel = Category::model();
 
                $model->default_image->filename = $file->getName();
                $model->default_image->extension = $file->getExtensionName();
                $model->default_image->byte_size = $file->getSize();
                $model->default_image->mime_type = $file->getType();
 
                if($model->default_image->save(true))
                {
                    $model->default_image = $model->default_image->getPrimaryKey();
                }
                else
                    throw new CHttpException(400, 'file not saved');
            }

If are you can made this problem with simple method please write here! )

#7208 report it
sluderitz at 2012/03/04 10:47am
Nice, some minor problems

Thank you for this nice extention. I encountered 2 little problems:

  1. Uploading file with extention in capital letters (pic.JPG) does not work
    Fix: in FileARBehavior insert strtolower in line 212 strpos(strtolower($this->extension), strtolower($file->extensionName))

  2. The image magick driver does not work correctly when chaining filters, like first applying resize and then crop. The problem is, that you changed the original class so that you have only one call to "convert" When preparing that single operation then the crop operation works with the original dimensions of the image rather than with the result of the initial resize. The original image magick driver class calls image magick "convert" for every image operation. This might take longer but works correctly.
    Fix: user original Kohona class, only applying minor changes to fit Yii

Please note that you should add quotes around the call to convert, so that it will also work if the path ($this->dir) has spaces in it, like this
if ($error = exec('"'.escapeshellcmd($this->dir.'convert'.$this->ext).'" '.$dir.' '.$this->cmd_image.' '.$this->cmd_image))

#4849 report it
paejan at 2011/08/21 01:43pm
multiple files..

is it anyway for us to upload multiple files / images for 1 model.?

#4743 report it
undsoft at 2011/08/10 05:44pm
Case

Component treats 'jpg' and 'JPG' differently.

#4089 report it
warden at 2011/06/05 11:36am
error

I'm getting: Property "ImageARBehavior.useImageMagick" is not defined.

Btw, please use GitHub for development.... :)

#4007 report it
oboema at 2011/05/27 01:28am
Error when creating

Hello,

Very nice extention. I am heving some problems though with the create. Update works fine but wnen i create a new item I get and error when the field is required.

#3614 report it
Parcouss at 2011/04/23 04:10pm
new version 0.7

New version with new things:

  • 3 new functions for image processing
  • formats and processing can be applied to FileARBehavior
  • FileARBehavior and ImageARBehavior don't need to be in components folder
  • ...

See the changelog, and look at the example file !

Tell me what you think, report bugs and so on;

#3541 report it
Parcouss at 2011/04/18 01:37pm
Questions/requests

Thank you intel352 for all your ideas !

I love them all ;

I just created a repository on sourceforge so it is now possible to post issues/feature requests.

If you want, you can help me by sending me your code (I will send you a private message soon). Then I will make a new version 0.7 with the new things and mention your nickname in sources.

Tanks again

#3526 report it
intel352 at 2011/04/17 09:57pm
Questions/requests

Nice job on this extension. Few questions & requests for you:

  1. Do you have this code stored in a project repository so we can report issues/feature requests?
  2. I installed your ext into a subfolder of my extensions directory, and encountered various errors as you've hard-coded the path to your other includes. I recommend you instead use a custom path alias (i.e.: fileImageAr) that we then define in main.php, so then we can store the extension wherever we want without issue.
  3. You mentioned that Paperclip exists, and is similar to your own extension. If you'll notice, it also somehow supports variables anywhere in the path string. That would be a good feature to have, so that we can (for instance) specify a path with the model PK as a subfolder in the path. I needed this functionality immediately, so implemented a workaround in my model using afterSave and afterConstruct to add model variables to the web root path.
  4. It would be great if you added an is_dir() check prior to saving the file, and if not exists, attempt to create the dir (with recursive set to true). That would match up with my previous request (variables in file path). I've done this in my own copy, but it's always nice to see such features adopted in the core.
#3323 report it
drumaddict at 2011/04/03 07:56am
Thanks!

Works perfect.Very useful,I wanted a picture field for an Article class,and didn't want/was bored to mess up with the database...Thanks!

#3273 report it
Parcouss at 2011/03/30 07:30pm
version 0.6

It is now possible to get the file(s) path(s), in order to test if a file exist or to delete it when you want.

#3250 report it
Parcouss at 2011/03/28 01:56pm
Random filename

In fact the name is generated by the primary key, or by the concatenation of keys separated by '_' in case of multiple primary keys. I'm not sure that a random generator is needed, because it is unique if you specify prefix and suffix correctly (or if you save into different directories).

But maybe you're right and can convince me ; we can discuss this on the dedicated forum newly created ; This way we won't annoy the followers.

#3242 report it
prchakal at 2011/03/28 09:04am
Random filename

Hi,

You can have a property to auto-generate the filename, example:

'recipeImgBehavior' => array( 'class' => 'ImageARBehavior', 'autoGenerateFileName' => true,

And the filename will be: /images/{prefix} + date('YmdHis') + {suffix} + {extension}

Instead of date('YmdHis') you can use another random generator.

It prevent image replace and can cause some problems if image is replaced.

Usually i use:

date('YmdHis') + '_' + {random numer 0 to 9} + {random numer 0 to 9} + {random numer 0 to 9} + {random numer 0 to 9} + {random numer 0 to 9}

And the final filename is (example):

20110101120000_01234

#3236 report it
Parcouss at 2011/03/28 04:22am
Get image url with different formats

I'm sorry, I forgot this ! Here is the way to do this :

$format = 'large'; //get the user-defined large format (in the example you can use 'large', 'thumb' and 'normal'.
// 'normal' is the default
 
$model->recipeImgBehavior->getFileUrl($format);
#3231 report it
prchakal at 2011/03/28 12:50am
very good

It's better each day.

If you want more image processing functions, you can view here: http://www.verot.net/php_class_upload_samples.htm

I have one extesion here in Yii with this class.

This class do everything with images.

I dont understand how the get the thumb, large and normal file, can you explain more?

Leave a comment

Please to leave your comment.

Create extension