Multiple file uploads in TYPO3 Flow

658 views Asked by At

I need support for the multiple attribute of file uploads (<input type="file" multiple />) in TYPO3 Flow. So this is what I've done so far.

I've an entity with a resource collection:

/**
 * @var \Doctrine\Common\Collections\Collection<\TYPO3\Flow\Resource\Resource>
 * @ORM\ManyToMany(cascade={"all"})
 * @ORM\JoinTable(inverseJoinColumns={@ORM\JoinColumn(unique=true,onDelete="cascade")})
 */
 protected $resources;

Therefor I've enhanced the f:form.upload view helper:

/**
 * Overrides upload view helper.
 */
class UploadViewHelper extends \TYPO3\Fluid\ViewHelpers\Form\UploadViewHelper {

    /**
     * Initialize the arguments.
     *
     * @return void
     * @api
     */
    public function initializeArguments() {
        parent::initializeArguments();

        $this->registerArgument('multiple', 'boolean', 'Specifies that the user is allowed to enter more than one value in the <input> element.', FALSE, FALSE);
    }

    /**
     * Renders the upload field.
     *
     * @return string
     * @api
     */
    public function render() {
        $name = $this->getName();
        $multiple = $this->arguments['multiple'];
        $this->registerFieldNameForFormTokenGeneration($name);

        $output = '';

        if ($multiple) {
            $collectionObject = $this->getUploadedResources();
            if ($collectionObject !== NULL) {
                foreach($collectionObject as $resourceObjectIndex => $resourceObject) {
                    $output .= $this->renderUploadedResource($resourceObject, $resourceObjectIndex);
                }
            }
            $this->tag->addAttribute('multiple', 'multiple');
        } else {
            $resourceObject = $this->getUploadedResource();
            if ($resourceObject !== NULL) {
                $output .= $this->renderUploadedResource($resourceObject);
            }
        }
        $this->tag->addAttribute('type', 'file');
        $this->tag->addAttribute('name', $name);

        $this->setErrorClassAttribute();

        $output .= $this->tag->render();
        return $output;
    }

    protected function renderUploadedResource($resourceObject, $resourceObjectIndex = NULL) {
        $output = '';
        $resourceObjectIndex = $resourceObjectIndex === NULL ? '' : '[' . $resourceObjectIndex . ']';

        $filenameIdAttribute = $resourcePointerIdAttribute = '';
        if ($this->hasArgument('id')) {
            $filenameIdAttribute = ' id="' . htmlspecialchars($this->arguments['id']) . '-filename"';
            $resourcePointerIdAttribute = ' id="' . htmlspecialchars($this->arguments['id']) . '-resourcePointer"';
        }
        $filenameValue = $resourceObject->getFilename();
        $resourcePointerValue = $resourceObject->getResourcePointer();
        $output .= '<input type="hidden" name="' . parent::getName() . $resourceObjectIndex . '[submittedFile][filename]" value="' . htmlspecialchars($filenameValue) . '"' . $filenameIdAttribute . ' />';
        $output .= '<input type="hidden" name="' . parent::getName() . $resourceObjectIndex . '[submittedFile][resourcePointer]" value="' . htmlspecialchars($resourcePointerValue) . '"' . $resourcePointerIdAttribute . ' />';

        return $output;
    }

    /**
     * Returns a previously uploaded resources.
     * If errors occurred during property mapping for this property, NULL is returned
     *
     * @return \Doctrine\Common\Collections\Collection
     */
    protected function getUploadedResources() {
        if ($this->getMappingResultsForProperty()->hasErrors()) {
            return NULL;
        }
        $collectionObject = $this->getValue(FALSE);
        if ($collectionObject instanceof Collection) {
            return $collectionObject;
        }
        return $this->propertyMapper->convert($collectionObject, 'Doctrine\Common\Collections\Collection<\TYPO3\Flow\Resource\Resource>');
    }

    /**
     * Override `getName()`.
     *
     * @return string Name
     */
    protected function getName() {
        $name = parent::getName();
        return $this->postfixFieldName($name);
    }

    /**
     * Append `[]` to the field name when the argument `multiple` is set.
     *
     * @param string $name 
     * @return string Name
     */
    protected function postfixFieldName($name) {
        return $this->hasArgument('multiple') && $this->arguments['multiple'] ? $name . '[]' : $name;
    }
}

So the view helper <my:form.upload property="resources" multiple="1" /> will create a array field for multiple files:

<input multiple="multiple" name="entity[resources][]" type="file">

If there are already uploads it looks like:

<input name="entity[resources][0][submittedFile][filename]" value="Foo.jpg" type="hidden">
<input name="entity[resources][0][submittedFile][resourcePointer]" value="7c94c0200e9f25b1ccb48c2853147f52286328d0" type="hidden">
<input multiple="multiple" name="entity[resources][]" type="file">

To make it work I've to add this into the controller:

protected function initializeAction() {
    parent::initializeAction();

    switch($this->actionMethodName) {
        case 'createAction':
        case 'updateAction':
            $this->arguments
                ->getArgument('resource')
                ->getPropertyMappingConfiguration()
                ->forProperty('resources.*')
                ->setTypeConverterOption(
                    'TYPO3\Flow\Property\TypeConverter\PersistentObjectConverter',
                    \TYPO3\Flow\Property\TypeConverter\PersistentObjectConverter::CONFIGURATION_MODIFICATION_ALLOWED,
                    TRUE
                );
            break;
    }
}

The problem is, with every call of the update action, whenever uploads already exist, the existing uploads are re-added and the table typo3_flow_resource_resource grows and grows. Does anybody know how to fix that?

Notice This solution should only allow to replace the uploaded files.

0

There are 0 answers