symfony2admingenerator / FormExtensionsBundle

Symfony2 form extensions for Admingenerator project (also working standalone!)
Other
13 stars 14 forks source link

[Collection Upload] Undefined variable: editable #12

Open ksn135 opened 9 years ago

ksn135 commented 9 years ago

I'm on dev-master. In vendor/symfony2admingenerator/form-extensions-bundle/Admingenerator/FormExtensionsBundle/Form/EventListener/CollectionUploadSubscriber.php at line 192

My config:

        files:
            label:                     Files
            dbType:               collection
            formType:             s2a_collection_upload
            addFormOptions:
                primary_key:          id
                nameable:             false
                nameable_field:       name
                sortable:             true
                sortable_field:       position
                editable:             [ description ]    
                type:               \Ksn135\CompanyBundle\Form\Type\Cabinet_file\EditType
                loadImageFileTypes:   /^image\/(gif|jpe?g|png)$/i
                previewMaxWidth:      100
                previewMaxHeight:     100
                previewAsCanvas:      true
                prependFiles:         false
                allow_add:            true
                allow_delete:         true
                error_bubbling:       false
                options:
                    data_class:       Ksn135\CompanyBundle\Entity\CabinetFile

My entity (almost the same as from example: https://github.com/symfony2admingenerator/FormExtensionsBundle/blob/master/Resources/doc/collection-upload/overview.md):

/**
 * @ORM\Table(name="cabinet_file")
 * @ORM\Entity(repositoryClass="Gedmo\Sortable\Entity\Repository\SortableRepository")
 * @Vich\Uploadable
 */
class CabinetFile implements UploadCollectionFileInterface
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @Vich\UploadableField(mapping="files", fileNameProperty="path")
     */
    protected $file;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     */
    protected $path;

    /**
     * (Optional) nameable field
     *
     * @ORM\Column(type="string", length=255, nullable=true)
     */
    protected $name;

    /**
     * (Optional) additional editable field
     *
     * @ORM\Column(type="string", length=255, nullable=true)
     */
    protected $description;

    /**
     * @ORM\Column(name="category_id", type="integer")
     */
    private $categoryId;

    /**
     * (Optional) sortable group
     *
     * @Gedmo\SortableGroup
     * @ORM\ManyToOne(targetEntity="CabinetCategory", inversedBy="files")
     * @ORM\JoinColumn(name="category_id", referencedColumnName="id")
     */
    protected $category;

    /**
     * (Optional) sortable position
     *
     * @Gedmo\SortablePosition
     * @ORM\Column(name="position", type="integer")
     */
    protected $position;

    public function setFile(\Symfony\Component\HttpFoundation\File\File $file) {
        $this->file = $file;
        return $this;
    }

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

    public function getId() {
        return $this->id;
    }

    public function getCategoryId() {
        return $this->categoryId;
    }

    public function getPath() {
        return $this->path;
    }

    public function getName() {
        return $this->name;
    }

    public function getPosition() {
        return $this->position;
    }

    public function getDescription() {
        return $this->description;
    }

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

    public function setParent($parent) {
        $this->setCategory($parent);
    }

    public function getPreview() {
        return (preg_match('/image\/.*/i', $this->file->getMimeType()));
    }

    /**
     * Set path
     *
     * @param string $path
     * @return CabinetFile
     */
    public function setPath($path)
    {
        $this->path = $path;

        return $this;
    }

    /**
     * Set name
     *
     * @param string $name
     * @return CabinetFile
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Set description
     *
     * @param string $description
     * @return CabinetFile
     */
    public function setDescription($description)
    {
        $this->description = $description;

        return $this;
    }

    /**
     * Set position
     *
     * @param integer $position
     * @return CabinetFile
     */
    public function setPosition($position)
    {
        $this->position = $position;

        return $this;
    }

    /**
     * Get category
     *
     * @return \Ksn135\CompanyBundle\Entity\CabinetCategory 
     */
    public function getCategory()
    {
        return $this->category;
    }
}
sescandell commented 9 years ago

Hi @ksn135

This is probably because you don't go through https://github.com/symfony2admingenerator/FormExtensionsBundle/blob/master/Form/EventListener/CollectionUploadSubscriber.php#L174

I don't have immediately time to check for it. Maybe later today or next week (working on AdminLTE in priority :) ). But if you can find what's wrong and make a PR, that would be great !

Question: do you have the same issue with the "legacy" bundle? I have a sample in some of my codes, I'll try to show it to you ASAP

ksn135 commented 9 years ago

What is the "legacy" bundle ? You mean AvocodeFormExtensionsBundle ?

sescandell commented 9 years ago

Yes... the current one is still unstable and in active development

ksn135 commented 9 years ago

My bad, production project already running on it Well, very good motivated hands avaiable to work on issues to speed up stabilisation!!! ))

ksn135 commented 9 years ago

Well, I try to get it fixed by myself

sescandell commented 9 years ago

Here is some CollectionUpload I'm using in a production project working well:

$builder->add('medias', 'afe_collection_upload', array(
                    'primary_key' => 'pathHashed',
                    'label' => false,
                    'sortable' => false,
                    'nameable' => false,
                    'editable' => $options['with_description'] ? array('description') : array(),
                    'allow_add' => true,
                    'allow_delete' => true,
                    'type' => 'my_project_media',
                    'uploadRouteName' => 'async_upload',
                    'autoUpload' => $options['auto_upload'],
                    'options' => array(
                        'data_class' => Media::class,
                        'media_type' => Media::PICTURE,
                        'for_library' => true,
                        'with_description' => $options['with_description']
                    )
                ));

Could you please show us the generated Form?

Thank you,

ksn135 commented 9 years ago

Thank you very much for your help! Curently it starts working. I'm testing it. Code changes in CollectionUploadSubscriber.php:

        if ($this->allow_add) {
            // create file entites for each file
            foreach ($this->uploads as $upload) {
                $editable = $this->editable; // ADD THIS LINE
                if (!is_object($upload) && !is_null($this->storage)) {
                    // read submitted editable
                    // $editable = $this->editable[$upload];  // COMMENT OUT THIS ONE
                    $upload = $this->storage->getFile($upload);
                }

And my form class:

<?php
namespace Ksn135\CompanyBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormBuilderInterface;
use JMS\DiExtraBundle\Annotation\FormType;

/**
 * @FormType
 */
class CabinetFileType extends AbstractType
{
    /**
     * (non-PHPdoc)
     * @see \Symfony\Component\Form\AbstractType::buildForm()
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('id', 'hidden')
            ->add('name', 'text', array( 'label' => 'Название'))
            ->add('description', 'text', array( 'label' => 'Описание'))
            ->add('position', 'hidden')
            ->add('file','file');
    }
    /**
     * (non-PHPdoc)
     * @see \Symfony\Component\Form\AbstractType::setDefaultOptions()
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
       $resolver->setDefaults(array(
               'data_class' => 'Ksn135\CompanyBundle\Entity\CabinetFile',
                'groups' => array(),
            'cascade_validation' => true
       ));
        $resolver->setAllowedTypes(array(
            'groups' => 'array',
        ));
    }
    /* (non-PHPdoc)
     * @see \Symfony\Component\Form\FormTypeInterface::getName()
     */
    public function getName()
    {
        return 'cabinet_file_type';
    }

}
sescandell commented 9 years ago

This is your CabinetFileType, could you please show us the generated CabinetType (I assumed) form. Regarding your fix, take care that you change a big behavior there... Could you please double check what the type of $this->editable is? (is it an array or something else?)

I assume you are not in a "async" mode, am I right?

So basically, your fix might be wrong in async mode for example (I'm not sure, there is a long time I worked on that part, I could double check what I'm saying if you want). I think the right fix should be something like:

if ($this->allow_add) {
            // create file entites for each file
           $editable = array(); // Prevent strange behavior... or don't define it and so Exception will be thrown meaning the user mis-configured its form?
            foreach ($this->uploads as $index => $upload) {
                if (!is_object($upload) && !is_null($this->storage)) {
                    // read submitted editable
                    $editable = $this->editable[$upload];
                    $upload = $this->storage->getFile($upload);
                } elseif (array_key_exists($index, $this->editable)) {
                  $editable = $this->editable[$index];
                }

Not sure at all about it. You can also take a look here: https://github.com/sescandell/CollectionUploadSample

sescandell commented 9 years ago

My mistake, in my sample project, I'm not using the editable param... I should make an example...

ksn135 commented 9 years ago

I assume you are not in a "async" mode, am I right?

With my fix it works in both modes.

If you return back my uncommented line you've got undefined index error, because $upload is null when you save already filed form.

My autogenerated form type for CabinetCategory:

<?php

namespace Admingenerated\Ksn135CompanyBundle\Form\BaseCabinet_categoryType;

use Admingenerator\GeneratorBundle\Form\BaseType;
use Admingenerator\GeneratorBundle\Form\BaseOptions;
use Symfony\Component\Form\FormBuilderInterface;
use JMS\SecurityExtraBundle\Security\Authorization\Expression\Expression;

class EditType extends BaseType
{
    protected $securityContext;

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $this->groups = $options['groups'];

            if ($this->isDisplayedTitle()) {
            $builder->add('title', $this->getTypeTitle(), $this->getOptionsTitle($options));
        }

            if ($this->isDisplayedSlug()) {
            $builder->add('slug', $this->getTypeSlug(), $this->getOptionsSlug($options));
        }

            if ($this->isDisplayedFiles()) {
            $builder->add('files', $this->getTypeFiles(), $this->getOptionsFiles($options));
        }

        }

    /**
     * Get form type for title field.
     *
     * @return string|FormTypeInterface Field form type.
     */
    protected function getTypeTitle()
    {
        return $this->inject('text');
    }

    /**
     * Get options for title field.
     *
     * @param  array $builderOptions The builder options.
     * @return array Field options.
     */
    protected function getOptionsTitle(array $builderOptions = array())
    {
        $optionsClass = 'Ksn135\CompanyBundle\Form\Type\Cabinet_category\Options';
        $options = class_exists($optionsClass) ? new $optionsClass() : null;

        return $this->resolveOptions('title', array(  'required' => true,  'label' => 'Наименование',  'translation_domain' => 'Admin',), $builderOptions, $options);
    }

    /**
     * Check groups for title field.
     *
     * @return boolean
     */
    protected function isDisplayedTitle()
    {
        return $this->checkGroups(array());
    }

    /**
     * Get form type for slug field.
     *
     * @return string|FormTypeInterface Field form type.
     */
    protected function getTypeSlug()
    {
        return $this->inject('text');
    }

    /**
     * Get options for slug field.
     *
     * @param  array $builderOptions The builder options.
     * @return array Field options.
     */
    protected function getOptionsSlug(array $builderOptions = array())
    {
        $optionsClass = 'Ksn135\CompanyBundle\Form\Type\Cabinet_category\Options';
        $options = class_exists($optionsClass) ? new $optionsClass() : null;

        return $this->resolveOptions('slug', array(  'required' => true,  'label' => 'Ссылка',  'translation_domain' => 'Admin',), $builderOptions, $options);
    }

    /**
     * Check groups for slug field.
     *
     * @return boolean
     */
    protected function isDisplayedSlug()
    {
        return $this->checkGroups(array());
    }

    /**
     * Get form type for files field.
     *
     * @return string|FormTypeInterface Field form type.
     */
    protected function getTypeFiles()
    {
        return $this->inject('s2a_collection_upload');
    }

    /**
     * Get options for files field.
     *
     * @param  array $builderOptions The builder options.
     * @return array Field options.
     */
    protected function getOptionsFiles(array $builderOptions = array())
    {
        $optionsClass = 'Ksn135\CompanyBundle\Form\Type\Cabinet_category\Options';
        $options = class_exists($optionsClass) ? new $optionsClass() : null;

        return $this->resolveOptions('files', array(  'required' => false,  'primary_key' => 'id',  'nameable' => true,  'nameable_field' => 'name',  'sortable' => true,  'sortable_field' => 'position',  'editable' =>   array(    0 => 'description',  ),  'type' => new \Ksn135\CompanyBundle\Form\Type\CabinetFileType(),  'autoUpload' => true,  'loadImageFileTypes' => '/^image\\/(gif|jpe?g|png)$/i',  'previewMaxWidth' => 100,  'previewMaxHeight' => 100,  'previewAsCanvas' => true,  'prependFiles' => false,  'allow_add' => true,  'allow_delete' => true,  'error_bubbling' => false,  'options' =>   array(    'data_class' => 'Ksn135\\CompanyBundle\\Entity\\CabinetFile',  ),  'label' => 'Загруженные файлы',  'translation_domain' => 'Admin',), $builderOptions, $options);
    }

    /**
     * Check groups for files field.
     *
     * @return boolean
     */
    protected function isDisplayedFiles()
    {
        return $this->checkGroups(array());
    }

    public function getName()
    {
        return 'edit_companybundle_cabinetcategory';
    }
}
sescandell commented 9 years ago

OK... I'll take a look later, if it works for now for you, it's okay :+1:

ksn135 commented 9 years ago

No, async mode IS NOT working in my case. It's problem with folder permissions in MAC OS X: http://stackoverflow.com/questions/16378828/result-of-sys-get-temp-dir-not-writable-by-apache-processs-user . Sync mode is worked after my patch, but now I can't delete uploaded file. Form submited and filed back again with just deleted file. Continue investigation... )

ksn135 commented 9 years ago

Well, javascript just remove uploaded file row from DOM on "delete" button and then on "save" send empty form. CollectionUploadSubscriber can't do anything because it dosen't recieve expected "delete_uploads" key in empty data array. Moving on... May be just piece of Javascript code just dissapers ? Or may be it's again something wrong in my case, don't know /

sescandell commented 9 years ago

Did you try the other bundle: https://github.com/symfony2admingenerator/AvocodeFormExtensionsBundle ?

rpostolov commented 9 years ago

Hi,

I have the same problem with editable :

Undefined variable: editable

Was it fixed ?

Thanks

nicoip commented 9 years ago

Hi,

I have the same problem too, with field editable : Undefined variable: editable

Thanks !

tknuppe commented 8 years ago

I have encountered the same issue. My fix is as follows (Starting in line 174 in CollectionUploadSubscriber.php):


                $editable = array();
                if (!is_object($upload) && !is_null($this->storage)) {
                    // read submitted editable
                    $editable[] = array_key_exists($upload, $this->editable) ? $this->editable[$upload] : array();
                    $upload = $this->storage->getFile($upload);
                }

I don't know exactly how this works but I assumed that on first upload (synchronous) there are objects in $upload and then the variable §editable will never be set. And when it is set, it has to be an array.

Please fix this soon. I would do this on my own but I'm not quite sure if my solution is best. It works though (at least for me but there is a chance to break async mode with my modifiaction) ;)

In fact $editable has to be a multidimensional array (see foreach call), maybe there is some more work to do to clean it up.

Greetings.

ksn135 commented 8 years ago

@tknuppe For me your code works with the following changes: $editable = array_key_exists($upload, $this->editable) ? $this->editable[$upload] : array();