From e1c9af795c4bb88aa2c531a1529c2409bb2d0ad4 Mon Sep 17 00:00:00 2001 From: Kevin Papst Date: Mon, 15 Mar 2021 14:18:44 +0100 Subject: [PATCH] refactored page actions to event subscriber (#2420) --- src/Controller/ActivityController.php | 4 + src/Controller/CustomerController.php | 4 + src/Controller/ProjectController.php | 4 + src/Entity/ActivityMeta.php | 3 + src/Entity/CustomerMeta.php | 3 + src/Entity/ProjectMeta.php | 3 + src/Entity/Timesheet.php | 5 + src/Entity/TimesheetMeta.php | 3 + src/Event/PageActionsEvent.php | 150 +++++++++++++++++- .../Actions/AbstractActionsSubscriber.php | 21 +++ .../Actions/AbstractTimesheetSubscriber.php | 58 +++++++ .../Actions/AbstractTimesheetsSubscriber.php | 39 +++++ .../Actions/ActivitiesSubscriber.php | 33 ++++ .../Actions/ActivitySubscriber.php | 75 +++++++++ .../Actions/CalendarSubscriber.php | 29 ++++ .../Actions/CustomerSubscriber.php | 79 +++++++++ .../Actions/CustomersSubscriber.php | 33 ++++ .../Actions/ExportSubscriber.php | 31 ++++ .../Actions/InvoiceArchiveSubscriber.php | 22 +-- .../Actions/InvoiceSubscriber.php | 44 +++++ .../Actions/InvoiceTemplateSubscriber.php | 42 +++++ .../InvoiceTemplateUploadSubscriber.php | 29 ++++ .../Actions/InvoiceTemplatesSubscriber.php | 40 +++++ .../Actions/InvoicesSubscriber.php | 39 +++++ .../Actions/PermissionsSubscriber.php | 34 ++++ .../Actions/PluginSubscriber.php | 31 ++++ .../Actions/PluginsSubscriber.php | 27 ++++ .../Actions/ProjectSubscriber.php | 75 +++++++++ .../Actions/ProjectsSubscriber.php | 33 ++++ .../Actions/ReportingSubscriber.php | 42 +++++ .../Actions/SystemConfigurationSubscriber.php | 25 +++ src/EventSubscriber/Actions/TagSubscriber.php | 71 +++++++++ .../Actions/TagsSubscriber.php | 31 ++++ .../Actions/TeamSubscriber.php | 59 +++++++ .../Actions/TeamsSubscriber.php | 31 ++++ .../TimesheetMultiUpdateSubscriber.php | 26 +++ .../Actions/TimesheetSubscriber.php | 25 +++ .../TimesheetTeamMultiUpdateSubscriber.php | 26 +++ .../Actions/TimesheetTeamSubscriber.php | 25 +++ .../Actions/TimesheetsSubscriber.php | 36 +++++ .../Actions/TimesheetsTeamSubscriber.php | 37 +++++ .../Actions/UserSubscriber.php | 51 ++---- .../Actions/UsersSubscriber.php | 35 ++++ .../UserPreferenceSubscriber.php | 2 + .../ProjectView/ProjectViewModel.php | 8 +- .../ProjectView/ProjectViewService.php | 13 +- src/Twig/IconExtension.php | 1 + src/Twig/Runtime/ExporterExtension.php | 36 ----- src/Twig/Runtime/ThemeExtension.php | 8 +- src/Twig/RuntimeExtensions.php | 4 - templates/about/actions.html.twig | 5 +- templates/activity/actions.html.twig | 59 +------ templates/activity/permissions.html.twig | 4 +- templates/calendar/actions.html.twig | 11 -- templates/calendar/user.html.twig | 7 +- templates/customer/actions.html.twig | 67 +------- templates/customer/permissions.html.twig | 4 +- templates/doctor/actions.html.twig | 8 - templates/doctor/index.html.twig | 8 +- templates/export/actions.html.twig | 14 -- templates/export/index.html.twig | 6 +- templates/invoice/actions.html.twig | 106 ++----------- templates/permission/actions.html.twig | 16 +- templates/plugin/actions.html.twig | 22 --- templates/plugin/index.html.twig | 9 +- templates/project/actions.html.twig | 61 +------ templates/project/permissions.html.twig | 4 +- templates/reporting/actions.html.twig | 12 -- templates/reporting/layout.html.twig | 7 +- .../system-configuration/actions.html.twig | 8 +- templates/tags/actions.html.twig | 46 +----- templates/team/actions.html.twig | 35 +--- templates/timesheet-team/actions.html.twig | 80 ++-------- templates/timesheet/actions.html.twig | 78 +-------- templates/user/actions.html.twig | 29 +--- templates/user/layout.html.twig | 2 +- tests/Controller/ActivityControllerTest.php | 4 +- tests/Controller/CustomerControllerTest.php | 4 +- tests/Controller/ProjectControllerTest.php | 4 +- tests/Event/PageActionsEventTest.php | 96 ++++++++++- .../Actions/AbstractActionsSubscriberTest.php | 29 ++++ .../Actions/ActivitiesSubscriberTest.php | 23 +++ .../Actions/ActivitySubscriberTest.php | 23 +++ .../Actions/CalendarSubscriberTest.php | 23 +++ .../Actions/CustomerSubscriberTest.php | 23 +++ .../Actions/CustomersSubscriberTest.php | 23 +++ .../Actions/ExportSubscriberTest.php | 23 +++ .../Actions/InvoiceArchiveSubscriberTest.php | 23 +++ .../Actions/InvoiceTemplateSubscriberTest.php | 23 +++ .../InvoiceTemplateUploadSubscriberTest.php | 23 +++ .../InvoiceTemplatesSubscriberTest.php | 23 +++ .../Actions/InvoicesSubscriberTest.php | 23 +++ .../Actions/PermissionsSubscriberTest.php | 23 +++ .../Actions/PluginSubscriberTest.php | 23 +++ .../Actions/PluginsSubscriberTest.php | 23 +++ .../Actions/ProjectSubscriberTest.php | 23 +++ .../Actions/ProjectsSubscriberTest.php | 23 +++ .../Actions/ReportingSubscriberTest.php | 23 +++ .../SystemConfigurationSubscriberTest.php | 23 +++ .../Actions/TagSubscriberTest.php | 23 +++ .../Actions/TagsSubscriberTest.php | 23 +++ .../Actions/TeamSubscriberTest.php | 23 +++ .../Actions/TeamsSubscriberTest.php | 23 +++ .../TimesheetMultiUpdateSubscriberTest.php | 23 +++ .../Actions/TimesheetSubscriberTest.php | 23 +++ ...TimesheetTeamMultiUpdateSubscriberTest.php | 23 +++ .../Actions/TimesheetTeamSubscriberTest.php | 23 +++ .../Actions/TimesheetsSubscriberTest.php | 23 +++ .../Actions/TimesheetsTeamSubscriberTest.php | 23 +++ .../Actions/UserSubscriberTest.php | 23 +++ .../Actions/UsersSubscriberTest.php | 23 +++ .../UserPreferenceSubscriberTest.php | 12 +- tests/Twig/Runtime/ExporterExtensionTest.php | 33 ---- tests/Twig/Runtime/ReportingExtensionTest.php | 43 ----- tests/Twig/RuntimeExtensionsTest.php | 2 - translations/actions.de.xlf | 4 + translations/actions.en.xlf | 4 + 117 files changed, 2403 insertions(+), 815 deletions(-) create mode 100644 src/EventSubscriber/Actions/AbstractTimesheetSubscriber.php create mode 100644 src/EventSubscriber/Actions/AbstractTimesheetsSubscriber.php create mode 100644 src/EventSubscriber/Actions/ActivitiesSubscriber.php create mode 100644 src/EventSubscriber/Actions/ActivitySubscriber.php create mode 100644 src/EventSubscriber/Actions/CalendarSubscriber.php create mode 100644 src/EventSubscriber/Actions/CustomerSubscriber.php create mode 100644 src/EventSubscriber/Actions/CustomersSubscriber.php create mode 100644 src/EventSubscriber/Actions/ExportSubscriber.php create mode 100644 src/EventSubscriber/Actions/InvoiceSubscriber.php create mode 100644 src/EventSubscriber/Actions/InvoiceTemplateSubscriber.php create mode 100644 src/EventSubscriber/Actions/InvoiceTemplateUploadSubscriber.php create mode 100644 src/EventSubscriber/Actions/InvoiceTemplatesSubscriber.php create mode 100644 src/EventSubscriber/Actions/InvoicesSubscriber.php create mode 100644 src/EventSubscriber/Actions/PermissionsSubscriber.php create mode 100644 src/EventSubscriber/Actions/PluginSubscriber.php create mode 100644 src/EventSubscriber/Actions/PluginsSubscriber.php create mode 100644 src/EventSubscriber/Actions/ProjectSubscriber.php create mode 100644 src/EventSubscriber/Actions/ProjectsSubscriber.php create mode 100644 src/EventSubscriber/Actions/ReportingSubscriber.php create mode 100644 src/EventSubscriber/Actions/SystemConfigurationSubscriber.php create mode 100644 src/EventSubscriber/Actions/TagSubscriber.php create mode 100644 src/EventSubscriber/Actions/TagsSubscriber.php create mode 100644 src/EventSubscriber/Actions/TeamSubscriber.php create mode 100644 src/EventSubscriber/Actions/TeamsSubscriber.php create mode 100644 src/EventSubscriber/Actions/TimesheetMultiUpdateSubscriber.php create mode 100644 src/EventSubscriber/Actions/TimesheetSubscriber.php create mode 100644 src/EventSubscriber/Actions/TimesheetTeamMultiUpdateSubscriber.php create mode 100644 src/EventSubscriber/Actions/TimesheetTeamSubscriber.php create mode 100644 src/EventSubscriber/Actions/TimesheetsSubscriber.php create mode 100644 src/EventSubscriber/Actions/TimesheetsTeamSubscriber.php create mode 100644 src/EventSubscriber/Actions/UsersSubscriber.php delete mode 100644 src/Twig/Runtime/ExporterExtension.php delete mode 100644 templates/calendar/actions.html.twig delete mode 100644 templates/doctor/actions.html.twig delete mode 100644 templates/export/actions.html.twig delete mode 100644 templates/plugin/actions.html.twig delete mode 100644 templates/reporting/actions.html.twig create mode 100644 tests/EventSubscriber/Actions/AbstractActionsSubscriberTest.php create mode 100644 tests/EventSubscriber/Actions/ActivitiesSubscriberTest.php create mode 100644 tests/EventSubscriber/Actions/ActivitySubscriberTest.php create mode 100644 tests/EventSubscriber/Actions/CalendarSubscriberTest.php create mode 100644 tests/EventSubscriber/Actions/CustomerSubscriberTest.php create mode 100644 tests/EventSubscriber/Actions/CustomersSubscriberTest.php create mode 100644 tests/EventSubscriber/Actions/ExportSubscriberTest.php create mode 100644 tests/EventSubscriber/Actions/InvoiceArchiveSubscriberTest.php create mode 100644 tests/EventSubscriber/Actions/InvoiceTemplateSubscriberTest.php create mode 100644 tests/EventSubscriber/Actions/InvoiceTemplateUploadSubscriberTest.php create mode 100644 tests/EventSubscriber/Actions/InvoiceTemplatesSubscriberTest.php create mode 100644 tests/EventSubscriber/Actions/InvoicesSubscriberTest.php create mode 100644 tests/EventSubscriber/Actions/PermissionsSubscriberTest.php create mode 100644 tests/EventSubscriber/Actions/PluginSubscriberTest.php create mode 100644 tests/EventSubscriber/Actions/PluginsSubscriberTest.php create mode 100644 tests/EventSubscriber/Actions/ProjectSubscriberTest.php create mode 100644 tests/EventSubscriber/Actions/ProjectsSubscriberTest.php create mode 100644 tests/EventSubscriber/Actions/ReportingSubscriberTest.php create mode 100644 tests/EventSubscriber/Actions/SystemConfigurationSubscriberTest.php create mode 100644 tests/EventSubscriber/Actions/TagSubscriberTest.php create mode 100644 tests/EventSubscriber/Actions/TagsSubscriberTest.php create mode 100644 tests/EventSubscriber/Actions/TeamSubscriberTest.php create mode 100644 tests/EventSubscriber/Actions/TeamsSubscriberTest.php create mode 100644 tests/EventSubscriber/Actions/TimesheetMultiUpdateSubscriberTest.php create mode 100644 tests/EventSubscriber/Actions/TimesheetSubscriberTest.php create mode 100644 tests/EventSubscriber/Actions/TimesheetTeamMultiUpdateSubscriberTest.php create mode 100644 tests/EventSubscriber/Actions/TimesheetTeamSubscriberTest.php create mode 100644 tests/EventSubscriber/Actions/TimesheetsSubscriberTest.php create mode 100644 tests/EventSubscriber/Actions/TimesheetsTeamSubscriberTest.php create mode 100644 tests/EventSubscriber/Actions/UserSubscriberTest.php create mode 100644 tests/EventSubscriber/Actions/UsersSubscriberTest.php delete mode 100644 tests/Twig/Runtime/ExporterExtensionTest.php delete mode 100644 tests/Twig/Runtime/ReportingExtensionTest.php diff --git a/src/Controller/ActivityController.php b/src/Controller/ActivityController.php index c14c5a6a..0417604a 100644 --- a/src/Controller/ActivityController.php +++ b/src/Controller/ActivityController.php @@ -232,6 +232,10 @@ final class ActivityController extends AbstractController $this->repository->saveActivity($activity); $this->flashSuccess('action.update.success'); + if ($this->isGranted('view', $activity)) { + return $this->redirectToRoute('activity_details', ['id' => $activity->getId()]); + } + return $this->redirectToRoute('admin_activity'); } catch (Exception $ex) { $this->flashUpdateException($ex); diff --git a/src/Controller/CustomerController.php b/src/Controller/CustomerController.php index 7b10fbbe..46e18620 100644 --- a/src/Controller/CustomerController.php +++ b/src/Controller/CustomerController.php @@ -142,6 +142,10 @@ final class CustomerController extends AbstractController $this->repository->saveCustomer($customer); $this->flashSuccess('action.update.success'); + if ($this->isGranted('view', $customer)) { + return $this->redirectToRoute('customer_details', ['id' => $customer->getId()]); + } + return $this->redirectToRoute('admin_customer'); } catch (\Exception $ex) { $this->flashUpdateException($ex); diff --git a/src/Controller/ProjectController.php b/src/Controller/ProjectController.php index 956d9614..c40ef05e 100644 --- a/src/Controller/ProjectController.php +++ b/src/Controller/ProjectController.php @@ -136,6 +136,10 @@ final class ProjectController extends AbstractController $this->projectService->updateProject($project); $this->flashSuccess('action.update.success'); + if ($this->isGranted('view', $project)) { + return $this->redirectToRoute('project_details', ['id' => $project->getId()]); + } + return $this->redirectToRoute('admin_project'); } catch (\Exception $ex) { $this->flashUpdateException($ex); diff --git a/src/Entity/ActivityMeta.php b/src/Entity/ActivityMeta.php index 9acf5d6c..be4e8e0a 100644 --- a/src/Entity/ActivityMeta.php +++ b/src/Entity/ActivityMeta.php @@ -49,6 +49,9 @@ class ActivityMeta implements MetaTableTypeInterface return $this; } + /** + * @return Activity|null + */ public function getEntity(): ?EntityWithMetaFields { return $this->activity; diff --git a/src/Entity/CustomerMeta.php b/src/Entity/CustomerMeta.php index 0ff913f4..e03933e8 100644 --- a/src/Entity/CustomerMeta.php +++ b/src/Entity/CustomerMeta.php @@ -49,6 +49,9 @@ class CustomerMeta implements MetaTableTypeInterface return $this; } + /** + * @return Customer|null + */ public function getEntity(): ?EntityWithMetaFields { return $this->customer; diff --git a/src/Entity/ProjectMeta.php b/src/Entity/ProjectMeta.php index fc6bf97c..208d7481 100644 --- a/src/Entity/ProjectMeta.php +++ b/src/Entity/ProjectMeta.php @@ -49,6 +49,9 @@ class ProjectMeta implements MetaTableTypeInterface return $this; } + /** + * @return Project|null + */ public function getEntity(): ?EntityWithMetaFields { return $this->project; diff --git a/src/Entity/Timesheet.php b/src/Entity/Timesheet.php index e1b3d9a9..1e30b1be 100644 --- a/src/Entity/Timesheet.php +++ b/src/Entity/Timesheet.php @@ -367,6 +367,11 @@ class Timesheet implements EntityWithMetaFields, ExportItemInterface return $this->end; } + public function isRunning(): bool + { + return $this->end === null; + } + /** * @param DateTime $end * @return Timesheet diff --git a/src/Entity/TimesheetMeta.php b/src/Entity/TimesheetMeta.php index e31e0991..4ab2e746 100644 --- a/src/Entity/TimesheetMeta.php +++ b/src/Entity/TimesheetMeta.php @@ -49,6 +49,9 @@ class TimesheetMeta implements MetaTableTypeInterface return $this; } + /** + * @return Timesheet|null + */ public function getEntity(): ?EntityWithMetaFields { return $this->timesheet; diff --git a/src/Event/PageActionsEvent.php b/src/Event/PageActionsEvent.php index 6d55deac..28c559a2 100644 --- a/src/Event/PageActionsEvent.php +++ b/src/Event/PageActionsEvent.php @@ -11,17 +11,29 @@ namespace App\Event; use App\Entity\User; +/** + * This event is triggered once per side load. + * It stores all toolbar items, which should be rendered in the upper right corner. + */ class PageActionsEvent extends ThemeEvent { private $action; + private $view; + private $divider = 0; - public function __construct(User $user, array $payload, string $action) + public function __construct(User $user, array $payload, string $action, string $view) { + // only for BC reasons, do not access it directly! if (!\array_key_exists('actions', $payload)) { $payload['actions'] = []; } + // only for BC reasons, do not access it directly! + if (!\array_key_exists('view', $payload)) { + $payload['view'] = $view; + } parent::__construct($user, $payload); $this->action = $action; + $this->view = $view; } public function getActionName(): string @@ -29,13 +41,141 @@ class PageActionsEvent extends ThemeEvent return $this->action; } - public function getActions(): array + public function isView(string $view): bool { - return $this->payload['actions']; + return $this->view === $view; } - public function setActions(array $actions): void + public function isIndexView(): bool { - $this->payload['actions'] = $actions; + return $this->isView('index'); + } + + public function getView(): string + { + return $this->view; + } + + public function getActions(): array + { + $actions = $this->payload['actions']; + + // move documentation to end of list + if (\array_key_exists('help', $actions)) { + $help = $actions['help']; + unset($actions['help']); + $actions += ['help' => $help]; + } + + // move trash to end of list + if (\array_key_exists('trash', $actions)) { + $delete = $actions['trash']; + unset($actions['trash']); + $actions += ['trash' => $delete]; + } + + return $actions; + } + + public function hasAction(string $key): bool + { + return \array_key_exists($key, $this->payload['actions']); + } + + public function hasSubmenu(string $submenu): bool + { + if (!\array_key_exists($submenu, $this->payload['actions'])) { + return false; + } + + return \array_key_exists('children', $this->payload['actions'][$submenu]); + } + + public function addActionToSubmenu(string $submenu, string $key, array $action): void + { + if (\array_key_exists($submenu, $this->payload['actions'])) { + if (!\array_key_exists('children', $this->payload['actions'][$submenu])) { + $this->payload['actions'][$submenu]['children'] = []; + } + } + $this->payload['actions'][$submenu]['children'][$key] = $action; + } + + public function replaceAction(string $key, array $action): void + { + $this->payload['actions'][$key] = $action; + } + + public function addAction(string $key, array $action): void + { + if (!\array_key_exists($key, $this->payload['actions'])) { + $this->payload['actions'][$key] = $action; + } + } + + public function removeAction(string $key): void + { + if (\array_key_exists($key, $this->payload['actions'])) { + unset($this->payload['actions'][$key]); + } + } + + public function addDivider(): void + { + $key = 'divider' . $this->divider++; + $this->payload['actions'][$key] = null; + } + + public function addSearchToggle(): void + { + $this->addAction('search', ['class' => 'search-toggle visible-xs-inline']); + } + + public function addQuickExport(string $url): void + { + $this->addAction('download', ['url' => $url, 'class' => 'toolbar-action']); + } + + public function addCreate(string $url, bool $modal = true): void + { + $this->addAction('create', ['url' => $url, 'class' => ($modal ? 'modal-ajax-form' : '')]); + } + + public function addHelp(string $url): void + { + $this->addAction('help', ['url' => $url, 'target' => '_blank']); + } + + public function addBack(string $url): void + { + $this->addAction('back', ['url' => $url, 'translation_domain' => 'actions']); + } + + public function addDelete(string $url, bool $remoteConfirm = true): void + { + if ($remoteConfirm) { + $this->addAction('trash', ['url' => $url, 'class' => 'modal-ajax-form text-red']); + } else { + $this->addAction('trash', ['url' => $url, 'class' => 'confirmation-link text-red', 'attr' => ['data-question' => 'confirm.delete']]); + } + } + + public function addColumnToggle(string $modal): void + { + $modal = '#' . ltrim($modal, '#'); + $this->addAction('visibility', ['modal' => $modal]); + } + + public function countActions(?string $submenu = null): int + { + if ($submenu !== null) { + if (!$this->hasSubmenu($submenu)) { + return 0; + } + + return \count($this->payload['actions'][$submenu]['children']); + } + + return \count($this->payload['actions']); } } diff --git a/src/EventSubscriber/Actions/AbstractActionsSubscriber.php b/src/EventSubscriber/Actions/AbstractActionsSubscriber.php index e6298e02..ab486e6f 100644 --- a/src/EventSubscriber/Actions/AbstractActionsSubscriber.php +++ b/src/EventSubscriber/Actions/AbstractActionsSubscriber.php @@ -10,10 +10,14 @@ namespace App\EventSubscriber\Actions; use App\Constants; +use App\Event\PageActionsEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +/** + * Base class for all listeners, which adds the pages default toolbars. + */ abstract class AbstractActionsSubscriber implements EventSubscriberInterface { private $auth; @@ -39,4 +43,21 @@ abstract class AbstractActionsSubscriber implements EventSubscriberInterface { return Constants::HOMEPAGE . '/documentation/' . $url; } + + public static function getSubscribedEvents(): array + { + return [ + 'actions.' . static::getActionName() => ['onActions', 1000], + ]; + } + + public static function getActionName(): string + { + throw new \Exception('You need to overwrite getActionName() or getSubscribedEvents() in ' . static::class); + } + + public function onActions(PageActionsEvent $event): void + { + // non abstract - so the usage is completely optional + } } diff --git a/src/EventSubscriber/Actions/AbstractTimesheetSubscriber.php b/src/EventSubscriber/Actions/AbstractTimesheetSubscriber.php new file mode 100644 index 00000000..03c7708d --- /dev/null +++ b/src/EventSubscriber/Actions/AbstractTimesheetSubscriber.php @@ -0,0 +1,58 @@ +getPayload(); + + /** @var Timesheet $timesheet */ + $timesheet = $payload['timesheet']; + if (!$event->isIndexView()) { + $event->addBack($this->path($routeListing)); + } + + if ($timesheet->getId() !== null) { + if ($this->isGranted('edit', $timesheet)) { + $class = $event->isView('edit') ? '' : 'modal-ajax-form'; + $event->addAction('edit', ['url' => $this->path($routeEdit, ['id' => $timesheet->getId()]), 'class' => $class]); + } + + if ($timesheet->isRunning() && $this->isGranted('stop', $timesheet)) { + $event->addAction('stop', ['url' => $this->path('stop_timesheet', ['id' => $timesheet->getId()]), 'class' => 'api-link', 'attr' => ['data-event' => 'kimai.timesheetStop kimai.timesheetUpdate', 'data-method' => 'PATCH', 'data-msg-error' => 'timesheet.stop.error', 'data-msg-success' => 'timesheet.stop.success']]); + } + + if (!$timesheet->isRunning() && $this->isGranted('start', $timesheet)) { + $event->addAction('repeat', ['url' => $this->path('restart_timesheet', ['id' => $timesheet->getId()]), 'class' => 'api-link', 'attr' => ['data-payload' => '{"copy": "all"}', 'data-event' => 'kimai.timesheetStart kimai.timesheetUpdate', 'data-method' => 'PATCH', 'data-msg-error' => 'timesheet.start.error', 'data-msg-success' => 'timesheet.start.success']]); + } + + if ($this->isGranted('duplicate', $timesheet)) { + $event->addAction('copy', ['url' => $this->path('duplicate_timesheet', ['id' => $timesheet->getId()]), 'class' => 'api-link', 'attr' => ['data-payload' => '{"copy": "all"}', 'data-event' => 'kimai.timesheetStart kimai.timesheetUpdate', 'data-method' => 'PATCH', 'data-msg-error' => 'action.update.error', 'data-msg-success' => 'action.update.success']]); + } + + if ($event->countActions() > 0) { + $event->addDivider(); + } + + if ($event->isIndexView() && $this->isGranted('delete', $timesheet)) { + $event->addAction('trash', ['url' => $this->path('delete_timesheet', ['id' => $timesheet->getId()]), 'class' => 'api-link', 'attr' => ['data-event' => 'kimai.timesheetDelete kimai.timesheetUpdate', 'data-method' => 'DELETE', 'data-question' => 'confirm.delete', 'data-msg-error' => 'action.delete.error', 'data-msg-success' => 'action.delete.success']]); + } + } + + if (!$event->isIndexView()) { + $event->addHelp($this->documentationLink('timesheet.html')); + } + } +} diff --git a/src/EventSubscriber/Actions/AbstractTimesheetsSubscriber.php b/src/EventSubscriber/Actions/AbstractTimesheetsSubscriber.php new file mode 100644 index 00000000..5c446a20 --- /dev/null +++ b/src/EventSubscriber/Actions/AbstractTimesheetsSubscriber.php @@ -0,0 +1,39 @@ +serviceExport = $serviceExport; + } + + protected function addExporter(PageActionsEvent $event, string $routeExport): void + { + $allExporter = $this->serviceExport->getTimesheetExporter(); + if (\count($allExporter) === 1) { + $event->addAction('download', ['url' => $this->path($routeExport, ['exporter' => $allExporter[0]->getId()]), 'class' => 'toolbar-action']); + } else { + foreach ($allExporter as $exporter) { + $id = $exporter->getId(); + $event->addActionToSubmenu('download', 'exporter.' . $id, ['title' => 'button.' . $id, 'url' => $this->path($routeExport, ['exporter' => $id]), 'class' => 'toolbar-action exporter-' . $id]); + } + } + } +} diff --git a/src/EventSubscriber/Actions/ActivitiesSubscriber.php b/src/EventSubscriber/Actions/ActivitiesSubscriber.php new file mode 100644 index 00000000..a1bcb86a --- /dev/null +++ b/src/EventSubscriber/Actions/ActivitiesSubscriber.php @@ -0,0 +1,33 @@ +addSearchToggle(); + $event->addColumnToggle('#modal_activity_admin'); + $event->addQuickExport($this->path('activity_export')); + + if ($this->isGranted('create_activity')) { + $event->addCreate($this->path('admin_activity_create')); + } + + $event->addHelp($this->documentationLink('activity.html')); + } +} diff --git a/src/EventSubscriber/Actions/ActivitySubscriber.php b/src/EventSubscriber/Actions/ActivitySubscriber.php new file mode 100644 index 00000000..8c2a0a13 --- /dev/null +++ b/src/EventSubscriber/Actions/ActivitySubscriber.php @@ -0,0 +1,75 @@ +getPayload(); + + /** @var Activity $activity */ + $activity = $payload['activity']; + + if ($activity->getId() === null) { + return; + } + if ($this->isGranted('view', $activity)) { + $event->addAction('details', ['url' => $this->path('activity_details', ['id' => $activity->getId()])]); + } + + if ($this->isGranted('edit', $activity)) { + $class = $event->isView('edit') ? '' : 'modal-ajax-form'; + $event->addAction('edit', ['url' => $this->path('admin_activity_edit', ['id' => $activity->getId()]), 'class' => $class]); + } + + if ($this->isGranted('permissions', $activity)) { + $class = $event->isView('permissions') ? '' : 'modal-ajax-form'; + $event->addAction('permissions', ['url' => $this->path('admin_activity_permissions', ['id' => $activity->getId()]), 'class' => $class]); + } + + if ($event->countActions() > 0) { + $event->addDivider(); + } + + if ($this->isGranted('view_other_timesheet')) { + $parameters = ['activities[]' => $activity->getId()]; + if (!$activity->isGlobal()) { + $parameters['customers[]'] = $activity->getProject()->getCustomer()->getId(); + $parameters['projects[]'] = $activity->getProject()->getId(); + } + $event->addActionToSubmenu('filter', 'timesheet', ['title' => 'timesheet', 'translation_domain' => 'actions', 'url' => $this->path('admin_timesheet', $parameters)]); + } + + if ($event->hasSubmenu('filter')) { + $event->addDivider(); + } + + if ($activity->isVisible() && $this->isGranted('create_other_timesheet')) { + $parameters = ['activity' => $activity->getId()]; + if (!$activity->isGlobal()) { + $parameters['project'] = $activity->getProject()->getId(); + } + $event->addAction('create-timesheet', ['icon' => 'start', 'url' => $this->path('admin_timesheet_create', $parameters), 'class' => 'modal-ajax-form']); + } + + if ($event->isIndexView() && $this->isGranted('delete', $activity)) { + $event->addDelete($this->path('admin_activity_delete', ['id' => $activity->getId()])); + } + } +} diff --git a/src/EventSubscriber/Actions/CalendarSubscriber.php b/src/EventSubscriber/Actions/CalendarSubscriber.php new file mode 100644 index 00000000..880cbc71 --- /dev/null +++ b/src/EventSubscriber/Actions/CalendarSubscriber.php @@ -0,0 +1,29 @@ +isGranted('create_own_timesheet')) { + $event->addCreate($this->path('timesheet_create')); + } + + $event->addHelp($this->documentationLink('calendar.html')); + } +} diff --git a/src/EventSubscriber/Actions/CustomerSubscriber.php b/src/EventSubscriber/Actions/CustomerSubscriber.php new file mode 100644 index 00000000..7b9d11d7 --- /dev/null +++ b/src/EventSubscriber/Actions/CustomerSubscriber.php @@ -0,0 +1,79 @@ +getPayload(); + + /** @var Customer $customer */ + $customer = $payload['customer']; + + if ($customer->getId() === null) { + return; + } + + if ($this->isGranted('view', $customer)) { + $event->addAction('details', ['url' => $this->path('customer_details', ['id' => $customer->getId()])]); + } + + if ($this->isGranted('edit', $customer)) { + $class = $event->isView('edit') ? '' : 'modal-ajax-form'; + $event->addAction('edit', ['url' => $this->path('admin_customer_edit', ['id' => $customer->getId()]), 'class' => $class]); + } + + if ($this->isGranted('permissions', $customer)) { + $class = $event->isView('permissions') ? '' : 'modal-ajax-form'; + $event->addAction('permissions', ['url' => $this->path('admin_customer_permissions', ['id' => $customer->getId()]), 'class' => $class]); + } + + if ($event->countActions() > 0) { + $event->addDivider(); + } + + if ($this->isGranted('view_project') || $this->isGranted('view_teamlead_project') || $this->isGranted('view_team_project')) { + $event->addActionToSubmenu('filter', 'project', ['title' => 'project', 'translation_domain' => 'actions', 'url' => $this->path('admin_project', ['customers[]' => $customer->getId()])]); + } + + if ($this->isGranted('view_activity')) { + $event->addActionToSubmenu('filter', 'activity', ['title' => 'activity', 'translation_domain' => 'actions', 'url' => $this->path('admin_activity', ['customers[]' => $customer->getId()])]); + } + + if ($this->isGranted('view_other_timesheet')) { + $event->addActionToSubmenu('filter', 'timesheet', ['title' => 'timesheet', 'translation_domain' => 'actions', 'url' => $this->path('admin_timesheet', ['customers[]' => $customer->getId()])]); + } + + if ($event->hasSubmenu('filter')) { + $event->addDivider(); + } + + if ($customer->isVisible() && $this->isGranted('create_project')) { + $event->addAction('create-project', ['icon' => 'create', 'url' => $this->path('admin_project_create_with_customer', ['customer' => $customer->getId()]), 'class' => 'modal-ajax-form']); + } + + if ($event->isIndexView() && $this->isGranted('delete', $customer)) { + $event->addAction('trash', ['url' => $this->path('admin_customer_delete', ['id' => $customer->getId()]), 'class' => 'modal-ajax-form text-red']); + } + + if ($this->isGranted('view_reporting') && $this->isGranted('budget_project')) { + $event->addAction('report_project_view', ['url' => $this->path('report_project_view', ['customer' => $customer->getId()]), 'icon' => 'reporting', 'translation_domain' => 'reporting']); + } + } +} diff --git a/src/EventSubscriber/Actions/CustomersSubscriber.php b/src/EventSubscriber/Actions/CustomersSubscriber.php new file mode 100644 index 00000000..d83b2750 --- /dev/null +++ b/src/EventSubscriber/Actions/CustomersSubscriber.php @@ -0,0 +1,33 @@ +addSearchToggle(); + $event->addColumnToggle('#modal_customer_admin'); + $event->addQuickExport($this->path('customer_export')); + + if ($this->isGranted('create_customer')) { + $event->addCreate($this->path('admin_customer_create')); + } + + $event->addHelp($this->documentationLink('customer.html')); + } +} diff --git a/src/EventSubscriber/Actions/ExportSubscriber.php b/src/EventSubscriber/Actions/ExportSubscriber.php new file mode 100644 index 00000000..0de355df --- /dev/null +++ b/src/EventSubscriber/Actions/ExportSubscriber.php @@ -0,0 +1,31 @@ +addColumnToggle('#modal_export'); + + if ($event->isView('preview')) { + $event->addAction('off', ['id' => 'export-toggle-button']); + } + + $event->addHelp($this->documentationLink('export.html')); + } +} diff --git a/src/EventSubscriber/Actions/InvoiceArchiveSubscriber.php b/src/EventSubscriber/Actions/InvoiceArchiveSubscriber.php index 6983bf5a..f51f1e8d 100644 --- a/src/EventSubscriber/Actions/InvoiceArchiveSubscriber.php +++ b/src/EventSubscriber/Actions/InvoiceArchiveSubscriber.php @@ -13,25 +13,19 @@ use App\Event\PageActionsEvent; class InvoiceArchiveSubscriber extends AbstractActionsSubscriber { - public static function getSubscribedEvents(): array + public static function getActionName(): string { - return [ - 'actions.invoice_details' => ['onActions', 1000], - ]; + return 'invoice_details'; } - public function onActions(PageActionsEvent $event) + public function onActions(PageActionsEvent $event): void { - $actions = $event->getActions(); - if ($this->isGranted('view_invoice')) { - $actions['back'] = ['url' => $this->path('invoice'), 'translation_domain' => 'actions']; + $event->addBack($this->path('invoice')); } - - $actions['visibility'] = '#modal_invoices'; - $actions['download'] = ['url' => $this->path('invoice_export'), 'class' => 'toolbar-action']; - $actions['help'] = ['url' => $this->documentationLink('invoices.html'), 'target' => '_blank']; - - $event->setActions($actions); + $event->addSearchToggle(); + $event->addColumnToggle('#modal_invoices'); + $event->addQuickExport($this->path('invoice_export')); + $event->addHelp($this->documentationLink('invoices.html')); } } diff --git a/src/EventSubscriber/Actions/InvoiceSubscriber.php b/src/EventSubscriber/Actions/InvoiceSubscriber.php new file mode 100644 index 00000000..5b9ad3c0 --- /dev/null +++ b/src/EventSubscriber/Actions/InvoiceSubscriber.php @@ -0,0 +1,44 @@ +getPayload(); + + /** @var Invoice $invoice */ + $invoice = $payload['invoice']; + + if ($invoice->getId() === null) { + return; + } + + if ($this->isGranted('history_invoice')) { + if ($invoice->isNew()) { + $event->addAction('invoice.pending', ['url' => $this->path('admin_invoice_status', ['id' => $invoice->getId(), 'status' => 'pending'])]); + } elseif ($invoice->isPending()) { + $event->addAction('invoice.paid', ['url' => $this->path('admin_invoice_status', ['id' => $invoice->getId(), 'status' => 'paid'])]); + } + + $event->addAction('download', ['url' => $this->path('admin_invoice_download', ['id' => $invoice->getId()]), 'target' => '_blank']); + $event->addDelete($this->path('admin_invoice_delete', ['id' => $invoice->getId()]), false); + } + } +} diff --git a/src/EventSubscriber/Actions/InvoiceTemplateSubscriber.php b/src/EventSubscriber/Actions/InvoiceTemplateSubscriber.php new file mode 100644 index 00000000..0ccf3868 --- /dev/null +++ b/src/EventSubscriber/Actions/InvoiceTemplateSubscriber.php @@ -0,0 +1,42 @@ +getPayload(); + + /** @var InvoiceTemplate $template */ + $template = $payload['template']; + + if ($template->getId() === null) { + return; + } + + if ($this->isGranted('manage_invoice_template')) { + if (!$event->isIndexView()) { + $event->addBack($this->path('invoice')); + } + $event->addAction('edit', ['url' => $this->path('admin_invoice_template_edit', ['id' => $template->getId()]), 'class' => 'modal-ajax-form']); + $event->addAction('copy', ['url' => $this->path('admin_invoice_template_copy', ['id' => $template->getId()])]); + $event->addDelete($this->path('admin_invoice_template_delete', ['id' => $template->getId()]), false); + } + } +} diff --git a/src/EventSubscriber/Actions/InvoiceTemplateUploadSubscriber.php b/src/EventSubscriber/Actions/InvoiceTemplateUploadSubscriber.php new file mode 100644 index 00000000..8b76c968 --- /dev/null +++ b/src/EventSubscriber/Actions/InvoiceTemplateUploadSubscriber.php @@ -0,0 +1,29 @@ +isIndexView() && $this->isGranted('manage_invoice_template')) { + $event->addBack($this->path('admin_invoice_template')); + } + + $event->addHelp($this->documentationLink('invoices.html')); + } +} diff --git a/src/EventSubscriber/Actions/InvoiceTemplatesSubscriber.php b/src/EventSubscriber/Actions/InvoiceTemplatesSubscriber.php new file mode 100644 index 00000000..e3a25d1c --- /dev/null +++ b/src/EventSubscriber/Actions/InvoiceTemplatesSubscriber.php @@ -0,0 +1,40 @@ +isGranted('view_invoice')) { + $event->addBack($this->path('invoice')); + } + + $event->addColumnToggle('#modal_invoice_template'); + + if ($this->isGranted('manage_invoice_template')) { + $event->addAction('create', ['url' => $this->path('admin_invoice_template_create'), 'class' => 'modal-ajax-form']); + } + + // File upload does not work in a modal right now + if ($this->isGranted('upload_invoice_template')) { + $event->addAction('upload', ['url' => $this->path('admin_invoice_document_upload')]); + } + + $event->addHelp($this->documentationLink('invoices.html')); + } +} diff --git a/src/EventSubscriber/Actions/InvoicesSubscriber.php b/src/EventSubscriber/Actions/InvoicesSubscriber.php new file mode 100644 index 00000000..f22ea55e --- /dev/null +++ b/src/EventSubscriber/Actions/InvoicesSubscriber.php @@ -0,0 +1,39 @@ +addColumnToggle('#modal_invoice'); + + if ($this->isGranted('history_invoice')) { + $event->addAction('list', ['url' => $this->path('admin_invoice_list')]); + } + + if ($this->isGranted('manage_invoice_template')) { + $event->addAction('invoice-template', ['url' => $this->path('admin_invoice_template')]); + } + + if ($this->isGranted('system_configuration')) { + $event->addAction('settings', ['url' => $this->path('system_configuration_section', ['section' => 'invoice']), 'class' => 'modal-ajax-form']); + } + + $event->addHelp($this->documentationLink('invoices.html')); + } +} diff --git a/src/EventSubscriber/Actions/PermissionsSubscriber.php b/src/EventSubscriber/Actions/PermissionsSubscriber.php new file mode 100644 index 00000000..68bad376 --- /dev/null +++ b/src/EventSubscriber/Actions/PermissionsSubscriber.php @@ -0,0 +1,34 @@ +isIndexView() && $this->isGranted('role_permissions')) { + //$event->addBack($this->path('admin_user_permissions')); + } + + if ($this->isGranted('role_permissions')) { + $event->addCreate($this->path('admin_user_roles'), !$event->isView('role')); + } + + $event->addHelp($this->documentationLink('permissions.html')); + } +} diff --git a/src/EventSubscriber/Actions/PluginSubscriber.php b/src/EventSubscriber/Actions/PluginSubscriber.php new file mode 100644 index 00000000..c38ea0b8 --- /dev/null +++ b/src/EventSubscriber/Actions/PluginSubscriber.php @@ -0,0 +1,31 @@ +getPayload(); + + /** @var Plugin $plugin */ + $plugin = $payload['plugin']; + + $event->addAction('home', ['url' => $plugin->getMetadata()->getHomepage(), 'target' => '_blank']); + } +} diff --git a/src/EventSubscriber/Actions/PluginsSubscriber.php b/src/EventSubscriber/Actions/PluginsSubscriber.php new file mode 100644 index 00000000..40925ad5 --- /dev/null +++ b/src/EventSubscriber/Actions/PluginsSubscriber.php @@ -0,0 +1,27 @@ +addAction('shop', ['url' => Constants::HOMEPAGE . '/store/', 'target' => '_blank']); + $event->addHelp($this->documentationLink('plugins.html')); + } +} diff --git a/src/EventSubscriber/Actions/ProjectSubscriber.php b/src/EventSubscriber/Actions/ProjectSubscriber.php new file mode 100644 index 00000000..8d56d386 --- /dev/null +++ b/src/EventSubscriber/Actions/ProjectSubscriber.php @@ -0,0 +1,75 @@ +getPayload(); + + /** @var Project $project */ + $project = $payload['project']; + + if ($project->getId() === null) { + return; + } + + if ($this->isGranted('view', $project)) { + $event->addAction('details', ['url' => $this->path('project_details', ['id' => $project->getId()])]); + } + + if ($this->isGranted('edit', $project)) { + $class = $event->isView('edit') ? '' : 'modal-ajax-form'; + $event->addAction('edit', ['url' => $this->path('admin_project_edit', ['id' => $project->getId()]), 'class' => $class]); + } + + if ($this->isGranted('permissions', $project)) { + $class = $event->isView('permissions') ? '' : 'modal-ajax-form'; + $event->addAction('permissions', ['url' => $this->path('admin_project_permissions', ['id' => $project->getId()]), 'class' => $class]); + } + + if ($event->countActions() > 0) { + $event->addDivider(); + } + + if ($this->isGranted('view_activity')) { + $event->addActionToSubmenu('filter', 'activity', ['title' => 'activity', 'translation_domain' => 'actions', 'url' => $this->path('admin_activity', ['customers[]' => $project->getCustomer()->getId(), 'projects[]' => $project->getId()])]); + } + + if ($this->isGranted('view_other_timesheet')) { + $event->addActionToSubmenu('filter', 'timesheet', ['title' => 'timesheet', 'translation_domain' => 'actions', 'url' => $this->path('admin_timesheet', ['customers[]' => $project->getCustomer()->getId(), 'projects[]' => $project->getId()])]); + } + + if ($event->hasSubmenu('filter')) { + $event->addDivider(); + } + + if ($project->isVisible() && $project->getCustomer()->isVisible() && $this->isGranted('create_activity')) { + $event->addAction('create-activity', ['icon' => 'create', 'url' => $this->path('admin_activity_create_with_project', ['project' => $project->getId()]), 'class' => 'modal-ajax-form']); + } + + if ($this->isGranted('edit', $project)) { + $event->addAction('copy', ['url' => $this->path('admin_project_duplicate', ['id' => $project->getId()])]); + } + + if ($event->isIndexView() && $this->isGranted('delete', $project)) { + $event->addAction('trash', ['url' => $this->path('admin_project_delete', ['id' => $project->getId()]), 'class' => 'modal-ajax-form text-red']); + } + } +} diff --git a/src/EventSubscriber/Actions/ProjectsSubscriber.php b/src/EventSubscriber/Actions/ProjectsSubscriber.php new file mode 100644 index 00000000..8795c4bf --- /dev/null +++ b/src/EventSubscriber/Actions/ProjectsSubscriber.php @@ -0,0 +1,33 @@ +addSearchToggle(); + $event->addColumnToggle('#modal_project_admin'); + $event->addQuickExport($this->path('project_export')); + + if ($this->isGranted('create_project')) { + $event->addCreate($this->path('admin_project_create')); + } + + $event->addHelp($this->documentationLink('project.html')); + } +} diff --git a/src/EventSubscriber/Actions/ReportingSubscriber.php b/src/EventSubscriber/Actions/ReportingSubscriber.php new file mode 100644 index 00000000..43458c9e --- /dev/null +++ b/src/EventSubscriber/Actions/ReportingSubscriber.php @@ -0,0 +1,42 @@ +reportingService = $reportingService; + } + + public static function getActionName(): string + { + return 'reporting'; + } + + public function onActions(PageActionsEvent $event): void + { + $reports = $this->reportingService->getAvailableReports($event->getUser()); + + foreach ($reports as $report) { + $event->addActionToSubmenu('reporting', $report->getId(), ['title' => $report->getLabel(), 'translation_domain' => 'reporting', 'url' => $this->path($report->getRoute()), 'class' => 'toolbar-action report-' . $report->getId()]); + } + + $event->addHelp($this->documentationLink('reporting.html')); + } +} diff --git a/src/EventSubscriber/Actions/SystemConfigurationSubscriber.php b/src/EventSubscriber/Actions/SystemConfigurationSubscriber.php new file mode 100644 index 00000000..fc76e29f --- /dev/null +++ b/src/EventSubscriber/Actions/SystemConfigurationSubscriber.php @@ -0,0 +1,25 @@ +addHelp($this->documentationLink('configurations.html')); + } +} diff --git a/src/EventSubscriber/Actions/TagSubscriber.php b/src/EventSubscriber/Actions/TagSubscriber.php new file mode 100644 index 00000000..ed44668c --- /dev/null +++ b/src/EventSubscriber/Actions/TagSubscriber.php @@ -0,0 +1,71 @@ +getPayload(); + + $tag = $payload['tag']; + $id = null; + $name = null; + + if (\is_array($tag)) { + // tag can be either and array (on index page => [id, name, color, amount]) ... + $id = $tag['id']; + $name = $tag['name']; + } elseif ($tag instanceof Tag) { + // ...or an entity on detail page + $id = $tag->getId(); + $name = $tag->getName(); + } + + if (!$event->isIndexView() && $this->isGranted('view_tag')) { + //$event->addBack($this->path('tags')); + } + + if ($id === null) { + return; + } + + if ($this->isGranted('manage_tag')) { + $class = ($event->isView('edit') ? '' : 'modal-ajax-form'); + $event->addAction('edit', ['url' => $this->path('tags_edit', ['id' => $id]), 'class' => $class]); + } + + if ($this->isGranted('view_other_timesheet')) { + $event->addActionToSubmenu('filter', 'timesheet', ['title' => 'timesheet', 'translation_domain' => 'actions', 'url' => $this->path('admin_timesheet', ['tags' => $name])]); + } + + if ($event->isIndexView() && $this->isGranted('delete_tag')) { + $event->addAction('trash', [ + 'url' => $this->path('delete_tag', ['id' => $id]), + 'class' => 'api-link', + 'attr' => [ + 'data-event' => 'kimai.tagDelete kimai.tagUpdate', + 'data-method' => 'DELETE', + 'data-question' => 'confirm.delete', + 'data-msg-error' => 'action.delete.error', + 'data-msg-success' => 'action.delete.success' + ] + ]); + } + } +} diff --git a/src/EventSubscriber/Actions/TagsSubscriber.php b/src/EventSubscriber/Actions/TagsSubscriber.php new file mode 100644 index 00000000..bf2caccb --- /dev/null +++ b/src/EventSubscriber/Actions/TagsSubscriber.php @@ -0,0 +1,31 @@ +addSearchToggle(); + + if ($this->isGranted('manage_tag')) { + $event->addCreate($this->path('tags_create')); + } + + $event->addHelp($this->documentationLink('tags.html')); + } +} diff --git a/src/EventSubscriber/Actions/TeamSubscriber.php b/src/EventSubscriber/Actions/TeamSubscriber.php new file mode 100644 index 00000000..2d6e7500 --- /dev/null +++ b/src/EventSubscriber/Actions/TeamSubscriber.php @@ -0,0 +1,59 @@ +getPayload(); + + /** @var Team $team */ + $team = $payload['team']; + + if (!$event->isIndexView() && $this->isGranted('view_tag')) { + //$event->addBack($this->path('admin_team')); + } + + if ($team->getId() === null) { + return; + } + + if ($this->isGranted('edit', $team)) { + $event->addAction('edit', ['url' => $this->path('admin_team_edit', ['id' => $team->getId()])]); + + if ($this->isGranted('create_team')) { + $event->addAction('copy', ['url' => $this->path('team_duplicate', ['id' => $team->getId()])]); + } + } + + if ($event->isIndexView() && $this->isGranted('delete', $team)) { + $event->addAction('trash', [ + 'url' => $this->path('delete_team', ['id' => $team->getId()]), + 'class' => 'api-link', + 'attr' => [ + 'data-event' => 'kimai.teamDelete kimai.teamUpdate', + 'data-method' => 'DELETE', + 'data-question' => 'confirm.delete', + 'data-msg-error' => 'action.delete.error', + 'data-msg-success' => 'action.delete.success' + ] + ]); + } + } +} diff --git a/src/EventSubscriber/Actions/TeamsSubscriber.php b/src/EventSubscriber/Actions/TeamsSubscriber.php new file mode 100644 index 00000000..3fec96b6 --- /dev/null +++ b/src/EventSubscriber/Actions/TeamsSubscriber.php @@ -0,0 +1,31 @@ +addSearchToggle(); + + if ($this->isGranted('create_team')) { + $event->addCreate($this->path('admin_team_create'), false); + } + + $event->addHelp($this->documentationLink('teams.html')); + } +} diff --git a/src/EventSubscriber/Actions/TimesheetMultiUpdateSubscriber.php b/src/EventSubscriber/Actions/TimesheetMultiUpdateSubscriber.php new file mode 100644 index 00000000..9eb86f94 --- /dev/null +++ b/src/EventSubscriber/Actions/TimesheetMultiUpdateSubscriber.php @@ -0,0 +1,26 @@ +addBack($this->path('timesheet')); + $event->addHelp($this->documentationLink('timesheet.html')); + } +} diff --git a/src/EventSubscriber/Actions/TimesheetSubscriber.php b/src/EventSubscriber/Actions/TimesheetSubscriber.php new file mode 100644 index 00000000..69c667db --- /dev/null +++ b/src/EventSubscriber/Actions/TimesheetSubscriber.php @@ -0,0 +1,25 @@ +timesheetActions($event, 'timesheet', 'timesheet_edit'); + } +} diff --git a/src/EventSubscriber/Actions/TimesheetTeamMultiUpdateSubscriber.php b/src/EventSubscriber/Actions/TimesheetTeamMultiUpdateSubscriber.php new file mode 100644 index 00000000..71ae37ec --- /dev/null +++ b/src/EventSubscriber/Actions/TimesheetTeamMultiUpdateSubscriber.php @@ -0,0 +1,26 @@ +addBack($this->path('admin_timesheet')); + $event->addHelp($this->documentationLink('timesheet.html')); + } +} diff --git a/src/EventSubscriber/Actions/TimesheetTeamSubscriber.php b/src/EventSubscriber/Actions/TimesheetTeamSubscriber.php new file mode 100644 index 00000000..9f510c4a --- /dev/null +++ b/src/EventSubscriber/Actions/TimesheetTeamSubscriber.php @@ -0,0 +1,25 @@ +timesheetActions($event, 'admin_timesheet', 'admin_timesheet_edit'); + } +} diff --git a/src/EventSubscriber/Actions/TimesheetsSubscriber.php b/src/EventSubscriber/Actions/TimesheetsSubscriber.php new file mode 100644 index 00000000..5fffa75e --- /dev/null +++ b/src/EventSubscriber/Actions/TimesheetsSubscriber.php @@ -0,0 +1,36 @@ +addSearchToggle(); + $event->addColumnToggle('#modal_timesheet'); + + if ($this->isGranted('export_own_timesheet')) { + $this->addExporter($event, 'timesheet_export'); + } + + if ($this->isGranted('create_own_timesheet')) { + $event->addCreate($this->path('timesheet_create')); + } + + $event->addHelp($this->documentationLink('timesheet.html')); + } +} diff --git a/src/EventSubscriber/Actions/TimesheetsTeamSubscriber.php b/src/EventSubscriber/Actions/TimesheetsTeamSubscriber.php new file mode 100644 index 00000000..f0d0d1b3 --- /dev/null +++ b/src/EventSubscriber/Actions/TimesheetsTeamSubscriber.php @@ -0,0 +1,37 @@ +addSearchToggle(); + $event->addColumnToggle('#modal_timesheet_admin'); + + if ($this->isGranted('export_other_timesheet')) { + $this->addExporter($event, 'admin_timesheet_export'); + } + + if ($this->isGranted('create_other_timesheet')) { + $event->addActionToSubmenu('create', 'single', ['title' => 'create', 'url' => $this->path('admin_timesheet_create'), 'class' => 'create-ts modal-ajax-form']); + $event->addActionToSubmenu('create', 'multi-user', ['title' => 'create-timesheet-multiuser', 'translation_domain' => 'actions', 'url' => $this->path('admin_timesheet_create_multiuser'), 'class' => 'create-ts-mu modal-ajax-form']); + } + + $event->addHelp($this->documentationLink('timesheet.html')); + } +} diff --git a/src/EventSubscriber/Actions/UserSubscriber.php b/src/EventSubscriber/Actions/UserSubscriber.php index 18dbc056..83142c8c 100644 --- a/src/EventSubscriber/Actions/UserSubscriber.php +++ b/src/EventSubscriber/Actions/UserSubscriber.php @@ -14,21 +14,15 @@ use App\Event\PageActionsEvent; class UserSubscriber extends AbstractActionsSubscriber { - public static function getSubscribedEvents(): array + public static function getActionName(): string { - return [ - 'actions.user' => ['onActions', 1000], - ]; + return 'user'; } - public function onActions(PageActionsEvent $event) + public function onActions(PageActionsEvent $event): void { $payload = $event->getPayload(); - if (!isset($payload['user'])) { - return; - } - /** @var User $user */ $user = $payload['user']; @@ -36,58 +30,47 @@ class UserSubscriber extends AbstractActionsSubscriber return; } - $actions = $event->getActions(); - if ($this->isGranted('view', $user)) { - $actions['profile-stats'] = ['icon' => 'avatar', 'url' => $this->path('user_profile', ['username' => $user->getUsername()]), 'translation_domain' => 'actions']; + $event->addAction('profile-stats', ['icon' => 'avatar', 'url' => $this->path('user_profile', ['username' => $user->getUsername()]), 'translation_domain' => 'actions']); + $event->addDivider(); } - if (\count($actions) > 0) { - $actions['divider'] = null; - } - - $subActions = []; if ($this->isGranted('edit', $user)) { - $subActions['edit'] = ['url' => $this->path('user_profile_edit', ['username' => $user->getUsername()]), 'title' => 'edit', 'translation_domain' => 'actions']; + $event->addActionToSubmenu('edit', 'edit', ['url' => $this->path('user_profile_edit', ['username' => $user->getUsername()]), 'title' => 'edit', 'translation_domain' => 'actions']); } if ($this->isGranted('preferences', $user)) { - $subActions['settings'] = ['url' => $this->path('user_profile_preferences', ['username' => $user->getUsername()]), 'title' => 'settings', 'translation_domain' => 'actions']; + $event->addActionToSubmenu('edit', 'settings', ['url' => $this->path('user_profile_preferences', ['username' => $user->getUsername()]), 'title' => 'settings', 'translation_domain' => 'actions']); } if ($this->isGranted('password', $user)) { - $subActions['password'] = ['url' => $this->path('user_profile_password', ['username' => $user->getUsername()]), 'title' => 'profile.password']; + $event->addActionToSubmenu('edit', 'password', ['url' => $this->path('user_profile_password', ['username' => $user->getUsername()]), 'title' => 'profile.password']); } if ($this->isGranted('api-token', $user)) { - $subActions['api-token'] = ['url' => $this->path('user_profile_api_token', ['username' => $user->getUsername()]), 'title' => 'profile.api-token']; + $event->addActionToSubmenu('edit', 'api-token', ['url' => $this->path('user_profile_api_token', ['username' => $user->getUsername()]), 'title' => 'profile.api-token']); } if ($this->isGranted('teams', $user)) { - $subActions['teams'] = ['url' => $this->path('user_profile_teams', ['username' => $user->getUsername()]), 'title' => 'profile.teams']; + $event->addActionToSubmenu('edit', 'teams', ['url' => $this->path('user_profile_teams', ['username' => $user->getUsername()]), 'title' => 'profile.teams']); } if ($this->isGranted('roles', $user)) { - $subActions['roles'] = ['url' => $this->path('user_profile_roles', ['username' => $user->getUsername()]), 'title' => 'profile.roles']; + $event->addActionToSubmenu('edit', 'roles', ['url' => $this->path('user_profile_roles', ['username' => $user->getUsername()]), 'title' => 'profile.roles']); } - if (\count($subActions) > 0) { - $actions['edit'] = ['children' => $subActions, 'title' => 'edit']; - $actions['divider2'] = null; + if ($event->hasSubmenu('edit')) { + $event->addDivider(); } $viewOther = $this->isGranted('view_other_timesheet'); if ($this->isGranted('view_reporting')) { if ($viewOther || ($event->getUser()->getId() === $user->getId())) { - $actions['menu.reporting'] = ['url' => $this->path('report_user_month', ['user' => $user->getId()]), 'icon' => 'reporting']; + $event->addAction('menu.reporting', ['url' => $this->path('report_user_month', ['user' => $user->getId()]), 'icon' => 'reporting']); } } if ($viewOther && $user->isEnabled()) { - $actions['timesheet'] = $this->path('admin_timesheet', ['users[]' => $user->getId()]); + $event->addAction('timesheet', ['url' => $this->path('admin_timesheet', ['users[]' => $user->getId()])]); } - $view = $payload['view'] ?? null; - - if ($view === 'index' && $this->isGranted('delete', $user)) { - $actions['trash'] = ['url' => $this->path('admin_user_delete', ['id' => $user->getId()]), 'class' => 'modal-ajax-form']; + if ($event->isIndexView() && $this->isGranted('delete', $user)) { + $event->addAction('trash', ['url' => $this->path('admin_user_delete', ['id' => $user->getId()]), 'class' => 'modal-ajax-form']); } - - $event->setActions($actions); } } diff --git a/src/EventSubscriber/Actions/UsersSubscriber.php b/src/EventSubscriber/Actions/UsersSubscriber.php new file mode 100644 index 00000000..0e3ed055 --- /dev/null +++ b/src/EventSubscriber/Actions/UsersSubscriber.php @@ -0,0 +1,35 @@ +addSearchToggle(); + if ($event->isIndexView()) { + $event->addColumnToggle('#modal_user_admin'); + } + $event->addQuickExport($this->path('user_export')); + + if ($this->isGranted('create_user')) { + $event->addCreate($this->path('admin_user_create'), false); + } + + $event->addHelp($this->documentationLink('users.html')); + } +} diff --git a/src/EventSubscriber/UserPreferenceSubscriber.php b/src/EventSubscriber/UserPreferenceSubscriber.php index 07aceaba..1c9fca18 100644 --- a/src/EventSubscriber/UserPreferenceSubscriber.php +++ b/src/EventSubscriber/UserPreferenceSubscriber.php @@ -61,6 +61,7 @@ final class UserPreferenceSubscriber implements EventSubscriberInterface $timezone = date_default_timezone_get(); } + $enableDefaultReport = $this->voter->isGranted('view_reporting'); $enableHourlyRate = false; $hourlyRateOptions = []; @@ -151,6 +152,7 @@ final class UserPreferenceSubscriber implements EventSubscriberInterface ->setValue(ReportingService::DEFAULT_VIEW) ->setOrder(650) ->setSection('behaviour') + ->setEnabled($enableDefaultReport) ->setType(ReportType::class), (new UserPreference()) diff --git a/src/Reporting/ProjectView/ProjectViewModel.php b/src/Reporting/ProjectView/ProjectViewModel.php index b8fcc1e4..08598c78 100644 --- a/src/Reporting/ProjectView/ProjectViewModel.php +++ b/src/Reporting/ProjectView/ProjectViewModel.php @@ -34,9 +34,9 @@ final class ProjectViewModel */ private $durationTotal = 0; /** - * @var int + * @var float */ - private $rateTotal = 0; + private $rateTotal = 0.00; /** * @var int */ @@ -116,12 +116,12 @@ final class ProjectViewModel $this->notExportedRate = $notExportedRate; } - public function getRateTotal(): int + public function getRateTotal(): float { return $this->rateTotal; } - public function setRateTotal(int $rateTotal): void + public function setRateTotal(float $rateTotal): void { $this->rateTotal = $rateTotal; } diff --git a/src/Reporting/ProjectView/ProjectViewService.php b/src/Reporting/ProjectView/ProjectViewService.php index e585a2ef..c11d66ea 100644 --- a/src/Reporting/ProjectView/ProjectViewService.php +++ b/src/Reporting/ProjectView/ProjectViewService.php @@ -52,8 +52,15 @@ final class ProjectViewService ->leftJoin(Timesheet::class, 't', 'WITH', 'p.id = t.project') ->andWhere($qb->expr()->eq('p.visible', true)) ->andWhere($qb->expr()->eq('c.visible', true)) + ->andWhere( + $qb->expr()->orX( + $qb->expr()->isNull('p.end'), + $qb->expr()->gte('p.end', ':project_end') + ) + ) ->addGroupBy('p') ->addGroupBy('t.project') + ->setParameter('project_end', $today, Types::DATETIME_MUTABLE) ; if ($query->getCustomer() !== null) { @@ -68,8 +75,8 @@ final class ProjectViewService if (!$query->isIncludeNoBudget()) { $qb->andWhere( $qb->expr()->orX( - $qb->expr()->gt('p.budget', 0), - $qb->expr()->gt('p.timeBudget', 0) + $qb->expr()->gt('p.timeBudget', 0), + $qb->expr()->gt('p.budget', 0) ) ); } @@ -83,7 +90,7 @@ final class ProjectViewService $entity = new ProjectViewModel(); $entity->setProject($res['project']); $entity->setDurationTotal($res['totalDuration'] ?? 0); - $entity->setRateTotal($res['totalRate'] ?? 0); + $entity->setRateTotal($res['totalRate'] ?? 0.00); $projectViews[$entity->getProject()->getId()] = $entity; } diff --git a/src/Twig/IconExtension.php b/src/Twig/IconExtension.php index 91c34019..d668cfc5 100644 --- a/src/Twig/IconExtension.php +++ b/src/Twig/IconExtension.php @@ -36,6 +36,7 @@ final class IconExtension extends AbstractExtension 'dashboard' => 'fas fa-tachometer-alt', 'debug' => 'far fa-file-alt', 'delete' => 'far fa-trash-alt', + 'details' => 'fas fa-info-circle', 'doctor' => 'fas fa-medkit', 'dot' => 'fas fa-circle', 'download' => 'fas fa-download', diff --git a/src/Twig/Runtime/ExporterExtension.php b/src/Twig/Runtime/ExporterExtension.php deleted file mode 100644 index 1fdb18d5..00000000 --- a/src/Twig/Runtime/ExporterExtension.php +++ /dev/null @@ -1,36 +0,0 @@ -service = $service; - } - - public function getTimesheetExporter(): array - { - $ids = []; - foreach ($this->service->getTimesheetExporter() as $exporter) { - $ids[] = $exporter->getId(); - } - - return $ids; - } -} diff --git a/src/Twig/Runtime/ThemeExtension.php b/src/Twig/Runtime/ThemeExtension.php index 2676d2ff..d21ff943 100644 --- a/src/Twig/Runtime/ThemeExtension.php +++ b/src/Twig/Runtime/ThemeExtension.php @@ -52,13 +52,9 @@ final class ThemeExtension implements RuntimeExtensionInterface return $themeEvent; } - public function actions(User $user, string $action, array $payload): ThemeEvent + public function actions(User $user, string $action, string $view, array $payload = []): ThemeEvent { - if (!\array_key_exists('actions', $payload)) { - $payload['actions'] = []; - } - - $themeEvent = new PageActionsEvent($user, $payload, $action); + $themeEvent = new PageActionsEvent($user, $payload, $action, $view); $eventName = 'actions.' . $action; diff --git a/src/Twig/RuntimeExtensions.php b/src/Twig/RuntimeExtensions.php index 1d084877..4a47fd70 100644 --- a/src/Twig/RuntimeExtensions.php +++ b/src/Twig/RuntimeExtensions.php @@ -10,9 +10,7 @@ namespace App\Twig; use App\Twig\Runtime\EncoreExtension; -use App\Twig\Runtime\ExporterExtension; use App\Twig\Runtime\MarkdownExtension; -use App\Twig\Runtime\ReportingExtension; use App\Twig\Runtime\ThemeExtension; use App\Twig\Runtime\TimesheetExtension; use App\Twig\Runtime\WidgetExtension; @@ -31,11 +29,9 @@ class RuntimeExtensions extends AbstractExtension new TwigFunction('trigger', [ThemeExtension::class, 'trigger'], ['needs_environment' => true]), new TwigFunction('actions', [ThemeExtension::class, 'actions']), new TwigFunction('javascript_translations', [ThemeExtension::class, 'getJavascriptTranslations']), - new TwigFunction('timesheet_exporter', [ExporterExtension::class, 'getTimesheetExporter']), new TwigFunction('active_timesheets', [TimesheetExtension::class, 'activeEntries']), new TwigFunction('encore_entry_css_source', [EncoreExtension::class, 'getEncoreEntryCssSource']), new TwigFunction('render_widget', [WidgetExtension::class, 'renderWidget'], ['is_safe' => ['html']]), - new TwigFunction('available_reports', [ReportingExtension::class, 'getAvailableReports'], []), ]; } diff --git a/templates/about/actions.html.twig b/templates/about/actions.html.twig index 4b518c82..005d642e 100644 --- a/templates/about/actions.html.twig +++ b/templates/about/actions.html.twig @@ -1,6 +1,5 @@ {% macro about(view) %} {% import "macros/widgets.html.twig" as widgets %} - {% set actions = {} %} - {% set event = trigger('actions.about', {'actions': actions, 'view': view}) %} - {{ widgets.page_actions(event.payload.actions) }} + {% set event = actions(app.user, 'about', view) %} + {{ widgets.page_actions(event.actions) }} {% endmacro %} diff --git a/templates/activity/actions.html.twig b/templates/activity/actions.html.twig index dc7eedec..432d4c64 100644 --- a/templates/activity/actions.html.twig +++ b/templates/activity/actions.html.twig @@ -1,64 +1,15 @@ {% macro activities(view) %} {% import "macros/widgets.html.twig" as widgets %} - - {% set actions = {'search': {'class': 'search-toggle visible-xs-inline'}, 'visibility': '#modal_activity_admin'} %} - - {% set actions = actions|merge({'download': {'url': path('activity_export'), 'class': 'toolbar-action'}}) %} - - {% if is_granted('create_activity') %} - {% set actions = actions|merge({'create': {'url': path('admin_activity_create'), 'class': 'modal-ajax-form'}}) %} - {% endif %} - - {% set actions = actions|merge({'help': {'url': 'activity.html'|docu_link, 'target': '_blank'}}) %} - - {% set event = trigger('actions.activities', {'actions': actions, 'view': view}) %} - {{ widgets.page_actions(event.payload.actions) }} + {% set event = actions(app.user, 'activities', view) %} + {{ widgets.page_actions(event.actions) }} {% endmacro %} {% macro activity(activity, view, options) %} {% import "macros/widgets.html.twig" as widgets %} - {% set actions = {} %} - - {% if activity.id is not empty %} - {% if view != 'details' and is_granted('view', activity) %} - {% set actions = actions|merge({'details': path('activity_details', {'id': activity.id})}) %} - {% endif %} - {% if is_granted('edit', activity) %} - {% set class = '' %} - {% if view != 'edit' %} - {% set class = 'modal-ajax-form' %} - {% endif %} - {% set actions = actions|merge({'edit': {'url': path('admin_activity_edit', {'id': activity.id}), 'class': class}}) %} - {% endif %} - {% if is_granted('permissions', activity) %} - {% set class = '' %} - {% if view != 'permissions' %} - {% set class = 'modal-ajax-form' %} - {% endif %} - {% set actions = actions|merge({'permissions': {'url': path('admin_activity_permissions', {'id': activity.id}), 'class': class}}) %} - {% endif %} - {% if actions|length > 0 %} - {% set actions = actions|merge({'divider': null}) %} - {% endif %} - {% if is_granted('view_other_timesheet') %} - {% set actions = actions|merge({'timesheet': path('admin_timesheet', {'customers[]': activity.project ? activity.project.customer.id : null, 'projects[]': activity.project ? activity.project.id : null, 'activities[]': activity.id})}) %} - {% endif %} - {% if is_granted('create_other_timesheet') %} - {% set actions = actions|merge({'create-timesheet': {'url': path('admin_timesheet_create', {'project': activity.project ? activity.project.id : null, 'activity': activity.id}), 'class': 'modal-ajax-form'}}) %} - {% endif %} - {% if (view == 'index' or view == 'custom') and is_granted('delete', activity) %} - {% set actions = actions|merge({'trash': {'url': path('admin_activity_delete', {'id': activity.id}), 'class': 'modal-ajax-form'}}) %} - {% endif %} - {% endif %} - - {% if view != 'index' and view != 'custom' %} - {% set actions = actions|merge({'back': options.back|default(path('admin_activity'))}) %} - {% endif %} - - {% set event = trigger('actions.activity', {'actions': actions, 'view': view, 'activity': activity}) %} + {% set event = actions(app.user, 'activity', view, {'activity': activity}) %} {% if view == 'index' or view == 'custom' %} - {{ widgets.table_actions(event.payload.actions) }} + {{ widgets.table_actions(event.actions) }} {% else %} - {{ widgets.entity_actions(event.payload.actions) }} + {{ widgets.page_actions(event.actions) }} {% endif %} {% endmacro %} diff --git a/templates/activity/permissions.html.twig b/templates/activity/permissions.html.twig index 774c5666..ea90a4d5 100644 --- a/templates/activity/permissions.html.twig +++ b/templates/activity/permissions.html.twig @@ -6,8 +6,8 @@ {% block main %} {{ include(app.request.xmlHttpRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig', { - 'title': activity.name, + 'title': ('permissions'|trans({}, 'actions')) ~ ': ' ~ activity.name, 'form': form, - 'back': path('admin_activity') + 'back': path('activity_details', {'id': activity.id}) }) }} {% endblock %} diff --git a/templates/calendar/actions.html.twig b/templates/calendar/actions.html.twig deleted file mode 100644 index 3a95e62b..00000000 --- a/templates/calendar/actions.html.twig +++ /dev/null @@ -1,11 +0,0 @@ -{% macro calendar(view) %} - {% import "macros/widgets.html.twig" as widgets %} - - {% set actions = {} %} - {% if is_granted('create_own_timesheet') %} - {% set actions = actions|merge({'create': {'url': path('timesheet_create'), 'class': 'modal-ajax-form'}}) %} - {% endif %} - - {% set event = trigger('actions.calendar', {'actions': actions, 'view': view}) %} - {{ widgets.page_actions(event.payload.actions) }} -{% endmacro %} diff --git a/templates/calendar/user.html.twig b/templates/calendar/user.html.twig index 8b68b255..68172267 100644 --- a/templates/calendar/user.html.twig +++ b/templates/calendar/user.html.twig @@ -1,9 +1,12 @@ {% extends 'base.html.twig' %} {% import "macros/widgets.html.twig" as widgets %} -{% import "calendar/actions.html.twig" as actions %} +{% import "macros/widgets.html.twig" as widgets %} {% block page_title %}{{ 'calendar.title'|trans }}{% endblock %} -{% block page_actions %}{{ actions.calendar('index') }}{% endblock %} +{% block page_actions %} + {% set event = actions(app.user, 'calendar', 'index') %} + {{ widgets.page_actions(event.actions) }} +{% endblock %} {% block main %}
diff --git a/templates/customer/actions.html.twig b/templates/customer/actions.html.twig index b98cf350..6dbac7cc 100644 --- a/templates/customer/actions.html.twig +++ b/templates/customer/actions.html.twig @@ -1,72 +1,15 @@ {% macro customers(view) %} {% import "macros/widgets.html.twig" as widgets %} - - {% set actions = { - 'search': {'class': 'search-toggle visible-xs-inline'}, - 'visibility': {'modal': '#modal_customer_admin'}, - 'download': {'url': path('customer_export'), 'class': 'toolbar-action'} - } %} - - {% if is_granted('create_customer') %} - {% set actions = actions|merge({'create': {'url': path('admin_customer_create'), 'class': 'modal-ajax-form'}}) %} - {% endif %} - - {% set actions = actions|merge({'help': {'url': 'customer.html'|docu_link, 'target': '_blank'}}) %} - - {% set event = trigger('actions.customers', {'actions': actions, 'view': view}) %} - {{ widgets.page_actions(event.payload.actions) }} + {% set event = actions(app.user, 'customers', view) %} + {{ widgets.page_actions(event.actions) }} {% endmacro %} {% macro customer(customer, view, options) %} {% import "macros/widgets.html.twig" as widgets %} - {% set actions = {} %} - - {% if customer.id is not empty %} - {% if view != 'details' and is_granted('view', customer) %} - {% set actions = actions|merge({'details': path('customer_details', {'id': customer.id})}) %} - {% endif %} - {% if is_granted('edit', customer) %} - {% set class = '' %} - {% if view != 'edit' %} - {% set class = 'modal-ajax-form' %} - {% endif %} - {% set actions = actions|merge({'edit': {'url': path('admin_customer_edit', {'id': customer.id}), 'class': class}}) %} - {% endif %} - {% if is_granted('permissions', customer) %} - {% set class = '' %} - {% if view != 'permissions' %} - {% set class = 'modal-ajax-form' %} - {% endif %} - {% set actions = actions|merge({'permissions': {'url': path('admin_customer_permissions', {'id': customer.id}), 'class': class}}) %} - {% endif %} - {% if actions|length > 0 %} - {% set actions = actions|merge({'divider': null}) %} - {% endif %} - {% if is_granted('view_project') or is_granted('view_teamlead_project') or is_granted('view_team_project') %} - {% set actions = actions|merge({'project': path('admin_project', {'customers[]': customer.id})}) %} - {% endif %} - {% if is_granted('view_activity') %} - {% set actions = actions|merge({'activity': path('admin_activity', {'customers[]': customer.id})}) %} - {% endif %} - {% if is_granted('view_other_timesheet') %} - {% set actions = actions|merge({'timesheet': path('admin_timesheet', {'customers[]': customer.id})}) %} - {% endif %} - {% if customer.visible and is_granted('create_project') %} - {% set actions = actions|merge({'create-project': {'url': path('admin_project_create_with_customer', {'customer': customer.id}), 'class': 'modal-ajax-form'}}) %} - {% endif %} - {% if view == 'index' and is_granted('delete', customer) %} - {% set actions = actions|merge({'trash': {'url': path('admin_customer_delete', {'id': customer.id}), 'class': 'modal-ajax-form'}}) %} - {% endif %} - {% endif %} - - {% if view != 'index' and view != 'custom' %} - {% set actions = actions|merge({'back': options.back|default(path('admin_customer'))}) %} - {% endif %} - - {% set event = trigger('actions.customer', {'actions': actions, 'view': view, 'customer': customer}) %} + {% set event = actions(app.user, 'customer', view, {'customer': customer}) %} {% if view == 'index' or view == 'custom' %} - {{ widgets.table_actions(event.payload.actions) }} + {{ widgets.table_actions(event.actions) }} {% else %} - {{ widgets.entity_actions(event.payload.actions) }} + {{ widgets.page_actions(event.actions) }} {% endif %} {% endmacro %} diff --git a/templates/customer/permissions.html.twig b/templates/customer/permissions.html.twig index 76858124..b6de737d 100644 --- a/templates/customer/permissions.html.twig +++ b/templates/customer/permissions.html.twig @@ -6,8 +6,8 @@ {% block main %} {{ include(app.request.xmlHttpRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig', { - 'title': customer.name, + 'title': ('permissions'|trans({}, 'actions')) ~ ': ' ~ customer.name, 'form': form, - 'back': path('admin_customer') + 'back': path('customer_details', {'id': customer.id}) }) }} {% endblock %} diff --git a/templates/doctor/actions.html.twig b/templates/doctor/actions.html.twig deleted file mode 100644 index 40d29747..00000000 --- a/templates/doctor/actions.html.twig +++ /dev/null @@ -1,8 +0,0 @@ -{% macro doctor(view) %} - {% import "macros/widgets.html.twig" as widgets %} - - {% set actions = {} %} - - {% set event = trigger('actions.doctor', {'actions': actions, 'view': view}) %} - {{ widgets.page_actions(event.payload.actions) }} -{% endmacro %} diff --git a/templates/doctor/index.html.twig b/templates/doctor/index.html.twig index ee7cfcab..24447ea2 100644 --- a/templates/doctor/index.html.twig +++ b/templates/doctor/index.html.twig @@ -1,9 +1,12 @@ {% extends 'base.html.twig' %} -{% import "doctor/actions.html.twig" as actions %} +{% import "macros/widgets.html.twig" as widgets %} {% block page_title %}{{ 'menu.doctor'|trans }}{% endblock %} {% block page_subtitle %}Environment: {{ environment }} – Version: {{ constant('App\\Constants::VERSION') }} {{ constant('App\\Constants::STATUS') }}{% endblock %} -{% block page_actions %}{{ actions.doctor('index') }}{% endblock %} +{% block page_actions %} + {% set event = actions(app.user, 'doctor', 'index') %} + {{ widgets.page_actions(event.actions) }} +{% endblock %} {% block main %} @@ -158,4 +161,5 @@ {% endblock %} {% endembed %} + {% endblock %} diff --git a/templates/export/actions.html.twig b/templates/export/actions.html.twig deleted file mode 100644 index 22aba17c..00000000 --- a/templates/export/actions.html.twig +++ /dev/null @@ -1,14 +0,0 @@ - -{% macro export(view) %} - {% import "macros/widgets.html.twig" as widgets %} - - {% set actions = {'visibility': '#modal_export'} %} - - {% if view == 'preview' %} - {% set actions = actions|merge({'off': {'id':'export-toggle-button'}}) %} - {% endif %} - {% set actions = actions|merge({'help': {'url': 'export.html'|docu_link, 'target': '_blank'}}) %} - - {% set event = trigger('actions.export', {'actions': actions, 'view': view}) %} - {{ widgets.page_actions(event.payload.actions) }} -{% endmacro %} diff --git a/templates/export/index.html.twig b/templates/export/index.html.twig index c26d28cb..cbe9d737 100644 --- a/templates/export/index.html.twig +++ b/templates/export/index.html.twig @@ -2,7 +2,6 @@ {% import "macros/widgets.html.twig" as widgets %} {% import "macros/toolbar.html.twig" as toolbar %} {% import "macros/datatables.html.twig" as tables %} -{% import "export/actions.html.twig" as actions %} {% set columns = { 'date': {'class': 'alwaysVisible text-nowrap', 'orderBy': false}, @@ -21,7 +20,10 @@ {% block page_title %}{{ 'export.title'|trans }}{% endblock %} {% block page_subtitle %}{{ 'export.subtitle'|trans }}{% endblock %} -{% block page_actions %}{{ actions.export((preview_show ? 'preview' : 'index')) }}{% endblock %} +{% block page_actions %} + {% set event = actions(app.user, 'export', (preview_show ? 'preview' : 'index')) %} + {{ widgets.page_actions(event.actions) }} +{% endblock %} {% block main_before %} {{ tables.data_table_column_modal(tableName, columns) }} diff --git a/templates/invoice/actions.html.twig b/templates/invoice/actions.html.twig index da140572..4292da7e 100644 --- a/templates/invoice/actions.html.twig +++ b/templates/invoice/actions.html.twig @@ -1,111 +1,35 @@ - {% macro invoices(view) %} {% import "macros/widgets.html.twig" as widgets %} - - {% set actions = {'visibility': '#modal_invoice'} %} - - {% if is_granted('history_invoice') %} - {% set actions = actions|merge({'list': path('admin_invoice_list')}) %} - {% endif %} - - {% if is_granted('manage_invoice_template') %} - {% set actions = actions|merge({'invoice-template': path('admin_invoice_template')}) %} - {% endif %} - - {% if is_granted('system_configuration') %} - {% set actions = actions|merge({'settings': {'url': path('system_configuration_section', {'section': 'invoice'}), 'class': 'modal-ajax-form'}}) %} - {% endif %} - - {% set actions = actions|merge({'help': {'url': 'invoices.html'|docu_link, 'target': '_blank'}}) %} - - {% set event = trigger('actions.invoices', {'actions': actions, 'view': view}) %} - {{ widgets.page_actions(event.payload.actions) }} + {% set event = actions(app.user, 'invoices', view) %} + {{ widgets.page_actions(event.actions) }} {% endmacro %} -{% macro invoice_templates(view) %} +{% macro invoice(invoice, view) %} {% import "macros/widgets.html.twig" as widgets %} - - {% set actions = {} %} - {% if is_granted('view_invoice') %} - {% set actions = actions|merge({'back': path('invoice')}) %} - {% endif %} - - {% set actions = actions|merge({'visibility': '#modal_invoice_template'}) %} - - {% if is_granted('manage_invoice_template') %} - {% set actions = actions|merge({'create': {'url': path('admin_invoice_template_create'), 'class': 'modal-ajax-form'}}) %} - {% endif %} - - {% if is_granted('upload_invoice_template') %} - {# File upload does not work in a modal right now #} - {% set actions = actions|merge({'upload': {'url': path('admin_invoice_document_upload')}}) %} - {% endif %} - - {% set actions = actions|merge({'help': {'url': 'invoices.html'|docu_link, 'target': '_blank'}}) %} - - {% set event = trigger('actions.invoice_templates', {'actions': actions, 'view': 'index'}) %} - {{ widgets.page_actions(actions) }} -{% endmacro %} - -{% macro invoice(invoice, view, options) %} - {% import "macros/widgets.html.twig" as widgets %} - - {% set actions = {} %} - - {% if is_granted('history_invoice') %} - {% if invoice.new %} - {% set actions = actions|merge({'invoice.pending': path('admin_invoice_status', {'id': invoice.id, 'status': 'pending'})}) %} - {% elseif invoice.pending %} - {% set actions = actions|merge({'invoice.paid': path('admin_invoice_status', {'id': invoice.id, 'status': 'paid'})}) %} - {% endif %} - - {% set actions = actions|merge({'download': {'url': path('admin_invoice_download', {'id': invoice.id}), 'target': '_blank'}}) %} - {% set actions = actions|merge({'trash': {'url': path('admin_invoice_delete', {'id' : invoice.id}), 'class': 'confirmation-link', 'attr': {'data-question': 'confirm.delete'}}}) %} - {% endif %} - - {% if options.back is defined %} - {% set actions = actions|merge({'back': options.back}) %} - {% endif %} - - {% set event = trigger('actions.invoice', {'actions': actions, 'invoice': invoice}) %} - {% if view == 'index' or view == 'custom' %} - {{ widgets.table_actions(event.payload.actions) }} - {% else %} - {{ widgets.entity_actions(event.payload.actions) }} - {% endif %} + {% set event = actions(app.user, 'invoice', view, {'invoice': invoice}) %} + {{ widgets.table_actions(event.actions) }} {% endmacro %} {% macro invoice_listing(view) %} {% import "macros/widgets.html.twig" as widgets %} - {% set event = actions(app.user, 'invoice_details', {'view': view}) %} + {% set event = actions(app.user, 'invoice_details', view) %} {{ widgets.page_actions(event.actions) }} {% endmacro %} {% macro invoice_upload(view) %} {% import "macros/widgets.html.twig" as widgets %} + {% set event = actions(app.user, 'invoice_upload', view) %} + {{ widgets.page_actions(event.actions) }} +{% endmacro %} - {% set actions = {} %} - {% if view == 'index' and is_granted('manage_invoice_template') %} - {% set actions = actions|merge({'back': path('admin_invoice_template')}) %} - {% endif %} - - {% set actions = actions|merge({'help': {'url': 'invoices.html'|docu_link, 'target': '_blank'}}) %} - - {% set event = trigger('actions.invoice_upload', {'actions': actions, 'view': 'index'}) %} - {{ widgets.page_actions(actions) }} +{% macro invoice_templates(view) %} + {% import "macros/widgets.html.twig" as widgets %} + {% set event = actions(app.user, 'invoice_templates', view) %} + {{ widgets.page_actions(event.actions) }} {% endmacro %} {% macro invoice_template(template, view) %} {% import "macros/widgets.html.twig" as widgets %} - - {% set actions = {} %} - - {% if is_granted('manage_invoice_template') %} - {% set actions = actions|merge({'edit': {'url': path('admin_invoice_template_edit', {'id' : template.id}), 'class': 'modal-ajax-form'}}) %} - {% set actions = actions|merge({'copy': path('admin_invoice_template_copy', {'id' : template.id})}) %} - {% set actions = actions|merge({'trash': {'url': path('admin_invoice_template_delete', {'id' : template.id}), 'class': 'confirmation-link', 'attr': {'data-question': 'confirm.delete'}}}) %} - {% endif %} - - {% set event = trigger('actions.invoice_template', {'actions': actions, 'view': view, 'template': template}) %} - {{ widgets.table_actions(event.payload.actions) }} + {% set event = actions(app.user, 'invoice_template', view, {'template': template}) %} + {{ widgets.table_actions(event.actions) }} {% endmacro %} diff --git a/templates/permission/actions.html.twig b/templates/permission/actions.html.twig index 55b65c82..cf5ca45e 100644 --- a/templates/permission/actions.html.twig +++ b/templates/permission/actions.html.twig @@ -1,17 +1,5 @@ {% macro user_permissions(view) %} {% import "macros/widgets.html.twig" as widgets %} - {% set actions = {} %} - - {% if view != 'index' and is_granted('role_permissions') %} - {% set actions = actions|merge({'permissions': path('admin_user_permissions')}) %} - {% endif %} - - {% if view != 'role' and is_granted('role_permissions') %} - {% set actions = actions|merge({'create': {'url': path('admin_user_roles'), 'class': 'modal-ajax-form'}}) %} - {% endif %} - - {% set actions = actions|merge({'help': {'url': 'permissions.html'|docu_link, 'target': '_blank'}}) %} - - {% set event = trigger('actions.user_permissions', {'actions': actions, 'view': view}) %} - {{ widgets.page_actions(event.payload.actions) }} + {% set event = actions(app.user, 'user_permissions', view) %} + {{ widgets.page_actions(event.actions) }} {% endmacro %} diff --git a/templates/plugin/actions.html.twig b/templates/plugin/actions.html.twig deleted file mode 100644 index 83b9136d..00000000 --- a/templates/plugin/actions.html.twig +++ /dev/null @@ -1,22 +0,0 @@ -{% macro plugins(view) %} - {% import "macros/widgets.html.twig" as widgets %} - - {% set actions = {'shop': {'url': constant('App\\Constants::HOMEPAGE') ~ '/store/', 'target': '_blank'}} %} - {% set actions = actions|merge({'help': {'url': 'plugins.html'|docu_link, 'target': '_blank'}}) %} - - {% set event = trigger('actions.plugins', {'actions': actions, 'view': view}) %} - {{ widgets.page_actions(event.payload.actions) }} -{% endmacro %} - -{% macro plugin(plugin, view) %} - {% import "macros/widgets.html.twig" as widgets %} - - {% set actions = {'home': {'url': plugin.metadata.homepage, 'target': '_blank'}} %} - - {% set event = trigger('actions.plugin', {'actions': actions, 'view': view, 'plugin': plugin}) %} - {% if view == 'index' %} - {{ widgets.table_actions(event.payload.actions) }} - {% else %} - {{ widgets.entity_actions(event.payload.actions) }} - {% endif %} -{% endmacro %} diff --git a/templates/plugin/index.html.twig b/templates/plugin/index.html.twig index 651314ea..5afdbd6a 100644 --- a/templates/plugin/index.html.twig +++ b/templates/plugin/index.html.twig @@ -1,11 +1,13 @@ {% extends 'base.html.twig' %} {% import "macros/widgets.html.twig" as widgets %} -{% import "plugin/actions.html.twig" as actions %} {% import "macros/datatables.html.twig" as tables %} {% block page_title %}{{ 'plugins.title'|trans({}, 'plugins') }}{% endblock %} {% block page_subtitle %}{{ 'plugins.subtitle'|trans({}, 'plugins') }}{% endblock %} -{% block page_actions %}{{ actions.plugins('index') }}{% endblock %} +{% block page_actions %} + {% set event = actions(app.user, 'plugins', 'index') %} + {{ widgets.page_actions(event.actions) }} +{% endblock %} {% block main %} @@ -45,7 +47,8 @@ {% endif %} - {{ actions.plugin(plugin, 'index') }} + {% set event = actions(app.user, 'plugin', 'index', {'plugin': plugin}) %} + {{ widgets.table_actions(event.actions) }} {% endfor %} diff --git a/templates/project/actions.html.twig b/templates/project/actions.html.twig index ff4f38c1..ccd68f6f 100644 --- a/templates/project/actions.html.twig +++ b/templates/project/actions.html.twig @@ -1,68 +1,15 @@ {% macro projects(view) %} {% import "macros/widgets.html.twig" as widgets %} - - {% set actions = {'search': {'class': 'search-toggle visible-xs-inline'}, 'visibility': '#modal_project_admin'} %} - - {% set actions = actions|merge({'download': {'url': path('project_export'), 'class': 'toolbar-action'}}) %} - - {% if is_granted('create_project') %} - {% set actions = actions|merge({'create': {'url': path('admin_project_create'), 'class': 'modal-ajax-form'}}) %} - {% endif %} - - {% set actions = actions|merge({'help': {'url': 'project.html'|docu_link, 'target': '_blank'}}) %} - - {% set event = trigger('actions.projects', {'actions': actions, 'view': view}) %} - {{ widgets.page_actions(event.payload.actions) }} + {% set event = actions(app.user, 'projects', view) %} + {{ widgets.page_actions(event.actions) }} {% endmacro %} {% macro project(project, view, options) %} {% import "macros/widgets.html.twig" as widgets %} - {% set actions = {} %} - - {% if project.id is not empty %} - {% if view != 'details' and is_granted('view', project) %} - {% set actions = actions|merge({'details': path('project_details', {'id': project.id})}) %} - {% endif %} - {% if is_granted('edit', project) %} - {% set class = '' %} - {% if view != 'edit' %} - {% set class = 'modal-ajax-form' %} - {% endif %} - {% set actions = actions|merge({'edit': {'url': path('admin_project_edit', {'id': project.id}), 'class': class}}) %} - {% set actions = actions|merge({'copy': {'url': path('admin_project_duplicate', {'id': project.id})}}) %} - {% endif %} - {% if is_granted('permissions', project) %} - {% set class = '' %} - {% if view != 'permissions' %} - {% set class = 'modal-ajax-form' %} - {% endif %} - {% set actions = actions|merge({'permissions': {'url': path('admin_project_permissions', {'id': project.id}), 'class': class}}) %} - {% endif %} - {% if actions|length > 0 %} - {% set actions = actions|merge({'divider': null}) %} - {% endif %} - {% if is_granted('view_activity') %} - {% set actions = actions|merge({'activity': path('admin_activity', {'customers[]': project.customer.id, 'projects[]': project.id})}) %} - {% endif %} - {% if is_granted('view_other_timesheet') %} - {% set actions = actions|merge({'timesheet': path('admin_timesheet', {'customers[]': project.customer.id, 'projects[]': project.id})}) %} - {% endif %} - {% if project.visible and project.customer.visible and is_granted('create_activity') %} - {% set actions = actions|merge({'create-activity': {'url': path('admin_activity_create_with_project', {'project': project.id}), 'class': 'modal-ajax-form'}}) %} - {% endif %} - {% if (view == 'index' or view == 'custom') and is_granted('delete', project) %} - {% set actions = actions|merge({'trash': {'url': path('admin_project_delete', {'id': project.id}), 'class': 'modal-ajax-form'}}) %} - {% endif %} - {% endif %} - - {% if view != 'index' and view != 'custom' %} - {% set actions = actions|merge({'back': options.back|default(path('admin_project'))}) %} - {% endif %} - - {% set event = trigger('actions.project', {'actions': actions, 'view': view, 'project': project}) %} + {% set event = actions(app.user, 'project', view, {'project': project}) %} {% if view == 'index' or view == 'custom' %} {{ widgets.table_actions(event.payload.actions) }} {% else %} - {{ widgets.entity_actions(event.payload.actions) }} + {{ widgets.page_actions(event.payload.actions) }} {% endif %} {% endmacro %} diff --git a/templates/project/permissions.html.twig b/templates/project/permissions.html.twig index ac2efb90..cf788063 100644 --- a/templates/project/permissions.html.twig +++ b/templates/project/permissions.html.twig @@ -6,8 +6,8 @@ {% block main %} {{ include(app.request.xmlHttpRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig', { - 'title': project.name, + 'title': ('permissions'|trans({}, 'actions')) ~ ': ' ~ project.name, 'form': form, - 'back': path('admin_project') + 'back': path('project_details', {'id': project.id}) }) }} {% endblock %} diff --git a/templates/reporting/actions.html.twig b/templates/reporting/actions.html.twig deleted file mode 100644 index 2906dd7a..00000000 --- a/templates/reporting/actions.html.twig +++ /dev/null @@ -1,12 +0,0 @@ -{% macro reporting() %} - {% import "macros/widgets.html.twig" as widgets %} - {% set actions = {} %} - {% set children = {} %} - {% for id, report in available_reports(app.user) %} - {% set children = children|merge({(report.id): {'title': report.label|trans({}, 'reporting'), 'url': path(report.route), 'class': 'toolbar-action report-' ~ report.id}}) %} - {% endfor %} - {% set actions = actions|merge({'reporting': {'children': children}}) %} - {% set actions = actions|merge({'help': {'url': 'reporting.html'|docu_link, 'target': '_blank'}}) %} - {% set event = trigger('actions.reporting', {'actions': actions}) %} - {{ widgets.page_actions(event.payload.actions) }} -{% endmacro %} diff --git a/templates/reporting/layout.html.twig b/templates/reporting/layout.html.twig index dff08e02..3462c0d2 100644 --- a/templates/reporting/layout.html.twig +++ b/templates/reporting/layout.html.twig @@ -1,9 +1,12 @@ {% extends 'base.html.twig' %} -{% import "reporting/actions.html.twig" as actions %} +{% import "macros/widgets.html.twig" as widgets %} {% block page_title %}{{ 'menu.reporting'|trans }}{% endblock %} {% block page_subtitle %}{% block report_title %}{% endblock %}{% endblock %} -{% block page_actions %}{{ actions.reporting() }}{% endblock %} +{% block page_actions %} + {% set event = actions(app.user, 'reporting', 'index') %} + {{ widgets.page_actions(event.actions) }} +{% endblock %} {% block main %}
diff --git a/templates/system-configuration/actions.html.twig b/templates/system-configuration/actions.html.twig index 9ef5ffbe..c49b2505 100644 --- a/templates/system-configuration/actions.html.twig +++ b/templates/system-configuration/actions.html.twig @@ -1,9 +1,5 @@ {% macro system_configuration(view) %} {% import "macros/widgets.html.twig" as widgets %} - - {% set actions = {} %} - {% set actions = actions|merge({'help': {'url': 'configurations.html'|docu_link, 'target': '_blank'}}) %} - - {% set event = trigger('actions.system_configuration', {'actions': actions, 'view': view}) %} - {{ widgets.page_actions(event.payload.actions) }} + {% set event = actions(app.user, 'system_configuration', view) %} + {{ widgets.page_actions(event.actions) }} {% endmacro %} diff --git a/templates/tags/actions.html.twig b/templates/tags/actions.html.twig index cfeb8ed4..35739064 100644 --- a/templates/tags/actions.html.twig +++ b/templates/tags/actions.html.twig @@ -1,49 +1,15 @@ {% macro tags(view) %} {% import "macros/widgets.html.twig" as widgets %} - - {% set actions = {'search': {'class': 'search-toggle visible-xs-inline'}} %} - - {% if is_granted('manage_tag') %} - {% set actions = actions|merge({'create': {'url': path('tags_create'), 'class': 'modal-ajax-form'}}) %} - {% endif %} - - {% set actions = actions|merge({'help': {'url': 'tags.html'|docu_link, 'target': '_blank'}}) %} - - {% set event = trigger('actions.tags', {'actions': actions, 'view': view}) %} - {{ widgets.page_actions(event.payload.actions) }} + {% set event = actions(app.user, 'tags', view) %} + {{ widgets.page_actions(event.actions) }} {% endmacro %} {% macro tag(tag, view) %} {% import "macros/widgets.html.twig" as widgets %} - - {% set actions = {} %} - - {% if tag.id is not empty %} - {% if is_granted('manage_tag') %} - {% set class = '' %} - {% if view != 'edit' %} - {% set class = 'modal-ajax-form' %} - {% endif %} - {% set actions = actions|merge({'edit': {'url': path('tags_edit', {'id': tag.id}), 'class': class}}) %} - {% endif %} - - {% if is_granted('view_other_timesheet') %} - {% set actions = actions|merge({'timesheet': path('admin_timesheet', {'tags': tag.name})}) %} - {% endif %} - - {% if is_granted('delete_tag') %} - {% set actions = actions|merge({'trash': {'url': path('delete_tag', {'id' : tag.id}), 'class': 'api-link', 'attr': {'data-event': 'kimai.tagDelete kimai.tagUpdate', 'data-method': 'DELETE', 'data-question': 'confirm.delete', 'data-msg-error': 'action.delete.error', 'data-msg-success': 'action.delete.success'}}}) %} - {% endif %} - {% endif %} - - {% if view != 'index' %} - {% set actions = actions|merge({'back': path('tags')}) %} - {% endif %} - - {% set event = trigger('actions.tag', {'actions': actions, 'view': view, 'tag': tag}) %} - {% if view == 'index' %} - {{ widgets.table_actions(event.payload.actions) }} + {% set event = actions(app.user, 'tag', view, {'tag': tag}) %} + {% if view == 'index' or view == 'custom' %} + {{ widgets.table_actions(event.actions) }} {% else %} - {{ widgets.entity_actions(event.payload.actions) }} + {{ widgets.page_actions(event.actions) }} {% endif %} {% endmacro %} diff --git a/templates/team/actions.html.twig b/templates/team/actions.html.twig index c8fe2ccf..6059ac3a 100644 --- a/templates/team/actions.html.twig +++ b/templates/team/actions.html.twig @@ -1,40 +1,15 @@ {% macro teams(view) %} {% import "macros/widgets.html.twig" as widgets %} - - {% set actions = {'search': {'class': 'search-toggle visible-xs-inline'}} %} - - {% if is_granted('create_team') %} - {% set actions = actions|merge({'create': {'url': path('admin_team_create')}}) %} - {% endif %} - - {% set actions = actions|merge({'help': {'url': 'teams.html'|docu_link, 'target': '_blank'}}) %} - - {% set event = trigger('actions.teams', {'actions': actions, 'view': view}) %} - {{ widgets.page_actions(event.payload.actions) }} + {% set event = actions(app.user, 'teams', view) %} + {{ widgets.page_actions(event.actions) }} {% endmacro %} {% macro team(team, view) %} {% import "macros/widgets.html.twig" as widgets %} - - {% set actions = {} %} - {% if team.id is not empty %} - {% if is_granted('edit', team) %} - {% set class = '' %} - {% set actions = actions|merge({'edit': {'url': path('admin_team_edit', {'id': team.id}), 'class': class}}) %} - {% if is_granted('create_team') %} - {% set actions = actions|merge({'copy': {'url': path('team_duplicate', {'id': team.id})}}) %} - {% endif %} - {% endif %} - {% endif %} - - {% if view == 'index' and is_granted('delete', team) %} - {% set actions = actions|merge({'trash': {'url': path('delete_team', {'id' : team.id}), 'class': 'api-link', 'attr': {'data-event': 'kimai.teamDelete kimai.teamUpdate', 'data-method': 'DELETE', 'data-question': 'confirm.delete', 'data-msg-error': 'action.delete.error', 'data-msg-success': 'action.delete.success'}}}) %} - {% endif %} - - {% set event = trigger('actions.team', {'actions': actions, 'view': view, 'team': team}) %} + {% set event = actions(app.user, 'team', view, {'team': team}) %} {% if view == 'index' %} - {{ widgets.table_actions(event.payload.actions) }} + {{ widgets.table_actions(event.actions) }} {% else %} - {{ widgets.entity_actions(event.payload.actions) }} + {{ widgets.page_actions(event.actions) }} {% endif %} {% endmacro %} diff --git a/templates/timesheet-team/actions.html.twig b/templates/timesheet-team/actions.html.twig index fa5d7c47..49b7daab 100644 --- a/templates/timesheet-team/actions.html.twig +++ b/templates/timesheet-team/actions.html.twig @@ -1,83 +1,21 @@ {% macro timesheets_team(view) %} {% import "macros/widgets.html.twig" as widgets %} - - {% set actions = {'search': {'class': 'search-toggle visible-xs-inline'}} %} - {% if is_granted('export_own_timesheet') %} - {% set exporterIds = timesheet_exporter() %} - {% if exporterIds|length == 1 %} - {% set actions = actions|merge({'download': {'url': path('admin_timesheet_export', {'exporter': exporterIds.0}), 'class': 'toolbar-action'}}) %} - {% elseif exporterIds|length > 1 %} - {% set children = {} %} - {% for exporter in exporterIds %} - {% set children = children|merge({(exporter): {'title': ('button.' ~ exporter)|trans, 'url': path('admin_timesheet_export', {'exporter': exporter}), 'class': 'toolbar-action exporter-' ~ exporter}}) %} - {% endfor %} - {% set actions = actions|merge({'download': {'children': children}}) %} - {% endif %} - {% endif %} - {% set actions = actions|merge({'visibility': '#modal_timesheet_admin'}) %} - {% if is_granted('create_other_timesheet') %} - {% set actions = actions|merge({'create': {'children': {'single': {'title': 'create'|trans,'url': path('admin_timesheet_create'), 'class': 'create-ts modal-ajax-form'}, 'multi-user': {'title': 'create-timesheet-multiuser'|trans({}, 'actions'),'url': path('admin_timesheet_create_multiuser'), 'class': 'create-ts-mu modal-ajax-form'}}}}) %} - {% endif %} - - {% set actions = actions|merge({'help': {'url': 'timesheet.html'|docu_link, 'target': '_blank'}}) %} - - {% set event = trigger('actions.timesheets_team', {'actions': actions, 'view': view}) %} - {{ widgets.page_actions(event.payload.actions) }} + {% set event = actions(app.user, 'timesheets_team', view) %} + {{ widgets.page_actions(event.actions) }} {% endmacro %} -{% macro timesheet_team(timesheet, view, options) %} +{% macro timesheet_team(timesheet, view) %} {% import "macros/widgets.html.twig" as widgets %} - {% set actions = {} %} - - {% if timesheet.id is not empty %} - {% if not timesheet.end and is_granted('stop', timesheet) %} - {% set actions = actions|merge({'stop': {'url': path('stop_timesheet', {'id' : timesheet.id}), 'class': 'api-link', 'attr': {'data-event': 'kimai.timesheetStop kimai.timesheetUpdate', 'data-method': 'PATCH', 'data-msg-error': 'timesheet.stop.error', 'data-msg-success': 'timesheet.stop.success'}}}) %} - {% endif %} - - {% if timesheet.end and is_granted('start', timesheet) %} - {% set actions = actions|merge({'repeat': {'url': path('restart_timesheet', {'id' : timesheet.id}), 'class': 'api-link', 'attr': {'data-payload': '{"copy": "all"}', 'data-event': 'kimai.timesheetStart kimai.timesheetUpdate', 'data-method': 'PATCH', 'data-msg-error': 'timesheet.start.error', 'data-msg-success': 'timesheet.start.success'}}}) %} - {% endif %} - - {% if is_granted('duplicate', timesheet) %} - {% set actions = actions|merge({'copy': {'url': path('duplicate_timesheet', {'id' : timesheet.id}), 'class': 'api-link', 'attr': {'data-payload': '{"copy": "all"}', 'data-event': 'kimai.timesheetStart kimai.timesheetUpdate', 'data-method': 'PATCH', 'data-msg-error': 'action.update.error', 'data-msg-success': 'action.update.success'}}}) %} - {% endif %} - - {% if is_granted('edit', timesheet) %} - {% set class = '' %} - {% if view != 'edit' %} - {% set class = 'modal-ajax-form' %} - {% endif %} - {% set actions = actions|merge({'edit': {'url': path('admin_timesheet_edit', {'id': timesheet.id}), 'class': class}}) %} - {% endif %} - - {% if actions|length > 0 %} - {% set actions = actions|merge({'divider': null}) %} - {% endif %} - - {% if view == 'index' and is_granted('delete', timesheet) %} - {% set actions = actions|merge({'trash': {'url': path('delete_timesheet', {'id' : timesheet.id}), 'class': 'api-link', 'attr': {'data-event': 'kimai.timesheetDelete kimai.timesheetUpdate', 'data-method': 'DELETE', 'data-question': 'confirm.delete', 'data-msg-error': 'action.delete.error', 'data-msg-success': 'action.delete.success'}}}) %} - {% endif %} - {% endif %} - - {% if view != 'index' %} - {% set actions = actions|merge({'back': options.back|default(path('admin_timesheet'))}) %} - {% endif %} - - {% set event = trigger('actions.timesheet_team', {'actions': actions, 'view': view, 'timesheet': timesheet}) %} - {% if view == 'index' %} - {{ widgets.table_actions(event.payload.actions) }} + {% set event = actions(app.user, 'timesheet_team', view, {'timesheet': timesheet}) %} + {% if view == 'index' or view == 'custom' %} + {{ widgets.table_actions(event.actions) }} {% else %} - {{ widgets.entity_actions(event.payload.actions) }} + {{ widgets.page_actions(event.actions) }} {% endif %} {% endmacro %} {% macro timesheets_team_multi_update(view) %} {% import "macros/widgets.html.twig" as widgets %} - - {% set actions = {} %} - {% set actions = actions|merge({'back': path('admin_timesheet')}) %} - {% set actions = actions|merge({'help': {'url': 'timesheet.html'|docu_link, 'target': '_blank'}}) %} - - {% set event = trigger('actions.timesheets_team_multi_update', {'actions': actions, 'view': view}) %} - {{ widgets.page_actions(event.payload.actions) }} + {% set event = actions(app.user, 'timesheets_team_multi_update', view) %} + {{ widgets.page_actions(event.actions) }} {% endmacro %} diff --git a/templates/timesheet/actions.html.twig b/templates/timesheet/actions.html.twig index cdb0f2a5..04356c0f 100644 --- a/templates/timesheet/actions.html.twig +++ b/templates/timesheet/actions.html.twig @@ -1,85 +1,21 @@ {% macro timesheets(view) %} {% import "macros/widgets.html.twig" as widgets %} - - {% set actions = {'search': {'class': 'search-toggle visible-xs-inline'}} %} - {% if is_granted('export_own_timesheet') %} - {% set exporterIds = timesheet_exporter() %} - {% if exporterIds|length == 1 %} - {% set actions = actions|merge({'download': {'url': path('timesheet_export', {'exporter': exporterIds.0}), 'class': 'toolbar-action'}}) %} - {% elseif exporterIds|length > 1 %} - {% set children = {} %} - {% for exporter in exporterIds %} - {% set children = children|merge({(exporter): {'title': ('button.' ~ exporter)|trans, 'url': path('timesheet_export', {'exporter': exporter}), 'class': 'toolbar-action exporter-' ~ exporter}}) %} - {% endfor %} - {% set actions = actions|merge({'download': {'children': children}}) %} - {% endif %} - {% endif %} - {% set actions = actions|merge({'visibility': {'modal': '#modal_timesheet'}}) %} - {% if is_granted('create_own_timesheet') %} - {% set actions = actions|merge({'create': {'url': path('timesheet_create'), 'class': 'modal-ajax-form'}}) %} - {% endif %} - - {% set actions = actions|merge({'help': {'url': 'timesheet.html'|docu_link, 'target': '_blank'}}) %} - - {% set event = trigger('actions.timesheets', {'actions': actions, 'view': view}) %} - {{ widgets.page_actions(event.payload.actions) }} + {% set event = actions(app.user, 'timesheets', view) %} + {{ widgets.page_actions(event.actions) }} {% endmacro %} {% macro timesheet(timesheet, view, options) %} - {%- apply spaceless -%} {% import "macros/widgets.html.twig" as widgets %} - {% set actions = {} %} - - {% if timesheet.id is not empty %} - {% if not timesheet.end and is_granted('stop', timesheet) %} - {% set actions = actions|merge({'stop': {'url': path('stop_timesheet', {'id' : timesheet.id}), 'class': 'api-link', 'attr': {'data-event': 'kimai.timesheetStop kimai.timesheetUpdate', 'data-method': 'PATCH', 'data-msg-error': 'timesheet.stop.error', 'data-msg-success': 'timesheet.stop.success'}}}) %} - {% endif %} - - {% if timesheet.end and is_granted('start', timesheet) %} - {% set actions = actions|merge({'repeat': {'url': path('restart_timesheet', {'id' : timesheet.id}), 'class': 'api-link', 'attr': {'data-payload': '{"copy": "all"}', 'data-event': 'kimai.timesheetStart kimai.timesheetUpdate', 'data-method': 'PATCH', 'data-msg-error': 'timesheet.start.error', 'data-msg-success': 'timesheet.start.success'}}}) %} - {% endif %} - - {% if is_granted('duplicate', timesheet) %} - {% set actions = actions|merge({'copy': {'url': path('duplicate_timesheet', {'id' : timesheet.id}), 'class': 'api-link', 'attr': {'data-payload': '{"copy": "all"}', 'data-event': 'kimai.timesheetStart kimai.timesheetUpdate', 'data-method': 'PATCH', 'data-msg-error': 'action.update.error', 'data-msg-success': 'action.update.success'}}}) %} - {% endif %} - - {% if is_granted('edit', timesheet) %} - {% set class = '' %} - {% if view != 'edit' %} - {% set class = 'modal-ajax-form' %} - {% endif %} - {% set actions = actions|merge({'edit': {'url': path('timesheet_edit', {'id': timesheet.id}), 'class': class}}) %} - {% endif %} - - {% if actions|length > 0 %} - {% set actions = actions|merge({'divider': null}) %} - {% endif %} - - {% if view == 'index' and is_granted('delete', timesheet) %} - {% set actions = actions|merge({'trash': {'url': path('delete_timesheet', {'id' : timesheet.id}), 'class': 'api-link', 'attr': {'data-event': 'kimai.timesheetDelete kimai.timesheetUpdate', 'data-method': 'DELETE', 'data-question': 'confirm.delete', 'data-msg-error': 'action.delete.error', 'data-msg-success': 'action.delete.success'}}}) %} - {% endif %} - {% endif %} - - {% if view != 'index' and view != 'custom' %} - {% set actions = actions|merge({'back': options.back|default(path('timesheet'))}) %} - {% endif %} - - {% set event = trigger('actions.timesheet', {'actions': actions, 'view': view, 'timesheet': timesheet}) %} + {% set event = actions(app.user, 'timesheet', view, {'timesheet': timesheet}) %} {% if view == 'index' or view == 'custom' %} - {{ widgets.table_actions(event.payload.actions) }} + {{ widgets.table_actions(event.actions) }} {% else %} - {{ widgets.entity_actions(event.payload.actions) }} + {{ widgets.page_actions(event.actions) }} {% endif %} - {%- endapply -%} {% endmacro %} {% macro timesheets_multi_update(view) %} {% import "macros/widgets.html.twig" as widgets %} - - {% set actions = {} %} - {% set actions = actions|merge({'back': path('timesheet')}) %} - {% set actions = actions|merge({'help': {'url': 'timesheet.html'|docu_link, 'target': '_blank'}}) %} - - {% set event = trigger('actions.timesheets_multi_update', {'actions': actions, 'view': view}) %} - {{ widgets.page_actions(event.payload.actions) }} + {% set event = actions(app.user, 'timesheets_multi_update', view) %} + {{ widgets.page_actions(event.actions) }} {% endmacro %} diff --git a/templates/user/actions.html.twig b/templates/user/actions.html.twig index a04d75a4..1f65322a 100644 --- a/templates/user/actions.html.twig +++ b/templates/user/actions.html.twig @@ -1,30 +1,7 @@ {% macro users(view) %} {% import "macros/widgets.html.twig" as widgets %} - - {% set actions = {'search': {'class': 'search-toggle visible-xs-inline'}} %} - - {% if view == 'index' %} - {% set actions = actions|merge({'visibility': '#modal_user_admin'}) %} - {% else %} - {% set actions = actions|merge({'back': path('admin_user')}) %} - {% endif %} - - {% set actions = actions|merge({'download': {'url': path('user_export'), 'class': 'toolbar-action'}}) %} - - {% if is_granted('create_user') %} - {% set actions = actions|merge({'create': {'url': path('admin_user_create')}}) %} - {% endif %} - - {% if view == 'index' %} - {% set actions = actions|merge({'help': {'url': 'users.html'|docu_link, 'target': '_blank'}}) %} - {% elseif view == 'permissions' %} - {% set actions = actions|merge({'help': {'url': 'permissions.html'|docu_link, 'target': '_blank'}}) %} - {% endif %} - - {% set actions = actions|merge({'help': {'url': 'users.html'|docu_link, 'target': '_blank'}}) %} - - {% set event = trigger('actions.users', {'actions': actions, 'view': view}) %} - {{ widgets.page_actions(event.payload.actions) }} + {% set event = actions(app.user, 'users', view) %} + {{ widgets.page_actions(event.actions) }} {% endmacro %} {% macro user_permissions(view) %} @@ -35,7 +12,7 @@ {% macro user(user, view, options) %} {% import "macros/widgets.html.twig" as widgets %} - {% set event = actions(app.user, 'user', {'view': view, 'user': user}) %} + {% set event = actions(app.user, 'user', view, {'user': user}) %} {% if view == 'index' %} {{ widgets.table_actions(event.actions) }} {% else %} diff --git a/templates/user/layout.html.twig b/templates/user/layout.html.twig index 68fc6a1e..d139239b 100644 --- a/templates/user/layout.html.twig +++ b/templates/user/layout.html.twig @@ -22,7 +22,7 @@
{% block profile_navbar %}{% endblock %} diff --git a/tests/Controller/ActivityControllerTest.php b/tests/Controller/ActivityControllerTest.php index cbacf262..5d1192aa 100644 --- a/tests/Controller/ActivityControllerTest.php +++ b/tests/Controller/ActivityControllerTest.php @@ -263,9 +263,7 @@ class ActivityControllerTest extends ControllerBaseTest $team2->tick(); $client->submit($form); - $this->assertIsRedirect($client, $this->createUrl('/admin/activity/')); - $client->followRedirect(); - $this->assertHasDataTable($client); + $this->assertIsRedirect($client, $this->createUrl('/admin/activity/' . $id . '/details')); /** @var Activity $activity */ $activity = $em->getRepository(Activity::class)->find($id); diff --git a/tests/Controller/CustomerControllerTest.php b/tests/Controller/CustomerControllerTest.php index 2795b064..5c06b408 100644 --- a/tests/Controller/CustomerControllerTest.php +++ b/tests/Controller/CustomerControllerTest.php @@ -344,9 +344,7 @@ class CustomerControllerTest extends ControllerBaseTest $team2->tick(); $client->submit($form); - $this->assertIsRedirect($client, $this->createUrl('/admin/customer/')); - $client->followRedirect(); - $this->assertHasDataTable($client); + $this->assertIsRedirect($client, $this->createUrl('/admin/customer/1/details')); /** @var Customer $customer */ $customer = $em->getRepository(Customer::class)->find(1); diff --git a/tests/Controller/ProjectControllerTest.php b/tests/Controller/ProjectControllerTest.php index d04d058a..f30b70ce 100644 --- a/tests/Controller/ProjectControllerTest.php +++ b/tests/Controller/ProjectControllerTest.php @@ -410,9 +410,7 @@ class ProjectControllerTest extends ControllerBaseTest $team2->tick(); $client->submit($form); - $this->assertIsRedirect($client, $this->createUrl('/admin/project/')); - $client->followRedirect(); - $this->assertHasDataTable($client); + $this->assertIsRedirect($client, $this->createUrl('/admin/project/1/details')); /** @var Project $project */ $project = $em->getRepository(Project::class)->find(1); diff --git a/tests/Event/PageActionsEventTest.php b/tests/Event/PageActionsEventTest.php index 3030007e..f210a310 100644 --- a/tests/Event/PageActionsEventTest.php +++ b/tests/Event/PageActionsEventTest.php @@ -23,22 +23,104 @@ class PageActionsEventTest extends TestCase $user = new User(); $user->setAlias('foo'); - $sut = new PageActionsEvent($user, [], 'foo'); + $sut = new PageActionsEvent($user, [], 'foo', 'bar'); + $this->assertEquals('bar', $sut->getView()); + $this->assertEquals('foo', $sut->getActionName()); + $this->assertTrue($sut->isView('bar')); + $this->assertFalse($sut->isView('foo')); + $this->assertFalse($sut->isIndexView()); $this->assertSame($user, $sut->getUser()); $this->assertEquals([], $sut->getActions()); - $this->assertEquals(['actions' => []], $sut->getPayload()); + $this->assertEquals(['actions' => [], 'view' => 'bar'], $sut->getPayload()); - $sut = new PageActionsEvent($user, ['hello' => 'world'], 'foo'); + $sut = new PageActionsEvent($user, ['hello' => 'world'], 'foo', 'bar'); $this->assertSame($user, $sut->getUser()); $this->assertEquals([], $sut->getActions()); - $this->assertEquals(['hello' => 'world', 'actions' => []], $sut->getPayload()); + $this->assertEquals(['hello' => 'world', 'actions' => [], 'view' => 'bar'], $sut->getPayload()); } public function testSetActions() { - $sut = new PageActionsEvent(new User(), ['hello' => 'world'], 'foo'); - $sut->setActions(['foo' => ['url' => 'bar']]); + $sut = new PageActionsEvent(new User(), ['hello' => 'world'], 'foo', 'xxx'); + $sut->addAction('foo', ['url' => 'bar']); $this->assertEquals(['foo' => ['url' => 'bar']], $sut->getActions()); - $this->assertEquals(['hello' => 'world', 'actions' => ['foo' => ['url' => 'bar']]], $sut->getPayload()); + $this->assertEquals(['hello' => 'world', 'actions' => ['foo' => ['url' => 'bar']], 'view' => 'xxx'], $sut->getPayload()); + + $this->assertEquals(1, $sut->countActions()); + + // make sure an action with tzhe same name cannot be added + $sut->addAction('foo', ['url' => 'bar']); + $this->assertEquals(1, $sut->countActions()); + + $this->assertEquals(0, $sut->countActions('foo')); + $this->assertTrue($sut->hasAction('foo')); + $this->assertFalse($sut->hasAction('sdsd')); + + $sut->removeAction('xxx'); + $this->assertEquals(1, $sut->countActions()); + $sut->removeAction('foo'); + $this->assertEquals(0, $sut->countActions()); + + $sut->addAction('foo', ['url' => 'bar']); + $this->assertEquals(['foo' => ['url' => 'bar']], $sut->getActions()); + $sut->replaceAction('foo', ['url' => 'xyz']); + $this->assertEquals(['foo' => ['url' => 'xyz']], $sut->getActions()); + } + + public function testSubmenu() + { + $sut = new PageActionsEvent(new User(), ['hello' => 'world'], 'foo', 'xxx'); + $this->assertFalse($sut->hasSubmenu('test')); + $sut->addActionToSubmenu('test', 'blub', ['url' => 'hello-world']); + $this->assertTrue($sut->hasSubmenu('test')); + $this->assertEquals(['test' => ['children' => ['blub' => ['url' => 'hello-world']]]], $sut->getActions()); + $sut->addActionToSubmenu('test', 'blub1', ['url' => 'hello-world']); + $this->assertEquals(2, $sut->countActions('test')); + } + + public function testAddHelper() + { + $sut = new PageActionsEvent(new User(), ['hello' => 'world'], 'foo', 'xxx'); + + $sut->addSearchToggle(); + $sut->addDivider(); + $sut->addBack('foo1'); + $sut->addColumnToggle('foo2'); + $sut->addDelete('foo3'); + $sut->addHelp('foo4'); + $sut->addCreate('foo5', true); + $sut->addCreate('foo6', false); + $sut->addQuickExport('foo7'); + + $this->assertEquals(8, $sut->countActions()); + + $expected = [ + 'search' => ['class' => 'search-toggle visible-xs-inline'], + 'divider0' => null, + 'back' => ['url' => 'foo1', 'translation_domain' => 'actions'], + 'visibility' => ['modal' => '#foo2'], + 'help' => ['url' => 'foo4', 'target' => '_blank'], + 'create' => ['url' => 'foo5', 'class' => 'modal-ajax-form'], + 'download' => ['url' => 'foo7', 'class' => 'toolbar-action'], + 'trash' => ['url' => 'foo3', 'class' => 'modal-ajax-form text-red'], + ]; + + $this->assertEquals($expected, $sut->getActions()); + } + + public function testAddOthers() + { + $sut = new PageActionsEvent(new User(), ['hello' => 'world'], 'foo', 'xxx'); + + // make sure that modal always start with #, no matter what was given + $sut->addColumnToggle('#fooX'); + $this->assertEquals(['visibility' => ['modal' => '#fooX']], $sut->getActions()); + // make sure that a second toggle cannot be added + $sut->addColumnToggle('fooY'); + $this->assertEquals(['visibility' => ['modal' => '#fooX']], $sut->getActions()); + + $sut->removeAction('visibility'); + $sut->addColumnToggle('fooY'); + $this->assertEquals(['visibility' => ['modal' => '#fooY']], $sut->getActions()); } } diff --git a/tests/EventSubscriber/Actions/AbstractActionsSubscriberTest.php b/tests/EventSubscriber/Actions/AbstractActionsSubscriberTest.php new file mode 100644 index 00000000..89afcce8 --- /dev/null +++ b/tests/EventSubscriber/Actions/AbstractActionsSubscriberTest.php @@ -0,0 +1,29 @@ +assertTrue(method_exists($className, 'getSubscribedEvents')); + $events = $className::getSubscribedEvents(); + $actionName = array_keys($events)[0]; + $config = $events[$actionName]; + $this->assertEquals('actions.' . $name, $actionName); + $this->assertTrue(method_exists($className, $config[0])); + $this->assertEquals(1000, $config[1]); + } +} diff --git a/tests/EventSubscriber/Actions/ActivitiesSubscriberTest.php b/tests/EventSubscriber/Actions/ActivitiesSubscriberTest.php new file mode 100644 index 00000000..3483d9e1 --- /dev/null +++ b/tests/EventSubscriber/Actions/ActivitiesSubscriberTest.php @@ -0,0 +1,23 @@ +assertGetSubscribedEvent(ActivitiesSubscriber::class, 'activities'); + } +} diff --git a/tests/EventSubscriber/Actions/ActivitySubscriberTest.php b/tests/EventSubscriber/Actions/ActivitySubscriberTest.php new file mode 100644 index 00000000..57044c59 --- /dev/null +++ b/tests/EventSubscriber/Actions/ActivitySubscriberTest.php @@ -0,0 +1,23 @@ +assertGetSubscribedEvent(ActivitySubscriber::class, 'activity'); + } +} diff --git a/tests/EventSubscriber/Actions/CalendarSubscriberTest.php b/tests/EventSubscriber/Actions/CalendarSubscriberTest.php new file mode 100644 index 00000000..9f554b9c --- /dev/null +++ b/tests/EventSubscriber/Actions/CalendarSubscriberTest.php @@ -0,0 +1,23 @@ +assertGetSubscribedEvent(CalendarSubscriber::class, 'calendar'); + } +} diff --git a/tests/EventSubscriber/Actions/CustomerSubscriberTest.php b/tests/EventSubscriber/Actions/CustomerSubscriberTest.php new file mode 100644 index 00000000..d52e0d4c --- /dev/null +++ b/tests/EventSubscriber/Actions/CustomerSubscriberTest.php @@ -0,0 +1,23 @@ +assertGetSubscribedEvent(CustomerSubscriber::class, 'customer'); + } +} diff --git a/tests/EventSubscriber/Actions/CustomersSubscriberTest.php b/tests/EventSubscriber/Actions/CustomersSubscriberTest.php new file mode 100644 index 00000000..5f5c06e0 --- /dev/null +++ b/tests/EventSubscriber/Actions/CustomersSubscriberTest.php @@ -0,0 +1,23 @@ +assertGetSubscribedEvent(CustomersSubscriber::class, 'customers'); + } +} diff --git a/tests/EventSubscriber/Actions/ExportSubscriberTest.php b/tests/EventSubscriber/Actions/ExportSubscriberTest.php new file mode 100644 index 00000000..17a5cfa1 --- /dev/null +++ b/tests/EventSubscriber/Actions/ExportSubscriberTest.php @@ -0,0 +1,23 @@ +assertGetSubscribedEvent(ExportSubscriber::class, 'export'); + } +} diff --git a/tests/EventSubscriber/Actions/InvoiceArchiveSubscriberTest.php b/tests/EventSubscriber/Actions/InvoiceArchiveSubscriberTest.php new file mode 100644 index 00000000..7badd31b --- /dev/null +++ b/tests/EventSubscriber/Actions/InvoiceArchiveSubscriberTest.php @@ -0,0 +1,23 @@ +assertGetSubscribedEvent(InvoiceArchiveSubscriber::class, 'invoice_details'); + } +} diff --git a/tests/EventSubscriber/Actions/InvoiceTemplateSubscriberTest.php b/tests/EventSubscriber/Actions/InvoiceTemplateSubscriberTest.php new file mode 100644 index 00000000..0bc80712 --- /dev/null +++ b/tests/EventSubscriber/Actions/InvoiceTemplateSubscriberTest.php @@ -0,0 +1,23 @@ +assertGetSubscribedEvent(InvoiceTemplateSubscriber::class, 'invoice_template'); + } +} diff --git a/tests/EventSubscriber/Actions/InvoiceTemplateUploadSubscriberTest.php b/tests/EventSubscriber/Actions/InvoiceTemplateUploadSubscriberTest.php new file mode 100644 index 00000000..80e1fbfa --- /dev/null +++ b/tests/EventSubscriber/Actions/InvoiceTemplateUploadSubscriberTest.php @@ -0,0 +1,23 @@ +assertGetSubscribedEvent(InvoiceTemplateUploadSubscriber::class, 'invoice_upload'); + } +} diff --git a/tests/EventSubscriber/Actions/InvoiceTemplatesSubscriberTest.php b/tests/EventSubscriber/Actions/InvoiceTemplatesSubscriberTest.php new file mode 100644 index 00000000..3a80c714 --- /dev/null +++ b/tests/EventSubscriber/Actions/InvoiceTemplatesSubscriberTest.php @@ -0,0 +1,23 @@ +assertGetSubscribedEvent(InvoiceTemplatesSubscriber::class, 'invoice_templates'); + } +} diff --git a/tests/EventSubscriber/Actions/InvoicesSubscriberTest.php b/tests/EventSubscriber/Actions/InvoicesSubscriberTest.php new file mode 100644 index 00000000..4ded0667 --- /dev/null +++ b/tests/EventSubscriber/Actions/InvoicesSubscriberTest.php @@ -0,0 +1,23 @@ +assertGetSubscribedEvent(InvoicesSubscriber::class, 'invoices'); + } +} diff --git a/tests/EventSubscriber/Actions/PermissionsSubscriberTest.php b/tests/EventSubscriber/Actions/PermissionsSubscriberTest.php new file mode 100644 index 00000000..5041ead3 --- /dev/null +++ b/tests/EventSubscriber/Actions/PermissionsSubscriberTest.php @@ -0,0 +1,23 @@ +assertGetSubscribedEvent(PermissionsSubscriber::class, 'user_permissions'); + } +} diff --git a/tests/EventSubscriber/Actions/PluginSubscriberTest.php b/tests/EventSubscriber/Actions/PluginSubscriberTest.php new file mode 100644 index 00000000..bc23e03b --- /dev/null +++ b/tests/EventSubscriber/Actions/PluginSubscriberTest.php @@ -0,0 +1,23 @@ +assertGetSubscribedEvent(PluginSubscriber::class, 'plugin'); + } +} diff --git a/tests/EventSubscriber/Actions/PluginsSubscriberTest.php b/tests/EventSubscriber/Actions/PluginsSubscriberTest.php new file mode 100644 index 00000000..376de413 --- /dev/null +++ b/tests/EventSubscriber/Actions/PluginsSubscriberTest.php @@ -0,0 +1,23 @@ +assertGetSubscribedEvent(PluginsSubscriber::class, 'plugins'); + } +} diff --git a/tests/EventSubscriber/Actions/ProjectSubscriberTest.php b/tests/EventSubscriber/Actions/ProjectSubscriberTest.php new file mode 100644 index 00000000..ec5da405 --- /dev/null +++ b/tests/EventSubscriber/Actions/ProjectSubscriberTest.php @@ -0,0 +1,23 @@ +assertGetSubscribedEvent(ProjectSubscriber::class, 'project'); + } +} diff --git a/tests/EventSubscriber/Actions/ProjectsSubscriberTest.php b/tests/EventSubscriber/Actions/ProjectsSubscriberTest.php new file mode 100644 index 00000000..e7ca12e7 --- /dev/null +++ b/tests/EventSubscriber/Actions/ProjectsSubscriberTest.php @@ -0,0 +1,23 @@ +assertGetSubscribedEvent(ProjectsSubscriber::class, 'projects'); + } +} diff --git a/tests/EventSubscriber/Actions/ReportingSubscriberTest.php b/tests/EventSubscriber/Actions/ReportingSubscriberTest.php new file mode 100644 index 00000000..6bbb120e --- /dev/null +++ b/tests/EventSubscriber/Actions/ReportingSubscriberTest.php @@ -0,0 +1,23 @@ +assertGetSubscribedEvent(ReportingSubscriber::class, 'reporting'); + } +} diff --git a/tests/EventSubscriber/Actions/SystemConfigurationSubscriberTest.php b/tests/EventSubscriber/Actions/SystemConfigurationSubscriberTest.php new file mode 100644 index 00000000..bce9c619 --- /dev/null +++ b/tests/EventSubscriber/Actions/SystemConfigurationSubscriberTest.php @@ -0,0 +1,23 @@ +assertGetSubscribedEvent(SystemConfigurationSubscriber::class, 'system_configuration'); + } +} diff --git a/tests/EventSubscriber/Actions/TagSubscriberTest.php b/tests/EventSubscriber/Actions/TagSubscriberTest.php new file mode 100644 index 00000000..75207b60 --- /dev/null +++ b/tests/EventSubscriber/Actions/TagSubscriberTest.php @@ -0,0 +1,23 @@ +assertGetSubscribedEvent(TagSubscriber::class, 'tag'); + } +} diff --git a/tests/EventSubscriber/Actions/TagsSubscriberTest.php b/tests/EventSubscriber/Actions/TagsSubscriberTest.php new file mode 100644 index 00000000..8bd7f12f --- /dev/null +++ b/tests/EventSubscriber/Actions/TagsSubscriberTest.php @@ -0,0 +1,23 @@ +assertGetSubscribedEvent(TagsSubscriber::class, 'tags'); + } +} diff --git a/tests/EventSubscriber/Actions/TeamSubscriberTest.php b/tests/EventSubscriber/Actions/TeamSubscriberTest.php new file mode 100644 index 00000000..3dad007e --- /dev/null +++ b/tests/EventSubscriber/Actions/TeamSubscriberTest.php @@ -0,0 +1,23 @@ +assertGetSubscribedEvent(TeamSubscriber::class, 'team'); + } +} diff --git a/tests/EventSubscriber/Actions/TeamsSubscriberTest.php b/tests/EventSubscriber/Actions/TeamsSubscriberTest.php new file mode 100644 index 00000000..e71cbc03 --- /dev/null +++ b/tests/EventSubscriber/Actions/TeamsSubscriberTest.php @@ -0,0 +1,23 @@ +assertGetSubscribedEvent(TeamsSubscriber::class, 'teams'); + } +} diff --git a/tests/EventSubscriber/Actions/TimesheetMultiUpdateSubscriberTest.php b/tests/EventSubscriber/Actions/TimesheetMultiUpdateSubscriberTest.php new file mode 100644 index 00000000..aec4db1c --- /dev/null +++ b/tests/EventSubscriber/Actions/TimesheetMultiUpdateSubscriberTest.php @@ -0,0 +1,23 @@ +assertGetSubscribedEvent(TimesheetMultiUpdateSubscriber::class, 'timesheets_multi_update'); + } +} diff --git a/tests/EventSubscriber/Actions/TimesheetSubscriberTest.php b/tests/EventSubscriber/Actions/TimesheetSubscriberTest.php new file mode 100644 index 00000000..4321f064 --- /dev/null +++ b/tests/EventSubscriber/Actions/TimesheetSubscriberTest.php @@ -0,0 +1,23 @@ +assertGetSubscribedEvent(TimesheetSubscriber::class, 'timesheet'); + } +} diff --git a/tests/EventSubscriber/Actions/TimesheetTeamMultiUpdateSubscriberTest.php b/tests/EventSubscriber/Actions/TimesheetTeamMultiUpdateSubscriberTest.php new file mode 100644 index 00000000..4121bf28 --- /dev/null +++ b/tests/EventSubscriber/Actions/TimesheetTeamMultiUpdateSubscriberTest.php @@ -0,0 +1,23 @@ +assertGetSubscribedEvent(TimesheetTeamMultiUpdateSubscriber::class, 'timesheets_team_multi_update'); + } +} diff --git a/tests/EventSubscriber/Actions/TimesheetTeamSubscriberTest.php b/tests/EventSubscriber/Actions/TimesheetTeamSubscriberTest.php new file mode 100644 index 00000000..6ddad4c3 --- /dev/null +++ b/tests/EventSubscriber/Actions/TimesheetTeamSubscriberTest.php @@ -0,0 +1,23 @@ +assertGetSubscribedEvent(TimesheetTeamSubscriber::class, 'timesheet_team'); + } +} diff --git a/tests/EventSubscriber/Actions/TimesheetsSubscriberTest.php b/tests/EventSubscriber/Actions/TimesheetsSubscriberTest.php new file mode 100644 index 00000000..4a647c93 --- /dev/null +++ b/tests/EventSubscriber/Actions/TimesheetsSubscriberTest.php @@ -0,0 +1,23 @@ +assertGetSubscribedEvent(TimesheetsSubscriber::class, 'timesheets'); + } +} diff --git a/tests/EventSubscriber/Actions/TimesheetsTeamSubscriberTest.php b/tests/EventSubscriber/Actions/TimesheetsTeamSubscriberTest.php new file mode 100644 index 00000000..9131167d --- /dev/null +++ b/tests/EventSubscriber/Actions/TimesheetsTeamSubscriberTest.php @@ -0,0 +1,23 @@ +assertGetSubscribedEvent(TimesheetsTeamSubscriber::class, 'timesheets_team'); + } +} diff --git a/tests/EventSubscriber/Actions/UserSubscriberTest.php b/tests/EventSubscriber/Actions/UserSubscriberTest.php new file mode 100644 index 00000000..39e2c9bf --- /dev/null +++ b/tests/EventSubscriber/Actions/UserSubscriberTest.php @@ -0,0 +1,23 @@ +assertGetSubscribedEvent(UserSubscriber::class, 'user'); + } +} diff --git a/tests/EventSubscriber/Actions/UsersSubscriberTest.php b/tests/EventSubscriber/Actions/UsersSubscriberTest.php new file mode 100644 index 00000000..7ac8b252 --- /dev/null +++ b/tests/EventSubscriber/Actions/UsersSubscriberTest.php @@ -0,0 +1,23 @@ +assertGetSubscribedEvent(UsersSubscriber::class, 'users'); + } +} diff --git a/tests/EventSubscriber/UserPreferenceSubscriberTest.php b/tests/EventSubscriber/UserPreferenceSubscriberTest.php index e0cc2837..dced6e1d 100644 --- a/tests/EventSubscriber/UserPreferenceSubscriberTest.php +++ b/tests/EventSubscriber/UserPreferenceSubscriberTest.php @@ -34,10 +34,10 @@ class UserPreferenceSubscriberTest extends TestCase 'theme.collapsed_sidebar', 'theme.update_browser_title', 'calendar.initial_view', - 'reporting.initial_view', - 'login.initial_view', - 'timesheet.daily_stats', - 'timesheet.export_decimal', + 'reporting.initial_view', + 'login.initial_view', + 'timesheet.daily_stats', + 'timesheet.export_decimal', ]; public function testGetSubscribedEvents() @@ -67,6 +67,7 @@ class UserPreferenceSubscriberTest extends TestCase switch ($pref->getName()) { case UserPreference::HOURLY_RATE: case UserPreference::INTERNAL_RATE: + case 'reporting.initial_view': self::assertTrue($pref->isEnabled()); break; @@ -97,6 +98,7 @@ class UserPreferenceSubscriberTest extends TestCase switch ($pref->getName()) { case UserPreference::HOURLY_RATE: case UserPreference::INTERNAL_RATE: + case 'reporting.initial_view': self::assertFalse($pref->isEnabled()); break; @@ -109,7 +111,7 @@ class UserPreferenceSubscriberTest extends TestCase protected function getSubscriber(bool $seeHourlyRate) { $authMock = $this->createMock(AuthorizationCheckerInterface::class); - $authMock->expects($this->once())->method('isGranted')->willReturn($seeHourlyRate); + $authMock->method('isGranted')->willReturn($seeHourlyRate); $eventMock = $this->createMock(EventDispatcherInterface::class); $formConfigMock = $this->createMock(SystemConfiguration::class); diff --git a/tests/Twig/Runtime/ExporterExtensionTest.php b/tests/Twig/Runtime/ExporterExtensionTest.php deleted file mode 100644 index 99ec8fa5..00000000 --- a/tests/Twig/Runtime/ExporterExtensionTest.php +++ /dev/null @@ -1,33 +0,0 @@ -getSut(); - self::assertEquals([], $sut->getTimesheetExporter()); - } -} diff --git a/tests/Twig/Runtime/ReportingExtensionTest.php b/tests/Twig/Runtime/ReportingExtensionTest.php deleted file mode 100644 index 4d2228c8..00000000 --- a/tests/Twig/Runtime/ReportingExtensionTest.php +++ /dev/null @@ -1,43 +0,0 @@ -createMock(AuthorizationCheckerInterface::class); - $authorization->expects($this->any())->method('isGranted')->willReturn($isGranted); - - $service = new ReportingService($eventDispatcher, $authorization); - - return new ReportingExtension($service); - } - - public function testRenderWidgetForInvalidValue() - { - $sut = $this->getSut(true); - $reports = $sut->getAvailableReports(new User()); - - $this->assertCount(4, $reports); - } -} diff --git a/tests/Twig/RuntimeExtensionsTest.php b/tests/Twig/RuntimeExtensionsTest.php index f304cb47..56877b50 100644 --- a/tests/Twig/RuntimeExtensionsTest.php +++ b/tests/Twig/RuntimeExtensionsTest.php @@ -41,11 +41,9 @@ class RuntimeExtensionsTest extends TestCase 'trigger', 'actions', 'javascript_translations', - 'timesheet_exporter', 'active_timesheets', 'encore_entry_css_source', 'render_widget', - 'available_reports' ]; $i = 0; diff --git a/translations/actions.de.xlf b/translations/actions.de.xlf index aca26fbd..210aa4b3 100644 --- a/translations/actions.de.xlf +++ b/translations/actions.de.xlf @@ -94,6 +94,10 @@ invoice.paid Rechnung bezahlt + + filter + Daten filtern + diff --git a/translations/actions.en.xlf b/translations/actions.en.xlf index d75de24b..c618f4f7 100644 --- a/translations/actions.en.xlf +++ b/translations/actions.en.xlf @@ -94,6 +94,10 @@ invoice.paid Invoice paid + + filter + Filter data +