mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-13 13:54:23 +00:00
Merge remote-tracking branch 'origin/master' into stable
This commit is contained in:
commit
61c11c3eee
10
CHANGELOG.md
10
CHANGELOG.md
@ -22,5 +22,13 @@ Version 1.5.4
|
|||||||
=============
|
=============
|
||||||
|
|
||||||
- replace default message on download button below dropzone ;
|
- replace default message on download button below dropzone ;
|
||||||
- launch event when dropzone is initialized, to allow to customize events on dropzone;
|
- launch event when dropzone is initialized, to allow to customize events on dropzone;
|
||||||
|
- add privacy events to document index / show
|
||||||
|
- add privacy events to document edit / update
|
||||||
|
- remove dump message
|
||||||
|
|
||||||
|
Version 1.5.5
|
||||||
|
=============
|
||||||
|
|
||||||
|
- add button to remove existing document in form, and improve UI in this part
|
||||||
|
- fix error when document is removed in form
|
||||||
|
@ -5,7 +5,9 @@ namespace Chill\DocStoreBundle\Controller;
|
|||||||
use Chill\DocStoreBundle\Entity\PersonDocument;
|
use Chill\DocStoreBundle\Entity\PersonDocument;
|
||||||
use Chill\DocStoreBundle\Form\PersonDocumentType;
|
use Chill\DocStoreBundle\Form\PersonDocumentType;
|
||||||
use Chill\DocStoreBundle\Repository\DocumentRepository;
|
use Chill\DocStoreBundle\Repository\DocumentRepository;
|
||||||
|
use Chill\PersonBundle\Privacy\PrivacyEvent;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||||
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
@ -29,9 +31,21 @@ class DocumentPersonController extends Controller
|
|||||||
*/
|
*/
|
||||||
protected $translator;
|
protected $translator;
|
||||||
|
|
||||||
public function __construct(TranslatorInterface $translator)
|
/**
|
||||||
|
* @var EventDispatcherInterface
|
||||||
|
*/
|
||||||
|
protected $eventDispatcher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DocumentPersonController constructor.
|
||||||
|
*
|
||||||
|
* @param TranslatorInterface $translator
|
||||||
|
* @param EventDispatcherInterface $eventDispatcher
|
||||||
|
*/
|
||||||
|
public function __construct(TranslatorInterface $translator, EventDispatcherInterface $eventDispatcher)
|
||||||
{
|
{
|
||||||
$this->translator = $translator;
|
$this->translator = $translator;
|
||||||
|
$this->eventDispatcher = $eventDispatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -59,6 +73,12 @@ class DocumentPersonController extends Controller
|
|||||||
array('date' => 'DESC')
|
array('date' => 'DESC')
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$event = new PrivacyEvent($person, array(
|
||||||
|
'element_class' => PersonDocument::class,
|
||||||
|
'action' => 'index'
|
||||||
|
));
|
||||||
|
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
|
||||||
|
|
||||||
return $this->render(
|
return $this->render(
|
||||||
'ChillDocStoreBundle:PersonDocument:index.html.twig',
|
'ChillDocStoreBundle:PersonDocument:index.html.twig',
|
||||||
[
|
[
|
||||||
@ -119,7 +139,14 @@ class DocumentPersonController extends Controller
|
|||||||
{
|
{
|
||||||
$this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person);
|
$this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person);
|
||||||
$this->denyAccessUnlessGranted('CHILL_PERSON_DOCUMENT_SEE', $document);
|
$this->denyAccessUnlessGranted('CHILL_PERSON_DOCUMENT_SEE', $document);
|
||||||
|
|
||||||
|
$event = new PrivacyEvent($person, array(
|
||||||
|
'element_class' => PersonDocument::class,
|
||||||
|
'element_id' => $document->getId(),
|
||||||
|
'action' => 'show'
|
||||||
|
));
|
||||||
|
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
|
||||||
|
|
||||||
return $this->render(
|
return $this->render(
|
||||||
'ChillDocStoreBundle:PersonDocument:show.html.twig',
|
'ChillDocStoreBundle:PersonDocument:show.html.twig',
|
||||||
['document' => $document, 'person' => $person]);
|
['document' => $document, 'person' => $person]);
|
||||||
@ -147,14 +174,29 @@ class DocumentPersonController extends Controller
|
|||||||
$this->getDoctrine()->getManager()->flush();
|
$this->getDoctrine()->getManager()->flush();
|
||||||
|
|
||||||
$this->addFlash('success', $this->translator->trans("The document is successfully updated"));
|
$this->addFlash('success', $this->translator->trans("The document is successfully updated"));
|
||||||
|
|
||||||
|
$event = new PrivacyEvent($person, array(
|
||||||
|
'element_class' => PersonDocument::class,
|
||||||
|
'element_id' => $document->getId(),
|
||||||
|
'action' => 'update'
|
||||||
|
));
|
||||||
|
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
|
||||||
|
|
||||||
return $this->redirectToRoute(
|
return $this->redirectToRoute(
|
||||||
'person_document_edit',
|
'person_document_edit',
|
||||||
['id' => $document->getId(), 'person' => $person->getId()]);
|
['id' => $document->getId(), 'person' => $person->getId()]);
|
||||||
|
|
||||||
} elseif ($form->isSubmitted() and !$form->isValid()) {
|
} elseif ($form->isSubmitted() and !$form->isValid()) {
|
||||||
$this->addFlash('error', $this->translator->trans("This form contains errors"));
|
$this->addFlash('error', $this->translator->trans("This form contains errors"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$event = new PrivacyEvent($person, array(
|
||||||
|
'element_class' => PersonDocument::class,
|
||||||
|
'element_id' => $document->getId(),
|
||||||
|
'action' => 'edit'
|
||||||
|
));
|
||||||
|
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
|
||||||
|
|
||||||
return $this->render(
|
return $this->render(
|
||||||
'ChillDocStoreBundle:PersonDocument:edit.html.twig',
|
'ChillDocStoreBundle:PersonDocument:edit.html.twig',
|
||||||
[
|
[
|
||||||
|
@ -30,6 +30,7 @@ class ChillDocStoreExtension extends Extension implements PrependExtensionInterf
|
|||||||
$loader->load('services/controller.yml');
|
$loader->load('services/controller.yml');
|
||||||
$loader->load('services/menu.yml');
|
$loader->load('services/menu.yml');
|
||||||
$loader->load('services/fixtures.yml');
|
$loader->load('services/fixtures.yml');
|
||||||
|
$loader->load('services/form.yml');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function prepend(ContainerBuilder $container)
|
public function prepend(ContainerBuilder $container)
|
||||||
|
@ -10,14 +10,25 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
|||||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
use Symfony\Component\Form\CallbackTransformer;
|
use Symfony\Component\Form\CallbackTransformer;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Form type which allow to join a document
|
||||||
*
|
*
|
||||||
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
|
||||||
*/
|
*/
|
||||||
class StoredObjectType extends AbstractType
|
class StoredObjectType extends AbstractType
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var EntityManagerInterface
|
||||||
|
*/
|
||||||
|
protected $em;
|
||||||
|
|
||||||
|
public function __construct(EntityManagerInterface $em)
|
||||||
|
{
|
||||||
|
$this->em = $em;
|
||||||
|
}
|
||||||
|
|
||||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
{
|
{
|
||||||
$builder
|
$builder
|
||||||
@ -86,6 +97,9 @@ class StoredObjectType extends AbstractType
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (NULL === $object->getFilename()) {
|
if (NULL === $object->getFilename()) {
|
||||||
|
// remove the original object
|
||||||
|
$this->em->remove($object);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,8 +30,6 @@ class ObjectToAsyncFileTransformer implements AsyncFileTransformerInterface
|
|||||||
|
|
||||||
public function toAsyncFile($data)
|
public function toAsyncFile($data)
|
||||||
{
|
{
|
||||||
dump($data);
|
|
||||||
|
|
||||||
if ($data instanceof StoredObject) {
|
if ($data instanceof StoredObject) {
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
@ -39,8 +37,6 @@ class ObjectToAsyncFileTransformer implements AsyncFileTransformerInterface
|
|||||||
|
|
||||||
public function toData(AsyncFileInterface $asyncFile)
|
public function toData(AsyncFileInterface $asyncFile)
|
||||||
{
|
{
|
||||||
dump($asyncFile);
|
|
||||||
|
|
||||||
$object = $this->em
|
$object = $this->em
|
||||||
->getRepository(StoredObject::class)
|
->getRepository(StoredObject::class)
|
||||||
->findByFilename($asyncFile->getObjectName())
|
->findByFilename($asyncFile->getObjectName())
|
||||||
|
@ -5,4 +5,5 @@ services:
|
|||||||
|
|
||||||
Chill\DocStoreBundle\Controller\DocumentPersonController:
|
Chill\DocStoreBundle\Controller\DocumentPersonController:
|
||||||
autowire: true
|
autowire: true
|
||||||
|
arguments:
|
||||||
|
$eventDispatcher: '@Symfony\Component\EventDispatcher\EventDispatcherInterface'
|
||||||
|
6
Resources/config/services/form.yml
Normal file
6
Resources/config/services/form.yml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
services:
|
||||||
|
Chill\DocStoreBundle\Form\StoredObjectType:
|
||||||
|
arguments:
|
||||||
|
$em: '@Doctrine\ORM\EntityManagerInterface'
|
||||||
|
tags:
|
||||||
|
- { name: form.type }
|
@ -1,6 +1,7 @@
|
|||||||
|
// override dropzone from dropzoneJS
|
||||||
.dropzone {
|
.dropzone {
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
|
|
||||||
.dz-preview {
|
.dz-preview {
|
||||||
display: initial;
|
display: initial;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
@ -19,7 +20,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sc-button.dz-bt-below-dropzone {
|
.chill-dropzone__below-zone {
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
& > *:not(:last-child) {
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sc-button.dz-bt-below-dropzone {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -98,15 +98,26 @@ var encryptFile = function(originalFile, zoneData, done) {
|
|||||||
reader.readAsArrayBuffer(originalFile);
|
reader.readAsArrayBuffer(originalFile);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var addBelowButton = (btn, zone, zoneData) => {
|
||||||
|
let
|
||||||
|
belowZone = zone.querySelector('.chill-dropzone__below-zone');
|
||||||
|
|
||||||
|
if (belowZone === null) {
|
||||||
|
belowZone = document.createElement('div');
|
||||||
|
belowZone.classList.add('chill-dropzone__below-zone');
|
||||||
|
zone.appendChild(belowZone);
|
||||||
|
}
|
||||||
|
|
||||||
|
belowZone.appendChild(btn);
|
||||||
|
};
|
||||||
|
|
||||||
var initialize = function(zone) {
|
var createZone = (zone, zoneData) => {
|
||||||
var
|
var
|
||||||
created = document.createElement('div'),
|
created = document.createElement('div'),
|
||||||
initMessage = document.createElement('div'),
|
initMessage = document.createElement('div'),
|
||||||
initContent = zone.dataset.labelInitMessage,
|
initContent = zone.dataset.labelInitMessage,
|
||||||
zoneData = { zone: zone, suffix: createFilename() },
|
|
||||||
dropzoneI;
|
dropzoneI;
|
||||||
|
|
||||||
created.classList.add('dropzone');
|
created.classList.add('dropzone');
|
||||||
initMessage.classList.add('dz-message');
|
initMessage.classList.add('dz-message');
|
||||||
initMessage.appendChild(document.createTextNode(initContent));
|
initMessage.appendChild(document.createTextNode(initContent));
|
||||||
@ -157,8 +168,6 @@ var initialize = function(zone) {
|
|||||||
|
|
||||||
zone.insertBefore(created, zone.firstChild);
|
zone.insertBefore(created, zone.firstChild);
|
||||||
|
|
||||||
insertDownloadButton(zone, zoneData);
|
|
||||||
|
|
||||||
let event = new CustomEvent("chill_dropzone_initialized", {
|
let event = new CustomEvent("chill_dropzone_initialized", {
|
||||||
detail: {
|
detail: {
|
||||||
dropzone: dropzoneI,
|
dropzone: dropzoneI,
|
||||||
@ -168,6 +177,21 @@ var initialize = function(zone) {
|
|||||||
window.dispatchEvent(event);
|
window.dispatchEvent(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var initialize = function(zone) {
|
||||||
|
var
|
||||||
|
allowRemove = zone.dataset.allowRemove,
|
||||||
|
zoneData = { zone: zone, suffix: createFilename(), allowRemove: allowRemove, old: null }
|
||||||
|
;
|
||||||
|
|
||||||
|
if (hasDataInForm(zone, zoneData)) {
|
||||||
|
insertRemoveButton(zone, zoneData);
|
||||||
|
insertDownloadButton(zone, zoneData);
|
||||||
|
} else {
|
||||||
|
createZone(zone, zoneData);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
var createFilename = () => {
|
var createFilename = () => {
|
||||||
var text = "";
|
var text = "";
|
||||||
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
@ -195,6 +219,35 @@ var storeDataInForm = (zone, zoneData) => {
|
|||||||
insertDownloadButton(zone);
|
insertDownloadButton(zone);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const restoreDataInForm = (zone, zoneData) => {
|
||||||
|
var
|
||||||
|
inputKey = zone.querySelector('input[data-stored-object-key]'),
|
||||||
|
inputIv = zone.querySelector('input[data-stored-object-iv]'),
|
||||||
|
inputObject = zone.querySelector('input[data-async-file-upload]'),
|
||||||
|
inputType = zone.querySelector('input[data-async-file-type]')
|
||||||
|
;
|
||||||
|
|
||||||
|
if (zoneData.old === null) {
|
||||||
|
console.log('should not have restored data');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
inputKey.value = zoneData.old.key;
|
||||||
|
inputIv.value = zoneData.old.iv;
|
||||||
|
inputType.value = zoneData.old.type;
|
||||||
|
inputObject.value = zoneData.old.obj;
|
||||||
|
|
||||||
|
insertDownloadButton(zone);
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasDataInForm = (zone, zoneData) => {
|
||||||
|
var
|
||||||
|
inputObject = zone.querySelector('input[data-async-file-upload]')
|
||||||
|
;
|
||||||
|
|
||||||
|
return inputObject.value.length > 0;
|
||||||
|
};
|
||||||
|
|
||||||
var removeDataInForm = (zone, zoneData) => {
|
var removeDataInForm = (zone, zoneData) => {
|
||||||
var
|
var
|
||||||
inputKey = zone.querySelector('input[data-stored-object-key]'),
|
inputKey = zone.querySelector('input[data-stored-object-key]'),
|
||||||
@ -202,7 +255,15 @@ var removeDataInForm = (zone, zoneData) => {
|
|||||||
inputObject = zone.querySelector('input[data-async-file-upload]'),
|
inputObject = zone.querySelector('input[data-async-file-upload]'),
|
||||||
inputType = zone.querySelector('input[data-async-file-type]')
|
inputType = zone.querySelector('input[data-async-file-type]')
|
||||||
;
|
;
|
||||||
|
|
||||||
|
// store data for future usage
|
||||||
|
zoneData.old = {
|
||||||
|
key: inputKey.value,
|
||||||
|
iv: inputIv.value,
|
||||||
|
obj: inputObject.value,
|
||||||
|
type: inputType.value
|
||||||
|
};
|
||||||
|
// set blank values
|
||||||
inputKey.value = "";
|
inputKey.value = "";
|
||||||
inputIv.value = "";
|
inputIv.value = "";
|
||||||
inputType.value = "";
|
inputType.value = "";
|
||||||
@ -211,7 +272,57 @@ var removeDataInForm = (zone, zoneData) => {
|
|||||||
insertDownloadButton(zone);
|
insertDownloadButton(zone);
|
||||||
};
|
};
|
||||||
|
|
||||||
var insertDownloadButton = (zone) => {
|
var insertRemoveButton = (zone, zoneData) => {
|
||||||
|
var
|
||||||
|
removeButton = document.createElement('a'),
|
||||||
|
cancelButton = document.createElement('a'),
|
||||||
|
labelRemove = zone.dataset.dictRemove,
|
||||||
|
labelCancel = 'Restaurer'
|
||||||
|
;
|
||||||
|
|
||||||
|
removeButton.classList.add('sc-button', 'bt-delete');
|
||||||
|
removeButton.textContent = labelRemove;
|
||||||
|
|
||||||
|
cancelButton.classList.add('sc-button');
|
||||||
|
cancelButton.textContent = labelCancel;
|
||||||
|
|
||||||
|
removeButton.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (zoneData.allowRemove === 'true') {
|
||||||
|
removeDataInForm(zone, zoneData);
|
||||||
|
cancelButton.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
restoreDataInForm(zone, zoneData);
|
||||||
|
|
||||||
|
cancelButton.remove();
|
||||||
|
zone.querySelector('.dropzone').remove();
|
||||||
|
|
||||||
|
initialize(zone);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
addBelowButton(cancelButton, zone, zoneData);
|
||||||
|
//zone.appendChild(cancelButton);
|
||||||
|
removeButton.remove();
|
||||||
|
createZone(zone, zoneData);
|
||||||
|
});
|
||||||
|
|
||||||
|
addBelowButton(removeButton, zone, zoneData);
|
||||||
|
// zone.appendChild(removeButton);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeDownloadButton = (zone, zoneData) => {
|
||||||
|
var
|
||||||
|
existingButtons = zone.querySelectorAll('a[data-download-button]')
|
||||||
|
;
|
||||||
|
|
||||||
|
// remove existing
|
||||||
|
existingButtons.forEach(function(b) {
|
||||||
|
b.remove();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var insertDownloadButton = (zone, zoneData) => {
|
||||||
var
|
var
|
||||||
existingButtons = zone.querySelectorAll('a[data-download-button]'),
|
existingButtons = zone.querySelectorAll('a[data-download-button]'),
|
||||||
newButton = document.createElement('a'),
|
newButton = document.createElement('a'),
|
||||||
@ -247,7 +358,8 @@ var insertDownloadButton = (zone) => {
|
|||||||
newButton.classList.add('sc-button', 'bt-download', 'dz-bt-below-dropzone');
|
newButton.classList.add('sc-button', 'bt-download', 'dz-bt-below-dropzone');
|
||||||
newButton.textContent = labelQuietButton;
|
newButton.textContent = labelQuietButton;
|
||||||
|
|
||||||
zone.appendChild(newButton);
|
addBelowButton(newButton, zone, zoneData);
|
||||||
|
//zone.appendChild(newButton);
|
||||||
initializeDownload(zone);
|
initializeDownload(zone);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -24,3 +24,4 @@ Max files exceeded. Remove previous files: Nombre maximum de fichier atteint. Su
|
|||||||
Cancel upload: Annuler le téléversement
|
Cancel upload: Annuler le téléversement
|
||||||
Are you sure you want to cancel this upload ?: Êtes-vous sûrs de vouloir annuler ce téléversement ?
|
Are you sure you want to cancel this upload ?: Êtes-vous sûrs de vouloir annuler ce téléversement ?
|
||||||
Upload canceled: Téléversement annulé
|
Upload canceled: Téléversement annulé
|
||||||
|
Remove existing file: Supprimer le document existant
|
@ -11,6 +11,8 @@
|
|||||||
data-dict-cancel-upload="{{ 'Cancel upload'|trans|escape('html_attr') }}"
|
data-dict-cancel-upload="{{ 'Cancel upload'|trans|escape('html_attr') }}"
|
||||||
data-dict-cancel-upload-confirm="{{ 'Are you sure you want to cancel this upload ?'|trans|escape('html_attr') }}"
|
data-dict-cancel-upload-confirm="{{ 'Are you sure you want to cancel this upload ?'|trans|escape('html_attr') }}"
|
||||||
data-dict-upload-canceled="{{ 'Upload canceled'|trans|escape('html_attr') }}"
|
data-dict-upload-canceled="{{ 'Upload canceled'|trans|escape('html_attr') }}"
|
||||||
|
data-dict-remove="{{ 'Remove existing file'|trans|escape('html_attr') }}"
|
||||||
|
data-allow-remove="{% if required %}false{% else %}true{% endif %}"
|
||||||
data-temp-url-generator="{{ path('async_upload.generate_url', { 'method': 'GET' })|escape('html_attr') }}">
|
data-temp-url-generator="{{ path('async_upload.generate_url', { 'method': 'GET' })|escape('html_attr') }}">
|
||||||
{{ form_widget(form.filename) }}
|
{{ form_widget(form.filename) }}
|
||||||
{{ form_widget(form.keyInfos, { 'attr': { 'data-stored-object-key': 1 } }) }}
|
{{ form_widget(form.keyInfos, { 'attr': { 'data-stored-object-key': 1 } }) }}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user