diff --git a/CHANGELOG.md b/CHANGELOG.md index abe783a41..3ad4ff222 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,5 +22,13 @@ Version 1.5.4 ============= - 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 diff --git a/Controller/DocumentPersonController.php b/Controller/DocumentPersonController.php index 0ff91e853..f6f074610 100644 --- a/Controller/DocumentPersonController.php +++ b/Controller/DocumentPersonController.php @@ -5,7 +5,9 @@ namespace Chill\DocStoreBundle\Controller; use Chill\DocStoreBundle\Entity\PersonDocument; use Chill\DocStoreBundle\Form\PersonDocumentType; use Chill\DocStoreBundle\Repository\DocumentRepository; +use Chill\PersonBundle\Privacy\PrivacyEvent; use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; @@ -29,9 +31,21 @@ class DocumentPersonController extends Controller */ 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->eventDispatcher = $eventDispatcher; } /** @@ -59,6 +73,12 @@ class DocumentPersonController extends Controller 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( 'ChillDocStoreBundle:PersonDocument:index.html.twig', [ @@ -119,7 +139,14 @@ class DocumentPersonController extends Controller { $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person); $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( 'ChillDocStoreBundle:PersonDocument:show.html.twig', ['document' => $document, 'person' => $person]); @@ -147,14 +174,29 @@ class DocumentPersonController extends Controller $this->getDoctrine()->getManager()->flush(); $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( 'person_document_edit', ['id' => $document->getId(), 'person' => $person->getId()]); + } elseif ($form->isSubmitted() and !$form->isValid()) { $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( 'ChillDocStoreBundle:PersonDocument:edit.html.twig', [ diff --git a/DependencyInjection/ChillDocStoreExtension.php b/DependencyInjection/ChillDocStoreExtension.php index 44141741e..ed36f1845 100644 --- a/DependencyInjection/ChillDocStoreExtension.php +++ b/DependencyInjection/ChillDocStoreExtension.php @@ -30,6 +30,7 @@ class ChillDocStoreExtension extends Extension implements PrependExtensionInterf $loader->load('services/controller.yml'); $loader->load('services/menu.yml'); $loader->load('services/fixtures.yml'); + $loader->load('services/form.yml'); } public function prepend(ContainerBuilder $container) diff --git a/Form/StoredObjectType.php b/Form/StoredObjectType.php index 824bd62ad..64cc32e83 100644 --- a/Form/StoredObjectType.php +++ b/Form/StoredObjectType.php @@ -10,14 +10,25 @@ use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Form\Extension\Core\Type\HiddenType; use Chill\DocStoreBundle\Entity\StoredObject; use Symfony\Component\Form\CallbackTransformer; +use Doctrine\ORM\EntityManagerInterface; /** - * + * Form type which allow to join a document * - * @author Julien Fastré */ class StoredObjectType extends AbstractType { + /** + * + * @var EntityManagerInterface + */ + protected $em; + + public function __construct(EntityManagerInterface $em) + { + $this->em = $em; + } + public function buildForm(FormBuilderInterface $builder, array $options) { $builder @@ -86,6 +97,9 @@ class StoredObjectType extends AbstractType } if (NULL === $object->getFilename()) { + // remove the original object + $this->em->remove($object); + return null; } diff --git a/Object/ObjectToAsyncFileTransformer.php b/Object/ObjectToAsyncFileTransformer.php index 5501be6ce..c5542d667 100644 --- a/Object/ObjectToAsyncFileTransformer.php +++ b/Object/ObjectToAsyncFileTransformer.php @@ -30,8 +30,6 @@ class ObjectToAsyncFileTransformer implements AsyncFileTransformerInterface public function toAsyncFile($data) { - dump($data); - if ($data instanceof StoredObject) { return $data; } @@ -39,8 +37,6 @@ class ObjectToAsyncFileTransformer implements AsyncFileTransformerInterface public function toData(AsyncFileInterface $asyncFile) { - dump($asyncFile); - $object = $this->em ->getRepository(StoredObject::class) ->findByFilename($asyncFile->getObjectName()) diff --git a/Resources/config/services/controller.yml b/Resources/config/services/controller.yml index 379d36829..0a2798508 100644 --- a/Resources/config/services/controller.yml +++ b/Resources/config/services/controller.yml @@ -5,4 +5,5 @@ services: Chill\DocStoreBundle\Controller\DocumentPersonController: autowire: true - \ No newline at end of file + arguments: + $eventDispatcher: '@Symfony\Component\EventDispatcher\EventDispatcherInterface' diff --git a/Resources/config/services/form.yml b/Resources/config/services/form.yml new file mode 100644 index 000000000..c84507e51 --- /dev/null +++ b/Resources/config/services/form.yml @@ -0,0 +1,6 @@ +services: + Chill\DocStoreBundle\Form\StoredObjectType: + arguments: + $em: '@Doctrine\ORM\EntityManagerInterface' + tags: + - { name: form.type } diff --git a/Resources/public/module/async_upload/index.scss b/Resources/public/module/async_upload/index.scss index 9d42d88d9..54299efce 100644 --- a/Resources/public/module/async_upload/index.scss +++ b/Resources/public/module/async_upload/index.scss @@ -1,6 +1,7 @@ +// override dropzone from dropzoneJS .dropzone { margin-bottom: 0.5rem; - + .dz-preview { display: initial; margin-left: auto; @@ -19,7 +20,15 @@ } } -.sc-button.dz-bt-below-dropzone { - width: 100%; -} +.chill-dropzone__below-zone { + display: flex; + + & > *:not(:last-child) { + margin-right: 1rem; + } + + .sc-button.dz-bt-below-dropzone { + width: 100%; + } +} diff --git a/Resources/public/module/async_upload/uploader.js b/Resources/public/module/async_upload/uploader.js index 9ace4282c..9e96070bb 100644 --- a/Resources/public/module/async_upload/uploader.js +++ b/Resources/public/module/async_upload/uploader.js @@ -98,15 +98,26 @@ var encryptFile = function(originalFile, zoneData, done) { 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 created = document.createElement('div'), initMessage = document.createElement('div'), initContent = zone.dataset.labelInitMessage, - zoneData = { zone: zone, suffix: createFilename() }, dropzoneI; - + created.classList.add('dropzone'); initMessage.classList.add('dz-message'); initMessage.appendChild(document.createTextNode(initContent)); @@ -157,8 +168,6 @@ var initialize = function(zone) { zone.insertBefore(created, zone.firstChild); - insertDownloadButton(zone, zoneData); - let event = new CustomEvent("chill_dropzone_initialized", { detail: { dropzone: dropzoneI, @@ -168,6 +177,21 @@ var initialize = function(zone) { 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 text = ""; var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; @@ -195,6 +219,35 @@ var storeDataInForm = (zone, zoneData) => { 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 inputKey = zone.querySelector('input[data-stored-object-key]'), @@ -202,7 +255,15 @@ var removeDataInForm = (zone, zoneData) => { inputObject = zone.querySelector('input[data-async-file-upload]'), 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 = ""; inputIv.value = ""; inputType.value = ""; @@ -211,7 +272,57 @@ var removeDataInForm = (zone, zoneData) => { 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 existingButtons = zone.querySelectorAll('a[data-download-button]'), newButton = document.createElement('a'), @@ -247,7 +358,8 @@ var insertDownloadButton = (zone) => { newButton.classList.add('sc-button', 'bt-download', 'dz-bt-below-dropzone'); newButton.textContent = labelQuietButton; - zone.appendChild(newButton); + addBelowButton(newButton, zone, zoneData); + //zone.appendChild(newButton); initializeDownload(zone); }; diff --git a/Resources/translations/messages.fr.yml b/Resources/translations/messages.fr.yml index adf017864..5e46490a6 100644 --- a/Resources/translations/messages.fr.yml +++ b/Resources/translations/messages.fr.yml @@ -24,3 +24,4 @@ Max files exceeded. Remove previous files: Nombre maximum de fichier atteint. Su 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 ? Upload canceled: Téléversement annulé +Remove existing file: Supprimer le document existant \ No newline at end of file diff --git a/Resources/views/Form/fields.html.twig b/Resources/views/Form/fields.html.twig index f9253c267..3ab0ff255 100644 --- a/Resources/views/Form/fields.html.twig +++ b/Resources/views/Form/fields.html.twig @@ -11,6 +11,8 @@ 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-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') }}"> {{ form_widget(form.filename) }} {{ form_widget(form.keyInfos, { 'attr': { 'data-stored-object-key': 1 } }) }}