Flushdrew Flushdrew - 3 months ago 36
jQuery Question

Symfony2-Cropit | How to get back my cropped image?

i'm very disapointed with this plugin.I'm a noob in JS/Jquery but i very need this plugin for my site...
So i found cropit here : http://scottcheng.github.io/cropit/

I don't know how to get back my cropped image in my controller and save it...
So my form is :

<div class="form-group">
{{ form_label(form.image, 'Image', {'label_attr': {'class': 'col-sm-3 control-label'}})}}
<div class="image-cropper">
<!-- This is where the preview image is displayed -->
<div class="row">
<div class="col-sm-offset-2 col-sm-8">
<div class="cropit-image-preview-container">
<div class="cropit-image-preview"></div>
</div>
</div>
</div>
<!-- This range input controls zoom -->
<!-- You can add additional elements here, e.g. the image icons -->
<div class="row">
<div class="col-sm-offset-4 col-sm-4">
<input type="range" class="cropit-image-zoom-input" />
</div>
</div>
{{ form_errors(form.image) }}

<div class="row">
<div class="col-sm-4">
{{ form_widget(form.image) }}
<div class="select-image-btn">Select new image</div>
<div class="send_image">Get Cropped image.</div>
</div>
</div>
</div>
</div>


my Jquery code :

<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<script type="text/javascript" src="{{ asset('js/cropit-master/dist/jquery.cropit.js') }}"></script>

<script>
$(function () {
$('.select-image-btn').click(function(){
$('.cropit-image-input').click();
});


var z = $('.image-cropper');
z.cropit({
exportZoom: 0.5,
imageBackground: true,
imageBackgroundBorderWidth: 15
});
$('.send_image').click(function() {
var h =z.cropit('export');
alert(h);
});
});
</script>


my Image.php entity :

public function getFile()
{
return $this->file;
}

public function setFile(UploadedFile $file = null)
{
$decoded = urldecode($file);
$exp = explode(';', $decoded);
$exp = explode(':', $exp[0]);
$data = array_pop($exp);
$this->file = imagecreatefromstring($file);


if (null !== $this->url) {
$this->tempFilename = $this->url;

$this->url = null;
$this->alt = null;
}
}

/**
* @ORM\PrePersist()
* @ORM\PreUpdate()
*/
public function preUpload()
{

if (null === $this->file) {
return;
}


$this->url = $this->file->guessExtension();


$this->alt = $this->file->getClientOriginalName();
}

/**
* @ORM\PostPersist()
* @ORM\PostUpdate()
*/
public function upload()
{

if (null === $this->file) {
return;
}


if (null !== $this->tempFilename) {
$oldFile = $this->getUploadRootDir().'/'.$this->id.'.'.$this->tempFilename;
if (file_exists($oldFile)) {
unlink($oldFile);
}
}


$this->file->move(
$this->getUploadRootDir(), // Le répertoire de destination
$this->id.'.'.$this->url // Le nom du fichier à créer, ici « id.extension »
);
}

/**
* @ORM\PreRemove()
*/
public function preRemoveUpload()
{
$this->tempFilename = $this->getUploadRootDir().'/'.$this->id.'.'.$this->url;
}

/**
* @ORM\PostRemove()
*/
public function removeUpload()
{
if (file_exists($this->tempFilename)) {
// On supprime le fichier
unlink($this->tempFilename);
}
}

public function getUploadDir()
{
// On retourne le chemin relatif vers l'image pour un navigateur
return 'uploads/img';
}

protected function getUploadRootDir()
{
// On retourne le chemin relatif vers l'image pour notre code PHP
return __DIR__.'/../../../../web/'.$this->getUploadDir();
}

public function getWebPath()
{
return $this->getUploadDir().'/'.$this->getId().'.'.$this->getUrl();
}


I saw lots of posts but nothing is working for me and i don't very understand.
So somebody can help and explain to me please?
thank you

EDITED : MY FIRST METHOD

my form was with an hidden input to save the base64 data :

$('form').submit(function() {
// Move cropped image data to hidden input
var imageData = $('.image-cropper').cropit('export');
$('.hidden-image-data').val(imageData);
};``


and my Imagetype with a file input to load the original image and hidden input to save the base64 image.

public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('file', 'file', array(
'attr' => array('class' => 'cropit-image-input')))
->add('file', 'hidden', array('attr' => array('class' => 'hidden-image-data')))

;
}


my controller was still the same.

Answer

I have resolved.

In addition using VichUploaderBundle

@K-Phoen response has been of great use to direct the solution. VichUploaderBundle & Cropit - Pass base64 to a File instance

this is my code:

User.php, this and more VichUploaderBundle setup look here

namespace Application\Sonata\UserBundle\Entity;

//...
use Vich\UploaderBundle\Mapping\Annotation as Vich;

/**
 * User
 *
 * ........
 * ........
 * @Vich\Uploadable
 * ........
 * ........
 * 
 */
class User extends BaseUser {

    /**
     * NOTE: This is not a mapped field of entity metadata, just a simple property.
     * 
     * @Vich\UploadableField(mapping="user_avatar", fileNameProperty="avatarName")
     * 
     * @var File
     */
    private $avatar;

    /**
     * @ORM\Column(type="string", length=255)
     *
     * @var string
     */
    private $avatarName;

    /**
     * @ORM\Column(type="datetime")
     *
     * @var \DateTime
     */
    private $updatedAt;

    //....


    /**
     * If manually uploading a file (i.e. not using Symfony Form) ensure an instance
     * of 'UploadedFile' is injected into this setter to trigger the  update. If this
     * bundle's configuration parameter 'inject_on_load' is set to 'true' this setter
     * must be able to accept an instance of 'File' as the bundle will inject one here
     * during Doctrine hydration.
     *
     * @param File|\Symfony\Component\HttpFoundation\File\UploadedFile $image
     */
    public function setAvatar(File $image = null) {
        $this->avatar= $image;

        if ($image) {
            // It is required that at least one field changes if you are using doctrine
            // otherwise the event listeners won't be called and the file is lost
            $this->updatedAt = new \DateTime('now');
        }
    }

    /**
     * @return File
     */
    public function getAvatar() {
        return $this->avatar;
    }

    /**
     * @param string $avatarName
     */
    public function setAvatarName($avatarName) {
        $this->avatarName= $avatarName;
    }

    /**
     * @return string
     */
    public function getAvatarName() {
        return $this->avatarName;
    }

}

UserType.php

namespace AppBundle\Form;

//...
use Doctrine\Common\Persistence\ObjectManager;
use AppBundle\Form\DataTransformer\ImageStringToFileTransformer;

class UserType extends AbstractType {

    private $manager;

    public function __construct(ObjectManager $manager)
    {
        $this->manager = $manager;
    }

    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options) {
        $builder                
            ->add('avatar', 'hidden', [
                'attr' => [
                    'class' => 'hidden-image-data'
                ]
            ])
            ->add('imagedata', 'file', [
                'mapped' => false,
                'required' => false,
                'attr' => [
                    'class' => 'cropit-image-input'
                ]
            ])
            //...
            ;

        $builder->get('avatar')
            ->addModelTransformer(new ImageStringToFileTransformer($this->manager));
    }

    //...

}

ImageStringToFileTransformer.php, here the magic is done, we convert base64 string to UploadedFile

namespace AppBundle\Form\DataTransformer;

use Symfony\Component\Form\DataTransformerInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\HttpFoundation\File\UploadedFile;

/**
 * Description of ImageStringToFileTransformer
 *
 * @author Juanjo García
 */
class ImageStringToFileTransformer implements DataTransformerInterface {

    private $manager;

    public function __construct(ObjectManager $manager) {
        $this->manager = $manager;
    }

    /**
     * Transforms a string (base64) to an object (File).
     *
     * @param  string $imageString
     * @return File|null
     * @throws TransformationFailedException if no object (File)
     */
    public function reverseTransform($imageString) {

        // no base64? It's optional, so that's ok       
        if (!$imageString) {
            return;
        }

        preg_match('/data:([^;]*);base64,(.*)/', $imageString, $matches);

        $mimeType = $matches[1];
        $imagenDecodificada = base64_decode($matches[2]);
        $filePath = sys_get_temp_dir() . "/" . uniqid() . '.png';
        file_put_contents($filePath, $imagenDecodificada);

        $file = new UploadedFile($filePath, "user.png", $mimeType, null, null, true);

        if (null === $file) {
            // causes a validation error
            // this message is not shown to the user
            // see the invalid_message option
            throw new TransformationFailedException(sprintf(
                 'An issue with number "%s" does not exist!', $imageString
            ));
        }

        return $file;
    }

    /**
     * Transforms an object (File) to a string (base64).
     *
     * @param  File|null $file
     * @return string
     */
    public function transform($file) {
        return '';
    }

}

services.yml

services:

    # datatransformer before saving to image
    app.form.type.user:
        class: AppBundle\Form\UserType
        arguments: 
            entityManager: "@doctrine.orm.entity_manager"
        tags:
            - 
                name: form.type

in my Twig, my_form.html.twig

<script src="{{ asset('jquery.js') }}"></script>
<script src="{{ asset('jquery.cropit.js') }}"></script>
<script src="{{ asset('jquery.custom.js') }}"></script>

{% form_theme form with ['AppBundle:Forms:vich_cropit_fields.html.twig'] %}

{{ form_start(form, {'action': path('the_path'), 'method': 'POST', 'multipart': true, 'attr': {'class': 'the_class', 'role': 'form'} }) }}
    <div class="image-editor">
        <div class="cropit-image-preview-container">
            <div class="cropit-image-preview"></div>
        </div>
        {{ form_widget(form.avatar) }}
        {{ form_widget(form.imagedata) }} 
        <div class="image-size-label">
        Resize image
        </div>
        <input type="range" class="cropit-image-zoom-input">
        {% if app.user.avatarName %}
            <img src="{{ vich_uploader_asset(app.user, 'avatar') }}" id="current-avatar" class="hide" />
        {% endif %}
    </div>   

    {{ form_rest(form) }}       

    <button type="submit" name="submit">Save</button>  

{{ form_end(form) }}

init Cropit, jquery.custom.js

(function ($) {

    $('.image-editor').cropit({
          exportZoom: 0.5,
          imageBackground: true,
          imageBackgroundBorderWidth: 50
      });

    $('.image-editor').cropit('imageSrc', $("#current-avatar").attr('src'));

    $('form').submit(function () {
        // Move cropped image data to hidden input
        var imageData = $('.image-editor').cropit('export');
        $('.hidden-image-data').val(imageData);
    });

}).apply(this, [jQuery]);

since I have not found an answer to the solution anywhere, I decided to leave here, for everyone who needs it. I hope you find it helps.