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.