diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/show.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/show.html.twig index 53bd3e8a7..1ec0ab274 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/show.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/show.html.twig @@ -168,7 +168,7 @@ {% if entity.documents|length > 0 %} {% else %} diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/showAccompanyingCourse.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/showAccompanyingCourse.html.twig index fbd7b20b4..3486f47bc 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/showAccompanyingCourse.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/showAccompanyingCourse.html.twig @@ -8,12 +8,14 @@ {{ parent() }} {{ encore_entry_script_tags('mod_notification_toggle_read_status') }} {{ encore_entry_script_tags('mod_async_upload') }} + {{ encore_entry_script_tags('mod_document_action_buttons_group') }} {% endblock %} {% block css %} {{ parent() }} {{ encore_entry_link_tags('mod_notification_toggle_read_status') }} {{ encore_entry_link_tags('mod_async_upload') }} + {{ encore_entry_link_tags('mod_document_action_buttons_group') }} {% endblock %} {% import 'ChillActivityBundle:ActivityReason:macro.html.twig' as m %} diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/showPerson.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/showPerson.html.twig index 5776eddb5..43a8eb86b 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/showPerson.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/showPerson.html.twig @@ -7,13 +7,13 @@ {% block js %} {{ parent() }} {{ encore_entry_script_tags('mod_notification_toggle_read_status') }} - {{ encore_entry_link_tags('mod_async_upload') }} + {{ encore_entry_script_tags('mod_document_action_buttons_group') }} {% endblock %} {% block css %} {{ parent() }} {{ encore_entry_link_tags('mod_notification_toggle_read_status') }} - {{ encore_entry_link_tags('mod_async_upload') }} + {{ encore_entry_link_tags('mod_document_action_buttons_group') }} {% endblock %} {% import 'ChillActivityBundle:ActivityReason:macro.html.twig' as m %} diff --git a/src/Bundle/ChillBudgetBundle/Config/ConfigRepository.php b/src/Bundle/ChillBudgetBundle/Config/ConfigRepository.php deleted file mode 100644 index f28a83a4d..000000000 --- a/src/Bundle/ChillBudgetBundle/Config/ConfigRepository.php +++ /dev/null @@ -1,102 +0,0 @@ -resources = $resources; - $this->charges = $charges; - } - - public function getChargesKeys(bool $onlyActive = false): array - { - return array_map(static function ($element) { - return $element['key']; - }, $this->getCharges($onlyActive)); - } - - /** - * @return array where keys are the resource'key and label the ressource label - */ - public function getChargesLabels(bool $onlyActive = false) - { - $charges = []; - - foreach ($this->getCharges($onlyActive) as $definition) { - $charges[$definition['key']] = $this->normalizeLabel($definition['labels']); - } - - return $charges; - } - - public function getResourcesKeys(bool $onlyActive = false): array - { - return array_map(static function ($element) { - return $element['key']; - }, $this->getResources($onlyActive)); - } - - /** - * @return array where keys are the resource'key and label the ressource label - */ - public function getResourcesLabels(bool $onlyActive = false) - { - $resources = []; - - foreach ($this->getResources($onlyActive) as $definition) { - $resources[$definition['key']] = $this->normalizeLabel($definition['labels']); - } - - return $resources; - } - - private function getCharges(bool $onlyActive = false): array - { - return $onlyActive ? - array_filter($this->charges, static function ($el) { - return $el['active']; - }) - : $this->charges; - } - - private function getResources(bool $onlyActive = false): array - { - return $onlyActive ? - array_filter($this->resources, static function ($el) { - return $el['active']; - }) - : $this->resources; - } - - private function normalizeLabel($labels) - { - $normalizedLabels = []; - - foreach ($labels as $labelDefinition) { - $normalizedLabels[$labelDefinition['lang']] = $labelDefinition['label']; - } - - return $normalizedLabels; - } -} diff --git a/src/Bundle/ChillBudgetBundle/DependencyInjection/ChillBudgetExtension.php b/src/Bundle/ChillBudgetBundle/DependencyInjection/ChillBudgetExtension.php index ed7fd1ae9..57976f846 100644 --- a/src/Bundle/ChillBudgetBundle/DependencyInjection/ChillBudgetExtension.php +++ b/src/Bundle/ChillBudgetBundle/DependencyInjection/ChillBudgetExtension.php @@ -35,7 +35,6 @@ class ChillBudgetExtension extends Extension implements PrependExtensionInterfac $config = $this->processConfiguration($configuration, $configs); $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../config')); - $loader->load('services/config.yaml'); $loader->load('services/form.yaml'); $loader->load('services/repository.yaml'); $loader->load('services/security.yaml'); diff --git a/src/Bundle/ChillBudgetBundle/Entity/AbstractElement.php b/src/Bundle/ChillBudgetBundle/Entity/AbstractElement.php index 73060f40b..f12ba3d72 100644 --- a/src/Bundle/ChillBudgetBundle/Entity/AbstractElement.php +++ b/src/Bundle/ChillBudgetBundle/Entity/AbstractElement.php @@ -75,7 +75,7 @@ abstract class AbstractElement /** * @ORM\Column(name="type", type="string", length=255) */ - private string $type; + private string $type = ''; /*Getters and Setters */ diff --git a/src/Bundle/ChillBudgetBundle/Form/ChargeType.php b/src/Bundle/ChillBudgetBundle/Form/ChargeType.php index 3dc2e230c..3356057cf 100644 --- a/src/Bundle/ChillBudgetBundle/Form/ChargeType.php +++ b/src/Bundle/ChillBudgetBundle/Form/ChargeType.php @@ -11,7 +11,6 @@ declare(strict_types=1); namespace Chill\BudgetBundle\Form; -use Chill\BudgetBundle\Config\ConfigRepository; use Chill\BudgetBundle\Entity\Charge; use Chill\BudgetBundle\Entity\ChargeKind; use Chill\BudgetBundle\Repository\ChargeKindRepository; @@ -25,13 +24,9 @@ use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Contracts\Translation\TranslatorInterface; -use function array_flip; -use function asort; class ChargeType extends AbstractType { - protected ConfigRepository $configRepository; - protected TranslatableStringHelperInterface $translatableStringHelper; private ChargeKindRepository $repository; @@ -39,12 +34,10 @@ class ChargeType extends AbstractType private TranslatorInterface $translator; public function __construct( - ConfigRepository $configRepository, TranslatableStringHelperInterface $translatableStringHelper, ChargeKindRepository $repository, TranslatorInterface $translator ) { - $this->configRepository = $configRepository; $this->translatableStringHelper = $translatableStringHelper; $this->repository = $repository; $this->translator = $translator; @@ -116,19 +109,4 @@ class ChargeType extends AbstractType { return 'chill_budgetbundle_charge'; } - - private function getTypes() - { - $charges = $this->configRepository - ->getChargesLabels(true); - - // rewrite labels to filter in language - foreach ($charges as $key => $labels) { - $charges[$key] = $this->translatableStringHelper->localize($labels); - } - - asort($charges); - - return array_flip($charges); - } } diff --git a/src/Bundle/ChillBudgetBundle/Form/ResourceType.php b/src/Bundle/ChillBudgetBundle/Form/ResourceType.php index 106f50ad1..fd859217a 100644 --- a/src/Bundle/ChillBudgetBundle/Form/ResourceType.php +++ b/src/Bundle/ChillBudgetBundle/Form/ResourceType.php @@ -11,7 +11,6 @@ declare(strict_types=1); namespace Chill\BudgetBundle\Form; -use Chill\BudgetBundle\Config\ConfigRepository; use Chill\BudgetBundle\Entity\Resource; use Chill\BudgetBundle\Entity\ResourceKind; use Chill\BudgetBundle\Repository\ResourceKindRepository; @@ -24,12 +23,9 @@ use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Contracts\Translation\TranslatorInterface; -use function array_flip; class ResourceType extends AbstractType { - protected ConfigRepository $configRepository; - protected TranslatableStringHelperInterface $translatableStringHelper; private ResourceKindRepository $repository; @@ -37,12 +33,10 @@ class ResourceType extends AbstractType private TranslatorInterface $translator; public function __construct( - ConfigRepository $configRepository, TranslatableStringHelperInterface $translatableStringHelper, ResourceKindRepository $repository, TranslatorInterface $translator ) { - $this->configRepository = $configRepository; $this->translatableStringHelper = $translatableStringHelper; $this->repository = $repository; $this->translator = $translator; @@ -98,19 +92,4 @@ class ResourceType extends AbstractType { return 'chill_budgetbundle_resource'; } - - private function getTypes() - { - $resources = $this->configRepository - ->getResourcesLabels(true); - - // rewrite labels to filter in language - foreach ($resources as $key => $labels) { - $resources[$key] = $this->translatableStringHelper->localize($labels); - } - - asort($resources); - - return array_flip($resources); - } } diff --git a/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php b/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php index f02d2e64a..096531c52 100644 --- a/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php +++ b/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php @@ -11,13 +11,17 @@ declare(strict_types=1); namespace Chill\BudgetBundle\Service\Summary; -use Chill\BudgetBundle\Config\ConfigRepository; +use Chill\BudgetBundle\Entity\ChargeKind; +use Chill\BudgetBundle\Entity\ResourceKind; +use Chill\BudgetBundle\Repository\ChargeKindRepository; +use Chill\BudgetBundle\Repository\ResourceKindRepository; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Person; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Query\ResultSetMapping; use LogicException; +use RuntimeException; use function count; /** @@ -25,31 +29,36 @@ use function count; */ class SummaryBudget implements SummaryBudgetInterface { - private const QUERY_CHARGE_BY_HOUSEHOLD = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, type FROM chill_budget.charge WHERE (person_id IN (_ids_) OR household_id = ?) AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type'; + private const QUERY_CHARGE_BY_HOUSEHOLD = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, charge_id AS kind_id FROM chill_budget.charge WHERE (person_id IN (_ids_) OR household_id = ?) AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY charge_id'; - private const QUERY_CHARGE_BY_PERSON = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, type FROM chill_budget.charge WHERE person_id = ? AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type'; + private const QUERY_CHARGE_BY_PERSON = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, charge_id AS kind_id FROM chill_budget.charge WHERE person_id = ? AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY charge_id'; - private const QUERY_RESOURCE_BY_HOUSEHOLD = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, type FROM chill_budget.resource WHERE (person_id IN (_ids_) OR household_id = ?) AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type'; + private const QUERY_RESOURCE_BY_HOUSEHOLD = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, resource_id AS kind_id FROM chill_budget.resource WHERE (person_id IN (_ids_) OR household_id = ?) AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY resource_id'; - private const QUERY_RESOURCE_BY_PERSON = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, type FROM chill_budget.resource WHERE person_id = ? AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type'; + private const QUERY_RESOURCE_BY_PERSON = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, resource_id AS kind_id FROM chill_budget.resource WHERE person_id = ? AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY resource_id'; + + private ChargeKindRepository $chargeKindRepository; private array $chargeLabels; - private ConfigRepository $configRepository; - private EntityManagerInterface $em; + private ResourceKindRepository $resourceKindRepository; + private array $resourcesLabels; private TranslatableStringHelperInterface $translatableStringHelper; - public function __construct(EntityManagerInterface $em, ConfigRepository $configRepository, TranslatableStringHelperInterface $translatableStringHelper) - { + public function __construct( + EntityManagerInterface $em, + TranslatableStringHelperInterface $translatableStringHelper, + ResourceKindRepository $resourceKindRepository, + ChargeKindRepository $chargeKindRepository + ) { $this->em = $em; - $this->configRepository = $configRepository; - $this->chargeLabels = $configRepository->getChargesLabels(); - $this->resourcesLabels = $configRepository->getResourcesLabels(); $this->translatableStringHelper = $translatableStringHelper; + $this->resourceKindRepository = $resourceKindRepository; + $this->chargeKindRepository = $chargeKindRepository; } public function getSummaryForHousehold(?Household $household): array @@ -112,7 +121,7 @@ class SummaryBudget implements SummaryBudgetInterface $rsm = new ResultSetMapping(); $rsm ->addScalarResult('sum', 'sum') - ->addScalarResult('type', 'type') + ->addScalarResult('kind_id', 'kind_id') ->addScalarResult('comment', 'comment'); return $rsm; @@ -120,51 +129,62 @@ class SummaryBudget implements SummaryBudgetInterface private function getEmptyChargeArray(): array { - $keys = $this->configRepository->getChargesKeys(); - $labels = $this->chargeLabels; + $keys = array_map(static fn (ChargeKind $kind) => $kind->getId(), $this->chargeKindRepository->findAll()); - return array_combine($keys, array_map(function ($i) use ($labels) { - return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($labels[$i]), 'comment' => '']; + return array_combine($keys, array_map(function ($id) { + return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($this->chargeKindRepository->find($id)->getName()), 'comment' => '']; }, $keys)); } private function getEmptyResourceArray(): array { - $keys = $this->configRepository->getResourcesKeys(); - $labels = $this->resourcesLabels; + $keys = array_map(static fn (ResourceKind $kind) => $kind->getId(), $this->resourceKindRepository->findAll()); - return array_combine($keys, array_map(function ($i) use ($labels) { - return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($labels[$i]), 'comment' => '']; + return array_combine($keys, array_map(function ($id) { + return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($this->resourceKindRepository->find($id)->getName()), 'comment' => '']; }, $keys)); } private function rowToArray(array $rows, string $kind): array { + $result = []; + switch ($kind) { case 'charge': - $label = $this->chargeLabels; + foreach ($rows as $row) { + $chargeKind = $this->chargeKindRepository->find($row['kind_id']); - break; + if (null === $chargeKind) { + throw new RuntimeException('charge kind not found'); + } + $result[$chargeKind->getKind()] = [ + 'sum' => (float) $row['sum'], + 'label' => $this->translatableStringHelper->localize($chargeKind->getName()), + 'comment' => (string) $row['comment'], + ]; + } + + return $result; case 'resource': - $label = $this->resourcesLabels; + foreach ($rows as $row) { + $resourceKind = $this->resourceKindRepository->find($row['kind_id']); - break; + if (null === $resourceKind) { + throw new RuntimeException('charge kind not found'); + } + + $result[$resourceKind->getKind()] = [ + 'sum' => (float) $row['sum'], + 'label' => $this->translatableStringHelper->localize($resourceKind->getName()), + 'comment' => (string) $row['comment'], + ]; + } + + return $result; default: throw new LogicException(); } - - $result = []; - - foreach ($rows as $row) { - $result[$row['type']] = [ - 'sum' => (float) $row['sum'], - 'label' => $this->translatableStringHelper->localize($label[$row['type']]), - 'comment' => (string) $row['comment'], - ]; - } - - return $result; } } diff --git a/src/Bundle/ChillBudgetBundle/Templating/Twig.php b/src/Bundle/ChillBudgetBundle/Templating/Twig.php deleted file mode 100644 index b4395f375..000000000 --- a/src/Bundle/ChillBudgetBundle/Templating/Twig.php +++ /dev/null @@ -1,65 +0,0 @@ -configRepository = $configRepository; - $this->translatableStringHelper = $translatableStringHelper; - } - - public function displayLink($link, $family) - { - switch ($family) { - case 'resource': - return $this->translatableStringHelper->localize( - $this->configRepository->getResourcesLabels()[$link] - ); - - case 'charge': - return $this->translatableStringHelper->localize( - $this->configRepository->getChargesLabels()[$link] - ); - - default: - throw new UnexpectedValueException("This family of element: {$family} is not " - . "supported. Supported families are 'resource', 'charge'"); - } - } - - public function getFilters() - { - return [ - new TwigFilter('budget_element_type_display', [$this, 'displayLink'], ['is_safe' => ['html']]), - ]; - } -} diff --git a/src/Bundle/ChillBudgetBundle/config/services/config.yaml b/src/Bundle/ChillBudgetBundle/config/services/config.yaml deleted file mode 100644 index 21136db2f..000000000 --- a/src/Bundle/ChillBudgetBundle/config/services/config.yaml +++ /dev/null @@ -1,5 +0,0 @@ -services: - Chill\BudgetBundle\Config\ConfigRepository: - arguments: - $resources: '%chill_budget.resources%' - $charges: '%chill_budget.charges%' diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/module/document_action_buttons_group/index.ts b/src/Bundle/ChillDocStoreBundle/Resources/public/module/document_action_buttons_group/index.ts new file mode 100644 index 000000000..ec1d50a86 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/module/document_action_buttons_group/index.ts @@ -0,0 +1,35 @@ +import {_createI18n} from "../../../../../ChillMainBundle/Resources/public/vuejs/_js/i18n"; +import DocumentActionButtonsGroup from "../../vuejs/DocumentActionButtonsGroup.vue"; +import {createApp} from "vue"; +import {StoredObject} from "../../types"; + +const i18n = _createI18n({}); + +window.addEventListener('DOMContentLoaded', function (e) { + document.querySelectorAll('div[data-download-buttons]').forEach((el) => { + const app = createApp({ + components: {DocumentActionButtonsGroup}, + data() { + + const datasets = el.dataset as { + filename: string, + canEdit: string, + storedObject: string, + small: string, + }; + + const + storedObject = JSON.parse(datasets.storedObject), + filename = datasets.filename, + canEdit = datasets.canEdit === '1', + small = datasets.small === '1' + ; + + return { storedObject, filename, canEdit, small }; + }, + template: '', + }); + + app.use(i18n).mount(el); + }) +}); diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/types.ts b/src/Bundle/ChillDocStoreBundle/Resources/public/types.ts new file mode 100644 index 000000000..918526117 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/types.ts @@ -0,0 +1,25 @@ +import {DateTime} from "../../../ChillMainBundle/Resources/public/types"; + +export interface StoredObject { + id: number, + + /** + * filename of the object in the object storage + */ + filename: string, + creationDate: DateTime, + datas: object, + iv: number[], + keyInfos: object, + title: string, + type: string, + uuid: string +} + +/** + * Function executed by the WopiEditButton component. + */ +export type WopiEditButtonExecutableBeforeLeaveFunction = { + (): Promise +} + diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentActionButtonsGroup.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentActionButtonsGroup.vue new file mode 100644 index 000000000..cb72fa5cd --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentActionButtonsGroup.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/README.md b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/README.md new file mode 100644 index 000000000..2d10dace8 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/README.md @@ -0,0 +1,5 @@ +# About buttons and components available + +## DocumentActionButtonsGroup + +This is an component to use to render a group of button with actions linked to a document. diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/ConvertButton.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/ConvertButton.vue new file mode 100644 index 000000000..aa99a223f --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/ConvertButton.vue @@ -0,0 +1,46 @@ + + + diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/DownloadButton.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/DownloadButton.vue new file mode 100644 index 000000000..ca6a0f618 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/DownloadButton.vue @@ -0,0 +1,53 @@ + + + diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/WopiEditButton.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/WopiEditButton.vue new file mode 100644 index 000000000..d68f60f86 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/WopiEditButton.vue @@ -0,0 +1,44 @@ + + + + + + diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/helpers.ts b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/helpers.ts new file mode 100644 index 000000000..58d2ebc71 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/helpers.ts @@ -0,0 +1,168 @@ + +const SUPPORTED_MIMES = new Set([ + 'image/svg+xml', + 'application/vnd.ms-powerpoint', + 'application/vnd.ms-excel', + 'application/vnd.sun.xml.writer', + 'application/vnd.oasis.opendocument.text', + 'application/vnd.oasis.opendocument.text-flat-xml', + 'application/vnd.sun.xml.calc', + 'application/vnd.oasis.opendocument.spreadsheet', + 'application/vnd.oasis.opendocument.spreadsheet-flat-xml', + 'application/vnd.sun.xml.impress', + 'application/vnd.oasis.opendocument.presentation', + 'application/vnd.oasis.opendocument.presentation-flat-xml', + 'application/vnd.sun.xml.draw', + 'application/vnd.oasis.opendocument.graphics', + 'application/vnd.oasis.opendocument.graphics-flat-xml', + 'application/vnd.oasis.opendocument.chart', + 'application/vnd.sun.xml.writer.global', + 'application/vnd.oasis.opendocument.text-master', + 'application/vnd.sun.xml.writer.template', + 'application/vnd.oasis.opendocument.text-template', + 'application/vnd.oasis.opendocument.text-master-template', + 'application/vnd.sun.xml.calc.template', + 'application/vnd.oasis.opendocument.spreadsheet-template', + 'application/vnd.sun.xml.impress.template', + 'application/vnd.oasis.opendocument.presentation-template', + 'application/vnd.sun.xml.draw.template', + 'application/vnd.oasis.opendocument.graphics-template', + 'application/msword', + 'application/msword', + 'application/vnd.ms-excel', + 'application/vnd.ms-powerpoint', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/vnd.ms-word.document.macroEnabled.12', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'application/vnd.ms-word.template.macroEnabled.12', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'application/vnd.ms-excel.template.macroEnabled.12', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + 'application/vnd.ms-excel.sheet.macroEnabled.12', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'application/vnd.ms-powerpoint.template.macroEnabled.12', + 'application/vnd.wordperfect', + 'application/x-aportisdoc', + 'application/x-hwp', + 'application/vnd.ms-works', + 'application/x-mswrite', + 'application/x-dif-document', + 'text/spreadsheet', + 'text/csv', + 'application/x-dbase', + 'application/vnd.lotus-1-2-3', + 'image/cgm', + 'image/vnd.dxf', + 'image/x-emf', + 'image/x-wmf', + 'application/coreldraw', + 'application/vnd.visio2013', + 'application/vnd.visio', + 'application/vnd.ms-visio.drawing', + 'application/x-mspublisher', + 'application/x-sony-bbeb', + 'application/x-gnumeric', + 'application/macwriteii', + 'application/x-iwork-numbers-sffnumbers', + 'application/vnd.oasis.opendocument.text-web', + 'application/x-pagemaker', + 'text/rtf', + 'text/plain', + 'application/x-fictionbook+xml', + 'application/clarisworks', + 'image/x-wpg', + 'application/x-iwork-pages-sffpages', + 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'application/x-iwork-keynote-sffkey', + 'application/x-abiword', + 'image/x-freehand', + 'application/vnd.sun.xml.chart', + 'application/x-t602', + 'image/bmp', + 'image/png', + 'image/gif', + 'image/tiff', + 'image/jpg', + 'image/jpeg', + 'application/pdf', +]); + +function is_extension_editable(mimeType: string): boolean { + return SUPPORTED_MIMES.has(mimeType); +} + +function build_convert_link(uuid: string) { + return `/chill/wopi/convert/${uuid}`; +} + +function build_download_info_link(object_name: string) { + return `/asyncupload/temp_url/generate/GET?object_name=${object_name}`; +} + +function build_wopi_editor_link(uuid: string, returnPath?: string) { + if (returnPath === undefined) { + returnPath = window.location.pathname + window.location.search + window.location.hash; + } + + return `/chill/wopi/edit/${uuid}?returnPath=` + encodeURIComponent(returnPath); +} + +function download_doc(url: string): Promise { + return window.fetch(url).then(r => { + if (r.ok) { + return r.blob() + } + + throw new Error('Could not download document'); + }); +} + +async function download_and_decrypt_doc(urlGenerator: string, keyData: JsonWebKey, iv: Uint8Array): Promise +{ + const algo = 'AES-CBC'; + // get an url to download the object + const downloadInfoResponse = await window.fetch(urlGenerator); + + if (!downloadInfoResponse.ok) { + throw new Error("error while downloading url " + downloadInfoResponse.status + " " + downloadInfoResponse.statusText); + } + + const downloadInfo = await downloadInfoResponse.json() as {url: string}; + const rawResponse = await window.fetch(downloadInfo.url); + + if (!rawResponse.ok) { + throw new Error("error while downloading raw file " + rawResponse.status + " " + rawResponse.statusText); + } + + if (iv.length === 0) { + return rawResponse.blob(); + } + + const rawBuffer = await rawResponse.arrayBuffer(); + + try { + const key = await window.crypto.subtle + .importKey('jwk', keyData, { name: algo }, false, ['decrypt']); + const decrypted = await window.crypto.subtle + .decrypt({ name: algo, iv: iv }, key, rawBuffer); + + return Promise.resolve(new Blob([decrypted])); + } catch (e) { + console.error('get error while keys and decrypt operations'); + console.error(e); + + throw e; + } +} + +export { + build_convert_link, + build_download_info_link, + build_wopi_editor_link, + download_and_decrypt_doc, + download_doc, + is_extension_editable, +}; diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/_workflow.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/_workflow.html.twig index f914bd7f5..58d4903b4 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/_workflow.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/_workflow.html.twig @@ -46,21 +46,8 @@ {% endif %}
  • - {{ m.download_button(document.object, document.title) }} + {{ document.object|chill_document_button_group(document.title, not freezed) }}
  • - {% if chill_document_is_editable(document.object) %} - {% if not freezed %} -
  • - {{ document.object|chill_document_edit_button({'title': document.title|e('html') }) }} -
  • - {% else %} -
  • - - {{ 'Update document'|trans }} - -
  • - {% endif %} - {% endif %} {% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE', document) and document.course != null %}
  • diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/index.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/index.html.twig index df18efa71..7a013260c 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/index.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/index.html.twig @@ -8,16 +8,16 @@ {% block js %} {{ parent() }} - {{ encore_entry_script_tags('mod_async_upload') }} {{ encore_entry_script_tags('mod_docgen_picktemplate') }} {{ encore_entry_script_tags('mod_entity_workflow_pick') }} + {{ encore_entry_script_tags('mod_document_action_buttons_group') }} {% endblock %} {% block css %} {{ parent() }} - {{ encore_entry_link_tags('mod_async_upload') }} {{ encore_entry_link_tags('mod_docgen_picktemplate') }} {{ encore_entry_link_tags('mod_entity_workflow_pick') }} + {{ encore_entry_link_tags('mod_document_action_buttons_group') }} {% endblock %} {% block content %} diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/show.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/show.html.twig index 45ed3988b..3c62451a9 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/show.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/show.html.twig @@ -14,6 +14,7 @@ {{ parent() }} {{ encore_entry_link_tags('mod_async_upload') }} {{ encore_entry_link_tags('mod_entity_workflow_pick') }} + {{ encore_entry_link_tags('mod_document_action_buttons_group') }} {% endblock %} {% block content %} @@ -61,13 +62,8 @@
  • {% endif %}
  • - {{ m.download_button(document.object, document.title) }} + {{ document.object|chill_document_button_group(document.title) }}
  • - {% if chill_document_is_editable(document.object) %} -
  • - {{ document.object|chill_document_edit_button }} -
  • - {% endif %} {% set workflows_frame = chill_entity_workflow_list('Chill\\DocStoreBundle\\Entity\\AccompanyingCourseDocument', document.id) %} {% if workflows_frame is not empty %}
  • @@ -86,4 +82,5 @@ {{ parent() }} {{ encore_entry_script_tags('mod_async_upload') }} {{ encore_entry_script_tags('mod_entity_workflow_pick') }} + {{ encore_entry_script_tags('mod_document_action_buttons_group') }} {% endblock %} diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/Button/button_group.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/Button/button_group.html.twig new file mode 100644 index 000000000..f83cafd51 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/Button/button_group.html.twig @@ -0,0 +1,7 @@ +{%- import "@ChillDocStore/Macro/macro.html.twig" as m -%} +
    diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/List/list_item.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/List/list_item.html.twig index 3963b0715..4cbcb7bef 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/List/list_item.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/List/list_item.html.twig @@ -53,15 +53,10 @@
  • - {% if chill_document_is_editable(document.object) %} -
  • - {{ document.object|chill_document_edit_button }} -
  • - {% endif %} {% endif %} {% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE_DETAILS', document) %}
  • - {{ m.download_button(document.object, document.title) }} + {{ document.object|chill_document_button_group(document.title, is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE', document)) }}
  • @@ -80,15 +75,10 @@
  • - {% if chill_document_is_editable(document.object) %} -
  • - {{ document.object|chill_document_edit_button }} -
  • - {% endif %} {% endif %} {% if is_granted('CHILL_PERSON_DOCUMENT_SEE_DETAILS', document) %}
  • - {{ m.download_button(document.object, document.title) }} + {{ document.object|chill_document_button_group(document.title, is_granted('CHILL_PERSON_DOCUMENT_UPDATE', document)) }}
  • diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/Macro/macro.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/Macro/macro.html.twig index 0e739d94b..199a86c15 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/Macro/macro.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/Macro/macro.html.twig @@ -2,13 +2,13 @@ {% if storedObject is null %} {% else %} - {{ 'Download'|trans }} {% endif %} @@ -28,4 +28,21 @@ data-mime-type="{{ storedObject.type|escape('html_attr') }}" {% if filename is not null %}data-filename="{{ filename|escape('html_attr') }}"{% endif %}> {{ 'Download'|trans }} {% endif %} -{% endmacro %} \ No newline at end of file +{% endmacro %} + +{% macro download_button_group(storedObject, canEdit = true, filename = null, options = {}) %} + {% if storedObject is null %} + + {% else %} +
    + {% endif %} +{% endmacro %} diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/index.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/index.html.twig index 5dc359fa8..8d201605e 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/index.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/index.html.twig @@ -27,16 +27,16 @@ {% block js %} {{ parent() }} - {{ encore_entry_script_tags('mod_async_upload') }} {{ encore_entry_script_tags('mod_docgen_picktemplate') }} {{ encore_entry_script_tags('mod_entity_workflow_pick') }} + {{ encore_entry_script_tags('mod_document_action_buttons_group') }} {% endblock %} {% block css %} {{ parent() }} - {{ encore_entry_link_tags('mod_async_upload') }} {{ encore_entry_link_tags('mod_docgen_picktemplate') }} {{ encore_entry_link_tags('mod_entity_workflow_pick') }} + {{ encore_entry_link_tags('mod_document_action_buttons_group') }} {% endblock %} {% block content %} diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/show.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/show.html.twig index b26aa4b8b..c276e067e 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/show.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/show.html.twig @@ -24,7 +24,11 @@ {% block title %}{{ 'Detail of document of %name%'|trans({ '%name%': person|chill_entity_render_string } ) }}{% endblock %} {% block js %} - {{ encore_entry_script_tags('mod_async_upload') }} + {{ encore_entry_script_tags('mod_document_action_buttons_group') }} +{% endblock %} + +{% block css %} + {{ encore_entry_link_tags('mod_document_action_buttons_group') }} {% endblock %} {% block content %} @@ -70,6 +74,10 @@
  • {% endif %} +
  • + {{ document.object|chill_document_button_group(document.title, is_granted('CHILL_PERSON_DOCUMENT_UPDATE', document)) }} +
  • + {% if is_granted('CHILL_PERSON_DOCUMENT_UPDATE', document) %}
  • @@ -77,16 +85,4 @@
  • {% endif %} - -
  • - {{ m.download_button(document.object, document.title) }} -
  • - - {% if chill_document_is_editable(document.object) %} -
  • - {{ document.object|chill_document_edit_button }} -
  • - {% endif %} - - {# {{ include('ChillDocStoreBundle:PersonDocument:_delete_form.html.twig') }} #} {% endblock %} diff --git a/src/Bundle/ChillDocStoreBundle/Templating/WopiEditTwigExtension.php b/src/Bundle/ChillDocStoreBundle/Templating/WopiEditTwigExtension.php index 43dc28f15..754c3fb3c 100644 --- a/src/Bundle/ChillDocStoreBundle/Templating/WopiEditTwigExtension.php +++ b/src/Bundle/ChillDocStoreBundle/Templating/WopiEditTwigExtension.php @@ -24,6 +24,10 @@ class WopiEditTwigExtension extends AbstractExtension 'needs_environment' => true, 'is_safe' => ['html'], ]), + new TwigFilter('chill_document_button_group', [WopiEditTwigExtensionRuntime::class, 'renderButtonGroup'], [ + 'needs_environment' => true, + 'is_safe' => ['html'], + ]), ]; } diff --git a/src/Bundle/ChillDocStoreBundle/Templating/WopiEditTwigExtensionRuntime.php b/src/Bundle/ChillDocStoreBundle/Templating/WopiEditTwigExtensionRuntime.php index f53d6336b..bae436b0a 100644 --- a/src/Bundle/ChillDocStoreBundle/Templating/WopiEditTwigExtensionRuntime.php +++ b/src/Bundle/ChillDocStoreBundle/Templating/WopiEditTwigExtensionRuntime.php @@ -13,6 +13,8 @@ namespace Chill\DocStoreBundle\Templating; use ChampsLibres\WopiLib\Contract\Service\Discovery\DiscoveryInterface; use Chill\DocStoreBundle\Entity\StoredObject; +use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Twig\Environment; use Twig\Extension\RuntimeExtensionInterface; @@ -112,20 +114,53 @@ final class WopiEditTwigExtensionRuntime implements RuntimeExtensionInterface 'application/pdf', ]; + private const DEFAULT_OPTIONS_TEMPLATE_BUTTON_GROUP = [ + 'small' => false, + ]; + private const TEMPLATE = '@ChillDocStore/Button/wopi_edit_document.html.twig'; + private const TEMPLATE_BUTTON_GROUP = '@ChillDocStore/Button/button_group.html.twig'; + private DiscoveryInterface $discovery; - public function __construct(DiscoveryInterface $discovery) + private NormalizerInterface $normalizer; + + public function __construct(DiscoveryInterface $discovery, NormalizerInterface $normalizer) { $this->discovery = $discovery; + $this->normalizer = $normalizer; } + /** + * return true if the document is editable. + * + * **NOTE**: as the Vue button does have similar test, this is not required if in use with + * the dedicated Vue component (GroupDownloadButton.vue, WopiEditButton.vue) + */ public function isEditable(StoredObject $document): bool { return in_array($document->getType(), self::SUPPORTED_MIMES, true); } + /** + * @param array{small: boolean} $options + * + * @throws \Twig\Error\LoaderError + * @throws \Twig\Error\RuntimeError + * @throws \Twig\Error\SyntaxError + */ + public function renderButtonGroup(Environment $environment, StoredObject $document, ?string $title = null, bool $canEdit = true, array $options = []): string + { + return $environment->render(self::TEMPLATE_BUTTON_GROUP, [ + 'document' => $document, + 'document_json' => $this->normalizer->normalize($document, 'json', [AbstractNormalizer::GROUPS => ['read']]), + 'title' => $title, + 'can_edit' => $canEdit, + 'options' => array_merge($options, self::DEFAULT_OPTIONS_TEMPLATE_BUTTON_GROUP), + ]); + } + public function renderEditButton(Environment $environment, StoredObject $document, ?array $options = null): string { return $environment->render(self::TEMPLATE, [ diff --git a/src/Bundle/ChillDocStoreBundle/chill.webpack.config.js b/src/Bundle/ChillDocStoreBundle/chill.webpack.config.js index c9b2b8877..3499fcf55 100644 --- a/src/Bundle/ChillDocStoreBundle/chill.webpack.config.js +++ b/src/Bundle/ChillDocStoreBundle/chill.webpack.config.js @@ -4,4 +4,5 @@ module.exports = function(encore) ChillDocStoreAssets: __dirname + '/Resources/public' }); encore.addEntry('mod_async_upload', __dirname + '/Resources/public/module/async_upload/index.js'); + encore.addEntry('mod_document_action_buttons_group', __dirname + '/Resources/public/module/document_action_buttons_group/index'); }; diff --git a/src/Bundle/ChillMainBundle/Resources/views/Workflow/index.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Workflow/index.html.twig index 1a6f3103b..782eae136 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Workflow/index.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Workflow/index.html.twig @@ -11,6 +11,7 @@ {{ encore_entry_script_tags('mod_entity_workflow_subscribe') }} {{ encore_entry_script_tags('page_workflow_show') }} {{ encore_entry_script_tags('mod_wopi_link') }} + {{ encore_entry_script_tags('mod_document_action_buttons_group') }} {% endblock %} {% block css %} @@ -19,6 +20,7 @@ {{ encore_entry_link_tags('mod_entity_workflow_subscribe') }} {{ encore_entry_link_tags('page_workflow_show') }} {{ encore_entry_link_tags('mod_wopi_link') }} + {{ encore_entry_link_tags('mod_document_action_buttons_group') }} {% endblock %} {% import '@ChillMain/Workflow/macro_breadcrumb.html.twig' as macro %} diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue index 66918ad71..4bf9e1a8a 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue @@ -111,14 +111,12 @@
  • - - -
  • -
  • - +
  • @@ -174,6 +172,7 @@ import AddAsyncUpload from 'ChillDocStoreAssets/vuejs/_components/AddAsyncUpload import AddAsyncUploadDownloader from 'ChillDocStoreAssets/vuejs/_components/AddAsyncUploadDownloader.vue'; import ListWorkflowModal from 'ChillMainAssets/vuejs/_components/EntityWorkflow/ListWorkflowModal.vue'; import {buildLinkCreate} from 'ChillMainAssets/lib/entity-workflow/api.js'; +import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue"; const i18n = { messages: { @@ -212,6 +211,7 @@ export default { AddAsyncUpload, AddAsyncUploadDownloader, ListWorkflowModal, + DocumentActionButtonsGroup, }, i18n, data() { @@ -223,78 +223,6 @@ export default { maxPostSize: 15000000, required: false, }, - mime: [ - // TODO temporary hardcoded. to be replaced by twig extension or a collabora server query - 'application/clarisworks', - 'application/coreldraw', - 'application/macwriteii', - 'application/msword', - 'application/vnd.lotus-1-2-3', - 'application/vnd.ms-excel', - 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', - 'application/vnd.ms-excel.sheet.macroEnabled.12', - 'application/vnd.ms-excel.template.macroEnabled.12', - 'application/vnd.ms-powerpoint', - 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', - 'application/vnd.ms-powerpoint.template.macroEnabled.12', - 'application/vnd.ms-visio.drawing', - 'application/vnd.ms-word.document.macroEnabled.12', - 'application/vnd.ms-word.template.macroEnabled.12', - 'application/vnd.ms-works', - 'application/vnd.oasis.opendocument.chart', - 'application/vnd.oasis.opendocument.formula', - 'application/vnd.oasis.opendocument.graphics', - 'application/vnd.oasis.opendocument.graphics-flat-xml', - 'application/vnd.oasis.opendocument.graphics-template', - 'application/vnd.oasis.opendocument.presentation', - 'application/vnd.oasis.opendocument.presentation-flat-xml', - 'application/vnd.oasis.opendocument.presentation-template', - 'application/vnd.oasis.opendocument.spreadsheet', - 'application/vnd.oasis.opendocument.spreadsheet-flat-xml', - 'application/vnd.oasis.opendocument.spreadsheet-template', - 'application/vnd.oasis.opendocument.text', - 'application/vnd.oasis.opendocument.text-flat-xml', - 'application/vnd.oasis.opendocument.text-master', - 'application/vnd.oasis.opendocument.text-master-template', - 'application/vnd.oasis.opendocument.text-template', - 'application/vnd.oasis.opendocument.text-web', - 'application/vnd.openxmlformats-officedocument.presentationml.presentation', - 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', - 'application/vnd.openxmlformats-officedocument.presentationml.template', - 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', - 'application/vnd.sun.xml.calc', - 'application/vnd.sun.xml.calc.template', - 'application/vnd.sun.xml.chart', - 'application/vnd.sun.xml.draw', - 'application/vnd.sun.xml.draw.template', - 'application/vnd.sun.xml.impress', - 'application/vnd.sun.xml.impress.template', - 'application/vnd.sun.xml.math', - 'application/vnd.sun.xml.writer', - 'application/vnd.sun.xml.writer.global', - 'application/vnd.sun.xml.writer.template', - 'application/vnd.visio', - 'application/vnd.visio2013', - 'application/vnd.wordperfect', - 'application/x-abiword', - 'application/x-aportisdoc', - 'application/x-dbase', - 'application/x-dif-document', - 'application/x-fictionbook+xml', - 'application/x-gnumeric', - 'application/x-hwp', - 'application/x-iwork-keynote-sffkey', - 'application/x-iwork-numbers-sffnumbers', - 'application/x-iwork-pages-sffpages', - 'application/x-mspublisher', - 'application/x-mswrite', - 'application/x-pagemaker', - 'application/x-sony-bbeb', - 'application/x-t602', - ] } }, computed: { @@ -343,10 +271,6 @@ export default { }, methods: { ISOToDatetime, - canEditDocument(document) { - return 'storedObject' in document ? - this.mime.includes(document.storedObject.type) : false; - }, listAllStatus() { console.log('load all status'); let url = `/api/`; @@ -359,10 +283,25 @@ export default { }) ; }, - buildEditLink(storedObject) { - return `/chill/wopi/edit/${storedObject.uuid}?returnPath=` + encodeURIComponent( + buildEditLink(document) { + return `/chill/wopi/edit/${document.storedObject.uuid}?returnPath=` + encodeURIComponent( window.location.pathname + window.location.search + window.location.hash); }, + submitBeforeLeaveToEditor() { + console.log('submit beore edit 2'); + // empty callback + const callback = () => null; + return this.$store.dispatch('submit', callback).catch(e => { console.log(e); throw e; }); + }, + submitBeforeEdit(storedObject) { + const callback = (data) => { + let evaluation = data.accompanyingPeriodWorkEvaluations.find(e => e.key === this.evaluation.key); + let document = evaluation.documents.find(d => d.storedObject.id === storedObject.id); + //console.log('=> document', document); + window.location.assign(this.buildEditLink(document)); + }; + return this.$store.dispatch('submit', callback).catch(e => { console.log(e); throw e; }); + }, submitBeforeGenerate({template}) { const callback = (data) => { let evaluationId = data.accompanyingPeriodWorkEvaluations.find(e => e.key === this.evaluation.key).id; diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_objectifs_results_evaluations.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_objectifs_results_evaluations.html.twig index aa3838348..63e4ff638 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_objectifs_results_evaluations.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_objectifs_results_evaluations.html.twig @@ -142,7 +142,7 @@ {{ mm.mimeIcon(d.storedObject.type) }}
    - {{ m.download_button_small(d.storedObject, d.title) }} + {{ d.storedObject|chill_document_button_group(d.title, is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', w), {'small': true}) }}
    {% endfor %} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/show.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/show.html.twig index 9e47b61d1..bddffebdf 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/show.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/show.html.twig @@ -6,20 +6,20 @@ {% block css %} {{ parent() }} - {{ encore_entry_link_tags('mod_async_upload') }} {{ encore_entry_link_tags('mod_entity_workflow_pick') }} + {{ encore_entry_link_tags('mod_document_action_buttons_group') }} {% endblock %} {% block js %} {{ parent() }} - {{ encore_entry_script_tags('mod_async_upload') }} {{ encore_entry_script_tags('mod_entity_workflow_pick') }} + {{ encore_entry_script_tags('mod_document_action_buttons_group') }} {% endblock %} {% block content %}
    {% endblock %} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Workflow/_evaluation_document.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Workflow/_evaluation_document.html.twig index 5ab93896a..e5d8f9c9a 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Workflow/_evaluation_document.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Workflow/_evaluation_document.html.twig @@ -120,20 +120,13 @@ {% if display_action is defined and display_action == true %} - {% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', evaluation.accompanyingPeriodWork) %} - {% endif %} {% endif %} {% endif %} diff --git a/src/Bundle/ChillWopiBundle/src/Controller/Convert.php b/src/Bundle/ChillWopiBundle/src/Controller/Convert.php new file mode 100644 index 000000000..0a4dc8762 --- /dev/null +++ b/src/Bundle/ChillWopiBundle/src/Controller/Convert.php @@ -0,0 +1,108 @@ +httpClient = $httpClient; + $this->security = $security; + $this->storedObjectManager = $storedObjectManager; + $this->logger = $logger; + $this->collaboraDomain = $parameters->get('wopi')['server']; + } + + public function __invoke(StoredObject $storedObject): Response + { + if (!$this->security->getUser() instanceof User) { + throw new AccessDeniedHttpException('User must be authenticated'); + } + + $content = $this->storedObjectManager->read($storedObject); + + try { + $url = sprintf('%s/cool/convert-to/pdf', $this->collaboraDomain); + $form = new FormDataPart([ + 'data' => new DataPart($content, $storedObject->getUuid()->toString(), $storedObject->getType()), + ]); + $response = $this->httpClient->request('POST', $url, [ + 'headers' => $form->getPreparedHeaders()->toArray(), + 'body' => $form->bodyToString(), + 'timeout' => 10, + ]); + + return new Response($response->getContent(), Response::HTTP_OK, [ + 'Content-Type' => 'application/pdf', + ]); + } catch (ClientExceptionInterface $exception) { + return $this->onConversionFailed($url, $response); + } catch (RedirectionExceptionInterface $e) { + return $this->onConversionFailed($url, $response); + } catch (ServerExceptionInterface $e) { + return $this->onConversionFailed($url, $response); + } catch (TransportExceptionInterface $e) { + return $this->onConversionFailed($url, $response); + } + } + + private function onConversionFailed(string $url, ResponseInterface $response): JsonResponse + { + $this->logger->error(self::LOG_PREFIX . ' could not convert document', [ + 'response_status' => $response->getStatusCode(), + 'message' => $response->getContent(false), + 'server' => $this->collaboraDomain, + 'url' => $url, + ]); + + return new JsonResponse(['message' => 'conversion failed : ' . $response->getContent(false)], Response::HTTP_SERVICE_UNAVAILABLE); + } +} diff --git a/src/Bundle/ChillWopiBundle/src/Resources/config/routes/routes.php b/src/Bundle/ChillWopiBundle/src/Resources/config/routes/routes.php index ddf4d5531..141799207 100644 --- a/src/Bundle/ChillWopiBundle/src/Resources/config/routes/routes.php +++ b/src/Bundle/ChillWopiBundle/src/Resources/config/routes/routes.php @@ -16,4 +16,8 @@ return static function (RoutingConfigurator $routes) { $routes ->add('chill_wopi_file_edit', '/edit/{fileId}') ->controller(Editor::class); + + $routes + ->add('chill_wopi_object_convert', '/convert/{uuid}') + ->controller(\Chill\WopiBundle\Controller\Convert::class); }; diff --git a/src/Bundle/ChillWopiBundle/tests/Controller/ConverTest.php b/src/Bundle/ChillWopiBundle/tests/Controller/ConverTest.php new file mode 100644 index 000000000..27d162a34 --- /dev/null +++ b/src/Bundle/ChillWopiBundle/tests/Controller/ConverTest.php @@ -0,0 +1,92 @@ +setType('application/vnd.oasis.opendocument.text'); + + $httpClient = new MockHttpClient([ + new MockResponse('not authorized', ['http_code' => 401]), + ], 'http://collabora:9980'); + + $security = $this->prophesize(Security::class); + $security->getUser()->willReturn(new User()); + + $storeManager = $this->prophesize(StoredObjectManagerInterface::class); + $storeManager->read($storedObject)->willReturn('content'); + + $parameterBag = new ParameterBag(['wopi' => ['server' => 'http://collabora:9980']]); + + $convert = new Convert( + $httpClient, + $security->reveal(), + $storeManager->reveal(), + new NullLogger(), + $parameterBag + ); + + $response = $convert($storedObject); + + $this->assertNotEquals(200, $response->getStatusCode()); + } + + public function testEverythingWentFine(): void + { + $storedObject = (new StoredObject())->setType('application/vnd.oasis.opendocument.text'); + + $httpClient = new MockHttpClient([ + new MockResponse('1234', ['http_code' => 200]), + ], 'http://collabora:9980'); + + $security = $this->prophesize(Security::class); + $security->getUser()->willReturn(new User()); + + $storeManager = $this->prophesize(StoredObjectManagerInterface::class); + $storeManager->read($storedObject)->willReturn('content'); + + $parameterBag = new ParameterBag(['wopi' => ['server' => 'http://collabora:9980']]); + + $convert = new Convert( + $httpClient, + $security->reveal(), + $storeManager->reveal(), + new NullLogger(), + $parameterBag + ); + + $response = $convert($storedObject); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('1234', $response->getContent()); + } +}